1
0
mirror of https://gitlab.com/animath/si/plateforme-corres2math.git synced 2025-06-24 11:48:46 +02:00

Add a lot of comments

This commit is contained in:
Yohann D'ANELLO
2020-10-30 19:46:46 +01:00
parent 971169fe2c
commit 8236a9fe14
16 changed files with 491 additions and 7 deletions

View File

@ -3,6 +3,9 @@ from django.db.models.signals import post_save, pre_delete, pre_save
class ParticipationConfig(AppConfig):
"""
The participation app contains the data about the teams, videos, ...
"""
name = 'participation'
def ready(self):

View File

@ -9,6 +9,10 @@ from .models import Participation, Phase, Team, Video
class TeamForm(forms.ModelForm):
"""
Form to create a team, with the name and the trigram,
and if the team accepts that Animath diffuse the videos.
"""
def clean_trigram(self):
trigram = self.cleaned_data["trigram"].upper()
if not re.match("[A-Z]{3}", trigram):
@ -21,6 +25,9 @@ class TeamForm(forms.ModelForm):
class JoinTeamForm(forms.ModelForm):
"""
Form to join a team by the access code.
"""
def clean_access_code(self):
access_code = self.cleaned_data["access_code"]
if not Team.objects.filter(access_code=access_code).exists():
@ -40,12 +47,18 @@ class JoinTeamForm(forms.ModelForm):
class ParticipationForm(forms.ModelForm):
"""
Form to update the problem of a team participation.
"""
class Meta:
model = Participation
fields = ('problem',)
class RequestValidationForm(forms.Form):
"""
Form to ask about validation.
"""
_form_type = forms.CharField(
initial="RequestValidationForm",
widget=forms.HiddenInput(),
@ -58,6 +71,9 @@ class RequestValidationForm(forms.Form):
class ValidateParticipationForm(forms.Form):
"""
Form to let administrators to accept or refuse a team.
"""
_form_type = forms.CharField(
initial="ValidateParticipationForm",
widget=forms.HiddenInput(),
@ -70,6 +86,9 @@ class ValidateParticipationForm(forms.Form):
class UploadVideoForm(forms.ModelForm):
"""
Form to upload a video, for a solution or a synthesis.
"""
class Meta:
model = Video
fields = ('link',)
@ -81,6 +100,9 @@ class UploadVideoForm(forms.ModelForm):
class PhaseForm(forms.ModelForm):
"""
Form to update the calendar of a phase.
"""
class Meta:
model = Phase
fields = ('start', 'end',)

View File

@ -17,6 +17,10 @@ from nio import RoomPreset, RoomVisibility
class Team(models.Model):
"""
The Team model represents a real team that participates to the Correspondances.
This only includes the registration detail.
"""
name = models.CharField(
max_length=255,
verbose_name=_("name"),
@ -45,9 +49,15 @@ class Team(models.Model):
@property
def email(self):
"""
:return: The mailing list to contact the team members.
"""
return f"equipe-{self.trigram.lower()}@{os.getenv('SYMPA_HOST', 'localhost')}"
def create_mailing_list(self):
"""
Create a new Sympa mailing list to contact the team.
"""
get_sympa_client().create_list(
f"equipe-{self.trigram.lower()}",
f"Équipe {self.name} ({self.trigram})",
@ -58,10 +68,15 @@ class Team(models.Model):
)
def delete_mailing_list(self):
"""
Drop the Sympa mailing list, if the team is empty or if the trigram changed.
"""
get_sympa_client().delete_list(f"equipe-{self.trigram}")
def save(self, *args, **kwargs):
if not self.access_code:
# if the team got created, generate the access code, create the contact mailing list
# and create a dedicated Matrix room.
self.access_code = get_random_string(6)
self.create_mailing_list()
@ -90,6 +105,10 @@ class Team(models.Model):
class Participation(models.Model):
"""
The Participation model contains all data that are related to the participation:
chosen problem, validity status, videos,...
"""
team = models.OneToOneField(
Team,
on_delete=models.CASCADE,
@ -149,6 +168,9 @@ class Participation(models.Model):
class Video(models.Model):
"""
The Video model only contains a link and a validity status.
"""
link = models.URLField(
verbose_name=_("link"),
help_text=_("The full video link."),
@ -163,23 +185,38 @@ class Video(models.Model):
@property
def participation(self):
"""
Retrives the participation that is associated to this video,
whatever it is a solution or a synthesis.
"""
try:
# If this is a solution
return self.participation_solution
except ObjectDoesNotExist:
# If this is a synthesis
return self.participation_synthesis
@property
def platform(self):
"""
According to the link, retrieve the platform that is used to upload the video.
"""
if "youtube.com" in self.link or "youtu.be" in self.link:
return "youtube"
return "unknown"
@property
def youtube_code(self):
"""
If the video is uploaded on Youtube, search in the URL the video code.
"""
return re.compile("(https?://|)(www\\.|)(youtube\\.com/watch\\?v=|youtu\\.be/)([a-zA-Z0-9-_]*)?.*?")\
.match("https://www.youtube.com/watch?v=73nsrixx7eI").group(4)
def as_iframe(self):
"""
Generate the HTML code to embed the video in an iframe, according to the type of the host platform.
"""
if self.platform == "youtube":
return render_to_string("participation/youtube_iframe.html", context=dict(youtube_code=self.youtube_code))
return None
@ -194,6 +231,9 @@ class Video(models.Model):
class Phase(models.Model):
"""
The Phase model corresponds to the dates of the phase.
"""
phase_number = models.AutoField(
primary_key=True,
unique=True,
@ -217,6 +257,9 @@ class Phase(models.Model):
@classmethod
def current_phase(cls):
"""
Retrieve the current phase of this day
"""
qs = Phase.objects.filter(start__lte=timezone.now(), end__gte=timezone.now())
if qs.exists():
return qs.get()

View File

@ -4,6 +4,9 @@ from .models import Participation, Team, Video
class TeamIndex(indexes.ModelSearchIndex, indexes.Indexable):
"""
Index all teams by their name and trigram.
"""
text = indexes.NgramField(document=True, use_template=True)
class Meta:
@ -11,6 +14,9 @@ class TeamIndex(indexes.ModelSearchIndex, indexes.Indexable):
class ParticipationIndex(indexes.ModelSearchIndex, indexes.Indexable):
"""
Index all participations by their team name and team trigram.
"""
text = indexes.NgramField(document=True, use_template=True)
class Meta:
@ -18,6 +24,9 @@ class ParticipationIndex(indexes.ModelSearchIndex, indexes.Indexable):
class VideoIndex(indexes.ModelSearchIndex, indexes.Indexable):
"""
Index all teams by their team name and team trigram.
"""
text = indexes.NgramField(document=True, use_template=True)
class Meta:

View File

@ -3,6 +3,9 @@ from participation.models import Participation, Team, Video
def create_team_participation(instance, **_):
"""
When a team got created, create an associated team and create Video objects.
"""
participation = Participation.objects.get_or_create(team=instance)[0]
if not participation.solution:
participation.solution = Video.objects.create()
@ -12,11 +15,17 @@ def create_team_participation(instance, **_):
def update_mailing_list(instance: Team, **_):
"""
When a team name or trigram got updated, update mailing lists and Matrix rooms
"""
if instance.pk:
old_team = Team.objects.get(pk=instance.pk)
if old_team.name != instance.name or old_team.trigram != instance.trigram:
# TODO Rename Matrix room
# Delete old mailing list, create a new one
old_team.delete_mailing_list()
instance.create_mailing_list()
# Subscribe all team members in the mailing list
for student in instance.students.all():
get_sympa_client().subscribe(student.user.email, f"equipe-{instance.trigram.lower()}", False,
f"{student.user.first_name} {student.user.last_name}")

View File

@ -34,6 +34,9 @@ class TestStudentParticipation(TestCase):
str(self.team.participation)
def test_create_team(self):
"""
Try to create a team.
"""
response = self.client.get(reverse("participation:create_team"))
self.assertEqual(response.status_code, 200)
@ -61,6 +64,9 @@ class TestStudentParticipation(TestCase):
))
def test_join_team(self):
"""
Try to join an existing team.
"""
response = self.client.get(reverse("participation:join_team"))
self.assertEqual(response.status_code, 200)
@ -84,10 +90,16 @@ class TestStudentParticipation(TestCase):
self.assertEqual(response.status_code, 403)
def test_no_myteam_redirect_noteam(self):
"""
Test redirection.
"""
response = self.client.get(reverse("participation:my_team_detail"))
self.assertTrue(response.status_code, 200)
def test_team_detail(self):
"""
Try to display the information of a team.
"""
self.user.registration.team = self.team
self.user.registration.save()
@ -98,6 +110,9 @@ class TestStudentParticipation(TestCase):
self.assertEqual(response.status_code, 200)
def test_update_team(self):
"""
Try to update team information.
"""
self.user.registration.team = self.team
self.user.registration.save()
@ -123,10 +138,16 @@ class TestStudentParticipation(TestCase):
self.assertTrue(Team.objects.filter(trigram="BBB", participation__problem=3).exists())
def test_no_myparticipation_redirect_nomyparticipation(self):
"""
Ensure a permission denied when we search my team participation when we are in no team.
"""
response = self.client.get(reverse("participation:my_participation_detail"))
self.assertTrue(response.status_code, 200)
self.assertEqual(response.status_code, 403)
def test_participation_detail(self):
"""
Try to display the detail of a team participation.
"""
self.user.registration.team = self.team
self.user.registration.save()
@ -146,6 +167,9 @@ class TestStudentParticipation(TestCase):
self.assertEqual(response.status_code, 200)
def test_upload_video(self):
"""
Try to send a solution video link.
"""
self.user.registration.team = self.team
self.user.registration.save()
@ -178,21 +202,30 @@ class TestAdminForbidden(TestCase):
self.client.force_login(self.user)
def test_create_team_forbidden(self):
"""
Ensure that an admin can't create a team.
"""
response = self.client.post(reverse("participation:create_team"), data=dict(
name="Test team",
trigram="TES",
grant_animath_access_videos=False,
))
self.assertTrue(response.status_code, 200)
self.assertEqual(response.status_code, 403)
def test_join_team_forbidden(self):
"""
Ensure that an admin can't join a team.
"""
team = Team.objects.create(name="Test", trigram="TES")
response = self.client.post(reverse("participation:join_team"), data=dict(
access_code=team.access_code,
))
self.assertTrue(response.status_code, 200)
self.assertTrue(response.status_code, 403)
def test_my_team_forbidden(self):
"""
Ensure that an admin can't access to "My team".
"""
response = self.client.get(reverse("participation:my_team_detail"))
self.assertTrue(response.status_code, 200)
self.assertEqual(response.status_code, 403)

View File

@ -27,6 +27,10 @@ from .tables import CalendarTable
class CreateTeamView(LoginRequiredMixin, CreateView):
"""
Display the page to create a team for new users.
"""
model = Team
form_class = TeamForm
extra_context = dict(title=_("Create team"))
@ -43,14 +47,24 @@ class CreateTeamView(LoginRequiredMixin, CreateView):
@transaction.atomic
def form_valid(self, form):
"""
When a team is about to be created, the user automatically
joins the team, a mailing list got created and the user is
automatically subscribed to this mailing list, and finally
a Matrix room is created and the user is invited in this room.
"""
ret = super().form_valid(form)
# The user joins the team
user = self.request.user
registration = user.registration
registration.team = form.instance
registration.save()
# Subscribe the user mail address to the team mailing list
get_sympa_client().subscribe(user.email, f"equipe-{form.instance.trigram.lower()}", False,
f"{user.first_name} {user.last_name}")
# Invite the user in the team Matrix room
Matrix.invite(f"#team-{form.instance.trigram.lower()}:correspondances-maths.fr",
f"@{user.registration.matrix_username}:correspondances-maths.fr")
return ret
@ -60,6 +74,9 @@ class CreateTeamView(LoginRequiredMixin, CreateView):
class JoinTeamView(LoginRequiredMixin, FormView):
"""
Participants can join a team with the access code of the team.
"""
model = Team
form_class = JoinTeamForm
extra_context = dict(title=_("Join team"))
@ -76,15 +93,24 @@ class JoinTeamView(LoginRequiredMixin, FormView):
@transaction.atomic
def form_valid(self, form):
"""
When a user joins a team, the user is automatically subscribed to
the team mailing list,the user is invited in the team Matrix room.
"""
self.object = form.instance
ret = super().form_valid(form)
# Join the team
user = self.request.user
registration = user.registration
registration.team = form.instance
registration.save()
# Subscribe to the team mailing list
get_sympa_client().subscribe(user.email, f"equipe-{form.instance.trigram.lower()}", False,
f"{user.first_name} {user.last_name}")
# Invite the user in the team Matrix room
Matrix.invite(f"#team-{form.instance.trigram.lower()}:correspondances-maths.fr",
f"@{user.registration.matrix_username}:correspondances-maths.fr")
return ret
@ -94,6 +120,10 @@ class JoinTeamView(LoginRequiredMixin, FormView):
class MyTeamDetailView(LoginRequiredMixin, RedirectView):
"""
Redirect to the detail of the team in which the user is.
"""
def get_redirect_url(self, *args, **kwargs):
user = self.request.user
registration = user.registration
@ -105,11 +135,15 @@ class MyTeamDetailView(LoginRequiredMixin, RedirectView):
class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView):
"""
Display the detail of a team.
"""
model = Team
def get(self, request, *args, **kwargs):
user = request.user
self.object = self.get_object()
# Ensure that the user is an admin or a member of the team
if user.registration.is_admin or user.registration.participates and user.registration.team.pk == kwargs["pk"]:
return super().get(request, *args, **kwargs)
raise PermissionDenied
@ -120,6 +154,8 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
team = self.get_object()
context["request_validation_form"] = RequestValidationForm(self.request.POST or None)
context["validation_form"] = ValidateParticipationForm(self.request.POST or None)
# A team is complete when there are at least 3 members that have sent their photo authorization
# and confirmed their email address
context["can_validate"] = team.students.count() >= 3 and \
all(r.email_confirmed for r in team.students.all()) and \
all(r.photo_authorization for r in team.students.all()) and \
@ -188,6 +224,9 @@ class TeamDetailView(LoginRequiredMixin, FormMixin, ProcessFormView, DetailView)
class TeamUpdateView(LoginRequiredMixin, UpdateView):
"""
Update the detail of a team
"""
model = Team
form_class = TeamForm
template_name = "participation/update_team.html"
@ -218,6 +257,9 @@ class TeamUpdateView(LoginRequiredMixin, UpdateView):
class TeamAuthorizationsView(LoginRequiredMixin, DetailView):
"""
Get as a ZIP archive all the authorizations that are sent
"""
model = Team
def dispatch(self, request, *args, **kwargs):
@ -245,6 +287,10 @@ class TeamAuthorizationsView(LoginRequiredMixin, DetailView):
class TeamLeaveView(LoginRequiredMixin, TemplateView):
"""
A team member leaves a team
"""
template_name = "participation/team_leave.html"
def dispatch(self, request, *args, **kwargs):
@ -258,6 +304,10 @@ class TeamLeaveView(LoginRequiredMixin, TemplateView):
@transaction.atomic()
def post(self, request, *args, **kwargs):
"""
When the team is left, the user is unsubscribed from the team mailing list
and kicked from the team room.
"""
team = request.user.registration.team
request.user.registration.team = None
request.user.registration.save()
@ -271,6 +321,9 @@ class TeamLeaveView(LoginRequiredMixin, TemplateView):
class MyParticipationDetailView(LoginRequiredMixin, RedirectView):
"""
Redirects to the detail view of the participation of the team.
"""
def get_redirect_url(self, *args, **kwargs):
user = self.request.user
registration = user.registration
@ -282,6 +335,9 @@ class MyParticipationDetailView(LoginRequiredMixin, RedirectView):
class ParticipationDetailView(LoginRequiredMixin, DetailView):
"""
Display detail about the participation of a team, and manage the video submission.
"""
model = Participation
def dispatch(self, request, *args, **kwargs):
@ -303,6 +359,9 @@ class ParticipationDetailView(LoginRequiredMixin, DetailView):
class UploadVideoView(LoginRequiredMixin, UpdateView):
"""
Upload a solution video for a team.
"""
model = Video
form_class = UploadVideoForm
template_name = "participation/upload_video.html"
@ -319,11 +378,17 @@ class UploadVideoView(LoginRequiredMixin, UpdateView):
class CalendarView(SingleTableView):
"""
Display the calendar of the action.
"""
table_class = CalendarTable
model = Phase
class PhaseUpdateView(AdminMixin, UpdateView):
"""
Update a phase of the calendar, if we have sufficient rights.
"""
model = Phase
form_class = PhaseForm

View File

@ -3,6 +3,9 @@ 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):

View File

@ -9,6 +9,11 @@ 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: [
@ -29,6 +34,10 @@ class SignupForm(UserCreationForm):
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
@ -41,12 +50,18 @@ class UserForm(forms.ModelForm):
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 = ('student_class', 'school', 'give_contact_to_animath',)
class PhotoAuthorizationForm(forms.ModelForm):
"""
Form to send a photo authorization.
"""
def clean_photo_authorization(self):
file = self.files["photo_authorization"]
if file.content_type not in ["application/pdf", "image/png", "image/jpeg"]:
@ -63,12 +78,18 @@ class PhotoAuthorizationForm(forms.ModelForm):
class CoachRegistrationForm(forms.ModelForm):
"""
A coach can tell its professional activity.
"""
class Meta:
model = CoachRegistration
fields = ('professional_activity', 'give_contact_to_animath',)
class AdminRegistrationForm(forms.ModelForm):
"""
Admins can tell everything they want.
"""
class Meta:
model = AdminRegistration
fields = ('role', 'give_contact_to_animath',)

View File

@ -11,6 +11,11 @@ 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,
@ -28,6 +33,10 @@ class Registration(PolymorphicModel):
)
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))
@ -84,6 +93,10 @@ def get_random_filename(instance, filename):
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",
@ -129,6 +142,10 @@ class StudentRegistration(Registration):
class CoachRegistration(Registration):
"""
Specific registration for coaches.
They have a team and a professional activity.
"""
team = models.ForeignKey(
"participation.Team",
related_name="coachs",
@ -157,6 +174,10 @@ class CoachRegistration(Registration):
class AdminRegistration(Registration):
"""
Specific registration for admins.
They have a field to justify they status.
"""
role = models.TextField(
verbose_name=_("role of the administrator"),
)

View File

@ -4,6 +4,9 @@ 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:

View File

@ -5,10 +5,17 @@ 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:
@ -25,5 +32,9 @@ def send_email_link(instance, **_):
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)

