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.
This commit is contained in:
parent
7c0bd2a271
commit
fe840f2003
12 changed files with 435 additions and 67 deletions
|
@ -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 {
|
||||
|
|
|
@ -15,7 +15,21 @@
|
|||
Se déconnecter <span class="glyphicon glyphicon-log-out"></span>
|
||||
</a></span>
|
||||
</div>
|
||||
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
|
||||
<h2 class="member-status">
|
||||
<a href="{% url "gestion:profile" %}">
|
||||
{% if user.first_name %}
|
||||
{{ user.first_name }},
|
||||
{% else %}
|
||||
<tt>{{ user.username }}</tt>,
|
||||
{% endif %}
|
||||
</a>
|
||||
|
||||
{% if user.profile.is_cof %}
|
||||
<tt class="user-is-cof">au COF</tt>
|
||||
{% else %}
|
||||
<tt class="user-is-not-cof">non-COF</tt>
|
||||
{% endif %}
|
||||
</h2>
|
||||
</div><!-- /.container -->
|
||||
</header>
|
||||
{% block interm_content %}{% endblock %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "cof/base_header.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block homelink %}
|
||||
{% endblock %}
|
||||
|
@ -57,7 +58,11 @@
|
|||
<li><a href="{% url "calendar" %}">Calendrier dynamique</a></li>
|
||||
{% if user.profile.cof.is_cof %}<li><a href="{% url "petits-cours-inscription" %}">Inscription pour donner des petits cours</a></li>{% endif %}
|
||||
|
||||
<li><a href="{% url "gestion:profile" %}">Éditer mon profil</a></li>
|
||||
<li>
|
||||
<a href="{% url "gestion:profile" %}">
|
||||
{% trans "Voir/Éditer mon profil" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if not user.profile.login_clipper %}<li><a href="{% url "password_change" %}">Changer mon mot de passe</a></li>{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -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',)
|
||||
|
|
|
@ -13,4 +13,4 @@ class UserForm(forms.ModelForm):
|
|||
class ProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ["phone", "departement"]
|
||||
fields = ["phone", "departement", "occupation"]
|
||||
|
|
|
@ -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)
|
||||
|
|
82
gestion/templates/gestion/club_detail.html
Normal file
82
gestion/templates/gestion/club_detail.html
Normal file
|
@ -0,0 +1,82 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_size %}col-sm-8{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h2>
|
||||
{% trans "Club" %} - {{ club.name|title }}
|
||||
{% if user.is_staff %}
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="{% url "admin:gestion_club_change" club.pk %}">
|
||||
{% trans "Voir dans l'admin" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</h2>
|
||||
|
||||
<div class="row" style="margin: 10px 0;">
|
||||
|
||||
<p>
|
||||
{% trans "Pas de description" as no_desc_str %}
|
||||
{{ club.description|linebreaks|default:no_desc_str }}
|
||||
</p>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
{% trans "Cotisation supplémentaire :" %}
|
||||
{% if club.needs_cotiz %}
|
||||
<b>{{ club.price }} € / {{ club.cotisation_frequency }}</b>
|
||||
{% else %}
|
||||
{% trans "Non" %}
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Total :" %} <b>{{ memberships|length }}</b>
|
||||
{# Member name #}
|
||||
</th>
|
||||
<th>
|
||||
{% if club.needs_cotiz %}
|
||||
{% trans "Statut cotiz" %}
|
||||
{% endif %}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user, membership in memberships %}
|
||||
|
||||
{% ifchanged membership.is_respo %}
|
||||
|
||||
<tr style="background-color:#f9f9f9;">
|
||||
<td colspan="2">
|
||||
<b>
|
||||
{% trans "Respo(s),Membre(s)" as title_respo_str %}
|
||||
{{ membership.is_respo|yesno:"Respo(s),Membre(s)" }}
|
||||
</b>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% endifchanged %}
|
||||
|
||||
<tr>
|
||||
<td>{{ user.get_full_name }}</td>
|
||||
<td>
|
||||
{% if club.needs_cotiz %}
|
||||
<span class="glyphicon glyphicon-{{ membership.has_paid|yesno:"ok,remove" }}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -1,37 +1,134 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block page_size %}col-sm-8{%endblock%}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Modifier mon profil</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre profil a été mis à jour avec succès !</p>
|
||||
{% endif %}
|
||||
<form id="profile form-horizontal" method="post" action="{% url 'gestion:profile' %}">
|
||||
<div class="row" style="margin: 0 15%;">
|
||||
{% csrf_token %}
|
||||
<fieldset"center-block">
|
||||
{% for field in user_form %}
|
||||
{{ field | bootstrap }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset"center-block">
|
||||
{% for field in profile_form %}
|
||||
{{ field | bootstrap }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
|
||||
<h2>{% trans "Mon profil" %} - {{ user.get_full_name|title }}</h2>
|
||||
|
||||
<div class="row" style="margin: 10px 0;">
|
||||
|
||||
{# General #}
|
||||
|
||||
<h3 class="horizontal-title">
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="{% url "gestion:profile_edit" %}">
|
||||
Modifier
|
||||
</a>
|
||||
</div>
|
||||
{% trans "Informations générales" %}
|
||||
</h3>
|
||||
|
||||
<h4>Contact</h4>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
{% trans "Mail :" %}
|
||||
<b>{{ user.email|default:"n.c." }}</b>
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Téléphone :" %}
|
||||
<b>{{ user.profile.phone|default:"n.c." }}</b>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>Situation</h4>
|
||||
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
{% trans "Département :" %}
|
||||
<b>{{ user.profile.departement|default:"n.c." }}</b>
|
||||
</li>
|
||||
<li>
|
||||
{% trans "Occupation :" %}
|
||||
<b>{{ user.profile.occupation|default:"n.c." }}</b>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if user.profile.comments %}
|
||||
<div class="row" style="margin: 0 15%;">
|
||||
<h4>Commentaires</h4>
|
||||
<h4>{% trans "Commentaires" %}</h4>
|
||||
|
||||
<p>
|
||||
{{ user.profile.comments }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary pull-right" value="Enregistrer" />
|
||||
|
||||
{# Clubs #}
|
||||
|
||||
<h3 class="horizontal-title">Clubs</h3>
|
||||
|
||||
{% if memberships %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{# Actions #}</th>
|
||||
<th>{# Club name #}</th>
|
||||
<th>Statut cotiz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for club, membership in memberships %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if membership.is_respo %}
|
||||
<a href="{{ club.get_absolute_url }}" class="btn btn-sm btn-primary">
|
||||
{% trans "Respo" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ club.name }}</td>
|
||||
<td>
|
||||
{% if club.needs_cotiz %}
|
||||
<span class="glyphicon glyphicon-{{ membership.has_paid|yesno:"ok,remove" }}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% else %}
|
||||
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
Pas d'inscription aux clubs pour le moment.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{# Petit cours #}
|
||||
|
||||
{% if perms.cof.member %}
|
||||
<h3 class="horizontal-title">
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="{% url "petits-cours-inscription" %}">
|
||||
Modifier
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
{% trans "Recevoir des petits cours :" %}
|
||||
<span class="glyphicon glyphicon-{{ user.profile.cof.petit_cours_accept|yesno:"ok,remove" }}"></span>
|
||||
</h3>
|
||||
{% endif %}
|
||||
|
||||
{# K-Fêt #}
|
||||
|
||||
{% if user.profile.account_kfet %}
|
||||
{% with trigramme=user.profile.account_kfet.trigramme %}
|
||||
<h3 class="horizontal-title">
|
||||
<div class="actions">
|
||||
<a class="btn btn-primary" href="{% url "kfet.account.read" trigramme %}">
|
||||
Consulter
|
||||
</a>
|
||||
</div>
|
||||
{% trans "Compte K-Fêt :" %} {{ trigramme }}
|
||||
</h3>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
31
gestion/templates/gestion/profile_edit.html
Normal file
31
gestion/templates/gestion/profile_edit.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block page_size %}col-sm-8{%endblock%}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h2>{% trans "Modifier mon profil" %}</h2>
|
||||
|
||||
<form id="profile form-horizontal" method="post" action="{% url "gestion:profile_edit" %}">
|
||||
|
||||
<div class="row" style="margin: 0 15%;">
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in forms.user %}
|
||||
{{ field | bootstrap }}
|
||||
{% endfor %}
|
||||
|
||||
{% for field in forms.profile %}
|
||||
{{ field | bootstrap }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary pull-right" value="{% trans "Enregistrer" %}">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -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<pk>\d+)/$', views.club_detail,
|
||||
name='club_detail'),
|
||||
|
||||
# Authentication
|
||||
url(r'^cof/denied$',
|
||||
|
|
110
gestion/views.py
110
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
|
||||
@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:
|
||||
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})
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue