Vues gestion caisses et amélioration Account

- General :
    - Ajout de la gestion des messages dans le template base
    - jQuery et bootstrap ajoutés au template base
    - Ajout de DateTimeWidget utilisant bootstrap-datetimepicker pour
      les champs DateTime
- Account :
    - Ajout de propriétés aux modèles pour accéder directement à
      certains éléments sans avoir à passer par les relations
    - Suppression d'une méthode inutile dans le modèle
    - Correction de permission dans la vue update
    - Utilisation des messages pour la création et l'édition d'un compte
- Checkout :
    - gestion initiale CRU
This commit is contained in:
Aurélien Delobelle 2016-08-04 05:21:04 +02:00
parent 9677fd9ef6
commit 2786f834a5
16 changed files with 4647 additions and 59 deletions

View file

@ -1,8 +1,29 @@
from django import forms
from django.contrib.auth.models import User
from kfet.models import Account
from kfet.models import Account, Checkout
from gestioncof.models import CofProfile
# -----
# Widgets
# -----
class DateTimeWidget(forms.DateTimeInput):
def __init__(self, attrs = None):
super(DateTimeWidget, self).__init__(attrs)
self.attrs['format'] = '%Y-%m-%d %H:%M'
class Media:
css = {
'all': ('bootstrap-datetimepicker.min.css',)
}
js = (
'moment.js',
'moment-fr.js',
'bootstrap-datetimepicker.min.js',
)
# -----
# Account forms
# -----
class AccountForm(forms.ModelForm):
# Surcharge pour passer data à Account.save()
@ -56,3 +77,20 @@ class UserForm(forms.ModelForm):
class UserRestrictForm(UserForm):
class Meta(UserForm.Meta):
fields = ['first_name', 'last_name', 'email']
# -----
# Checkout forms
# -----
class CheckoutForm(forms.ModelForm):
class Meta:
model = Checkout
fields = ['name', 'valid_from', 'valid_to', 'balance', 'is_protected']
widgets = {
'valid_from': DateTimeWidget(),
'valid_to' : DateTimeWidget(),
}
class CheckoutRestrictForm(CheckoutForm):
class Meta(CheckoutForm.Meta):
fields = ['name', 'valid_from', 'valid_to']

View file

