diff --git a/apps/member/forms.py b/apps/member/forms.py index 6a9449f..e304e4e 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -1,4 +1,5 @@ from django.contrib.auth.forms import UserCreationForm +from django import forms from django.utils.translation import gettext_lazy as _ from member.models import TFJMUser @@ -37,3 +38,9 @@ class SignUpForm(UserCreationForm): 'responsible_email', 'description', ) + + +class TFJMUserForm(forms.ModelForm): + class Meta: + model = TFJMUser + fields = '__all__' diff --git a/apps/member/urls.py b/apps/member/urls.py index c918085..7a62cd1 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -1,13 +1,15 @@ from django.urls import path from django.views.generic import RedirectView -from .views import CreateUserView, ProfileListView, OrphanedProfileListView, OrganizersListView +from .views import CreateUserView, MyAccountView, UserDetailView,\ + ProfileListView, OrphanedProfileListView, OrganizersListView app_name = "member" urlpatterns = [ path('signup/', CreateUserView.as_view(), name="signup"), - path("my-account/", RedirectView.as_view(pattern_name="index"), name="my_account"), + path("my-account/", MyAccountView.as_view(), name="my_account"), + path("information//", UserDetailView.as_view(), name="information"), path("add-team/", RedirectView.as_view(pattern_name="index"), name="add_team"), path("join-team/", RedirectView.as_view(pattern_name="index"), name="join_team"), path("my-team/", RedirectView.as_view(pattern_name="index"), name="my_team"), diff --git a/apps/member/views.py b/apps/member/views.py index 6802b75..3cc1968 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -4,11 +4,11 @@ from django.db.models import Q from django.http import FileResponse from django.utils.translation import gettext_lazy as _ from django.views import View -from django.views.generic import CreateView +from django.views.generic import CreateView, UpdateView, DetailView from django_tables2 import SingleTableView from tournament.views import AdminMixin -from .forms import SignUpForm +from .forms import SignUpForm, TFJMUserForm from .models import TFJMUser, Document from .tables import UserTable @@ -19,6 +19,35 @@ class CreateUserView(CreateView): template_name = "registration/signup.html" +class MyAccountView(LoginRequiredMixin, UpdateView): + model = TFJMUser + form_class = TFJMUserForm + template_name = "member/my_account.html" + + def get_object(self, queryset=None): + return self.request.user + + +class UserDetailView(LoginRequiredMixin, DetailView): + model = TFJMUser + form_class = TFJMUserForm + context_object_name = "user" + + def dispatch(self, request, *args, **kwargs): + if not request.user.admin \ + and (self.object.team is not None and request.user not in self.object.team.tournament.organizers)\ + and self.request.user != self.object: + raise PermissionDenied + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context["title"] = str(self.object) + + return context + + class DocumentView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): doc = Document.objects.get(file=self.kwargs["file"]) @@ -29,7 +58,7 @@ class DocumentView(LoginRequiredMixin, View): return FileResponse(doc.file, content_type="application/pdf") -class ProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView): +class ProfileListView(AdminMixin, SingleTableView): model = TFJMUser queryset = TFJMUser.objects.order_by("role", "last_name", "first_name") table_class = UserTable @@ -37,7 +66,7 @@ class ProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView): extra_context = dict(title=_("All profiles")) -class OrphanedProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView): +class OrphanedProfileListView(AdminMixin, SingleTableView): model = TFJMUser queryset = TFJMUser.objects.filter((Q(role="2coach") | Q(role="3participant")) & Q(team__isnull=True))\ .order_by("role", "last_name", "first_name") @@ -46,7 +75,7 @@ class OrphanedProfileListView(LoginRequiredMixin, AdminMixin, SingleTableView): extra_context = dict(title=_("Orphaned profiles")) -class OrganizersListView(LoginRequiredMixin, AdminMixin, SingleTableView): +class OrganizersListView(AdminMixin, SingleTableView): model = TFJMUser queryset = TFJMUser.objects.filter(Q(role="0admin") | Q(role="1volunteer"))\ .order_by("role", "last_name", "first_name") diff --git a/apps/tournament/forms.py b/apps/tournament/forms.py new file mode 100644 index 0000000..37e2ec9 --- /dev/null +++ b/apps/tournament/forms.py @@ -0,0 +1,44 @@ +from django import forms +from django.utils.translation import gettext_lazy as _ + +from member.models import TFJMUser +from tfjm.inputs import DatePickerInput, DateTimePickerInput, AmountInput +from tournament.models import Tournament, Team + + +class TournamentForm(forms.ModelForm): + class Meta: + model = Tournament + fields = '__all__' + widgets = { + "price": AmountInput(), + "date_start": DatePickerInput(), + "date_end": DatePickerInput(), + "date_inscription": DateTimePickerInput(), + "date_solutions": DateTimePickerInput(), + "date_syntheses": DateTimePickerInput(), + } + + +class OrganizerForm(forms.ModelForm): + class Meta: + model = TFJMUser + fields = ('last_name', 'first_name', 'email', 'is_superuser',) + + def save(self, commit=True): + user = self.instance + user.role = '0admin' if user.is_superuser else '1organizer' + super().save(commit) + + +class TeamForm(forms.ModelForm): + class Meta: + model = Team + fields = ('name', 'trigram', 'tournament',) + + +class JoinTeam(forms.Form): + access_code = forms.CharField( + label=_("Access code"), + max_length=6, + ) diff --git a/apps/tournament/models.py b/apps/tournament/models.py index eeb94c2..6fcb286 100644 --- a/apps/tournament/models.py +++ b/apps/tournament/models.py @@ -2,6 +2,7 @@ import os from datetime import date from django.db import models +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ @@ -145,10 +146,20 @@ class Team(models.Model): def encadrants(self): return self.users.all().filter(role="2coach") + @property + def linked_encadrants(self): + return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '' + for user in self.encadrants] + @property def participants(self): return self.users.all().filter(role="3participant") + @property + def linked_participants(self): + return [''.format(url=reverse_lazy("member:information", args=(user.pk,))) + str(user) + '' + for user in self.participants] + class Meta: verbose_name = _("team") verbose_name_plural = _("teams") diff --git a/apps/tournament/urls.py b/apps/tournament/urls.py index 4d03d7f..f1d4800 100644 --- a/apps/tournament/urls.py +++ b/apps/tournament/urls.py @@ -1,16 +1,19 @@ from django.urls import path from django.views.generic import RedirectView -from .views import TournamentListView, TournamentDetailView, TeamDetailView, SolutionsView, SolutionsOrgaListView +from .views import TournamentListView, TournamentCreateView, TournamentDetailView, TournamentUpdateView,\ + TeamDetailView, TeamUpdateView, AddOrganizerView, SolutionsView, SolutionsOrgaListView app_name = "tournament" urlpatterns = [ path('list/', TournamentListView.as_view(), name="list"), - path("add/", RedirectView.as_view(pattern_name="index"), name="add"), + path("add/", TournamentCreateView.as_view(), name="add"), path('/', TournamentDetailView.as_view(), name="detail"), + path('/update/', TournamentUpdateView.as_view(), name="update"), path('team//', TeamDetailView.as_view(), name="team_detail"), - path("add-organizer/", RedirectView.as_view(pattern_name="index"), name="add_organizer"), + path('team//update/', TeamUpdateView.as_view(), name="team_update"), + path("add-organizer/", AddOrganizerView.as_view(), name="add_organizer"), path("solutions/", SolutionsView.as_view(), name="solutions"), path("all-solutions/", SolutionsOrgaListView.as_view(), name="all_solutions"), path("syntheses/", RedirectView.as_view(pattern_name="index"), name="syntheses"), diff --git a/apps/tournament/views.py b/apps/tournament/views.py index 041d29e..397b1a0 100644 --- a/apps/tournament/views.py +++ b/apps/tournament/views.py @@ -5,23 +5,26 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied from django.db.models import Q from django.http import HttpResponse +from django.shortcuts import redirect +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView +from django.views.generic import DetailView, CreateView, UpdateView from django_tables2.views import SingleTableView from member.models import TFJMUser, Solution +from .forms import TournamentForm, OrganizerForm, TeamForm from .models import Tournament, Team from .tables import TournamentTable, TeamTable, SolutionTable -class AdminMixin(object): +class AdminMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): if not request.user.admin: raise PermissionDenied return super().dispatch(request, *args, **kwargs) -class TeamMixin(object): +class TeamMixin(LoginRequiredMixin): def dispatch(self, request, *args, **kwargs): if not request.user.team: raise PermissionDenied @@ -47,6 +50,15 @@ class TournamentListView(SingleTableView): return context +class TournamentCreateView(AdminMixin, CreateView): + model = Tournament + form_class = TournamentForm + extra_context = dict(title=_("Add tournament"),) + + def get_success_url(self): + return reverse_lazy('tournament:detail', args=(self.object.pk,)) + + class TournamentDetailView(DetailView): model = Tournament @@ -71,6 +83,15 @@ class TournamentDetailView(DetailView): return context +class TournamentUpdateView(AdminMixin, UpdateView): + model = Tournament + form_class = TournamentForm + extra_context = dict(title=_("Update tournament"),) + + def get_success_url(self): + return reverse_lazy('tournament:detail', args=(self.object.pk,)) + + class TeamDetailView(LoginRequiredMixin, DetailView): model = Team @@ -97,6 +118,9 @@ class TeamDetailView(LoginRequiredMixin, DetailView): .format(_("Solutions for team {team}.zip") .format(team=str(team)).replace(" ", "%20")) return resp + elif "delete" in request.POST: + team.delete() + return redirect('tournament:detail', pk=team.tournament.pk) return self.get(request, *args, **kwargs) @@ -108,7 +132,25 @@ class TeamDetailView(LoginRequiredMixin, DetailView): return context -class SolutionsView(LoginRequiredMixin, TeamMixin, SingleTableView): +class TeamUpdateView(LoginRequiredMixin, UpdateView): + model = Team + form_class = TeamForm + extra_context = dict(title=_("Udpate team"),) + + def dispatch(self, request, *args, **kwargs): + if not request.user.admin and self.request.user not in self.get_object().tournament.organizers: + raise PermissionDenied + return super().dispatch(request, *args, **kwargs) + + +class AddOrganizerView(AdminMixin, CreateView): + model = TFJMUser + form_class = OrganizerForm + extra_context = dict(title=_("Add organizer"),) + template_name = "tournament/add_organizer.html" + + +class SolutionsView(TeamMixin, SingleTableView): model = Solution table_class = SolutionTable template_name = "tournament/solutions_list.html" @@ -149,7 +191,7 @@ class SolutionsView(LoginRequiredMixin, TeamMixin, SingleTableView): return qs.order_by('team__tournament__date_start', 'team__tournament__name', 'team__trigram', 'problem',) -class SolutionsOrgaListView(LoginRequiredMixin, AdminMixin, SingleTableView): +class SolutionsOrgaListView(AdminMixin, SingleTableView): model = Solution table_class = SolutionTable template_name = "tournament/solutions_orga_list.html" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 014f63e..03f2b72 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -442,7 +442,7 @@ msgstr "Liste des tournois" #: apps/tournament/views.py:56 msgid "Tournament of {name}" -msgstr "Tournoi de {user}" +msgstr "Tournoi de {name}" #: apps/tournament/views.py:97 apps/tournament/views.py:131 #, python-brace-format diff --git a/templates/member/my_account.html b/templates/member/my_account.html new file mode 100644 index 0000000..9031a48 --- /dev/null +++ b/templates/member/my_account.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% load i18n crispy_forms_filters %} + +{% block content %} +
+ {{ form|crispy }} + +
+ +
+ + {% trans "Update my password" %} +{% endblock %} diff --git a/templates/member/tfjmuser_detail.html b/templates/member/tfjmuser_detail.html new file mode 100644 index 0000000..486d172 --- /dev/null +++ b/templates/member/tfjmuser_detail.html @@ -0,0 +1,69 @@ +{% extends "base.html" %} + +{% load getconfig i18n django_tables2 static %} + +{% block content %} +
+
+