View File

@ -5,6 +5,9 @@ from .models import Registration
class RegistrationTable(tables.Table):
"""
Table of all registrations.
"""
last_name = tables.LinkColumn(
'registration:user_detail',
args=[tables.A("user_id")],

View File

@ -10,6 +10,9 @@ from .models import CoachRegistration, Registration, 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)
@ -29,6 +32,9 @@ class TestRegistration(TestCase):
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)
@ -45,6 +51,9 @@ class TestRegistration(TestCase):
self.assertEqual(response.status_code, 200)
def test_registration(self):
"""
Ensure that the signup form is working successfully.
"""
response = self.client.get(reverse("registration:signup"))
self.assertEqual(response.status_code, 200)
@ -109,6 +118,9 @@ class TestRegistration(TestCase):
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)
@ -127,6 +139,9 @@ class TestRegistration(TestCase):
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,)))
@ -134,6 +149,9 @@ class TestRegistration(TestCase):
self.assertEqual(response.status_code, 200)
def test_update_user(self):
"""
Update the user information, for each type of user.
"""
for user, data in [(self.user, dict(role="Bot")),
(self.student, dict(student_class=11, school="Sky")),
(self.coach, dict(professional_activity="God"))]:
@ -162,6 +180,9 @@ class TestRegistration(TestCase):
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)

