mirror of
				https://gitlab.com/animath/si/plateforme.git
				synced 2025-11-04 02:12:05 +01:00 
			
		
		
		
	Clone Corres2math platform
This commit is contained in:
		
							
								
								
									
										4
									
								
								apps/registration/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								apps/registration/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
default_app_config = 'registration.apps.RegistrationConfig'
 | 
			
		||||
							
								
								
									
										29
									
								
								apps/registration/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								apps/registration/admin.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django.contrib import admin
 | 
			
		||||
from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin
 | 
			
		||||
 | 
			
		||||
from .models import AdminRegistration, CoachRegistration, Registration, StudentRegistration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Registration)
 | 
			
		||||
class RegistrationAdmin(PolymorphicParentModelAdmin):
 | 
			
		||||
    child_models = (StudentRegistration, CoachRegistration, AdminRegistration,)
 | 
			
		||||
    list_display = ("user", "type", "email_confirmed",)
 | 
			
		||||
    polymorphic_list = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(StudentRegistration)
 | 
			
		||||
class StudentRegistrationAdmin(PolymorphicChildModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(CoachRegistration)
 | 
			
		||||
class CoachRegistrationAdmin(PolymorphicChildModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(AdminRegistration)
 | 
			
		||||
class AdminRegistrationAdmin(PolymorphicChildModelAdmin):
 | 
			
		||||
    pass
 | 
			
		||||
							
								
								
									
										23
									
								
								apps/registration/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								apps/registration/apps.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django.apps import AppConfig
 | 
			
		||||
from django.db.models.signals import post_save, pre_save
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RegistrationConfig(AppConfig):
 | 
			
		||||
    """
 | 
			
		||||
    Registration app contains the detail about users only.
 | 
			
		||||
    """
 | 
			
		||||
    name = 'registration'
 | 
			
		||||
 | 
			
		||||
    def ready(self):
 | 
			
		||||
        from registration.signals import create_admin_registration, invite_to_public_rooms, \
 | 
			
		||||
            set_username, send_email_link
 | 
			
		||||
        pre_save.connect(set_username, "auth.User")
 | 
			
		||||
        pre_save.connect(send_email_link, "auth.User")
 | 
			
		||||
        post_save.connect(create_admin_registration, "auth.User")
 | 
			
		||||
        post_save.connect(invite_to_public_rooms, "registration.Registration")
 | 
			
		||||
        post_save.connect(invite_to_public_rooms, "registration.StudentRegistration")
 | 
			
		||||
        post_save.connect(invite_to_public_rooms, "registration.CoachRegistration")
 | 
			
		||||
        post_save.connect(invite_to_public_rooms, "registration.AdminRegistration")
 | 
			
		||||
							
								
								
									
										17
									
								
								apps/registration/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								apps/registration/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from cas_server.auth import DjangoAuthUser  # pragma: no cover
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomAuthUser(DjangoAuthUser):  # pragma: no cover
 | 
			
		||||
    """
 | 
			
		||||
    Override Django Auth User model to define a custom Matrix username.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def attributs(self):
 | 
			
		||||
        d = super().attributs()
 | 
			
		||||
        if self.user:
 | 
			
		||||
            d["matrix_username"] = self.user.registration.matrix_username
 | 
			
		||||
            d["display_name"] = str(self.user.registration)
 | 
			
		||||
        return d
 | 
			
		||||
							
								
								
									
										26
									
								
								apps/registration/fixtures/initial.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								apps/registration/fixtures/initial.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
[
 | 
			
		||||
    {
 | 
			
		||||
        "model": "cas_server.servicepattern",
 | 
			
		||||
        "pk": 1,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "pos": 100,
 | 
			
		||||
            "name": "Plateforme du TFJM²",
 | 
			
		||||
            "pattern": "^https://tfjm.org:8448/.*$",
 | 
			
		||||
            "user_field": "matrix_username",
 | 
			
		||||
            "restrict_users": false,
 | 
			
		||||
            "proxy": true,
 | 
			
		||||
            "proxy_callback": true,
 | 
			
		||||
            "single_log_out": true,
 | 
			
		||||
            "single_log_out_callback": ""
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "cas_server.replaceattributname",
 | 
			
		||||
        "pk": 1,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "display_name",
 | 
			
		||||
            "replace": "",
 | 
			
		||||
            "service_pattern": 1
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										110
									
								
								apps/registration/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								apps/registration/forms.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django import forms
 | 
			
		||||
from django.contrib.auth.forms import UserCreationForm
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.forms import FileInput
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from .models import AdminRegistration, CoachRegistration, StudentRegistration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SignupForm(UserCreationForm):
 | 
			
		||||
    """
 | 
			
		||||
    Signup form to registers participants and coaches
 | 
			
		||||
    They can choose the role at the registration.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    role = forms.ChoiceField(
 | 
			
		||||
        label=lambda: _("role").capitalize(),
 | 
			
		||||
        choices=lambda: [
 | 
			
		||||
            ("participant", _("participant").capitalize()),
 | 
			
		||||
            ("coach", _("coach").capitalize()),
 | 
			
		||||
        ],
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def clean_email(self):
 | 
			
		||||
        """
 | 
			
		||||
        Ensure that the email address is unique.
 | 
			
		||||
        """
 | 
			
		||||
        email = self.data["email"]
 | 
			
		||||
        if User.objects.filter(email=email).exists():
 | 
			
		||||
            self.add_error("email", _("This email address is already used."))
 | 
			
		||||
        return email
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.fields["first_name"].required = True
 | 
			
		||||
        self.fields["last_name"].required = True
 | 
			
		||||
        self.fields["email"].required = True
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ('first_name', 'last_name', 'email', 'password1', 'password2', 'role',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Replace the default user form to require the first name, last name and the email.
 | 
			
		||||
    The username is always equal to the email.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.fields["first_name"].required = True
 | 
			
		||||
        self.fields["last_name"].required = True
 | 
			
		||||
        self.fields["email"].required = True
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ('first_name', 'last_name', 'email',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StudentRegistrationForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    A student can update its class, its school and if it allows Animath to contact him/her later.
 | 
			
		||||
    """
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = StudentRegistration
 | 
			
		||||
        fields = ('team', 'student_class', 'school', 'give_contact_to_animath', 'email_confirmed',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PhotoAuthorizationForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Form to send a photo authorization.
 | 
			
		||||
    """
 | 
			
		||||
    def clean_photo_authorization(self):
 | 
			
		||||
        if "photo_authorization" in self.files:
 | 
			
		||||
            file = self.files["photo_authorization"]
 | 
			
		||||
            if file.size > 2e6:
 | 
			
		||||
                raise ValidationError(_("The uploaded file size must be under 2 Mo."))
 | 
			
		||||
            if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
 | 
			
		||||
                raise ValidationError(_("The uploaded file must be a PDF, PNG of JPEG file."))
 | 
			
		||||
            return self.cleaned_data["photo_authorization"]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
        self.fields["photo_authorization"].widget = FileInput()
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = StudentRegistration
 | 
			
		||||
        fields = ('photo_authorization',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CoachRegistrationForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    A coach can tell its professional activity.
 | 
			
		||||
    """
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CoachRegistration
 | 
			
		||||
        fields = ('team', 'professional_activity', 'give_contact_to_animath', 'email_confirmed',)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminRegistrationForm(forms.ModelForm):
 | 
			
		||||
    """
 | 
			
		||||
    Admins can tell everything they want.
 | 
			
		||||
    """
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = AdminRegistration
 | 
			
		||||
        fields = ('role', 'give_contact_to_animath', 'email_confirmed',)
 | 
			
		||||
							
								
								
									
										74
									
								
								apps/registration/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								apps/registration/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
			
		||||
# Generated by Django 3.1.3 on 2020-11-04 12:05
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
import registration.models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    initial = True
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
 | 
			
		||||
        ('participation', '0001_initial'),
 | 
			
		||||
        ('contenttypes', '0002_remove_content_type_name'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='Registration',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
 | 
			
		||||
                ('give_contact_to_animath', models.BooleanField(default=False, verbose_name='Grant Animath to contact me in the future about other actions')),
 | 
			
		||||
                ('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
 | 
			
		||||
                ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_registration.registration_set+', to='contenttypes.contenttype')),
 | 
			
		||||
                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='user')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'registration',
 | 
			
		||||
                'verbose_name_plural': 'registrations',
 | 
			
		||||
            },
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='AdminRegistration',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
 | 
			
		||||
                ('role', models.TextField(verbose_name='role of the administrator')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'admin registration',
 | 
			
		||||
                'verbose_name_plural': 'admin registrations',
 | 
			
		||||
            },
 | 
			
		||||
            bases=('registration.registration',),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='StudentRegistration',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
 | 
			
		||||
                ('student_class', models.IntegerField(choices=[(12, '12th grade'), (11, '11th grade'), (10, '10th grade or lower')], verbose_name='student class')),
 | 
			
		||||
                ('school', models.CharField(max_length=255, verbose_name='school')),
 | 
			
		||||
                ('photo_authorization', models.FileField(blank=True, default='', upload_to=registration.models.get_random_filename, verbose_name='photo authorization')),
 | 
			
		||||
                ('team', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='students', to='participation.team', verbose_name='team')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'student registration',
 | 
			
		||||
                'verbose_name_plural': 'student registrations',
 | 
			
		||||
            },
 | 
			
		||||
            bases=('registration.registration',),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.CreateModel(
 | 
			
		||||
            name='CoachRegistration',
 | 
			
		||||
            fields=[
 | 
			
		||||
                ('registration_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='registration.registration')),
 | 
			
		||||
                ('professional_activity', models.TextField(verbose_name='professional activity')),
 | 
			
		||||
                ('team', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='coachs', to='participation.team', verbose_name='team')),
 | 
			
		||||
            ],
 | 
			
		||||
            options={
 | 
			
		||||
                'verbose_name': 'coach registration',
 | 
			
		||||
                'verbose_name_plural': 'coach registrations',
 | 
			
		||||
            },
 | 
			
		||||
            bases=('registration.registration',),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										2
									
								
								apps/registration/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								apps/registration/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
							
								
								
									
										199
									
								
								apps/registration/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										199
									
								
								apps/registration/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,199 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from tfjm.tokens import email_validation_token
 | 
			
		||||
from django.contrib.sites.models import Site
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.template import loader
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.crypto import get_random_string
 | 
			
		||||
from django.utils.encoding import force_bytes
 | 
			
		||||
from django.utils.http import urlsafe_base64_encode
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from polymorphic.models import PolymorphicModel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Registration(PolymorphicModel):
 | 
			
		||||
    """
 | 
			
		||||
    Registrations store extra content that are not asked in the User Model.
 | 
			
		||||
    This is specific to the role of the user, see StudentRegistration,
 | 
			
		||||
    ClassRegistration or AdminRegistration..
 | 
			
		||||
    """
 | 
			
		||||
    user = models.OneToOneField(
 | 
			
		||||
        "auth.User",
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
        verbose_name=_("user"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    give_contact_to_animath = models.BooleanField(
 | 
			
		||||
        default=False,
 | 
			
		||||
        verbose_name=_("Grant Animath to contact me in the future about other actions"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    email_confirmed = models.BooleanField(
 | 
			
		||||
        default=False,
 | 
			
		||||
        verbose_name=_("email confirmed"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def send_email_validation_link(self):
 | 
			
		||||
        """
 | 
			
		||||
        The account got created or the email got changed.
 | 
			
		||||
        Send an email that contains a link to validate the address.
 | 
			
		||||
        """
 | 
			
		||||
        subject = "[Corres2math] " + str(_("Activate your Correspondances account"))
 | 
			
		||||
        token = email_validation_token.make_token(self.user)
 | 
			
		||||
        uid = urlsafe_base64_encode(force_bytes(self.user.pk))
 | 
			
		||||
        site = Site.objects.first()
 | 
			
		||||
        message = loader.render_to_string('registration/mails/email_validation_email.txt',
 | 
			
		||||
                                          {
 | 
			
		||||
                                              'user': self.user,
 | 
			
		||||
                                              'domain': site.domain,
 | 
			
		||||
                                              'token': token,
 | 
			
		||||
                                              'uid': uid,
 | 
			
		||||
                                          })
 | 
			
		||||
        html = loader.render_to_string('registration/mails/email_validation_email.html',
 | 
			
		||||
                                       {
 | 
			
		||||
                                           'user': self.user,
 | 
			
		||||
                                           'domain': site.domain,
 | 
			
		||||
                                           'token': token,
 | 
			
		||||
                                           'uid': uid,
 | 
			
		||||
                                       })
 | 
			
		||||
        self.user.email_user(subject, message, html_message=html)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def type(self):  # pragma: no cover
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def form_class(self):  # pragma: no cover
 | 
			
		||||
        raise NotImplementedError
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def participates(self):
 | 
			
		||||
        return isinstance(self, StudentRegistration) or isinstance(self, CoachRegistration)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_admin(self):
 | 
			
		||||
        return isinstance(self, AdminRegistration) or self.user.is_superuser
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def matrix_username(self):
 | 
			
		||||
        return f"tfjm_{self.user.pk}"
 | 
			
		||||
 | 
			
		||||
    def get_absolute_url(self):
 | 
			
		||||
        return reverse_lazy("registration:user_detail", args=(self.user_id,))
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.user.first_name} {self.user.last_name}"
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("registration")
 | 
			
		||||
        verbose_name_plural = _("registrations")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_random_filename(instance, filename):
 | 
			
		||||
    return "authorization/photo/" + get_random_string(64)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StudentRegistration(Registration):
 | 
			
		||||
    """
 | 
			
		||||
    Specific registration for students.
 | 
			
		||||
    They have a team, a student class and a school.
 | 
			
		||||
    """
 | 
			
		||||
    team = models.ForeignKey(
 | 
			
		||||
        "participation.Team",
 | 
			
		||||
        related_name="students",
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        null=True,
 | 
			
		||||
        default=None,
 | 
			
		||||
        verbose_name=_("team"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    student_class = models.IntegerField(
 | 
			
		||||
        choices=[
 | 
			
		||||
            (12, _("12th grade")),
 | 
			
		||||
            (11, _("11th grade")),
 | 
			
		||||
            (10, _("10th grade or lower")),
 | 
			
		||||
        ],
 | 
			
		||||
        verbose_name=_("student class"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    school = models.CharField(
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        verbose_name=_("school"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    photo_authorization = models.FileField(
 | 
			
		||||
        verbose_name=_("photo authorization"),
 | 
			
		||||
        upload_to=get_random_filename,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        default="",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def type(self):
 | 
			
		||||
        return _("student")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def form_class(self):
 | 
			
		||||
        from registration.forms import StudentRegistrationForm
 | 
			
		||||
        return StudentRegistrationForm
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("student registration")
 | 
			
		||||
        verbose_name_plural = _("student registrations")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CoachRegistration(Registration):
 | 
			
		||||
    """
 | 
			
		||||
    Specific registration for coaches.
 | 
			
		||||
    They have a team and a professional activity.
 | 
			
		||||
    """
 | 
			
		||||
    team = models.ForeignKey(
 | 
			
		||||
        "participation.Team",
 | 
			
		||||
        related_name="coachs",
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        null=True,
 | 
			
		||||
        default=None,
 | 
			
		||||
        verbose_name=_("team"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    professional_activity = models.TextField(
 | 
			
		||||
        verbose_name=_("professional activity"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def type(self):
 | 
			
		||||
        return _("coach")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def form_class(self):
 | 
			
		||||
        from registration.forms import CoachRegistrationForm
 | 
			
		||||
        return CoachRegistrationForm
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("coach registration")
 | 
			
		||||
        verbose_name_plural = _("coach registrations")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AdminRegistration(Registration):
 | 
			
		||||
    """
 | 
			
		||||
    Specific registration for admins.
 | 
			
		||||
    They have a field to justify they status.
 | 
			
		||||
    """
 | 
			
		||||
    role = models.TextField(
 | 
			
		||||
        verbose_name=_("role of the administrator"),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def type(self):
 | 
			
		||||
        return _("admin")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def form_class(self):
 | 
			
		||||
        from registration.forms import AdminRegistrationForm
 | 
			
		||||
        return AdminRegistrationForm
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("admin registration")
 | 
			
		||||
        verbose_name_plural = _("admin registrations")
 | 
			
		||||
							
								
								
									
										16
									
								
								apps/registration/search_indexes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								apps/registration/search_indexes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from haystack import indexes
 | 
			
		||||
 | 
			
		||||
from .models import Registration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RegistrationIndex(indexes.ModelSearchIndex, indexes.Indexable):
 | 
			
		||||
    """
 | 
			
		||||
    Registrations are indexed by the user detail.
 | 
			
		||||
    """
 | 
			
		||||
    text = indexes.NgramField(document=True, use_template=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Registration
 | 
			
		||||
							
								
								
									
										56
									
								
								apps/registration/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								apps/registration/signals.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from tfjm.lists import get_sympa_client
 | 
			
		||||
from tfjm.matrix import Matrix
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
 | 
			
		||||
from .models import AdminRegistration, Registration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def set_username(instance, **_):
 | 
			
		||||
    """
 | 
			
		||||
    Ensure that the user username is always equal to the user email address.
 | 
			
		||||
    """
 | 
			
		||||
    instance.username = instance.email
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def send_email_link(instance, **_):
 | 
			
		||||
    """
 | 
			
		||||
    If the email address got changed, send a new validation link
 | 
			
		||||
     and update the registration status in the team mailing list.
 | 
			
		||||
    """
 | 
			
		||||
    if instance.pk:
 | 
			
		||||
        old_instance = User.objects.get(pk=instance.pk)
 | 
			
		||||
        if old_instance.email != instance.email:
 | 
			
		||||
            registration = Registration.objects.get(user=instance)
 | 
			
		||||
            registration.email_confirmed = False
 | 
			
		||||
            registration.save()
 | 
			
		||||
            registration.user = instance
 | 
			
		||||
            registration.send_email_validation_link()
 | 
			
		||||
 | 
			
		||||
            if registration.participates and registration.team:
 | 
			
		||||
                get_sympa_client().unsubscribe(old_instance.email, f"equipe-{registration.team.trigram.lower()}", False)
 | 
			
		||||
                get_sympa_client().subscribe(instance.email, f"equipe-{registration.team.trigram.lower()}", False,
 | 
			
		||||
                                             f"{instance.first_name} {instance.last_name}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def create_admin_registration(instance, **_):
 | 
			
		||||
    """
 | 
			
		||||
    When a super user got created through console,
 | 
			
		||||
    ensure that an admin registration is created.
 | 
			
		||||
    """
 | 
			
		||||
    if instance.is_superuser:
 | 
			
		||||
        AdminRegistration.objects.get_or_create(user=instance)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def invite_to_public_rooms(instance: Registration, created: bool, **_):
 | 
			
		||||
    """
 | 
			
		||||
    When a user got registered, automatically invite the Matrix user into public rooms.
 | 
			
		||||
    """
 | 
			
		||||
    if not created:
 | 
			
		||||
        Matrix.invite("#annonces:tfjm.org", f"@{instance.matrix_username}:tfjm.org")
 | 
			
		||||
        Matrix.invite("#faq:tfjm.org", f"@{instance.matrix_username}:tfjm.org")
 | 
			
		||||
        Matrix.invite("#je-cherche-une-equip:tfjm.org",
 | 
			
		||||
                      f"@{instance.matrix_username}:tfjm.org")
 | 
			
		||||
        Matrix.invite("#flood:tfjm.org", f"@{instance.matrix_username}:tfjm.org")
 | 
			
		||||
							
								
								
									
										27
									
								
								apps/registration/tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								apps/registration/tables.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
import django_tables2 as tables
 | 
			
		||||
 | 
			
		||||
from .models import Registration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RegistrationTable(tables.Table):
 | 
			
		||||
    """
 | 
			
		||||
    Table of all registrations.
 | 
			
		||||
    """
 | 
			
		||||
    last_name = tables.LinkColumn(
 | 
			
		||||
        'registration:user_detail',
 | 
			
		||||
        args=[tables.A("user_id")],
 | 
			
		||||
        verbose_name=lambda: _("last name").capitalize(),
 | 
			
		||||
        accessor="user__last_name",
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        attrs = {
 | 
			
		||||
            'class': 'table table condensed table-striped',
 | 
			
		||||
        }
 | 
			
		||||
        model = Registration
 | 
			
		||||
        fields = ('last_name', 'user__first_name', 'user__email', 'type',)
 | 
			
		||||
        template_name = 'django_tables2/bootstrap4.html'
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="card bg-light">
 | 
			
		||||
    <h3 class="card-header text-center">
 | 
			
		||||
        {{ title }}
 | 
			
		||||
    </h3>
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
        {% if validlink %}
 | 
			
		||||
        <p>
 | 
			
		||||
            {% trans "Your email have successfully been validated." %}
 | 
			
		||||
        </p>
 | 
			
		||||
        <p>
 | 
			
		||||
            {% blocktrans %}You can now <a href="{{ login_url }}">log in</a>.{% endblocktrans %}
 | 
			
		||||
        </p>
 | 
			
		||||
        {% else %}
 | 
			
		||||
        <p>
 | 
			
		||||
            {% if user.is_authenticated and user.registration.email_confirmed %}
 | 
			
		||||
                {% trans "The link was invalid. The token may have expired, or your account is already activated. However, your account seems to be already valid." %}
 | 
			
		||||
            {% else %}
 | 
			
		||||
                {% trans "The link was invalid. The token may have expired, or your account is already activated. Please send us an email to activate your account." %}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </p>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
     </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,18 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="card bg-light">
 | 
			
		||||
    <h3 class="card-header text-center">
 | 
			
		||||
        {% trans "Account activation" %}
 | 
			
		||||
    </h3>
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
        <p>
 | 
			
		||||
            {% trans "An email has been sent. Please click on the link to activate your account." %}
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="fr">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <title></title>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
 | 
			
		||||
<p>
 | 
			
		||||
    {% trans "Hi" %} {{ user.username }},
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<p>
 | 
			
		||||
    {% trans "You recently registered on the Correspondances platform. Please click on the link below to confirm your registration." %}
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<p>
 | 
			
		||||
    <a href="https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}">
 | 
			
		||||
        https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
 | 
			
		||||
    </a>
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<p>
 | 
			
		||||
    {% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
<p>
 | 
			
		||||
    {% trans "Thanks" %},
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
--
 | 
			
		||||
<p>
 | 
			
		||||
    {% trans "The Correspondances team." %}<br>
 | 
			
		||||
</p>
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% trans "Hi" %} {{ user.username }},
 | 
			
		||||
 | 
			
		||||
{% trans "You recently registered on the Correspondances platform. Please click on the link below to confirm your registration." %}
 | 
			
		||||
 | 
			
		||||
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
 | 
			
		||||
 | 
			
		||||
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
 | 
			
		||||
 | 
			
		||||
{% trans "Thanks" %},
 | 
			
		||||
 | 
			
		||||
{% trans "The Correspondances team." %}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>{% trans 'Your password was changed.' %}</p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n crispy_forms_tags %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <form method="post">{% csrf_token %}
 | 
			
		||||
        <p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
 | 
			
		||||
        {{ form | crispy }}
 | 
			
		||||
        <input class="btn btn-primary" type="submit" value="{% trans 'Change my password' %}">
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>{% trans "Your password has been set. You may go ahead and log in now." %}</p>
 | 
			
		||||
    <p>
 | 
			
		||||
        <a href="{{ login_url }}" class="btn btn-success">{% trans 'Log in' %}</a>
 | 
			
		||||
    </p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n crispy_forms_tags %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {% if validlink %}
 | 
			
		||||
        <p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
 | 
			
		||||
        <form method="post">{% csrf_token %}
 | 
			
		||||
            {{ form | crispy }}
 | 
			
		||||
            <input class="btn btn-primary" type="submit" value="{% trans 'Change my password' %}">
 | 
			
		||||
        </form>
 | 
			
		||||
    {% else %}
 | 
			
		||||
        <p>{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}</p>
 | 
			
		||||
    <p>{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}</p>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
{% comment %}
 | 
			
		||||
SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
{% endcomment %}
 | 
			
		||||
{% load i18n crispy_forms_tags %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>{% trans "Forgotten your password? Enter your email address below, and we'll email instructions for setting a new one." %}</p>
 | 
			
		||||
    <form method="post">{% csrf_token %}
 | 
			
		||||
        {{ form | crispy }}
 | 
			
		||||
        <input class="btn btn-primary" type="submit" value="{% trans 'Reset my password' %}">
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										44
									
								
								apps/registration/templates/registration/signup.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								apps/registration/templates/registration/signup.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
<!-- templates/signup.html -->
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% load crispy_forms_filters %}
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
{% block title %}{% trans "Sign up" %}{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <h2>{% trans "Sign up" %}</h2>
 | 
			
		||||
 | 
			
		||||
    <form method="post">
 | 
			
		||||
        {% csrf_token %}
 | 
			
		||||
        {{ form|crispy }}
 | 
			
		||||
        <div id="student_registration_form">
 | 
			
		||||
            {{ student_registration_form|crispy }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="coach_registration_form" class="d-none">
 | 
			
		||||
            {{ coach_registration_form|crispy }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <button class="btn btn-success" type="submit">
 | 
			
		||||
            {% trans "Sign up" %}
 | 
			
		||||
        </button>
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extrajavascript %}
 | 
			
		||||
    <script>
 | 
			
		||||
    $(document).ready(function() {
 | 
			
		||||
        $("#id_role").change(function() {
 | 
			
		||||
            let selected_role = $("#id_role :selected");
 | 
			
		||||
            if (selected_role.val() === "participant") {
 | 
			
		||||
                $("#student_registration_form").removeClass("d-none");
 | 
			
		||||
                $("#coach_registration_form").addClass("d-none");
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                $("#student_registration_form").addClass("d-none");
 | 
			
		||||
                $("#coach_registration_form").removeClass("d-none");
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        $("#student_registration_form :input").removeAttr("required");
 | 
			
		||||
        $("#coach_registration_form :input").removeAttr("required");
 | 
			
		||||
    });
 | 
			
		||||
    </script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										15
									
								
								apps/registration/templates/registration/update_user.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								apps/registration/templates/registration/update_user.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load crispy_forms_filters i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <form method="post">
 | 
			
		||||
        <div id="form-content">
 | 
			
		||||
            {% csrf_token %}
 | 
			
		||||
            {{ form|crispy }}
 | 
			
		||||
            {{ registration_form|crispy }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <button class="btn btn-success" type="submit">{% trans "Update" %}</button>
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load i18n static crispy_forms_filters %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <a class="btn btn-info" href="{% url "registration:user_detail" pk=object.user.pk %}"><i class="fas fa-arrow-left"></i> {% trans "Back to the user detail" %}</a>
 | 
			
		||||
    <hr>
 | 
			
		||||
    <form method="post" enctype="multipart/form-data">
 | 
			
		||||
        <div id="form-content">
 | 
			
		||||
            <div class="alert alert-info">
 | 
			
		||||
                {% trans "Authorzation templates:" %}
 | 
			
		||||
                <a class="alert-link" href="{% static "Autorisation de droit à l'image - majeur.pdf" %}">{% trans "Adult" %}</a> —
 | 
			
		||||
                <a class="alert-link" href="{% static "Autorisation de droit à l'image - mineur.pdf" %}">{% trans "Child" %}</a>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% csrf_token %}
 | 
			
		||||
            {{ form|crispy }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <button class="btn btn-success" type="submit">{% trans "Upload" %}</button>
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										98
									
								
								apps/registration/templates/registration/user_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								apps/registration/templates/registration/user_detail.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load i18n %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
{% trans "any" as any %}
 | 
			
		||||
 | 
			
		||||
    <div class="card bg-light shadow">
 | 
			
		||||
        <div class="card-header text-center">
 | 
			
		||||
            <h4>{{ user_object.first_name }} {{ user_object.last_name }}</h4>
 | 
			
		||||
        </div>
 | 
			
		||||
    <div class="card-body">
 | 
			
		||||
        <dl class="row">
 | 
			
		||||
            <dt class="col-sm-6 text-right">{% trans "Last name:" %}</dt>
 | 
			
		||||
            <dd class="col-sm-6">{{ user_object.last_name }}</dd>
 | 
			
		||||
 | 
			
		||||
            <dt class="col-sm-6 text-right">{% trans "First name:" %}</dt>
 | 
			
		||||
            <dd class="col-sm-6">{{ user_object.first_name }}</dd>
 | 
			
		||||
 | 
			
		||||
            <dt class="col-sm-6 text-right">{% trans "Email:" %}</dt>
 | 
			
		||||
            <dd class="col-sm-6"><a href="mailto:{{ user_object.email }}">{{ user_object.email }}</a>
 | 
			
		||||
                {% if not user_object.registration.email_confirmed %} (<em>{% trans "Not confirmed" %}, <a href="{% url "registration:email_validation_resend" pk=user_object.pk %}">{% trans "resend the validation link" %}</a></em>){% endif %}</dd>
 | 
			
		||||
 | 
			
		||||
            {% if user_object.registration.participates or True %}
 | 
			
		||||
                <dt class="col-sm-6 text-right">{% trans "Team:" %}</dt>
 | 
			
		||||
                {% trans "any" as any %}
 | 
			
		||||
                <dd class="col-sm-6">
 | 
			
		||||
                    <a href="{% if user_object.registration.team %}{% url "participation:team_detail" pk=user_object.registration.team.pk %}{% else %}#{% endif %}">
 | 
			
		||||
                        {{ user_object.registration.team|default:any }}
 | 
			
		||||
                    </a>
 | 
			
		||||
                </dd>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
 | 
			
		||||
            {% if user_object.registration.studentregistration %}
 | 
			
		||||
                <dt class="col-sm-6 text-right">{% trans "Student class:" %}</dt>
 | 
			
		||||
                <dd class="col-sm-6">{{ user_object.registration.get_student_class_display }}</dd>
 | 
			
		||||
 | 
			
		||||
                <dt class="col-sm-6 text-right">{% trans "School:" %}</dt>
 | 
			
		||||
                <dd class="col-sm-6">{{ user_object.registration.school }}</dd>
 | 
			
		||||
 | 
			
		||||
                <dt class="col-sm-6 text-right">{% trans "Photo authorization:" %}</dt>
 | 
			
		||||
                <dd class="col-sm-6">
 | 
			
		||||
                    {% if user_object.registration.photo_authorization %}
 | 
			
		||||
                        <a href="{{ user_object.registration.photo_authorization.url }}" data-turbolinks="false">{% trans "Download" %}</a>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    {% if user_object.pk == user.pk %}
 | 
			
		||||
                        <button class="btn btn-primary" data-toggle="modal" data-target="#uploadPhotoAuthorizationModal">{% trans "Replace" %}</button>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </dd>
 | 
			
		||||
            {% elif user_object.registration.coachregistration %}
 | 
			
		||||
                <dt class="col-sm-6 text-right">{% trans "Profesional activity:" %}</dt>
 | 
			
		||||
                <dd class="col-sm-6">{{ user_object.registration.professional_activity }}</dd>
 | 
			
		||||
            {% elif user_object.registration.adminregistration %}
 | 
			
		||||
                <dt class="col-sm-6 text-right">{% trans "Role:" %}</dt>
 | 
			
		||||
                <dd class="col-sm-6">{{ user_object.registration.role }}</dd>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
 | 
			
		||||
            <dt class="col-sm-6 text-right">{% trans "Grant Animath to contact me in the future about other actions:" %}</dt>
 | 
			
		||||
            <dd class="col-sm-6">{{ user_object.registration.give_contact_to_animath|yesno }}</dd>
 | 
			
		||||
        </dl>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% if user.pk == user_object.pk or user.registration.is_admin %}
 | 
			
		||||
        <div class="card-footer text-center">
 | 
			
		||||
            <button class="btn btn-primary" data-toggle="modal" data-target="#updateUserModal">{% trans "Update" %}</button>
 | 
			
		||||
            {% if user.registration.is_admin %}
 | 
			
		||||
                <a class="btn btn-info" href="{% url "registration:user_impersonate" pk=user_object.pk %}">{% trans "Impersonate" %}</a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {% trans "Update user" as modal_title %}
 | 
			
		||||
    {% trans "Update" as modal_button %}
 | 
			
		||||
    {% url "registration:update_user" pk=user_object.pk as modal_action %}
 | 
			
		||||
    {% include "base_modal.html" with modal_id="updateUser" %}
 | 
			
		||||
 | 
			
		||||
    {% trans "Upload photo authorization" as modal_title %}
 | 
			
		||||
    {% trans "Upload" as modal_button %}
 | 
			
		||||
    {% url "registration:upload_user_photo_authorization" pk=user_object.registration.pk as modal_action %}
 | 
			
		||||
    {% include "base_modal.html" with modal_id="uploadPhotoAuthorization" modal_enctype="multipart/form-data" %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block extrajavascript %}
 | 
			
		||||
    <script>
 | 
			
		||||
        $(document).ready(function() {
 | 
			
		||||
            $('button[data-target="#updateUserModal"]').click(function() {
 | 
			
		||||
                let modalBody = $("#updateUserModal div.modal-body");
 | 
			
		||||
                if (!modalBody.html().trim())
 | 
			
		||||
                    modalBody.load("{% url "registration:update_user" pk=user_object.pk %} #form-content");
 | 
			
		||||
            });
 | 
			
		||||
            $('button[data-target="#uploadPhotoAuthorizationModal"]').click(function() {
 | 
			
		||||
                let modalBody = $("#uploadPhotoAuthorizationModal div.modal-body");
 | 
			
		||||
                if (!modalBody.html().trim())
 | 
			
		||||
                    modalBody.load("{% url "registration:upload_user_photo_authorization" pk=user_object.registration.pk %} #form-content");
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    </script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
							
								
								
									
										7
									
								
								apps/registration/templates/registration/user_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								apps/registration/templates/registration/user_list.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
{% extends "base.html" %}
 | 
			
		||||
 | 
			
		||||
{% load django_tables2 %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {% render_table table %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,5 @@
 | 
			
		||||
{{ object.user.last_name }}
 | 
			
		||||
{{ object.user.first_name }}
 | 
			
		||||
{{ object.user.email }}
 | 
			
		||||
{{ object.type }}
 | 
			
		||||
{{ object.role }}
 | 
			
		||||
@@ -0,0 +1,7 @@
 | 
			
		||||
{{ object.user.first_name }}
 | 
			
		||||
{{ object.user.last_name }}
 | 
			
		||||
{{ object.user.email }}
 | 
			
		||||
{{ object.type }}
 | 
			
		||||
{{ object.professional_activity }}
 | 
			
		||||
{{ object.team.name }}
 | 
			
		||||
{{ object.team.trigram }}
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
{{ object.user.first_name }}
 | 
			
		||||
{{ object.user.last_name }}
 | 
			
		||||
{{ object.user.email }}
 | 
			
		||||
{{ object.type }}
 | 
			
		||||
{{ object.get_student_class_display }}
 | 
			
		||||
{{ object.school }}
 | 
			
		||||
{{ object.team.name }}
 | 
			
		||||
{{ object.team.trigram }}
 | 
			
		||||
							
								
								
									
										2
									
								
								apps/registration/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								apps/registration/templatetags/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
							
								
								
									
										28
									
								
								apps/registration/templatetags/search_results_tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								apps/registration/templatetags/search_results_tables.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django import template
 | 
			
		||||
from django_tables2 import Table
 | 
			
		||||
from participation.models import Participation, Team, Video
 | 
			
		||||
from participation.tables import ParticipationTable, TeamTable, VideoTable
 | 
			
		||||
 | 
			
		||||
from ..models import Registration
 | 
			
		||||
from ..tables import RegistrationTable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def search_table(results):
 | 
			
		||||
    model_class = results[0].object.__class__
 | 
			
		||||
    table_class = Table
 | 
			
		||||
    if issubclass(model_class, Registration):
 | 
			
		||||
        table_class = RegistrationTable
 | 
			
		||||
    elif issubclass(model_class, Team):
 | 
			
		||||
        table_class = TeamTable
 | 
			
		||||
    elif issubclass(model_class, Participation):
 | 
			
		||||
        table_class = ParticipationTable
 | 
			
		||||
    elif issubclass(model_class, Video):
 | 
			
		||||
        table_class = VideoTable
 | 
			
		||||
    return table_class([result.object for result in results], prefix=model_class._meta.model_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
register.filter("search_table", search_table)
 | 
			
		||||
							
								
								
									
										395
									
								
								apps/registration/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								apps/registration/tests.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,395 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from datetime import timedelta
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from tfjm.tokens import email_validation_token
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from django.contrib.sites.models import Site
 | 
			
		||||
from django.core.files.uploadedfile import SimpleUploadedFile
 | 
			
		||||
from django.core.management import call_command
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.encoding import force_bytes
 | 
			
		||||
from django.utils.http import urlsafe_base64_encode
 | 
			
		||||
from participation.models import Phase, Team
 | 
			
		||||
 | 
			
		||||
from .models import AdminRegistration, CoachRegistration, StudentRegistration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestIndexPage(TestCase):
 | 
			
		||||
    def test_index(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Display the index page, without any right.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("index"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
    def test_not_authenticated(self):
 | 
			
		||||
        """
 | 
			
		||||
        Try to load some pages without being authenticated.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("registration:reset_admin"))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:reset_admin"), 302, 200)
 | 
			
		||||
 | 
			
		||||
        User.objects.create()
 | 
			
		||||
        response = self.client.get(reverse("registration:user_detail", args=(1,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("registration:user_detail", args=(1,)))
 | 
			
		||||
 | 
			
		||||
        Team.objects.create()
 | 
			
		||||
        response = self.client.get(reverse("participation:team_detail", args=(1,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:team_detail", args=(1,)))
 | 
			
		||||
        response = self.client.get(reverse("participation:update_team", args=(1,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:update_team", args=(1,)))
 | 
			
		||||
        response = self.client.get(reverse("participation:create_team"))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:create_team"))
 | 
			
		||||
        response = self.client.get(reverse("participation:join_team"))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:join_team"))
 | 
			
		||||
        response = self.client.get(reverse("participation:team_authorizations", args=(1,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next="
 | 
			
		||||
                             + reverse("participation:team_authorizations", args=(1,)))
 | 
			
		||||
        response = self.client.get(reverse("participation:participation_detail", args=(1,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next="
 | 
			
		||||
                             + reverse("participation:participation_detail", args=(1,)))
 | 
			
		||||
        response = self.client.get(reverse("participation:upload_video", args=(1,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("login") + "?next=" + reverse("participation:upload_video", args=(1,)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestRegistration(TestCase):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
        self.user = User.objects.create_superuser(
 | 
			
		||||
            username="admin",
 | 
			
		||||
            password="admin",
 | 
			
		||||
            email="admin@example.com",
 | 
			
		||||
        )
 | 
			
		||||
        self.client.force_login(self.user)
 | 
			
		||||
 | 
			
		||||
        self.student = User.objects.create(email="student@example.com")
 | 
			
		||||
        StudentRegistration.objects.create(user=self.student, student_class=11, school="Earth")
 | 
			
		||||
        self.coach = User.objects.create(email="coach@example.com")
 | 
			
		||||
        CoachRegistration.objects.create(user=self.coach, professional_activity="Teacher")
 | 
			
		||||
 | 
			
		||||
    def test_admin_pages(self):
 | 
			
		||||
        """
 | 
			
		||||
        Check that admin pages are rendering successfully.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("admin:index") + "registration/registration/")
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("admin:index")
 | 
			
		||||
                                   + f"registration/registration/{self.user.registration.pk}/change/")
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        response = self.client.get(reverse("admin:index") +
 | 
			
		||||
                                   f"r/{ContentType.objects.get_for_model(AdminRegistration).id}/"
 | 
			
		||||
                                   f"{self.user.registration.pk}/")
 | 
			
		||||
        self.assertRedirects(response, "http://" + Site.objects.get().domain +
 | 
			
		||||
                             str(self.user.registration.get_absolute_url()), 302, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("admin:index")
 | 
			
		||||
                                   + f"registration/registration/{self.student.registration.pk}/change/")
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        response = self.client.get(reverse("admin:index") +
 | 
			
		||||
                                   f"r/{ContentType.objects.get_for_model(StudentRegistration).id}/"
 | 
			
		||||
                                   f"{self.student.registration.pk}/")
 | 
			
		||||
        self.assertRedirects(response, "http://" + Site.objects.get().domain +
 | 
			
		||||
                             str(self.student.registration.get_absolute_url()), 302, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("admin:index")
 | 
			
		||||
                                   + f"registration/registration/{self.coach.registration.pk}/change/")
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        response = self.client.get(reverse("admin:index") +
 | 
			
		||||
                                   f"r/{ContentType.objects.get_for_model(CoachRegistration).id}/"
 | 
			
		||||
                                   f"{self.coach.registration.pk}/")
 | 
			
		||||
        self.assertRedirects(response, "http://" + Site.objects.get().domain +
 | 
			
		||||
                             str(self.coach.registration.get_absolute_url()), 302, 200)
 | 
			
		||||
 | 
			
		||||
    def test_registration(self):
 | 
			
		||||
        """
 | 
			
		||||
        Ensure that the signup form is working successfully.
 | 
			
		||||
        """
 | 
			
		||||
        # After first phase
 | 
			
		||||
        response = self.client.get(reverse("registration:signup"))
 | 
			
		||||
        self.assertEqual(response.status_code, 403)
 | 
			
		||||
 | 
			
		||||
        Phase.objects.filter(phase_number__gte=2).update(start=timezone.now() + timedelta(days=1),
 | 
			
		||||
                                                         end=timezone.now() + timedelta(days=2))
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:signup"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        # Incomplete form
 | 
			
		||||
        response = self.client.post(reverse("registration:signup"), data=dict(
 | 
			
		||||
            last_name="Toto",
 | 
			
		||||
            first_name="Toto",
 | 
			
		||||
            email="toto@example.com",
 | 
			
		||||
            password1="azertyuiopazertyuiop",
 | 
			
		||||
            password2="azertyuiopazertyuiop",
 | 
			
		||||
            role="participant",
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse("registration:signup"), data=dict(
 | 
			
		||||
            last_name="Toto",
 | 
			
		||||
            first_name="Toto",
 | 
			
		||||
            email="toto@example.com",
 | 
			
		||||
            password1="azertyuiopazertyuiop",
 | 
			
		||||
            password2="azertyuiopazertyuiop",
 | 
			
		||||
            role="participant",
 | 
			
		||||
            student_class=12,
 | 
			
		||||
            school="God",
 | 
			
		||||
            give_contact_to_animath=False,
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
 | 
			
		||||
        self.assertTrue(User.objects.filter(email="toto@example.com").exists())
 | 
			
		||||
 | 
			
		||||
        # Email is already used
 | 
			
		||||
        response = self.client.post(reverse("registration:signup"), data=dict(
 | 
			
		||||
            last_name="Toto",
 | 
			
		||||
            first_name="Toto",
 | 
			
		||||
            email="toto@example.com",
 | 
			
		||||
            password1="azertyuiopazertyuiop",
 | 
			
		||||
            password2="azertyuiopazertyuiop",
 | 
			
		||||
            role="participant",
 | 
			
		||||
            student_class=12,
 | 
			
		||||
            school="God",
 | 
			
		||||
            give_contact_to_animath=False,
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:email_validation_sent"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse("registration:signup"), data=dict(
 | 
			
		||||
            last_name="Toto",
 | 
			
		||||
            first_name="Coach",
 | 
			
		||||
            email="coachtoto@example.com",
 | 
			
		||||
            password1="azertyuiopazertyuiop",
 | 
			
		||||
            password2="azertyuiopazertyuiop",
 | 
			
		||||
            role="coach",
 | 
			
		||||
            professional_activity="God",
 | 
			
		||||
            give_contact_to_animath=True,
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
 | 
			
		||||
        self.assertTrue(User.objects.filter(email="coachtoto@example.com").exists())
 | 
			
		||||
 | 
			
		||||
        user = User.objects.get(email="coachtoto@example.com")
 | 
			
		||||
        token = email_validation_token.make_token(user)
 | 
			
		||||
        uid = urlsafe_base64_encode(force_bytes(user.pk))
 | 
			
		||||
        response = self.client.get(reverse("registration:email_validation", kwargs=dict(uidb64=uid, token=token)))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        user.registration.refresh_from_db()
 | 
			
		||||
        self.assertTrue(user.registration.email_confirmed)
 | 
			
		||||
 | 
			
		||||
        # Token has expired
 | 
			
		||||
        response = self.client.get(reverse("registration:email_validation", kwargs=dict(uidb64=uid, token=token)))
 | 
			
		||||
        self.assertEqual(response.status_code, 400)
 | 
			
		||||
 | 
			
		||||
        # Uid does not exist
 | 
			
		||||
        response = self.client.get(reverse("registration:email_validation", kwargs=dict(uidb64=0, token="toto")))
 | 
			
		||||
        self.assertEqual(response.status_code, 400)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:email_validation_resend", args=(user.pk,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
 | 
			
		||||
 | 
			
		||||
    def test_login(self):
 | 
			
		||||
        """
 | 
			
		||||
        With a registered user, try to log in
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("login"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        self.client.logout()
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse("login"), data=dict(
 | 
			
		||||
            username="admin",
 | 
			
		||||
            password="toto",
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse("login"), data=dict(
 | 
			
		||||
            username="admin@example.com",
 | 
			
		||||
            password="admin",
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertRedirects(response, reverse("index"), 302, 200)
 | 
			
		||||
 | 
			
		||||
    def test_user_detail(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load a user detail page.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("registration:my_account_detail"))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.user.pk,)))
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
    def test_user_list(self):
 | 
			
		||||
        """
 | 
			
		||||
        Display the list of all users.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("registration:user_list"))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
    def test_update_user(self):
 | 
			
		||||
        """
 | 
			
		||||
        Update the user information, for each type of user.
 | 
			
		||||
        """
 | 
			
		||||
        # To test the modification of mailing lists
 | 
			
		||||
        from participation.models import Team
 | 
			
		||||
        self.student.registration.team = Team.objects.create(
 | 
			
		||||
            name="toto",
 | 
			
		||||
            trigram="TOT",
 | 
			
		||||
        )
 | 
			
		||||
        self.student.registration.save()
 | 
			
		||||
 | 
			
		||||
        for user, data in [(self.user, dict(role="Bot")),
 | 
			
		||||
                           (self.student, dict(student_class=11, school="Sky")),
 | 
			
		||||
                           (self.coach, dict(professional_activity="God"))]:
 | 
			
		||||
            response = self.client.get(reverse("registration:update_user", args=(user.pk,)))
 | 
			
		||||
            self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
            response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=dict(
 | 
			
		||||
                first_name="Changed",
 | 
			
		||||
                last_name="Name",
 | 
			
		||||
                email="new_" + user.email,
 | 
			
		||||
                give_contact_to_animath=True,
 | 
			
		||||
                email_confirmed=True,
 | 
			
		||||
                team_id="",
 | 
			
		||||
            ))
 | 
			
		||||
            self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
            data.update(
 | 
			
		||||
                first_name="Changed",
 | 
			
		||||
                last_name="Name",
 | 
			
		||||
                email="new_" + user.email,
 | 
			
		||||
                give_contact_to_animath=True,
 | 
			
		||||
                email_confirmed=True,
 | 
			
		||||
                team_id="",
 | 
			
		||||
            )
 | 
			
		||||
            response = self.client.post(reverse("registration:update_user", args=(user.pk,)), data=data)
 | 
			
		||||
            self.assertRedirects(response, reverse("registration:user_detail", args=(user.pk,)), 302, 200)
 | 
			
		||||
            user.refresh_from_db()
 | 
			
		||||
            self.assertEqual(user.email, user.username)
 | 
			
		||||
            self.assertFalse(user.registration.email_confirmed)
 | 
			
		||||
            self.assertEqual(user.first_name, "Changed")
 | 
			
		||||
 | 
			
		||||
    def test_upload_photo_authorization(self):
 | 
			
		||||
        """
 | 
			
		||||
        Try to upload a photo authorization.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("registration:upload_user_photo_authorization",
 | 
			
		||||
                                           args=(self.student.registration.pk,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        # README is not a valid PDF file
 | 
			
		||||
        response = self.client.post(reverse("registration:upload_user_photo_authorization",
 | 
			
		||||
                                            args=(self.student.registration.pk,)), data=dict(
 | 
			
		||||
            photo_authorization=open("README.md", "rb"),
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        # Don't send too large files
 | 
			
		||||
        response = self.client.post(reverse("registration:upload_user_photo_authorization",
 | 
			
		||||
                                            args=(self.student.registration.pk,)), data=dict(
 | 
			
		||||
            photo_authorization=SimpleUploadedFile("file.pdf", content=int(0).to_bytes(2000001, "big"),
 | 
			
		||||
                                                   content_type="application/pdf"),
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        response = self.client.post(reverse("registration:upload_user_photo_authorization",
 | 
			
		||||
                                            args=(self.student.registration.pk,)), data=dict(
 | 
			
		||||
            photo_authorization=open("tfjm/static/Autorisation de droit à l'image - majeur.pdf", "rb"),
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
 | 
			
		||||
 | 
			
		||||
        self.student.registration.refresh_from_db()
 | 
			
		||||
        self.assertTrue(self.student.registration.photo_authorization)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("photo_authorization",
 | 
			
		||||
                                           args=(self.student.registration.photo_authorization.name.split('/')[-1],)))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
 | 
			
		||||
        from participation.models import Team
 | 
			
		||||
        team = Team.objects.create(name="Test", trigram="TES")
 | 
			
		||||
        self.student.registration.team = team
 | 
			
		||||
        self.student.registration.save()
 | 
			
		||||
        response = self.client.get(reverse("participation:team_authorizations", args=(team.pk,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertEqual(response["content-type"], "application/zip")
 | 
			
		||||
 | 
			
		||||
        # Do it twice, ensure that the previous authorization got deleted
 | 
			
		||||
        old_authoratization = self.student.registration.photo_authorization.path
 | 
			
		||||
        response = self.client.post(reverse("registration:upload_user_photo_authorization",
 | 
			
		||||
                                            args=(self.student.registration.pk,)), data=dict(
 | 
			
		||||
            photo_authorization=open("tfjm/static/Autorisation de droit à l'image - majeur.pdf", "rb"),
 | 
			
		||||
        ))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
 | 
			
		||||
        self.assertFalse(os.path.isfile(old_authoratization))
 | 
			
		||||
 | 
			
		||||
        self.student.registration.refresh_from_db()
 | 
			
		||||
        self.student.registration.photo_authorization.delete()
 | 
			
		||||
 | 
			
		||||
    def test_user_detail_forbidden(self):
 | 
			
		||||
        """
 | 
			
		||||
        Create a new user and ensure that it can't see the detail of another user.
 | 
			
		||||
        """
 | 
			
		||||
        self.client.force_login(self.coach)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:user_detail", args=(self.user.pk,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 403)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:update_user", args=(self.user.pk,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 403)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("registration:upload_user_photo_authorization", args=(self.user.pk,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 403)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("photo_authorization", args=("inexisting-authorization",)))
 | 
			
		||||
        self.assertEqual(response.status_code, 404)
 | 
			
		||||
 | 
			
		||||
        with open("media/authorization/photo/example", "w") as f:
 | 
			
		||||
            f.write("I lost the game.")
 | 
			
		||||
        self.student.registration.photo_authorization = "authorization/photo/example"
 | 
			
		||||
        self.student.registration.save()
 | 
			
		||||
        response = self.client.get(reverse("photo_authorization", args=("example",)))
 | 
			
		||||
        self.assertEqual(response.status_code, 403)
 | 
			
		||||
        os.remove("media/authorization/photo/example")
 | 
			
		||||
 | 
			
		||||
    def test_impersonate(self):
 | 
			
		||||
        """
 | 
			
		||||
        Admin can impersonate other people to act as them.
 | 
			
		||||
        """
 | 
			
		||||
        response = self.client.get(reverse("registration:user_impersonate", args=(0x7ffff42ff,)))
 | 
			
		||||
        self.assertEqual(response.status_code, 404)
 | 
			
		||||
 | 
			
		||||
        # Impersonate student account
 | 
			
		||||
        response = self.client.get(reverse("registration:user_impersonate", args=(self.student.pk,)))
 | 
			
		||||
        self.assertRedirects(response, reverse("registration:user_detail", args=(self.student.pk,)), 302, 200)
 | 
			
		||||
        self.assertEqual(self.client.session["_fake_user_id"], self.student.id)
 | 
			
		||||
 | 
			
		||||
        # Reset admin view
 | 
			
		||||
        response = self.client.get(reverse("registration:reset_admin"))
 | 
			
		||||
        self.assertRedirects(response, reverse("index"), 302, 200)
 | 
			
		||||
        self.assertFalse("_fake_user_id" in self.client.session)
 | 
			
		||||
 | 
			
		||||
    def test_research(self):
 | 
			
		||||
        """
 | 
			
		||||
        Try to search some things.
 | 
			
		||||
        """
 | 
			
		||||
        call_command("rebuild_index", "--noinput", "-v", 0)
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("haystack_search") + "?q=" + self.user.email)
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertTrue(response.context["object_list"])
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("haystack_search") + "?q=" +
 | 
			
		||||
                                   str(self.coach.registration.professional_activity))
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertTrue(response.context["object_list"])
 | 
			
		||||
 | 
			
		||||
        response = self.client.get(reverse("haystack_search") + "?q=" +
 | 
			
		||||
                                   self.student.registration.get_student_class_display())
 | 
			
		||||
        self.assertEqual(response.status_code, 200)
 | 
			
		||||
        self.assertTrue(response.context["object_list"])
 | 
			
		||||
							
								
								
									
										26
									
								
								apps/registration/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								apps/registration/urls.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
from django.urls import path
 | 
			
		||||
 | 
			
		||||
from .views import MyAccountDetailView, ResetAdminView, SignupView, UserDetailView, UserImpersonateView, \
 | 
			
		||||
    UserListView, UserResendValidationEmailView, UserUpdateView, UserUploadPhotoAuthorizationView, UserValidateView, \
 | 
			
		||||
    UserValidationEmailSentView
 | 
			
		||||
 | 
			
		||||
app_name = "registration"
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("signup/", SignupView.as_view(), name="signup"),
 | 
			
		||||
    path('validate_email/sent/', UserValidationEmailSentView.as_view(), name='email_validation_sent'),
 | 
			
		||||
    path('validate_email/resend/<int:pk>/', UserResendValidationEmailView.as_view(),
 | 
			
		||||
         name='email_validation_resend'),
 | 
			
		||||
    path('validate_email/<uidb64>/<token>/', UserValidateView.as_view(), name='email_validation'),
 | 
			
		||||
    path("user/", MyAccountDetailView.as_view(), name="my_account_detail"),
 | 
			
		||||
    path("user/<int:pk>/", UserDetailView.as_view(), name="user_detail"),
 | 
			
		||||
    path("user/<int:pk>/update/", UserUpdateView.as_view(), name="update_user"),
 | 
			
		||||
    path("user/<int:pk>/upload-photo-authorization/", UserUploadPhotoAuthorizationView.as_view(),
 | 
			
		||||
         name="upload_user_photo_authorization"),
 | 
			
		||||
    path("user/<int:pk>/impersonate/", UserImpersonateView.as_view(), name="user_impersonate"),
 | 
			
		||||
    path("user/list/", UserListView.as_view(), name="user_list"),
 | 
			
		||||
    path("reset-admin/", ResetAdminView.as_view(), name="reset_admin"),
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										319
									
								
								apps/registration/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										319
									
								
								apps/registration/views.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,319 @@
 | 
			
		||||
# Copyright (C) 2020 by Animath
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from tfjm.tokens import email_validation_token
 | 
			
		||||
from tfjm.views import AdminMixin
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.mixins import LoginRequiredMixin
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.core.exceptions import PermissionDenied, ValidationError
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
from django.http import FileResponse, Http404
 | 
			
		||||
from django.shortcuts import redirect, resolve_url
 | 
			
		||||
from django.urls import reverse_lazy
 | 
			
		||||
from django.utils.http import urlsafe_base64_decode
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.views.generic import CreateView, DetailView, RedirectView, TemplateView, UpdateView, View
 | 
			
		||||
from django_tables2 import SingleTableView
 | 
			
		||||
from magic import Magic
 | 
			
		||||
from participation.models import Phase
 | 
			
		||||
 | 
			
		||||
from .forms import CoachRegistrationForm, PhotoAuthorizationForm, SignupForm, StudentRegistrationForm, UserForm
 | 
			
		||||
from .models import Registration, StudentRegistration
 | 
			
		||||
from .tables import RegistrationTable
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SignupView(CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Signup, as a participant or a coach.
 | 
			
		||||
    """
 | 
			
		||||
    model = User
 | 
			
		||||
    form_class = SignupForm
 | 
			
		||||
    template_name = "registration/signup.html"
 | 
			
		||||
    extra_context = dict(title=_("Sign up"))
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        The signup view is available only during the first phase.
 | 
			
		||||
        """
 | 
			
		||||
        current_phase = Phase.current_phase()
 | 
			
		||||
        if not current_phase or current_phase.phase_number >= 2:
 | 
			
		||||
            raise PermissionDenied(_("You can't register now."))
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data()
 | 
			
		||||
 | 
			
		||||
        context["student_registration_form"] = StudentRegistrationForm(self.request.POST or None)
 | 
			
		||||
        context["coach_registration_form"] = CoachRegistrationForm(self.request.POST or None)
 | 
			
		||||
 | 
			
		||||
        del context["student_registration_form"].fields["team"]
 | 
			
		||||
        del context["student_registration_form"].fields["email_confirmed"]
 | 
			
		||||
        del context["coach_registration_form"].fields["team"]
 | 
			
		||||
        del context["coach_registration_form"].fields["email_confirmed"]
 | 
			
		||||
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        role = form.cleaned_data["role"]
 | 
			
		||||
        if role == "participant":
 | 
			
		||||
            registration_form = StudentRegistrationForm(self.request.POST)
 | 
			
		||||
        else:
 | 
			
		||||
            registration_form = CoachRegistrationForm(self.request.POST)
 | 
			
		||||
        del registration_form.fields["team"]
 | 
			
		||||
        del registration_form.fields["email_confirmed"]
 | 
			
		||||
 | 
			
		||||
        if not registration_form.is_valid():
 | 
			
		||||
            return self.form_invalid(form)
 | 
			
		||||
 | 
			
		||||
        ret = super().form_valid(form)
 | 
			
		||||
        registration = registration_form.instance
 | 
			
		||||
        registration.user = form.instance
 | 
			
		||||
        registration.save()
 | 
			
		||||
 | 
			
		||||
        return ret
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self):
 | 
			
		||||
        return reverse_lazy("registration:email_validation_sent")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserValidateView(TemplateView):
 | 
			
		||||
    """
 | 
			
		||||
    A view to validate the email address.
 | 
			
		||||
    """
 | 
			
		||||
    title = _("Email validation")
 | 
			
		||||
    template_name = 'registration/email_validation_complete.html'
 | 
			
		||||
    extra_context = dict(title=_("Validate email"))
 | 
			
		||||
 | 
			
		||||
    def get(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        With a given token and user id (in params), validate the email address.
 | 
			
		||||
        """
 | 
			
		||||
        assert 'uidb64' in kwargs and 'token' in kwargs
 | 
			
		||||
 | 
			
		||||
        self.validlink = False
 | 
			
		||||
        user = self.get_user(kwargs['uidb64'])
 | 
			
		||||
        token = kwargs['token']
 | 
			
		||||
 | 
			
		||||
        # Validate the token
 | 
			
		||||
        if user is not None and email_validation_token.check_token(user, token):
 | 
			
		||||
            self.validlink = True
 | 
			
		||||
            user.registration.email_confirmed = True
 | 
			
		||||
            user.registration.save()
 | 
			
		||||
        return self.render_to_response(self.get_context_data(), status=200 if self.validlink else 400)
 | 
			
		||||
 | 
			
		||||
    def get_user(self, uidb64):
 | 
			
		||||
        """
 | 
			
		||||
        Get user from the base64-encoded string.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            # urlsafe_base64_decode() decodes to bytestring
 | 
			
		||||
            uid = urlsafe_base64_decode(uidb64).decode()
 | 
			
		||||
            user = User.objects.get(pk=uid)
 | 
			
		||||
        except (TypeError, ValueError, OverflowError, User.DoesNotExist, ValidationError):
 | 
			
		||||
            user = None
 | 
			
		||||
        return user
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context['user_object'] = self.get_user(self.kwargs["uidb64"])
 | 
			
		||||
        context['login_url'] = resolve_url(settings.LOGIN_URL)
 | 
			
		||||
        if self.validlink:
 | 
			
		||||
            context['validlink'] = True
 | 
			
		||||
        else:
 | 
			
		||||
            context.update({
 | 
			
		||||
                'title': _('Email validation unsuccessful'),
 | 
			
		||||
                'validlink': False,
 | 
			
		||||
            })
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserValidationEmailSentView(TemplateView):
 | 
			
		||||
    """
 | 
			
		||||
    Display the information that the validation link has been sent.
 | 
			
		||||
    """
 | 
			
		||||
    template_name = 'registration/email_validation_email_sent.html'
 | 
			
		||||
    extra_context = dict(title=_('Email validation email sent'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserResendValidationEmailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    Rensend the email validation link.
 | 
			
		||||
    """
 | 
			
		||||
    model = User
 | 
			
		||||
    extra_context = dict(title=_("Resend email validation link"))
 | 
			
		||||
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        user = self.get_object()
 | 
			
		||||
        user.registration.send_email_validation_link()
 | 
			
		||||
        return redirect('registration:email_validation_sent')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyAccountDetailView(LoginRequiredMixin, RedirectView):
 | 
			
		||||
    """
 | 
			
		||||
    Redirect to our own profile detail page.
 | 
			
		||||
    """
 | 
			
		||||
    def get_redirect_url(self, *args, **kwargs):
 | 
			
		||||
        return reverse_lazy("registration:user_detail", args=(self.request.user.pk,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    Display the detail about a user.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    model = User
 | 
			
		||||
    context_object_name = "user_object"
 | 
			
		||||
    template_name = "registration/user_detail.html"
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        if not user.is_authenticated:
 | 
			
		||||
            return self.handle_no_permission()
 | 
			
		||||
        # Only an admin or the concerned user can see the information
 | 
			
		||||
        if not user.registration.is_admin and user.pk != kwargs["pk"]:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context["title"] = _("Detail of user {user}").format(user=str(self.object.registration))
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserListView(AdminMixin, SingleTableView):
 | 
			
		||||
    """
 | 
			
		||||
    Display the list of all registered users.
 | 
			
		||||
    """
 | 
			
		||||
    model = Registration
 | 
			
		||||
    table_class = RegistrationTable
 | 
			
		||||
    template_name = "registration/user_list.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserUpdateView(LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    Update the detail about a user and its registration.
 | 
			
		||||
    """
 | 
			
		||||
    model = User
 | 
			
		||||
    form_class = UserForm
 | 
			
		||||
    template_name = "registration/update_user.html"
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        if not user.registration.is_admin and user.pk != kwargs["pk"]:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        user = self.get_object()
 | 
			
		||||
        context["title"] = _("Update user {user}").format(user=str(self.object.registration))
 | 
			
		||||
        context["registration_form"] = user.registration.form_class(data=self.request.POST or None,
 | 
			
		||||
                                                                    instance=self.object.registration)
 | 
			
		||||
        if not user.registration.is_admin:
 | 
			
		||||
            if "team" in context["registration_form"].fields:
 | 
			
		||||
                del context["registration_form"].fields["team"]
 | 
			
		||||
            del context["registration_form"].fields["email_confirmed"]
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        user = form.instance
 | 
			
		||||
        registration_form = user.registration.form_class(data=self.request.POST or None,
 | 
			
		||||
                                                         instance=self.object.registration)
 | 
			
		||||
        if not user.registration.is_admin:
 | 
			
		||||
            if "team" in registration_form.fields:
 | 
			
		||||
                del registration_form.fields["team"]
 | 
			
		||||
            del registration_form.fields["email_confirmed"]
 | 
			
		||||
 | 
			
		||||
        if not registration_form.is_valid():
 | 
			
		||||
            return self.form_invalid(form)
 | 
			
		||||
 | 
			
		||||
        registration_form.save()
 | 
			
		||||
        return super().form_valid(form)
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self):
 | 
			
		||||
        return reverse_lazy("registration:user_detail", args=(self.object.pk,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserUploadPhotoAuthorizationView(LoginRequiredMixin, UpdateView):
 | 
			
		||||
    """
 | 
			
		||||
    A participant can send its photo authorization.
 | 
			
		||||
    """
 | 
			
		||||
    model = StudentRegistration
 | 
			
		||||
    form_class = PhotoAuthorizationForm
 | 
			
		||||
    template_name = "registration/upload_photo_authorization.html"
 | 
			
		||||
    extra_context = dict(title=_("Upload photo authorization"))
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        if not user.registration.is_admin and user.registration.pk != kwargs["pk"]:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        old_instance = StudentRegistration.objects.get(pk=self.object.pk)
 | 
			
		||||
        if old_instance.photo_authorization:
 | 
			
		||||
            old_instance.photo_authorization.delete()
 | 
			
		||||
        return super().form_valid(form)
 | 
			
		||||
 | 
			
		||||
    def get_success_url(self):
 | 
			
		||||
        return reverse_lazy("registration:user_detail", args=(self.object.user.pk,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PhotoAuthorizationView(LoginRequiredMixin, View):
 | 
			
		||||
    """
 | 
			
		||||
    Display the sent photo authorization.
 | 
			
		||||
    """
 | 
			
		||||
    def get(self, request, *args, **kwargs):
 | 
			
		||||
        filename = kwargs["filename"]
 | 
			
		||||
        path = f"media/authorization/photo/{filename}"
 | 
			
		||||
        if not os.path.exists(path):
 | 
			
		||||
            raise Http404
 | 
			
		||||
        student = StudentRegistration.objects.get(photo_authorization__endswith=filename)
 | 
			
		||||
        user = request.user
 | 
			
		||||
        if not user.registration.is_admin and user.pk != student.user.pk:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
        # Guess mime type of the file
 | 
			
		||||
        mime = Magic(mime=True)
 | 
			
		||||
        mime_type = mime.from_file(path)
 | 
			
		||||
        ext = mime_type.split("/")[1].replace("jpeg", "jpg")
 | 
			
		||||
        # Replace file name
 | 
			
		||||
        true_file_name = _("Photo authorization of {student}.{ext}").format(student=str(student), ext=ext)
 | 
			
		||||
        return FileResponse(open(path, "rb"), content_type=mime_type, filename=true_file_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UserImpersonateView(LoginRequiredMixin, RedirectView):
 | 
			
		||||
    """
 | 
			
		||||
    An administrator can log in through this page as someone else, and act as this other person.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        if self.request.user.registration.is_admin:
 | 
			
		||||
            if not User.objects.filter(pk=kwargs["pk"]).exists():
 | 
			
		||||
                raise Http404
 | 
			
		||||
            session = request.session
 | 
			
		||||
            session["admin"] = request.user.pk
 | 
			
		||||
            session["_fake_user_id"] = kwargs["pk"]
 | 
			
		||||
        return super().dispatch(request, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    def get_redirect_url(self, *args, **kwargs):
 | 
			
		||||
        return reverse_lazy("registration:user_detail", args=(kwargs["pk"],))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ResetAdminView(LoginRequiredMixin, View):
 | 
			
		||||
    """
 | 
			
		||||
    Return to admin view, clear the session field that let an administrator to log in as someone else.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def dispatch(self, request, *args, **kwargs):
 | 
			
		||||
        user = request.user
 | 
			
		||||
        if not user.is_authenticated:
 | 
			
		||||
            return self.handle_no_permission()
 | 
			
		||||
        if "_fake_user_id" in request.session:
 | 
			
		||||
            del request.session["_fake_user_id"]
 | 
			
		||||
        return redirect(request.GET.get("path", reverse_lazy("index")))
 | 
			
		||||
		Reference in New Issue
	
	Block a user