{{ user }}

+
+
+
+
{% trans 'role'|capfirst %}
+
{{ user.get_role_display }}
+ +
{% trans 'team'|capfirst %}
+
{{ user.team }}
+ +
{% trans 'birth date'|capfirst %}
+
{{ user.birth_date }}
+ +
{% trans 'gender'|capfirst %}
+
{{ user.get_gender_display }}
+ +
{% trans 'address'|capfirst %}
+
{{ user.address }}, {{ user.postal_code }}, {{ user.city }}{% if user.country != "France" %}, {{ user.country }}{% endif %}
+ +
{% trans 'email'|capfirst %}
+
{{ user.email }}
+ +
{% trans 'phone number'|capfirst %}
+
{{ user.phone_number }}
+ + {% if user.role == '3participant' %} +
{% trans 'school'|capfirst %}
+
{{ user.school }}
+ +
{% trans 'class'|capfirst %}
+
{{ user.get_student_class_display }}
+ + {% if user.responsible_name %} +
{% trans 'responsible name'|capfirst %}
+
{{ user.responsible_name }}
+ {% endif %} + + {% if user.responsible_phone %} +
{% trans 'responsible phone'|capfirst %}
+
{{ user.responsible_phone }}
+ {% endif %} + + {% if user.responsible_email %} +
{% trans 'responsible email'|capfirst %}
+
{{ user.responsible_email }}
+ {% endif %} + {% endif %} + + {% if user.role == '2coach' %} +
{% trans 'description'|capfirst %}
+
{{ user.description }}
+ {% endif %} +
+
+
+ +
+ +