@ -1,4 +1,5 @@
from django.db import models
from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied
from django.contrib.auth.models import User, AnonymousUser
from django.core.validators import RegexValidator
@ -40,7 +41,46 @@ class Account(models.Model):
blank = True, null = True, default = None)
def __str__(self):
return self.trigramme
return '%s (%s)' % (self.trigramme, self.name)
# Propriétés pour accéder aux attributs de user et cofprofile et user
@property
def user(self):
return self.cofprofile.user
@property
def username(self):
return self.cofprofile.user.username
@property
def first_name(self):
return self.cofprofile.user.first_name
@property
def last_name(self):
return self.cofprofile.user.last_name
@property
def email(self):
return self.cofprofile.user.email
@property
def departement(self):
return self.cofprofile.departement
@property
def is_cof(self):
return self.cofprofile.is_cof
# Propriétés supplémentaires
@property
def real_balance(self):
if (hasattr(self, 'negative')):
return self.balance + self.negative.balance_offset
return self.balance
@property
def name(self):
if self.first_name and self.last_name:
return '%s %s' % (self.first_name, self.last_name)
elif self.first_name:
return '%s %s' % self.first_name
else:
return self.last_name
@staticmethod
def is_validandfree(trigramme):
@ -53,14 +93,6 @@ class Account(models.Model):
data['is_free'] = True
return data
def real_balance(self):
if (hasattr(self, 'negative')):
return self.balance + self.negative.balance_offset
return self.balance
def read(self, auth_user = AnonymousUser()):
user = self.cofprofile.user
# Surcharge Méthode save() avec gestions de User et CofProfile
# Args:
# - data : datas pour User et CofProfile
@ -72,7 +104,7 @@ class Account(models.Model):
# Account update
# Updating User with data
user = self.cofprofile.user
user = self.user
user.first_name = data.get("first_name", user.first_name)
user.last_name = data.get("last_name", user.last_name)
user.email = data.get("email", user.email)
@ -81,12 +113,6 @@ class Account(models.Model):
cof = self.cofprofile
cof.departement = data.get("departement", cof.departement)
cof.save()
# Nickname is not editable by the user
"""
if not auth_user.has_perm('kfet.change_account'):
account_old = Account.objects.get(pk=self.pk)
self.nickname = account_old.nickname
"""
else:
# New account
@ -149,9 +175,20 @@ class Checkout(models.Model):
name = models.CharField(max_length = 45)
valid_from = models.DateTimeField()
valid_to = models.DateTimeField()
balance = models.DecimalField(max_digits = 6, decimal_places = 2)
balance = models.DecimalField(
max_digits = 6, decimal_places = 2,
default = 0)
is_protected = models.BooleanField(default = False)
def get_absolute_url(self):
return reverse('kfet.checkout.read', kwargs={'pk': self.pk})
class Meta:
ordering = ['-valid_to']
def __str__(self):
return self.name
class CheckoutTransfer(models.Model):
from_checkout = models.ForeignKey(
Checkout, on_delete = models.PROTECT,

View file

@ -1,10 +1,9 @@
{% extends "kfet/base.html" %}
{% load static %}
{% load staticfiles %}
{% block title %}Création d'un nouveau compte{% endblock %}
{% block extra_head %}
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
{% endblock %}

View file

@ -1,9 +1,19 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %} | K-Fêt - ENS Ulm</title>
{# jQuery #}
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
{# <script type="text/javascript" src="{% static 'moment.js' %}"></script> #}
{# Bootstrap #}
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
{% block extra_head %}{% endblock %}
{# Vieux IE pas comprendre HTML5 #}
@ -13,6 +23,7 @@
</head>
<body>
{% include "kfet/base_nav.html" %}
{% include "kfet/base_messages.html" %}
<section id="content">
{% block content %}TGTG{% endblock %}
</section>

View file

@ -0,0 +1,7 @@
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}

View file

@ -14,6 +14,7 @@
{% if perms.is_team %}
<ul>
<li><a href="{% url "kfet.account" %}">Comptes</a></li>
<li><a href="{% url "kfet.checkout" %}">Caisses</a></li>
</ul>
{% endif %}
</nav>

View file

@ -0,0 +1,15 @@
{% extends "kfet/base.html" %}
{% block title %}Caisses{% endblock %}
{% block content %}
<a href="{% url 'kfet.checkout.create' %}">Nouvelle caisse</a>
<ul>
{% for checkout in checkouts %}
<li><a href="{% url 'kfet.checkout.update' checkout.pk %}">{{ checkout }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View file

@ -0,0 +1,47 @@
{% extends "kfet/base.html" %}
{% block extra_head %}{{ form.media }}{% endblock %}
{% block title %}Création d'une nouvelle caisse{% endblock %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }}
<div style="position:relative">{{ field }}</div>
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% endfor %}
<input type="submit" value="Enregistrer">
</form>
<script type="text/javascript">
$(document).ready(function() {
$('#id_valid_from').datetimepicker({
format : 'YYYY-MM-DD HH:mm',
stepping : 5,
locale : 'fr',
showTodayButton: true,
});
$('#id_valid_to').datetimepicker({
format : 'YYYY-MM-DD HH:mm',
stepping : 5,
useCurrent: false,
locale : 'fr',
showTodayButton: true,
});
$("#id_valid_from").on("dp.change", function (e) {
$('#id_valid_to').data("DateTimePicker").minDate(e.date);
});
$("#id_valid_to").on("dp.change", function (e) {
$('#id_valid_from').data("DateTimePicker").maxDate(e.date);
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,21 @@
{% extends 'kfet/base.html' %}
{% block title %}Informations sur la caisse {{ checkout }}{% endblock %}
{% block content %}
{% if not checkout.is_protected %}
<p>
<a href="{% url 'kfet.checkout.update' checkout.pk %}">
Modifier les informations
</a>
</p>
{% endif %}
<p>Nom: {{ checkout.name }}</p>
<p>Valide du {{ checkout.valid_from|date:'l j F Y, G:i' }} au {{ checkout.valid_to|date:'l j F Y, G:i' }}</p>
<p>Créée par: {{ checkout.created_by }}</p>
<p>Balance: {{ checkout.balance }} €</p>
<p>Protected: {{ checkout.is_protected }}</p>
{% endblock %}

View file

@ -0,0 +1,48 @@
{% extends 'kfet/base.html' %}
{% block extra_head %}{{ form.media }}{% endblock %}
{% block title %}Edition de la caisse {{ checkout }}{% endblock %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }}
<div style="position:relative">{{ field }}</div>
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
{% endfor %}
<input type=submit value="Mettre à jour">
</form>
<script type="text/javascript">
$(document).ready(function() {
$('#id_valid_from').datetimepicker({
//format : 'YYYY-MM-DD HH:mm',
stepping : 5,
locale : 'fr',
showTodayButton: true,
});
$('#id_valid_to').datetimepicker({
//format : 'YYYY-MM-DD HH:mm',
stepping : 5,
useCurrent: false,
locale : 'fr',
showTodayButton: true,
useCurrent: false
});
$("#id_valid_from").on("dp.change", function (e) {
$('#id_valid_to').data("DateTimePicker").minDate(e.date);
});
$("#id_valid_to").on("dp.change", function (e) {
$('#id_valid_from').data("DateTimePicker").maxDate(e.date);
});
});
</script>
{% endblock %}

View file

@ -1,4 +1,5 @@
from django.conf.urls import url
from django.contrib.auth.decorators import permission_required
from kfet import views
from kfet import autocomplete
@ -7,10 +8,10 @@ urlpatterns = [
name = 'kfet.home'),
# -----
# Account views
# Account urls
# -----
# General
# Account - General
url(r'^account/$', views.account,
name = 'kfet.account'),
url(r'^account/is_validandfree$', views.account_is_validandfree_ajax,
@ -34,5 +35,26 @@ urlpatterns = [
# Account - Update
url(r'^account/(?P<trigramme>.{3})/edit$', views.account_update,
name = 'kfet.account.update')
name = 'kfet.account.update'),
# -----
# Checkout urls
# -----
# Checkout - General
url('^checkout/$',
permission_required('kfet.is_team')(views.CheckoutList.as_view()),
name = 'kfet.checkout'),
# Checkout - Create
url('^checkout/new$',
permission_required('kfet.is_team')(views.CheckoutCreate.as_view()),
name = 'kfet.checkout.create'),
# Checkout - Read
url('^checkout/(?P<pk>\d+)$',
permission_required('kfet.is_team')(views.CheckoutRead.as_view()),
name = 'kfet.checkout.read'),
# Checkout - Update
url('^checkout/(?P<pk>\d+)/edit$',
permission_required('kfet.is_team')(views.CheckoutUpdate.as_view()),
name = 'kfet.checkout.update'),
]

View file

@ -1,11 +1,16 @@
from django.shortcuts import render, get_object_or_404
from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.core.urlresolvers import reverse_lazy
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.models import User
from django.http import HttpResponse, Http404
import json
from gestioncof.models import CofProfile, Clipper
from kfet.models import Account
from kfet.models import Account, Checkout
from kfet.forms import *
@login_required
@ -16,12 +21,29 @@ def put_cleaned_data_in_dict(dict, form):
for field in form.cleaned_data:
dict[field] = form.cleaned_data[field]
# -----
# Account views
# -----
# Account - General
@login_required
@permission_required('account.is_team')
def account(request):
accounts = Account.objects.all()
accounts = Account.objects.order_by('trigramme')
return render(request, "kfet/account.html", { 'accounts' : accounts })
@login_required
@permission_required('kfet.is_team')
def account_is_validandfree_ajax(request):
if not request.GET.get("trigramme"):
raise Http404
trigramme = request.GET.get("trigramme")
data = Account.is_validandfree(trigramme)
return HttpResponse(json.dumps(data), content_type = 'application/json')
# Account - Create
@login_required
@permission_required('account.is_team')
def account_create(request):
@ -29,16 +51,12 @@ def account_create(request):
# A envoyer au template
data_template = {
'account_trigramme_form': AccountTriForm(),
'post' : False,
'success' : False,
'trigramme' : '',
'errors' : {},
'errors' : {},
}
# Enregistrement
if request.method == "POST":
# Pour indiquer la tentative d'enregistrement au template
data_template['post'] = True
# Checking permission
if not request.user.has_perm('kfet.add_account'):
@ -74,13 +92,10 @@ def account_create(request):
account = trigramme_form.save(data = data)
account_form = AccountNoTriForm(request.POST, instance=account)
account_form.save()
data_template['success'] = True
data_template['trigramme'] = account.trigramme
messages.success(request, 'Compte créé : %s' % account.trigramme)
except Account.UserHasAccount as e:
data_template['errors']['global'] = \
"Cet utilisateur a déjà un compte K-Fêt : " + e.trigramme
except PermissionDenied:
print("perm")
messages.error(request, \
"Cet utilisateur a déjà un compte K-Fêt : %s" % e.trigramme)
return render(request, "kfet/account_create.html", data_template)
@ -153,14 +168,7 @@ def account_create_ajax(request, username=None, login_clipper=None):
'user_form' : user_form,
})
@login_required
@permission_required('kfet.is_team')
def account_is_validandfree_ajax(request):
if not request.GET.get("trigramme"):
raise Http404
trigramme = request.GET.get("trigramme")
data = Account.is_validandfree(trigramme)
return HttpResponse(json.dumps(data), content_type = 'application/json')
# Account - Read
@login_required
def account_read(request, trigramme):
@ -171,11 +179,13 @@ def account_read(request, trigramme):
# Checking permissions
if not request.user.has_perm('kfet.is_team') \
and request.user != account.cofprofile.user:
and request.user != account.user:
raise PermissionDenied
return render(request, "kfet/account_read.html", { 'account' : account })
# Account - Update
@login_required
def account_update(request, trigramme):
try:
@ -184,24 +194,25 @@ def account_update(request, trigramme):
raise Http404
# Checking permissions
if not request.user.has_perm('kfet.change_account') \
and request.user != account.cofprofile.user:
if not request.user.has_perm('kfet.is_team') \
and request.user != account.user:
raise PermissionDenied
# Pour le template
post = False
success = False
if request.method == "POST":
# Update attempt
post = True
# Checking permissions
if not request.user.has_perm('kfet.change_account') \
and request.user != account.user:
raise PermissionDenied
# Peuplement des forms
if request.user.has_perm('kfet.change_account'):
account_form = AccountForm(request.POST, instance = account)
else:
account_form = AccountRestrictForm(request.POST, instance = account)
cof_form = CofRestrictForm(request.POST, instance=account.cofprofile)
user_form = UserRestrictForm(request.POST, instance=account.cofprofile.user)
user_form = UserRestrictForm(request.POST, instance=account.user)
if all((account_form.is_valid(), cof_form.is_valid(), user_form.is_valid())):
data = {}
@ -211,21 +222,79 @@ def account_update(request, trigramme):
# Updating
account_form.save(data = data)
success = True
if request.user == account.user:
messages.success(request, \
'Vos informations ont été mises à jour')
else:
messages.success(request, \
'Informations du compte %s mises à jour' % account.trigramme)
return redirect('kfet.account.read', account.trigramme)
else:
messages.error(request, \
'Informations non mises à jour. Corrigez les erreurs')
else:
# No update attempt
if request.user.has_perm('kfet.change_account'):
if request.user.has_perm('kfet.is_team'):
account_form = AccountForm(instance = account)
else:
account_form = AccountRestrictForm(instance = account)
cof_form = CofRestrictForm(instance = account.cofprofile)
user_form = UserRestrictForm(instance = account.cofprofile.user)
user_form = UserRestrictForm(instance = account.user)
return render(request, "kfet/account_update.html", {
'account' : account,
'account_form' : account_form,
'cof_form' : cof_form,
'user_form' : user_form,
'post' : post,
'success' : success,
})
# -----
# Checkout views
# -----
# Checkout - General
class CheckoutList(ListView):
model = Checkout
template_name = 'kfet/checkout.html'
context_object_name = 'checkouts'
# Checkout - Create
class CheckoutCreate(SuccessMessageMixin, CreateView):
model = Checkout
template_name = 'kfet/checkout_create.html'
form_class = CheckoutForm
success_message = 'Nouvelle caisse : %(name)s'
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('add_checkout'):
raise PermissionDenied
# Creating
form.instance.created_by = self.request.user.profile.account_kfet
return super(CheckoutCreate, self).form_valid(form)
# Checkout - Read
class CheckoutRead(DetailView):
model = Checkout
template_name = 'kfet/checkout_read.html'
context_object_name = 'checkout'
# Checkout - Update
class CheckoutUpdate(SuccessMessageMixin, UpdateView):
model = Checkout
template_name = 'kfet/checkout_update.html'
form_class = CheckoutRestrictForm
success_message = 'Informations mises à jour pour la caisse : %(name)s'
# Surcharge de la validation
def form_valid(self, form):
# Checking permission
if not self.request.user.has_perm('change_checkout'):
raise PermissionDenied
# Updating
return super(CheckoutUpdate, self).form_valid(form)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

64
static/moment-fr.js Normal file
View file

@ -0,0 +1,64 @@
//! moment.js locale configuration
//! locale : French [fr]
//! author : John Fischer : https://github.com/jfroffice
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
&& typeof require === 'function' ? factory(require('../moment')) :
typeof define === 'function' && define.amd ? define(['../moment'], factory) :
factory(global.moment)
}(this, function (moment) { 'use strict';
var fr = moment.defineLocale('fr', {
months : 'Janvier_Février_Mars_Avril_Mai_Juin_Juillet_Août_Septembre_Octobre_Novembre_Décembre'.split('_'),
monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'),
monthsParseExact : true,
weekdays : 'Dimanche_Lundi_Mardi_Mercredi_Jeudi_Vendredi_Samedi'.split('_'),
weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'),
weekdaysMin : 'Di_Lu_Ma_Me_Je_Ve_Sa'.split('_'),
weekdaysParseExact : true,
longDateFormat : {
LT : 'HH:mm',
LTS : 'HH:mm:ss',
L : 'DD/MM/YYYY',
LL : 'D MMMM YYYY',
LLL : 'D MMMM YYYY HH:mm',
LLLL : 'dddd D MMMM YYYY HH:mm'
},
calendar : {
sameDay: '[Aujourd\'hui à] LT',
nextDay: '[Demain à] LT',
nextWeek: 'dddd [à] LT',
lastDay: '[Hier à] LT',
lastWeek: 'dddd [dernier à] LT',
sameElse: 'L'
},
relativeTime : {
future : 'dans %s',
past : 'il y a %s',
s : 'quelques secondes',
m : 'une minute',
mm : '%d minutes',
h : 'une heure',
hh : '%d heures',
d : 'un jour',
dd : '%d jours',
M : 'un mois',
MM : '%d mois',
y : 'un an',
yy : '%d ans'
},
ordinalParse: /\d{1,2}(er|)/,
ordinal : function (number) {
return number + (number === 1 ? 'er' : '');
},
week : {
dow : 1, // Monday is the first day of the week.
doy : 4 // The week that contains Jan 4th is the first week of the year.
}
});
return fr;
}));

4195
static/moment.js Normal file

File diff suppressed because it is too large Load diff