Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bashrc
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ echo "SQL file loaded : $1"

alias venv="source .venv/bin/activate"
alias popdb="uv run manage.py populate_db --img"
alias dshell="uv run manage.py shell"

41 changes: 21 additions & 20 deletions core/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from core.helpers import TokenHelper
from django.conf import settings

from core.models import Structure
from core.tasks import send_login_mail, send_invite_mail


def get_or_create_user(email, password=None, send_mail=False, set_active=False):
from core.tasks import send_login_mail
User = get_user_model()
# TODO check si on laisse ça comme ça pour set le username
# Si oui il faudrait ne pas afficher les utilisateurs qui n'ont pas mis de prénom
Expand Down Expand Up @@ -34,24 +37,22 @@ def get_or_create_user(email, password=None, send_mail=False, set_active=False):

return user

def generate_login_url(user, base_url=None):
"""
Generate a login url based on a user and return it
"""
token = TokenHelper.generate_user_token(user)

if base_url is None:
base_url = get_base_url()

url_path = reverse("core:user-login-from-email")

connexion_url = f"https://{base_url}{url_path}?token={token}"
return connexion_url


def get_base_url():
def invite_user_to_structure(email, role, structure):
"""
Return the base url of the application
Invite a user to a structure.
"""
base_url = os.environ.get("DOMAIN")
return base_url
user = get_or_create_user(email)

if role in Structure.ROLES[0] :
structure.admins.add(user)
elif role in Structure.ROLES[1] :
structure.editors.add(user)
elif role in Structure.ROLES[2] :
structure.users.add(user)

# New user
if not user.last_login:
send_invite_mail.delay(email, role, structure.pk, True)
# Existing user
else:
send_invite_mail.delay(email, role, structure.pk)
6 changes: 6 additions & 0 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ class Structure(models.Model):
('school', 'École'),
]

ROLES = [
('admin',"Administrateur"),
('editor', "Éditeur"),
('user', "Utilisateur"),
]

name = models.CharField(max_length=100, verbose_name="Nom")
logo_width = models.PositiveIntegerField(blank=True, null=True, editable=False)
logo_height = models.PositiveIntegerField(blank=True, null=True, editable=False)
Expand Down
69 changes: 66 additions & 3 deletions core/tasks.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
import os

from celery import shared_task
from django.contrib.auth import get_user_model
from django.urls import reverse

from core.helpers import Mailer
from core.helpers.utils import generate_login_url
from core.helpers import Mailer, TokenHelper
from fossbadge.celery import app
from core.models import User
from core.models import User, Structure
import django

#

def generate_login_url(user, base_url=None):
"""
Generate a login url based on a user and return it
"""
token = TokenHelper.generate_user_token(user)

if base_url is None:
base_url = get_base_url()

url_path = reverse("core:user-login-from-email")

connexion_url = f"https://{base_url}{url_path}?token={token}"
return connexion_url

def get_base_url():
"""
Return the base url of the application
"""
base_url = os.environ.get("DOMAIN")
return base_url