{% trans "Documents" %}

+ + {# TODO Display documents #} +{% endblock %} diff --git a/templates/tournament/add_organizer.html b/templates/tournament/add_organizer.html new file mode 100644 index 0000000..21daee8 --- /dev/null +++ b/templates/tournament/add_organizer.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load i18n crispy_forms_filters %} + +{% block content %} +
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock %} diff --git a/templates/tournament/team_detail.html b/templates/tournament/team_detail.html index 3d1ae5b..056dff1 100644 --- a/templates/tournament/team_detail.html +++ b/templates/tournament/team_detail.html @@ -19,18 +19,21 @@
{{ team.tournament }}
{% trans 'coachs'|capfirst %}
-
{{ team.encadrants.all|join:", " }}
+
{% autoescape off %}{{ team.linked_encadrants|join:", " }}{% endautoescape %}
{% trans 'participants'|capfirst %}
-
{{ team.participants.all|join:", " }}
+
{% autoescape off %}{{ team.linked_participants|join:", " }}{% endautoescape %}
{% if user.admin or user in team.tournament.organizers.all %} {% endif %} diff --git a/templates/tournament/team_form.html b/templates/tournament/team_form.html new file mode 100644 index 0000000..21daee8 --- /dev/null +++ b/templates/tournament/team_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load i18n crispy_forms_filters %} + +{% block content %} +
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock %} diff --git a/templates/tournament/tournament_detail.html b/templates/tournament/tournament_detail.html index 5eb0062..d5095da 100644 --- a/templates/tournament/tournament_detail.html +++ b/templates/tournament/tournament_detail.html @@ -39,15 +39,15 @@ {% if user.is_authenticated and user.admin %} {% endif %} {% if user.admin or user in tournament.organizers.all %} {% endif %} diff --git a/templates/tournament/tournament_form.html b/templates/tournament/tournament_form.html new file mode 100644 index 0000000..21daee8 --- /dev/null +++ b/templates/tournament/tournament_form.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} + +{% load i18n crispy_forms_filters %} + +{% block content %} +
+ {% csrf_token %} + {{ form|crispy }} + +
+{% endblock %} diff --git a/templates/tournament/tournament_list.html b/templates/tournament/tournament_list.html index 8811241..27b9835 100644 --- a/templates/tournament/tournament_list.html +++ b/templates/tournament/tournament_list.html @@ -5,8 +5,8 @@ {% block content %} {% if user.is_authenticated and user.admin %} {% endif %} {% render_table table %} diff --git a/tfjm/settings.py b/tfjm/settings.py index 7b855e9..3cd70cf 100644 --- a/tfjm/settings.py +++ b/tfjm/settings.py @@ -89,6 +89,8 @@ TEMPLATES = [ }, ] +FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' + WSGI_APPLICATION = 'tfjm.wsgi.application' @@ -150,7 +152,7 @@ LANGUAGES = [ ('fr', _('French')), ] -TIME_ZONE = 'UTC' +TIME_ZONE = 'Europe/Paris' USE_I18N = True