1
0
mirror of https://gitlab.com/animath/si/plateforme.git synced 2025-06-21 19:58:25 +02:00

Comment code, fix minor issues

This commit is contained in:
Yohann D'ANELLO
2020-05-11 14:08:19 +02:00
parent c9b9d01523
commit a561364bd0
22 changed files with 650 additions and 179 deletions

View File

@ -5,35 +5,51 @@ from member.models import TFJMUser, Document, Solution, Synthesis, MotivationLet
@admin.register(TFJMUser)
class TFJMUserAdmin(UserAdmin):
"""
Django admin page for users.
"""
list_display = ('email', 'first_name', 'last_name', 'role', )
@admin.register(Document)
class DocumentAdmin(PolymorphicParentModelAdmin):
"""
Django admin page for any documents.
"""
child_models = (Authorization, MotivationLetter, Solution, Synthesis,)
polymorphic_list = True
@admin.register(Authorization)
class AuthorizationAdmin(PolymorphicChildModelAdmin):
pass
"""
Django admin page for Authorization.
"""
@admin.register(MotivationLetter)
class MotivationLetterAdmin(PolymorphicChildModelAdmin):
pass
"""
Django admin page for Motivation letters.
"""
@admin.register(Solution)
class SolutionAdmin(PolymorphicChildModelAdmin):
pass
"""
Django admin page for solutions.
"""
@admin.register(Synthesis)
class SynthesisAdmin(PolymorphicChildModelAdmin):
pass
"""
Django admin page for syntheses.
"""
@admin.register(Config)
class ConfigAdmin(admin.ModelAdmin):
pass
"""
Django admin page for configurations.
"""

View File

@ -3,5 +3,8 @@ from django.utils.translation import gettext_lazy as _
class MemberConfig(AppConfig):
"""
The member app handles the information that concern a user, its documents, ...
"""
name = 'member'
verbose_name = _('member')

View File

