From fe840f2003e55c76adece0e5f8de6c877b07c6f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Mon, 7 Aug 2017 18:56:56 +0200 Subject: [PATCH] Add club view / update profile view Profile view - Let the user see his information. - List the clubs whose he is a member. Profile edition view - Renamed from previous "profile" view - User can now change "occupation" field. Club detail view - Informations about a club. - Accessible by staff members and "respos" of the club. - List members, with subscription fee (if applicable). Club admin - Change memberships of clubs added. --- cof/static/css/cof.css | 48 +++++- cof/templates/cof/base_header.html | 16 +- cof/templates/home.html | 7 +- gestion/admin.py | 17 ++- gestion/forms.py | 2 +- gestion/models.py | 16 +- gestion/templates/gestion/club_detail.html | 82 ++++++++++ gestion/templates/gestion/profile.html | 157 ++++++++++++++++---- gestion/templates/gestion/profile_edit.html | 31 ++++ gestion/urls.py | 11 +- gestion/views.py | 112 +++++++++++--- requirements.txt | 3 +- 12 files changed, 435 insertions(+), 67 deletions(-) create mode 100644 gestion/templates/gestion/club_detail.html create mode 100644 gestion/templates/gestion/profile_edit.html diff --git a/cof/static/css/cof.css b/cof/static/css/cof.css index 269736d0..2c573ae5 100644 --- a/cof/static/css/cof.css +++ b/cof/static/css/cof.css @@ -332,8 +332,8 @@ fieldset legend { padding: 20px; } -#main-content a, -#main-content a:hover { +#main-content a:not(.btn), +#main-content a:hover:not(.btn) { color : #D81138; } @@ -351,7 +351,7 @@ fieldset legend { #main-content h3.horizontal-title { display : block; padding : 12px; - margin: -20px -20px 10px -20px ; + margin: 0 -20px 10px -20px ; font-family: 'Dosis', sans-serif; font-weight: 700; color: white; @@ -365,11 +365,11 @@ fieldset legend { border-width: 0px 0px 5px 0px; } -#main-content h2 a { +#main-content h2 a:not(.btn) { color : #C9E5E1; } -#main-content h2 a:hover { +#main-content h2 a:hover:not(.btn) { color : #ABB8B6; } @@ -377,10 +377,46 @@ fieldset legend { font-weight: 700; font-size: large; background-color:#EAA594; - border-width: 0px 0px 3px 0px; + margin-top: 10px; } +#main-content > :first-child { + margin-top: -20px; +} +#main-content > div:first-of-type > h3.horizontal-title:first-child { + margin-top: -10px; +} + +#main-content h2 .actions , +#main-content h3 .actions { + float: right; + display: flex; + align-items: center; + + margin: -12px; + padding-right: 10px; +} + +#main-content h2 .actions { + min-height: 50px; +} + +#main-content h3 .actions { + min-height: 44px; +} + +#main-content h2 .actions > *, +#main-content h3 .actions > * { + height: 100%; + border-radius: 0; + vertical-align: middle; +} + +#main-content h2 .actions > * + *, +#main-content h3 .actions > * + * { + margin-left: 10px; +} /* main-container { diff --git a/cof/templates/cof/base_header.html b/cof/templates/cof/base_header.html index dd9c1967..8d2cddef 100644 --- a/cof/templates/cof/base_header.html +++ b/cof/templates/cof/base_header.html @@ -15,7 +15,21 @@ Se déconnecter  -

{% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}, {% if user.profile.is_cof %}au COF{% else %}non-COF{% endif %}

+

+ + {% if user.first_name %} + {{ user.first_name }}, + {% else %} + {{ user.username }}, + {% endif %} + + + {% if user.profile.is_cof %} + au COF + {% else %} + non-COF + {% endif %} +

