mirror of
https://gitlab.com/animath/si/plateforme.git
synced 2025-06-25 12:17:27 +02: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