View File

@ -19,6 +19,9 @@ from .models import StudentRegistration
class SignupView(CreateView):
"""
Signup, as a participant or a coach.
"""
model = User
form_class = SignupForm
template_name = "registration/signup.html"
@ -126,23 +129,34 @@ class UserResendValidationEmailView(LoginRequiredMixin, DetailView):
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
# 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)
class UserUpdateView(LoginRequiredMixin, UpdateView):
"""
Update the detail about a user and its registration.
"""
model = User
form_class = UserForm
template_name = "registration/update_user.html"
@ -176,6 +190,9 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
class UserUploadPhotoAuthorizationView(LoginRequiredMixin, UpdateView):
"""
A participant can send its photo authorization.
"""
model = StudentRegistration
form_class = PhotoAuthorizationForm
template_name = "registration/upload_photo_authorization.html"
@ -198,6 +215,9 @@ class UserUploadPhotoAuthorizationView(LoginRequiredMixin, UpdateView):
class PhotoAuthorizationView(LoginRequiredMixin, View):
"""
Display the sent photo authorization.
"""
def get(self, request, *args, **kwargs):
filename = kwargs["filename"]
path = f"media/authorization/photo/{filename}"
@ -207,18 +227,21 @@ class PhotoAuthorizationView(LoginRequiredMixin, View):
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):
"""
An administrator can log in through this page as someone else, and act as this other person.
"""
if self.request.user.registration.is_admin:
if not User.objects.filter(pk=kwargs["pk"]).exists():
raise Http404