{% block interm_content %}{% endblock %} diff --git a/cof/templates/home.html b/cof/templates/home.html index 7574e06e..556f663b 100644 --- a/cof/templates/home.html +++ b/cof/templates/home.html @@ -1,4 +1,5 @@ {% extends "cof/base_header.html" %} +{% load i18n %} {% block homelink %} {% endblock %} @@ -57,7 +58,11 @@
  • Calendrier dynamique
  • {% if user.profile.cof.is_cof %}
  • Inscription pour donner des petits cours
  • {% endif %} -
  • Éditer mon profil
  • +
  • + + {% trans "Voir/Éditer mon profil" %} + +
  • {% if not user.profile.login_clipper %}
  • Changer mon mot de passe
  • {% endif %} diff --git a/gestion/admin.py b/gestion/admin.py index a25c618d..02e3a7c3 100644 --- a/gestion/admin.py +++ b/gestion/admin.py @@ -20,14 +20,21 @@ class UserProfileAdmin(UserAdmin): ] +admin.site.unregister(User) +admin.site.register(User, UserProfileAdmin) + + # --- # Clubs # --- +class ClubUserInline(admin.TabularInline): + model = Club.members.through + + @admin.register(Club) class ClubAdmin(admin.ModelAdmin): - pass - - -admin.site.unregister(User) -admin.site.register(User, UserProfileAdmin) + inlines = [ + ClubUserInline, + ] + exclude = ('members',) diff --git a/gestion/forms.py b/gestion/forms.py index 7584d29e..0bf3832c 100644 --- a/gestion/forms.py +++ b/gestion/forms.py @@ -13,4 +13,4 @@ class UserForm(forms.ModelForm): class ProfileForm(forms.ModelForm): class Meta: model = Profile - fields = ["phone", "departement"] + fields = ["phone", "departement", "occupation"] diff --git a/gestion/models.py b/gestion/models.py index bba33dd9..013dcbf2 100644 --- a/gestion/models.py +++ b/gestion/models.py @@ -2,6 +2,7 @@ from django.contrib.auth.models import User, Group from django.db import models from django.dispatch import receiver from django.db.models.signals import post_save, post_delete +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from cof.petits_cours_models import choices_length @@ -92,11 +93,22 @@ class Club(models.Model): def __str__(self): template = ( - self.price and "{name} ({price}€ / {cotisation_frequency})" - or "{name}" + self.needs_cotiz and + "{name} ({price}€ / {cotisation_frequency})" or + "{name}" ) return template.format(**vars(self)) + def get_absolute_url(self): + return reverse('gestion:club_detail', args=[str(self.pk)]) + + @property + def needs_cotiz(self): + return bool(self.price) + + def is_respo(self, user): + return self.clubuser_set.filter(user=user, is_respo=True).exists() + class ClubUser(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) diff --git a/gestion/templates/gestion/club_detail.html b/gestion/templates/gestion/club_detail.html new file mode 100644 index 00000000..78146c57 --- /dev/null +++ b/gestion/templates/gestion/club_detail.html @@ -0,0 +1,82 @@ +{% extends "base_title.html" %} +{% load i18n %} + +{% block page_size %}col-sm-8{% endblock %} + +{% block realcontent %} + +

    + {% trans "Club" %} - {{ club.name|title }} + {% if user.is_staff %} + + {% endif %} +

    + +
    + +

    + {% trans "Pas de description" as no_desc_str %} + {{ club.description|linebreaks|default:no_desc_str }} +

    + +
      +
    • + {% trans "Cotisation supplémentaire :" %} + {% if club.needs_cotiz %} + {{ club.price }} € / {{ club.cotisation_frequency }} + {% else %} + {% trans "Non" %} + {% endif %} +
    • +
    + + + + + + + + + + {% for user, membership in memberships %} + + {% ifchanged membership.is_respo %} + + + + + + {% endifchanged %} + + + + + + + {% endfor %} + +
    + {% trans "Total :" %} {{ memberships|length }} + {# Member name #} + + {% if club.needs_cotiz %} + {% trans "Statut cotiz" %} + {% endif %} +
    + + {% trans "Respo(s),Membre(s)" as title_respo_str %} + {{ membership.is_respo|yesno:"Respo(s),Membre(s)" }} + +
    {{ user.get_full_name }} + {% if club.needs_cotiz %} + + {% endif %} +
    + +
    + +{% endblock %} diff --git a/gestion/templates/gestion/profile.html b/gestion/templates/gestion/profile.html index 183b36e4..e889776f 100644 --- a/gestion/templates/gestion/profile.html +++ b/gestion/templates/gestion/profile.html @@ -1,37 +1,134 @@ {% extends "base_title.html" %} +{% load i18n %} {% load bootstrap %} {% block page_size %}col-sm-8{%endblock%} {% block realcontent %} -

    Modifier mon profil

    - {% if success %} -

    Votre profil a été mis à jour avec succès !

    - {% endif %} -
    -
    - {% csrf_token %} - - {% for field in user_form %} - {{ field | bootstrap }} - {% endfor %} - - - {% for field in profile_form %} - {{ field | bootstrap }} - {% endfor %} - -
    - {% if user.profile.comments %} -
    -

    Commentaires

    -

    - {{ user.profile.comments }} -

    -
    - {% endif %} -
    - -
    -
    + +

    {% trans "Mon profil" %} - {{ user.get_full_name|title }}

    + +
    + + {# General #} + +

    + + {% trans "Informations générales" %} +

    + +

    Contact

    + +
      +
    • + {% trans "Mail :" %} + {{ user.email|default:"n.c." }} +
    • +
    • + {% trans "Téléphone :" %} + {{ user.profile.phone|default:"n.c." }} +
    • +
    + +

    Situation

    + +
      +
    • + {% trans "Département :" %} + {{ user.profile.departement|default:"n.c." }} +
    • +
    • + {% trans "Occupation :" %} + {{ user.profile.occupation|default:"n.c." }} +
    • +
    + + {% if user.profile.comments %} +

    {% trans "Commentaires" %}

    + +

    + {{ user.profile.comments }} +

    + {% endif %} + + {# Clubs #} + +

    Clubs

    + + {% if memberships %} + + + + + + + + + + + {% for club, membership in memberships %} + + + + + + {% endfor %} + +
    {# Actions #}{# Club name #}Statut cotiz
    + {% if membership.is_respo %} + + {% trans "Respo" %} + + {% endif %} + {{ club.name }} + {% if club.needs_cotiz %} + + {% endif %} +
    + + {% else %} + +

    + {% blocktrans %} + Pas d'inscription aux clubs pour le moment. + {% endblocktrans %} +

    + + {% endif %} + + {# Petit cours #} + + {% if perms.cof.member %} +

    + + {% trans "Recevoir des petits cours :" %} + +

    + {% endif %} + + {# K-Fêt #} + + {% if user.profile.account_kfet %} + {% with trigramme=user.profile.account_kfet.trigramme %} +

    + + {% trans "Compte K-Fêt :" %} {{ trigramme }} +

    + {% endwith %} + {% endif %} + +
    + {% endblock %} diff --git a/gestion/templates/gestion/profile_edit.html b/gestion/templates/gestion/profile_edit.html new file mode 100644 index 00000000..ba8e2d35 --- /dev/null +++ b/gestion/templates/gestion/profile_edit.html @@ -0,0 +1,31 @@ +{% extends "base_title.html" %} +{% load i18n %} +{% load bootstrap %} + +{% block page_size %}col-sm-8{%endblock%} + +{% block realcontent %} + +

    {% trans "Modifier mon profil" %}

    + +
    + +
    + {% csrf_token %} + + {% for field in forms.user %} + {{ field | bootstrap }} + {% endfor %} + + {% for field in forms.profile %} + {{ field | bootstrap }} + {% endfor %} +
    + +
    + +
    + +
    + +{% endblock %} diff --git a/gestion/urls.py b/gestion/urls.py index c1cb3db6..53a4d803 100644 --- a/gestion/urls.py +++ b/gestion/urls.py @@ -7,8 +7,15 @@ from . import views app_name = "gestion" urlpatterns = [ - # Profile edition - url(r"^profile/?$", views.profile, name="profile"), + # Profile + url(r'^profile/$', views.profile, + name='profile'), + url(r'^profile/edit/$', views.profile_edit, + name='profile_edit'), + + # Clubs + url(r'^club/(?P\d+)/$', views.club_detail, + name='club_detail'), # Authentication url(r'^cof/denied$', diff --git a/gestion/views.py b/gestion/views.py index 0a210918..83e1ef95 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -1,17 +1,26 @@ """ The common views of the different organisations. - Authentication -- Profile edition +- Profile +- Clubs """ from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import AccessMixin from django.contrib.auth.models import User from django.contrib.auth.views import ( login as django_login, logout as django_logout ) +from django.contrib.messages.views import SuccessMessageMixin +from django.views.generic import DetailView, TemplateView +from django.utils.decorators import method_decorator +from django.utils.translation import ugettext_lazy as _ + +from multi_form_view import MultiModelFormView from .forms import ProfileForm, UserForm +from .models import Club def login(request): @@ -54,20 +63,87 @@ def logout(request): return django_logout(request) -@login_required -def profile(request): - success = False - user = request.user - if request.method == "POST": - user_form = UserForm(request.POST, instance=user) - profile_form = ProfileForm(request.POST, instance=user.profile) - if (user_form.is_valid() and profile_form.is_valid()): - user_form.save() - profile_form.save() - success = True - else: - user_form = UserForm(instance=user) - profile_form = ProfileForm(instance=user.profile) - return render(request, "gestion/profile.html", - {"user_form": user_form, "profile_form": profile_form, - "success": success}) +@method_decorator(login_required, name='dispatch') +class ProfileView(TemplateView): + template_name = 'gestion/profile.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['memberships'] = self.get_memberships() + return context + + def get_memberships(self): + """Returns the clubs memberships of connected user. + + A membership is an instance of 'ClubUser' model. + + Returns: + List of (club, membership). + + """ + qs = ( + self.request.user.clubuser_set + .select_related('club') + .order_by('-is_respo', 'club__name') + ) + return [(m.club, m) for m in qs] + + +profile = ProfileView.as_view() + + +@method_decorator(login_required, name='dispatch') +class EditProfileView(SuccessMessageMixin, MultiModelFormView): + template_name = 'gestion/profile_edit.html' + form_classes = { + 'user': UserForm, + 'profile': ProfileForm, + } + success_url = '/profile/' + success_message = _("Votre profil a été mis à jour avec succès !") + + def get_objects(self): + user = self.request.user + return { + 'user': user, + 'profile': user.profile, + } + + +profile_edit = EditProfileView.as_view() + + +class ClubDetailView(AccessMixin, DetailView): + model = Club + + def dispatch(self, request, *args, **kwargs): + if (request.user.is_authenticated and + self.get_object().is_respo(request.user)): + return super().dispatch(request, *args, **kwargs) + else: + return self.handle_no_permission() + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['memberships'] = self.get_memberships() + return context + + def get_memberships(self): + """Returns the club' members with their membership. + + A membership is an instance of 'ClubUser' model. + + Returns: + List of (user, membership). + + """ + qs = ( + self.object.clubuser_set + .select_related('user') + .order_by('-is_respo', 'user__first_name') + ) + print(qs) + return [(m.user, m) for m in qs] + + +club_detail = ClubDetailView.as_view() diff --git a/requirements.txt b/requirements.txt index 08e82877..8ecc60b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,13 +3,14 @@ Django==1.11.* django-autocomplete-light==2.3.3 django-autoslug==1.9.3 django-cas-ng==3.5.6 +git+https://github.com/TimBest/django-multi-form-view.git@3a72bba#egg=dango-multi-form-view django-recaptcha==1.2.1 mysqlclient==1.3.10 Pillow six unicodecsv icalendar -django-bootstrap-form==3.2.1 +django-bootstrap-form==3.3 asgiref==1.0.0 daphne==1.0.3 asgi-redis==1.0.0