@ -2,18 +2,22 @@ from django.contrib.auth.forms import UserCreationForm
from django import forms
from django.utils.translation import gettext_lazy as _
from member.models import TFJMUser
from .models import TFJMUser
class SignUpForm(UserCreationForm):
"""
Coaches and participants register on the website through this form.
TODO: Check if this form works, render it better
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["first_name"].required = True
self.fields["last_name"].required = True
self.fields["role"].choices = [
('', _("Choose a role...")),
('participant', _("Participant")),
('encadrant', _("Encadrant")),
('3participant', _("Participant")),
('2coach', _("Coach")),
]
class Meta:
@ -40,6 +44,9 @@ class SignUpForm(UserCreationForm):
class TFJMUserForm(forms.ModelForm):
"""
Form to update our own information when we are participant.
"""
class Meta:
model = TFJMUser
fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
@ -48,6 +55,9 @@ class TFJMUserForm(forms.ModelForm):
class CoachUserForm(forms.ModelForm):
"""
Form to update our own information when we are coach.
"""
class Meta:
model = TFJMUser
fields = ('last_name', 'first_name', 'email', 'phone_number', 'gender', 'birth_date', 'address', 'postal_code',
@ -55,6 +65,9 @@ class CoachUserForm(forms.ModelForm):
class AdminUserForm(forms.ModelForm):
"""
Form to update our own information when we are organizer or admin.
"""
class Meta:
model = TFJMUser
fields = ('last_name', 'first_name', 'email', 'phone_number', 'description',)

View File

@ -7,6 +7,9 @@ from member.models import TFJMUser
class Command(BaseCommand):
def handle(self, *args, **options):
"""
Little script that generate a superuser.
"""
email = input("Email: ")
password = "1"
confirm_password = "2"

View File

@ -1,3 +1,5 @@
import os
from django.core.management import BaseCommand, CommandError
from django.db import transaction
from member.models import TFJMUser, Document, Solution, Synthesis, Authorization, MotivationLetter
@ -5,6 +7,11 @@ from tournament.models import Team, Tournament
class Command(BaseCommand):
"""
Import the old database.
Tables must be found into the import_olddb folder, as CSV files.
"""
def add_arguments(self, parser):
parser.add_argument('--tournaments', '-t', action="store", help="Import tournaments")
parser.add_argument('--teams', '-T', action="store", help="Import teams")
@ -26,6 +33,9 @@ class Command(BaseCommand):
@transaction.atomic
def import_tournaments(self):
"""
Import tournaments into the new database.
"""
print("Importing tournaments...")
with open("import_olddb/tournaments.csv") as f:
first_line = True
@ -75,6 +85,9 @@ class Command(BaseCommand):
@transaction.atomic
def import_teams(self):
"""
Import teams into new database.
"""
self.stdout.write("Importing teams...")
with open("import_olddb/teams.csv") as f:
first_line = True
@ -120,6 +133,10 @@ class Command(BaseCommand):
@transaction.atomic
def import_users(self):
"""
Import users into the new database.
:return:
"""
self.stdout.write("Importing users...")
with open("import_olddb/users.csv") as f:
first_line = True
@ -159,7 +176,7 @@ class Command(BaseCommand):
"team": Team.objects.get(pk=args[19]) if args[19] else None,
"year": args[20],
"date_joined": args[23],
"is_active": args[18] == "ADMIN", # TODO Replace it with "True"
"is_active": args[18] == "ADMIN" or os.getenv("TFJM_STAGE", "dev") == "prod",
"is_staff": args[18] == "ADMIN",
"is_superuser": args[18] == "ADMIN",
}
@ -168,6 +185,7 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS("Users imported"))
self.stdout.write("Importing organizers...")
# We also import the information about the organizers of a tournament.
with open("import_olddb/organizers.csv") as f:
first_line = True
for line in f:
@ -188,6 +206,9 @@ class Command(BaseCommand):
@transaction.atomic
def import_documents(self):
"""
Import the documents (authorizations, motivation letters, solutions, syntheses) from the old database.
"""
self.stdout.write("Importing documents...")
with open("import_olddb/documents.csv") as f:
first_line = True

View File

@ -10,12 +10,16 @@ from tournament.models import Team, Tournament
class TFJMUser(AbstractUser):
"""
The model of registered users (organizers/juries/admins/coachs/participants)
"""
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
email = models.EmailField(
unique=True,
verbose_name=_("email"),
help_text=_("This should be valid and will be controlled."),
)
team = models.ForeignKey(
@ -24,6 +28,7 @@ class TFJMUser(AbstractUser):
on_delete=models.SET_NULL,
related_name="users",
verbose_name=_("team"),
help_text=_("Concerns only coaches and participants."),
)
birth_date = models.DateField(
@ -141,14 +146,25 @@ class TFJMUser(AbstractUser):
@property
def participates(self):
"""
Return True iff this user is a participant or a coach, ie. if the user is a member of a team that worked
for the tournament.
"""
return self.role == "3participant" or self.role == "2coach"
@property
def organizes(self):
"""
Return True iff this user is a local or global organizer of the tournament. This includes juries.
"""
return self.role == "1volunteer" or self.role == "0admin"
@property
def admin(self):
"""
Return True iff this user is a global organizer, ie. an administrator. This should be equivalent to be
a superuser.
"""
return self.role == "0admin"
class Meta:
@ -156,6 +172,7 @@ class TFJMUser(AbstractUser):
verbose_name_plural = _("users")
def save(self, *args, **kwargs):
# We ensure that the username is the email of the user.
self.username = self.email
super().save(*args, **kwargs)
@ -164,6 +181,9 @@ class TFJMUser(AbstractUser):
class Document(PolymorphicModel):
"""
Abstract model of any saved document (solution, synthesis, motivation letter, authorization)
"""
file = models.FileField(
unique=True,
verbose_name=_("file"),
@ -184,6 +204,9 @@ class Document(PolymorphicModel):
class Authorization(Document):
"""
Model for authorization papers (parental consent, photo consent, sanitary plug, ...)
"""
user = models.ForeignKey(
TFJMUser,
on_delete=models.CASCADE,
@ -211,6 +234,9 @@ class Authorization(Document):
class MotivationLetter(Document):
"""
Model for motivation letters of a team.
"""
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
@ -227,6 +253,9 @@ class MotivationLetter(Document):
class Solution(Document):
"""
Model for solutions of team for a given problem, for the regional or final tournament.
"""
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
@ -245,6 +274,11 @@ class Solution(Document):
@property
def tournament(self):
"""
Get the concerned tournament of a solution.
Generally the local tournament of a team, but it can be the final tournament if this is a solution for the
final tournament.
"""
return Tournament.get_final() if self.final else self.team.tournament
class Meta:
@ -258,6 +292,9 @@ class Solution(Document):
class Synthesis(Document):
"""
Model for syntheses of a team for a given round and for a given role, for the regional or final tournament.
"""
team = models.ForeignKey(
Team,
on_delete=models.CASCADE,
@ -289,6 +326,11 @@ class Synthesis(Document):
@property
def tournament(self):
"""
Get the concerned tournament of a solution.
Generally the local tournament of a team, but it can be the final tournament if this is a solution for the
final tournament.
"""
return Tournament.get_final() if self.final else self.team.tournament
class Meta:
@ -303,6 +345,9 @@ class Synthesis(Document):
class Config(models.Model):
"""
Dictionary of configuration variables.
"""
key = models.CharField(
max_length=255,
primary_key=True,

View File

@ -1,10 +1,13 @@
import django_tables2 as tables
from django_tables2 import A
from member.models import TFJMUser
from .models import TFJMUser
class UserTable(tables.Table):
"""
Table of users that are matched with a given queryset.
"""
last_name = tables.LinkColumn(
"member:information",
args=[A("pk")],

View File

@ -6,11 +6,17 @@ from member.models import Config
def get_config(value):
"""
Return a value stored into the config table in the database with a given key.
"""
config = Config.objects.get_or_create(key=value)[0]
return config.value
def get_env(value):
"""
Get a specified environment variable.
"""
return os.getenv(value)

View File

@ -12,26 +12,33 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import CreateView, UpdateView, DetailView, FormView
from django_tables2 import SingleTableView
from tournament.forms import TeamForm, JoinTeam
from tournament.models import Team
from tournament.views import AdminMixin, TeamMixin
from .forms import SignUpForm, TFJMUserForm, AdminUserForm, CoachUserForm
from .models import TFJMUser, Document, Solution, MotivationLetter, Synthesis
from .tables import UserTable
class CreateUserView(CreateView):
"""
Signup form view.
"""
model = TFJMUser
form_class = SignUpForm
template_name = "registration/signup.html"
class MyAccountView(LoginRequiredMixin, UpdateView):
"""
Update our personal data.
"""
model = TFJMUser
template_name = "member/my_account.html"
def get_form_class(self):
# The used form can change according to the role of the user.
return AdminUserForm if self.request.user.organizes else TFJMUserForm \
if self.request.user.role == "3participant" else CoachUserForm
@ -40,6 +47,10 @@ class MyAccountView(LoginRequiredMixin, UpdateView):
class UserDetailView(LoginRequiredMixin, DetailView):
"""
View the personal information of a given user.
Only organizers can see this page, since there are personal data.
"""
model = TFJMUser
form_class = TFJMUserForm
context_object_name = "tfjmuser"
@ -57,7 +68,10 @@ class UserDetailView(LoginRequiredMixin, DetailView):
return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if "view_as" in request.POST:
"""
An administrator can log in through this page as someone else, and act as this other person.
"""
if "view_as" in request.POST and self.request.admin:
session = request.session
session["admin"] = request.user.pk
obj = self.get_object()
@ -74,6 +88,10 @@ class UserDetailView(LoginRequiredMixin, DetailView):
class AddTeamView(LoginRequiredMixin, CreateView):
"""
Register a new team.
Users can choose the name, the trigram and a preferred tournament.
"""
model = Team
form_class = TeamForm
@ -86,6 +104,7 @@ class AddTeamView(LoginRequiredMixin, CreateView):
form.add_error('name', _("You are already in a team."))
return self.form_invalid(form)
# Generate a random access code
team = form.instance
alphabet = "0123456789abcdefghijklmnopqrstuvwxyz0123456789"
code = ""
@ -107,6 +126,9 @@ class AddTeamView(LoginRequiredMixin, CreateView):
class JoinTeamView(LoginRequiredMixin, FormView):
"""
Join a team with a given access code.
"""
model = Team
form_class = JoinTeam
template_name = "tournament/team_form.html"
@ -122,7 +144,7 @@ class JoinTeamView(LoginRequiredMixin, FormView):
form.add_error('access_code', _("You are already in a team."))
return self.form_invalid(form)
if self.request.user.role == '2coach' and len(team.encadrants) == 3:
if self.request.user.role == '2coach' and len(team.coaches) == 3:
form.add_error('access_code', _("This team is full of coachs."))
return self.form_invalid(form)
@ -130,6 +152,9 @@ class JoinTeamView(LoginRequiredMixin, FormView):
form.add_error('access_code', _("This team is full of participants."))
return self.form_invalid(form)
if not team.invalid:
form.add_error('access_code', _("This team is already validated or waiting for validation."))
self.request.user.team = team
self.request.user.save()
return super().form_valid(form)
@ -139,11 +164,24 @@ class JoinTeamView(LoginRequiredMixin, FormView):
class MyTeamView(TeamMixin, View):
"""
Redirect to the page of the information of our personal team.
"""
def get(self, request, *args, **kwargs):
return redirect("tournament:team_detail", pk=request.user.team.pk)
class DocumentView(LoginRequiredMixin, View):
"""
View a PDF document, if we have the right.
- Everyone can see the documents that concern itself.
- An administrator can see anything.
- An organizer can see documents that are related to its tournament.
- A jury can see solutions and syntheses that are evaluated in their pools.
"""
def get(self, request, *args, **kwargs):
doc = Document.objects.get(file=self.kwargs["file"])
@ -172,6 +210,9 @@ class DocumentView(LoginRequiredMixin, View):
class ProfileListView(AdminMixin, SingleTableView):
"""
List all registered profiles.
"""
model = TFJMUser
queryset = TFJMUser.objects.order_by("role", "last_name", "first_name")
table_class = UserTable
@ -180,6 +221,9 @@ class ProfileListView(AdminMixin, SingleTableView):
class OrphanedProfileListView(AdminMixin, SingleTableView):
"""
List all orphaned profiles, ie. participants that have no team.
"""
model = TFJMUser
queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\
.order_by("role", "last_name", "first_name")
@ -189,6 +233,9 @@ class OrphanedProfileListView(AdminMixin, SingleTableView):
class OrganizersListView(AdminMixin, SingleTableView):
"""
List all organizers.
"""
model = TFJMUser
queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\
.order_by("role", "last_name", "first_name")
@ -198,6 +245,10 @@ class OrganizersListView(AdminMixin, SingleTableView):
class ResetAdminView(AdminMixin, 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):
if "_fake_user_id" in request.session:
del request.session["_fake_user_id"]