@shared_task
def send_login_mail(recipient_email):
"""
Expand All @@ -26,6 +49,7 @@ def send_login_mail(recipient_email):
'login_link': login_link,
}

# Create and send the mail
mail = Mailer(
'Demande de connexion',
f'Voici votre lien',
Expand All @@ -35,3 +59,42 @@ def send_login_mail(recipient_email):
)
mail.send()
return True

@shared_task
def send_invite_mail(recipient_email, role, structure_pk, new_user=False):
"""
Send an invitation mail for a structure to an existing user.
"""

User = get_user_model()
user = User.objects.get(email=recipient_email)
structure = Structure.objects.get(pk=structure_pk)

# Get the tuple corresponding to the role
role_tuple = [item for item in Structure.ROLES if role in item][0]
# Generate the structure link
structure_link = f"https://{get_base_url()}{reverse('core:structure-detail', kwargs={'pk': structure.pk})}"

# HTML email things
template = 'emails/structure_invite.html'
context = {
'structure': structure,
'role' : role_tuple[1].lower(),
'structure_link' : structure_link
}

# If it's a new user, we change the template and context
if new_user:
template = 'emails/structure_invite_new_user.html'
context["login_link"] = generate_login_url(user)

# Create and send the mail
mail = Mailer(
f'Invitation dans {structure.name}',
f'Vous avez été invité dans {structure.name}',
user.email,
html_template=template,
context=context
)
mail.send()
return True
35 changes: 31 additions & 4 deletions core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@
from django.core.exceptions import ValidationError, PermissionDenied
from django.core.signing import SignatureExpired
from django.core.validators import validate_email
from django.http import HttpResponseForbidden
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.db.models import Q
from django.urls import reverse
from rest_framework import viewsets
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework.decorators import action,authentication_classes, permission_classes
from .helpers import TokenHelper
from .helpers.utils import get_or_create_user
from .helpers.utils import get_or_create_user, invite_user_to_structure
from .models import Structure, Badge, User
from .forms import BadgeForm, StructureForm, UserForm, PartialUserForm
import sweetify
Expand Down Expand Up @@ -223,7 +222,7 @@ def get_permissions(self):
permissions_list += [AllowAny]
elif self.action in ["create_association"]:
permissions_list += [IsAuthenticated]
elif self.action in ["edit", "delete"]:
elif self.action in ["edit", "delete","invite"]:
permissions_list += [IsStructureAdmin]

return [permission() for permission in permissions_list]
Expand Down Expand Up @@ -297,6 +296,7 @@ def retrieve(self, request, pk=None):
'issued_badges': issued_badges,
'is_editor': is_editor,
'is_admin': is_admin,
"roles" : Structure.ROLES
})

@action(detail=True, methods=["get","post"])
Expand Down Expand Up @@ -361,6 +361,33 @@ def create_association(self, request):
'form': form
})

@action(detail=True, methods=['post'])
def invite(self, request, pk):
"""
Invite a user to a structure.
"""
structure = get_object_or_404(Structure, pk=pk)

email = request.POST['email']
role = request.POST['role']

res = HttpResponse(headers={"HX-Redirect": reverse('core:structure-detail', kwargs={'pk': structure.pk}),})

if not any(role in item for item in Structure.ROLES):
messages.add_message(request,messages.ERROR,"Le role fourni est invalide")
return res

try:
validate_email(email)
except ValidationError:
messages.add_message(request,messages.ERROR,"Le mail est invalide")
return res

invite_user_to_structure(email, role, structure)

messages.add_message(request, messages.SUCCESS, 'Invitation envoyé !')
return res

class UserViewSet(viewsets.ViewSet):
"""
ViewSet for user-related pages.
Expand Down
47 changes: 46 additions & 1 deletion templates/core/structures/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ <h1 class="display-4 mb-4">{{ structure.name }} </h1>
<div class="col-md-4">
<a class="btn btn-success mb-4" href="{% url 'core:structure-edit' pk=structure.pk %}"> Éditer </a>
<a class="btn btn-danger mb-4" href="{% url 'core:structure-delete' pk=structure.pk %}"> Supprimer </a>
<button class="btn btn-primary mb-4" onclick="openInviteUserPopUp()">Inviter</button>
</div>
{% endif %}
</div>
Expand Down Expand Up @@ -149,7 +150,6 @@ <h3 class="card-title">Badges créés</h3>
</div>
</div>
</div>

</div>
<div class="tab-pane fade" id="structure-users" role="tabpanel" aria-labelledby="structure-users-tab">
<div class="row">
Expand Down Expand Up @@ -180,6 +180,51 @@ <h3 class="card-title">Admins</h3>
</div>


<script>



window.openInviteUserPopUp = () =>{
Swal.fire({
title: "Inviter un utilisateur",
icon: "info",
html: `
<select id="role-input" class="form-select">
<option selected>--- Sélectionnez un rôle ---</option>>
{%for role in roles%}
<option value="{{role.0}}">{{role.1}}</option>>
{%endfor%}
</select>
<input type="email" id="email-input" class="form-control mt-2">

`,
showCloseButton: true,
preConfirm: () => {
return {
"email":document.getElementById("email-input").value,
"role":document.getElementById("role-input").value
};
},
showCancelButton: true,
focusConfirm: false,
confirmButtonText: `
Envoyer
`,
cancelButtonText: `
Annuler
`,
}).then((result) => {
if (result.isConfirmed) {
htmx.ajax('POST', "{% url 'core:structure-invite' structure.pk %}", {
target: 'body',
swap: 'innerHTML',
values: result.value
});

}
});
}
</script>


{% endblock %}
20 changes: 20 additions & 0 deletions templates/emails/structure_invite.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Invitation dans {{structure.name}}</title>
<style type="text/css">
html {
-webkit-text-size-adjust: none;
-ms-text-size-adjust: none;
}

</style>
</head>
<body style="margin: 0; padding: 0;">
<p>
Vous avez été invité comme {{role}} dans la structure :
<a href="{{ structure_link }}" target="_blank">{{structure.name}}</a>
</p>
</body>
</html>
23 changes: 23 additions & 0 deletions templates/emails/structure_invite_new_user.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Invitation dans {{structure.name}}</title>
<style type="text/css">
html {
-webkit-text-size-adjust: none;
-ms-text-size-adjust: none;
}

</style>
</head>
<body style="margin: 0; padding: 0;">
<p>
Vous avez été invité sur la plateforme fossbadge, voici un <a href="{{ login_link }}">lien</a> pour vous connecter
</p>
<p>
Vous recevez ce mail, car vous avez été invité comme {{ role }} dans la structure :
<a href="{{ structure_link }}" target="_blank">{{structure.name}}</a>
</p>
</body>
</html>