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 %}
+
{% 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 %}
+
+
+
+
+
+
+
+ {% trans "Total :" %} {{ memberships|length }}
+ {# Member name #}
+ |
+
+ {% if club.needs_cotiz %}
+ {% trans "Statut cotiz" %}
+ {% endif %}
+ |
+
+
+
+ {% for user, membership in memberships %}
+
+ {% ifchanged membership.is_respo %}
+
+
+
+
+ {% trans "Respo(s),Membre(s)" as title_respo_str %}
+ {{ membership.is_respo|yesno:"Respo(s),Membre(s)" }}
+
+ |
+
+
+ {% endifchanged %}
+
+
+ {{ user.get_full_name }} |
+
+ {% if club.needs_cotiz %}
+
+ {% endif %}
+ |
+
+
+ {% endfor %}
+
+
+
+
+
+{% 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 %}
-
+
+{% 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 %}
+
+
+
+
+ {# Actions #} |
+ {# Club name #} |
+ Statut cotiz |
+
+
+
+ {% for club, membership in memberships %}
+
+
+ {% if membership.is_respo %}
+
+ {% trans "Respo" %}
+
+ {% endif %}
+ |
+ {{ club.name }} |
+
+ {% if club.needs_cotiz %}
+
+ {% endif %}
+ |
+
+ {% endfor %}
+
+
+
+ {% 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" %}
+
+
+
+{% 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