3
0
Fork 0
mirror of https://github.com/nanoy42/coope synced 2024-11-22 11:23:11 +00:00

Initial commit pas initial

This commit is contained in:
Yoann Pétri 2018-10-06 00:03:02 +02:00
parent 9042adfd31
commit 15e13f978c
54 changed files with 2199 additions and 188 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"python.pythonPath": "/home/nanoy/.virtualenvs/coopeV3/bin/python"
}

View file

@ -40,6 +40,9 @@ INSTALLED_APPS = [
'gestion', 'gestion',
'users', 'users',
'preferences', 'preferences',
'coopeV3',
'dal',
'dal_select2',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View file

View file

@ -0,0 +1,40 @@
from django import template
from preferences.models import GeneralPreferences
register = template.Library()
@register.simple_tag
def president():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.president
@register.simple_tag
def vice_president():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.vice_president
@register.simple_tag
def treasurer():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.treasurer
@register.simple_tag
def secretary():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.secretary
@register.simple_tag
def brewer():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.brewer
@register.simple_tag
def grocer():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.grocer
@register.simple_tag
def global_message():
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
return gp.global_message

View file

@ -23,4 +23,5 @@ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('users/', include('users.urls')), path('users/', include('users.urls')),
path('gestion/', include('gestion.urls')), path('gestion/', include('gestion.urls')),
path('preferences/', include('preferences.urls')),
] ]

View file

@ -2,7 +2,7 @@ from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
def home(request): def home(request):
if request.user is not None: if request.user.is_authenticated:
if(request.user.has_perm('gestion.can_manage')): if(request.user.has_perm('gestion.can_manage')):
return redirect(reverse('gestion:manage')) return redirect(reverse('gestion:manage'))
else: else:

View file

@ -1,13 +1,11 @@
from django.forms.widgets import Input from django.forms.widgets import Select, Input
from django.template import Context, Template from django.template import Context, Template
from django.template.loader import get_template from django.template.loader import get_template
class SearchField: class SearchField(Input):
def __init__(self, url):
self.url = url
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
super().render(name, value, attrs) #super().render(name, value, attrs)
template = get_template('search_field.html') template = get_template('search_field.html')
context = Context({}) context = Context({})
return template.render(context) return template.render(context)

View file

@ -1,3 +1,8 @@
from django.contrib import admin from django.contrib import admin
# Register your models here. from .models import Reload, Refund, Product, Keg
admin.site.register(Reload)
admin.site.register(Refund)
admin.site.register(Product)
admin.site.register(Keg)

37
gestion/forms.py Normal file
View file

@ -0,0 +1,37 @@
from django import forms
from django.contrib.auth.models import User
from dal import autocomplete
from .models import Reload, Refund, Product, Keg, Menu
from preferences.models import PaymentMethod
from coopeV3.widgets import SearchField
class ReloadForm(forms.ModelForm):
class Meta:
model = Reload
fields = ("customer", "amount", "PaymentMethod")
class RefundForm(forms.ModelForm):
class Meta:
model = Refund
fields = ("customer", "amount")
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = "__all__"
class KegForm(forms.ModelForm):
class Meta:
model = Keg
fields = "__all__"
class MenuForm(forms.ModelForm):
class Meta:
model = Menu
fields = "__all__"
class GestionForm(forms.Form):
client = forms.ModelChoiceField(queryset=User.objects.filter(is_active=True), required=True, label="Client", widget=autocomplete.ModelSelect2(url='users:active-users-autocomplete', attrs={'data-minimum-input-length':2}))
paymentMethod = forms.ModelChoiceField(queryset=PaymentMethod.objects.all(), required=True, label="Moyen de paiement")

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1 on 2018-08-31 12:45 # Generated by Django 2.1 on 2018-10-04 09:32
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -11,23 +11,11 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('preferences', '0001_initial'), ('preferences', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Barrel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20)),
('stockHold', models.IntegerField(default=0)),
('barcode', models.CharField(max_length=20, unique=True)),
('amount', models.DecimalField(decimal_places=2, max_digits=5)),
('capacity', models.IntegerField(default=30)),
('active', models.BooleanField(default=False)),
],
),
migrations.CreateModel( migrations.CreateModel(
name='ConsumptionHistory', name='ConsumptionHistory',
fields=[ fields=[
@ -39,14 +27,38 @@ class Migration(migrations.Migration):
('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='consumption_taken', to=settings.AUTH_USER_MODEL)), ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='consumption_taken', to=settings.AUTH_USER_MODEL)),
], ],
), ),
migrations.CreateModel(
name='Keg',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True, verbose_name='Nom')),
('stockHold', models.IntegerField(default=0, verbose_name='Stock en soute')),
('barcode', models.CharField(max_length=20, unique=True, verbose_name='Code barre')),
('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Prix du fût')),
('capacity', models.IntegerField(default=30, verbose_name='Capacité (L)')),
('is_active', models.BooleanField(default=False, verbose_name='Actif')),
],
),
migrations.CreateModel(
name='KegHistory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('openingDate', models.DateTimeField(auto_now_add=True)),
('quantitySold', models.DecimalField(decimal_places=2, max_digits=5)),
('amountSold', models.DecimalField(decimal_places=2, max_digits=5)),
('closingDate', models.DateTimeField()),
('isCurrentKegHistory', models.BooleanField(default=True)),
('Keg', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Keg')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='Menu', name='Menu',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)), ('name', models.CharField(max_length=255, verbose_name='Nom')),
('amount', models.DecimalField(decimal_places=2, max_digits=5)), ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Montant')),
('barcode', models.CharField(max_length=20, unique=True)), ('barcode', models.CharField(max_length=20, unique=True, verbose_name='Code barre')),
('is_active', models.BooleanField(default=False)), ('is_active', models.BooleanField(default=False, verbose_name='Actif')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -66,17 +78,16 @@ class Migration(migrations.Migration):
name='Product', name='Product',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=40)), ('name', models.CharField(max_length=40, unique=True, verbose_name='Nom')),
('amount', models.DecimalField(decimal_places=2, max_digits=5)), ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Prix de vente')),
('stockHold', models.IntegerField(default=0)), ('stockHold', models.IntegerField(default=0, verbose_name='Stock en soute')),
('stockBar', models.IntegerField(default=0)), ('stockBar', models.IntegerField(default=0, verbose_name='Stock en bar')),
('barcode', models.CharField(max_length=20, unique=True)), ('barcode', models.CharField(max_length=20, unique=True, verbose_name='Code barre')),
('category', models.CharField(choices=[('PP', 'Pinte Pression'), ('DP', 'Demi Pression'), ('GP', 'Galopin pression'), ('BT', 'Bouteille'), ('SO', 'Soft'), ('FO', 'Bouffe')], default='FO', max_length=2)), ('category', models.CharField(choices=[('PP', 'Pinte Pression'), ('DP', 'Demi Pression'), ('GP', 'Galopin pression'), ('BT', 'Bouteille'), ('SO', 'Soft'), ('FO', 'Bouffe autre que panini'), ('PA', 'Bouffe pour panini')], default='FO', max_length=2, verbose_name='Catégorie')),
('needQuantityButton', models.BooleanField(default=False)), ('needQuantityButton', models.BooleanField(default=False, verbose_name='Bouton quantité')),
('is_active', models.BooleanField(default=True)), ('is_active', models.BooleanField(default=True, verbose_name='Actif')),
('is_beer', models.BooleanField(default=False)),
('volume', models.IntegerField(default=0)), ('volume', models.IntegerField(default=0)),
('deg', models.DecimalField(decimal_places=2, default=0, max_digits=5)), ('deg', models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='Degré')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -84,8 +95,8 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True)), ('date', models.DateTimeField(auto_now_add=True)),
('barrel', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Barrel')),
('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('keg', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Keg')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -93,20 +104,20 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True)), ('date', models.DateTimeField(auto_now_add=True)),
('amount', models.DecimalField(decimal_places=2, max_digits=5)), ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Montant')),
('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='refund_realized', to=settings.AUTH_USER_MODEL)), ('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='refund_realized', to=settings.AUTH_USER_MODEL)),
('cutsomer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='refund_taken', to=settings.AUTH_USER_MODEL)), ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='refund_taken', to=settings.AUTH_USER_MODEL, verbose_name='Client')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Reload', name='Reload',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=5)), ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Montant')),
('date', models.DateTimeField(auto_now_add=True)), ('date', models.DateTimeField(auto_now_add=True)),
('PaymentMethod', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='preferences.PaymentMethod')), ('PaymentMethod', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='preferences.PaymentMethod', verbose_name='Moyen de paiement')),
('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reload_realized', to=settings.AUTH_USER_MODEL)), ('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reload_realized', to=settings.AUTH_USER_MODEL)),
('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reload_taken', to=settings.AUTH_USER_MODEL)), ('customer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='reload_taken', to=settings.AUTH_USER_MODEL, verbose_name='Client')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -119,7 +130,22 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='menu', model_name='menu',
name='articles', name='articles',
field=models.ManyToManyField(to='gestion.Product'), field=models.ManyToManyField(to='gestion.Product', verbose_name='Produits'),
),
migrations.AddField(
model_name='keg',
name='demi',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='futd', to='gestion.Product', validators=[gestion.models.isDemi]),
),
migrations.AddField(
model_name='keg',
name='galopin',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='futg', to='gestion.Product', validators=[gestion.models.isGalopin]),
),
migrations.AddField(
model_name='keg',
name='pinte',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='futp', to='gestion.Product', validators=[gestion.models.isPinte]),
), ),
migrations.AddField( migrations.AddField(
model_name='consumptionhistory', model_name='consumptionhistory',
@ -136,19 +162,4 @@ class Migration(migrations.Migration):
name='product', name='product',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Product'), field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='gestion.Product'),
), ),
migrations.AddField(
model_name='barrel',
name='demi',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='futd', to='gestion.Product', validators=[gestion.models.isDemi]),
),
migrations.AddField(
model_name='barrel',
name='galopin',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='futg', to='gestion.Product', validators=[gestion.models.isGalopin]),
),
migrations.AddField(
model_name='barrel',
name='pinte',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='futp', to='gestion.Product', validators=[gestion.models.isPinte]),
),
] ]

View file

@ -1,6 +1,8 @@
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from preferences.models import PaymentMethod from preferences.models import PaymentMethod
from django.core.exceptions import ValidationError
class Product(models.Model): class Product(models.Model):
P_PRESSION = 'PP' P_PRESSION = 'PP'
D_PRESSION = 'DP' D_PRESSION = 'DP'
@ -8,28 +10,29 @@ class Product(models.Model):
BOTTLE = 'BT' BOTTLE = 'BT'
SOFT = 'SO' SOFT = 'SO'
FOOD = 'FO' FOOD = 'FO'
PANINI = 'PA'
TYPEINPUT_CHOICES_CATEGORIE = ( TYPEINPUT_CHOICES_CATEGORIE = (
(P_PRESSION, "Pinte Pression"), (P_PRESSION, "Pinte Pression"),
(D_PRESSION, "Demi Pression"), (D_PRESSION, "Demi Pression"),
(G_PRESSION, "Galopin pression"), (G_PRESSION, "Galopin pression"),
(BOTTLE, "Bouteille"), (BOTTLE, "Bouteille"),
(SOFT, "Soft"), (SOFT, "Soft"),
(FOOD, "Bouffe"), (FOOD, "Bouffe autre que panini"),
(PANINI, "Bouffe pour panini"),
) )
name = models.CharField(max_length=40) name = models.CharField(max_length=40, verbose_name="Nom", unique=True)
amount = models.DecimalField(max_digits=5, decimal_places=2) amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Prix de vente")
stockHold = models.IntegerField(default=0) stockHold = models.IntegerField(default=0, verbose_name="Stock en soute")
stockBar = models.IntegerField(default=0) stockBar = models.IntegerField(default=0, verbose_name="Stock en bar")
barcode= models.CharField(max_length=20, unique=True) barcode= models.CharField(max_length=20, unique=True, verbose_name="Code barre")
category = models.CharField(max_length=2, choices=TYPEINPUT_CHOICES_CATEGORIE, default=FOOD) category = models.CharField(max_length=2, choices=TYPEINPUT_CHOICES_CATEGORIE, default=FOOD, verbose_name="Catégorie")
needQuantityButton = models.BooleanField(default=False) needQuantityButton = models.BooleanField(default=False, verbose_name="Bouton quantité")
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True, verbose_name="Actif")
is_beer = models.BooleanField(default=False)
volume = models.IntegerField(default=0) volume = models.IntegerField(default=0)
deg = models.DecimalField(default=0,max_digits=5, decimal_places=2) deg = models.DecimalField(default=0,max_digits=5, decimal_places=2, verbose_name="Degré")
def __str__(self): def __str__(self):
return self.nom return self.name
def isPinte(id): def isPinte(id):
@ -43,7 +46,7 @@ def isPinte(id):
def isDemi(id): def isDemi(id):
product = Product.objects.get(id=id) product = Product.objects.get(id=id)
if produit.category != Product.D_PRESSION: if product.category != Product.D_PRESSION:
raise ValidationError( raise ValidationError(
('%(product)s n\'est pas un demi'), ('%(product)s n\'est pas un demi'),
params={'product': product}, params={'product': product},
@ -57,24 +60,32 @@ def isGalopin(id):
params={'product': product}, params={'product': product},
) )
class Barrel(models.Model): class Keg(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20, unique=True, verbose_name="Nom")
stockHold = models.IntegerField(default=0) stockHold = models.IntegerField(default=0, verbose_name="Stock en soute")
barcode = models.CharField(max_length=20, unique=True) barcode = models.CharField(max_length=20, unique=True, verbose_name="Code barre")
amount = models.DecimalField(max_digits=5, decimal_places=2) amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Prix du fût")
capacity = models.IntegerField(default=30) capacity = models.IntegerField(default=30, verbose_name="Capacité (L)")
pinte = models.ForeignKey(Product, on_delete=models.PROTECT, related_name="futp", validators=[isPinte]) pinte = models.ForeignKey(Product, on_delete=models.PROTECT, related_name="futp", validators=[isPinte])
demi = models.ForeignKey(Product, on_delete=models.PROTECT, related_name="futd", validators=[isDemi]) demi = models.ForeignKey(Product, on_delete=models.PROTECT, related_name="futd", validators=[isDemi])
galopin = models.ForeignKey(Product, on_delete=models.PROTECT, related_name="futg", validators=[isGalopin],null=True, blank=True) galopin = models.ForeignKey(Product, on_delete=models.PROTECT, related_name="futg", validators=[isGalopin],null=True, blank=True)
active= models.BooleanField(default=False) is_active = models.BooleanField(default=False, verbose_name="Actif")
def __str__(self): def __str__(self):
return self.name return self.name
class KegHistory(models.Model):
Keg = models.ForeignKey(Keg, on_delete=models.PROTECT)
openingDate = models.DateTimeField(auto_now_add=True)
quantitySold = models.DecimalField(decimal_places=2, max_digits=5)
amountSold = models.DecimalField(decimal_places=2, max_digits=5)
closingDate = models.DateTimeField()
isCurrentKegHistory = models.BooleanField(default=True)
class Reload(models.Model): class Reload(models.Model):
customer = models.ForeignKey(User, on_delete=models.PROTECT, related_name="reload_taken") customer = models.ForeignKey(User, on_delete=models.PROTECT, related_name="reload_taken", verbose_name="Client")
amount = models.DecimalField(max_digits=5, decimal_places=2) amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Montant")
PaymentMethod = models.ForeignKey(PaymentMethod, on_delete=models.PROTECT) PaymentMethod = models.ForeignKey(PaymentMethod, on_delete=models.PROTECT, verbose_name="Moyen de paiement")
coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="reload_realized") coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="reload_realized")
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
@ -83,12 +94,12 @@ class Reload(models.Model):
class Raming(models.Model): class Raming(models.Model):
barrel = models.ForeignKey(Barrel, on_delete=models.PROTECT) keg = models.ForeignKey(Keg, on_delete=models.PROTECT)
coopeman = models.ForeignKey(User, on_delete=models.PROTECT) coopeman = models.ForeignKey(User, on_delete=models.PROTECT)
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):
return "Percussion d'un {0} effectué par {1} le {2}".format(self.barrel, self.coopeman, self.date) return "Percussion d'un {0} effectué par {1} le {2}".format(self.keg, self.coopeman, self.date)
class Stocking(models.Model): class Stocking(models.Model):
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
@ -99,8 +110,8 @@ class Stocking(models.Model):
class Refund(models.Model): class Refund(models.Model):
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
cutsomer = models.ForeignKey(User, on_delete=models.PROTECT, related_name="refund_taken") customer = models.ForeignKey(User, on_delete=models.PROTECT, related_name="refund_taken", verbose_name="Client")
amount = models.DecimalField(max_digits=5, decimal_places=2) amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Montant")
coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="refund_realized") coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="refund_realized")
def __str__(self): def __str__(self):
@ -108,11 +119,11 @@ class Refund(models.Model):
class Menu(models.Model): class Menu(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255, verbose_name="Nom")
amount = models.DecimalField(max_digits=5, decimal_places=2) amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Montant")
barcode = models.CharField(max_length=20, unique=True) barcode = models.CharField(max_length=20, unique=True, verbose_name="Code barre")
articles = models.ManyToManyField(Product) articles = models.ManyToManyField(Product, verbose_name="Produits")
is_active = models.BooleanField(default=False) is_active = models.BooleanField(default=False, verbose_name="Actif")
def __str__(self): def __str__(self):
return self.name return self.name

View file

@ -0,0 +1,212 @@
{% extends "base.html" %}
{% load static %}
{%block entete%}<h1>Gestion de la Coopé™</h1>{%endblock%}
{% block navbar %}
<ul>
{% if perms.gestion.can_add_consumption_history %}<li><a href="#first">Commande</a></li>{% endif %}
{% if perms.gestion.can_add_reload %}<li><a href="#second">Rechargement Client</a></li>{% endif %}
{% if perms.gestion.can_add_refund %}<li><a href="#third">Remboursement client</a><li>{% endif %}
</ul>
{% endblock %}
{% block content %}
<a class="up_button" href="#intro">
UP
</a>
<style>
.up_button{
display:block;
background-color:white;
position:fixed;
border-radius:100%;
width:50px;
height:50px;
color:black;
text-align:center;
line-height:50px;
right:1em;
bottom : 1em;
}
</style>
<section id="intro" class="main">
<div class="spotlight">
<div class="content">
<header class="major">
<h2>Transaction</h2>
</header>
<div class="row uniform">
<div class="12u$">
{{gestion_form}}
</div>
</div>
<div class="row uniform">
<h3>Récapitulatif</h3>
</div>
<div class="row uniform">
<div class="12u$">
<table id="sumUpTable">
<thead>
<tr>
<th>Solde</th>
<th>Montant total de la commande</th>
<th>Solde après la commande</th>
<th>Payer</th>
</tr>
</thead>
<tbody>
<tr>
<td id="balance">0€</td>
<td id="totalAmount">0€</td>
<td id="totalAfter">0€</td>
<td><button class="btn small">Payer</button></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row uniform">
<h3>Produits</h3>
</div>
<div class="row uniform">
<div class="12u$">
<table id="productTable" type="input" name="tableau" class="alt">
<thead>
<tr>
<th>CodeBarre</th>
<th>Nom Produit</th>
<th>Prix Unitaire</th>
<th>Quantité</th>
<th>Sous-total</th>
</tr>
</thead>
<tbody id="items">
</tbody>
</table>
</div>
</div>
<div class="row uniform">
<div class="12u$">
<div class="boutonProduit">
<table>
<tbody class="actions" id="bouton Produit">
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Bières pression</td></tr>
{% for produit in bieresPression %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="product" target="{{produit.barcode}}">{{produit.name}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not bieresPression|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Bières bouteilles</td></tr>
{% for produit in bieresBouteille %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="boutonsProduit" disabled target="{{produit.codeBarre}}" type="{{produit.typeSaisie}}">{{produit.nom}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not bieresBouteille|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Paninis</td></tr>
{% for produit in panini %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="boutonsProduit" disabled target="{{produit.codeBarre}}" type="{{produit.typeSaisie}}">{{produit.nom}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not panini|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Softs</td></tr>
{% for produit in soft %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="boutonsProduit" disabled target="{{produit.codeBarre}}" type="{{produit.typeSaisie}}">{{produit.nom}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not soft|divisibleby:4 %}
</tr>
{% endif %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Bouffe</td></tr>
{% for produit in autreBouffe %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="boutonsProduit" disabled target="{{produit.codeBarre}}" type="{{produit.typeSaisie}}">{{produit.nom}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not autreBouffe|divisibleby:4 %}
</tr>
{% endif %}
{% if menus %}
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Menus</td></tr>
{% for produit in menus %}
{% if forloop.counter0|divisibleby:4 %}
<tr style="text-align:center">
{% endif %}
<td><button class="boutonsProduit" disabled target="{{produit.codeBarre}}" type="MN">{{produit.nom}}</button></td>
{% if forloop.counter|divisibleby:4 %}
</tr>
{% endif %}
{% endfor %}
{% if not menus|divisibleby:4 %}
</tr>
{% endif %}
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</section>
{% if perms.gestion.cand_add_reload %}
<section id="second" class="main">
<header class="major">
<h2>Rechargement client</h2>
</header>
<form method="post" action="{% url 'gestion:reload' %}">
{% csrf_token %}
{{reload_form}}
<br>
<button type="submit">Recharger</button>
</form>
</section>
{% endif %}
{% if perms.gestion.can_refund %}
<section id="third" class="main">
<header class="major">
<h2>Remboursement client</h2>
</header>
<form method="post" action="{% url 'gestion:refund' %}">
{% csrf_token %}
{{refund_form}}
<br>
<button type="submit">Rembourser</button>
</form>
</section>
{% endif %}
{{gestion_form.media}}
<script src="{% static 'manage.js' %}"></script>
{%endblock%}

View file

@ -0,0 +1,55 @@
{% extends 'base.html' %}
{% block entete %}<h1>Gestion des produits</h1>{% endblock %}
{% block navbar%}
<ul>
<li><a href="#first">Produits</a></li>
<li><a href="#second">Futs</a></li>
<li><a href="#third">Menus</a></li>
<li><a href="#fourth">Stocks</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Produits</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'gestion:addProduct' %}">Créer un produit</a></li>
<li><a href="{% url 'users:searchUser' %}">Rechercher un produit</a></li>
<li><a href="{% url 'users:usersIndex' %}">Lister tous les produits</a></li>
</ul>
</section>
<section id="second" class="main">
<header class="major">
<h2>Futs</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'gestion:addBarrel' %}">Créer un fut</a></li>
<li><a href="">Percuter un fut</a></li>
<li><a href="">Lister les futs</a></li>
</ul>
</section>
<section id="third" class="main">
<header class="major">
<h2>Menus</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'gestion:addMenu' %}">Créer un menu</a></li>
<li><a href="">Rechercher un menu</a></li>
<li><a href="{% url 'users:adminsIndex' %}">Lister les menus</a></li>
</ul>
</section>
<section id="fourth" class="main">
<header class="major">
<h2>Stocks</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'users:addSuperuser' %}">Voir les Stocks</a></li>
<li><a href="{% url 'users:superusersIndex' %}">Classement sur un produit</a></li>
</ul>
</section>
{% endblock %}

View file

@ -0,0 +1,55 @@
{% extends 'base.html' %}
{% block entete %}<h1>Gestion des produits</h1>{% endblock %}
{% block navbar%}
<ul>
<li><a href="#first">Produits</a></li>
<li><a href="#second">Futs</a></li>
<li><a href="#third">Menus</a></li>
<li><a href="#fourth">Stocks</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des produits</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'gestion:addProduct' %}">Créer un produit</a></li>
<li><a href="{% url 'users:searchUser' %}">Rechercher un produit</a></li>
<li><a href="{% url 'users:usersIndex' %}">Lister tous les produits</a></li>
</ul>
</section>
<section id="second" class="main">
<header class="major">
<h2>Futs</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'users:createGroup' %}">Créer un fut</a></li>
<li><a href="">Percuter un fut</a></li>
<li><a href="">Lister les futs</a><li>
</ul>
</section>
<section id="third" class="main">
<header class="major">
<h2>Menus</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'users:addAdmin' %}">Créer un menu</a></li>
<li><a href="">Rechercher un menu</a></li>
<li><a href="{% url 'users:adminsIndex' %}">Lister les menus</a></li>
</ul>
</section>
<section id="fourth" class="main">
<header class="major">
<h2>Stocks</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'users:addSuperuser' %}">Voir les Stocks</a></li>
<li><a href="{% url 'users:superusersIndex' %}">Classement sur un produit</a></li>
</ul>
</section>
{% endblock %}

View file

@ -5,4 +5,11 @@ from . import views
app_name="gestion" app_name="gestion"
urlpatterns = [ urlpatterns = [
path('manage', views.manage, name="manage"), path('manage', views.manage, name="manage"),
path('reload', views.reload, name="reload"),
path('refund', views.refund, name="refund"),
path('productsIndex', views.productsIndex, name="productsIndex"),
path('addProduct', views.addProduct, name="addProduct"),
path('addKeg', views.addKeg, name="addKeg"),
path('addMenu', views.addMenu, name="addMenu"),
path('getProduct/<str:barcode>', views.getProduct, name="getProduct"),
] ]

View file

@ -1,4 +1,108 @@
from django.shortcuts import render from django.shortcuts import render, redirect
from django.contrib import messages
from django.urls import reverse
from django.http import HttpResponse
from django.contrib.auth.models import User
import json
from dal import autocomplete
from .forms import ReloadForm, RefundForm, ProductForm, KegForm, MenuForm, GestionForm
from .models import Product, Menu, Keg
def manage(request): def manage(request):
return render(request, "base.html") gestion_form = GestionForm(request.POST or None)
reload_form = ReloadForm(request.POST or None)
refund_form = RefundForm(request.POST or None)
bieresPression = []
bieresBouteille = Product.objects.filter(category=Product.BOTTLE).filter(is_active=True)
panini = Product.objects.filter(category=Product.PANINI).filter(is_active=True)
food = Product.objects.filter(category=Product.FOOD).filter(is_active=True)
soft = Product.objects.filter(category=Product.SOFT).filter(is_active=True)
menus = Menu.objects.filter(is_active=True)
kegs = Keg.objects.filter(is_active=True)
for keg in kegs:
if(keg.pinte):
bieresPression.append(keg.pinte)
if(keg.demi):
bieresPression.append(keg.demi)
if(keg.galopin):
bieresPression.append(keg.galopin)
return render(request, "gestion/manage.html", {"gestion_form": gestion_form, "reload_form": reload_form, "refund_form": refund_form, "bieresPression": bieresPression, "bieresBouteille": bieresBouteille, "panini": panini, "food": food, "soft": soft, "menus": menus})
def reload(request):
reload_form = ReloadForm(request.POST or None)
if(reload_form.is_valid()):
reloadEntry = reload_form.save(commit=False)
reloadEntry.coopeman = request.user
reloadEntry.save()
user = reload_form.cleaned_data['customer']
amount = reload_form.cleaned_data['amount']
user.profile.credit += amount
user.save()
messages.success(request,"Le compte de " + user.username + " a bien été crédité de " + str(amount) + "")
else:
messages.error(request, "Le rechargement a échoué")
return redirect(reverse('gestion:manage'))
def refund(request):
refund_form = RefundForm(request.POST or None)
if(refund_form.is_valid()):
user = refund_form.cleaned_data['customer']
amount = refund_form.cleaned_data['amount']
if(amount <= user.profile.balance):
refundEntry = refund_form.save(commit = False)
refundEntry.coopeman = request.user
refundEntry.save()
user.profile.credit -= amount
user.save()
messages.success(request, "Le compte de " + user.username + " a bien été remboursé de " + str(amount) + "")
else:
messages.error(request, "Impossible de rembourser l'utilisateur " + user.username + " de " + str(amount) + "€ : il n'a que " + str(user.profile.balance) + "€ sur son compte.")
else:
messages.error(request, "Le remboursement a échoué")
return redirect(reverse('gestion:manage'))
def productsIndex(request):
return render(request, "gestion/products_index.html")
def addProduct(request):
form = ProductForm(request.POST or None)
if(form.is_valid()):
form.save()
messages.success(request, "Le produit a bien été ajouté")
return redirect(reverse('gestion:productsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Ajout d'un produit", "form_button": "Ajouter"})
def productsList(request):
products = Product.objects.all()
return render(request, "gestion/products_list.html", {"products": products})
def getProduct(request, barcode):
product = Product.objects.get(barcode=barcode)
data = json.dumps({"pk": product.pk, "barcode" : product.barcode, "name": product.name, "amount" : float(product.amount)})
return HttpResponse(data, content_type='application/json')
########## Kegs ##########
def addKeg(request):
form = KegForm(request.POST or None)
if(form.is_valid()):
keg = form.save()
messages.success(request, "Le fût " + keg.name + " a bien été ajouté")
return redirect(reverse('gestion:productsIndex'))
return render(request, "form.html", {"form":form, "form_title": "Ajout d'un fût", "form_button": "Ajouter"})
########## Menus ##########
def addMenu(request):
form = MenuForm(request.POST or None)
extra_css = "#id_articles{height:200px;}"
if(form.is_valid()):
menu = form.save()
messages.success(request, "Le menu " + menu.name + " a bien été ajouté")
return redirect(reverse('gestion:productsIndex'))
return render(request, "form.html", {"form":form, "form_title": "Ajout d'un menu", "form_button": "Ajouter", "extra_css": extra_css})

View file

@ -1,3 +1,8 @@
from django.contrib import admin from django.contrib import admin
from .models import PaymentMethod, GeneralPreferences, Cotisation
admin.site.register(PaymentMethod)
admin.site.register(GeneralPreferences)
admin.site.register(Cotisation)
# Register your models here. # Register your models here.

30
preferences/forms.py Normal file
View file

@ -0,0 +1,30 @@
from django import forms
from .models import Cotisation, PaymentMethod, GeneralPreferences
class CotisationForm(forms.ModelForm):
class Meta:
model = Cotisation
fields = "__all__"
class PaymentMethodForm(forms.ModelForm):
class Meta:
model = PaymentMethod
fields = "__all__"
class GeneralPreferencesForm(forms.ModelForm):
class Meta:
model = GeneralPreferences
fields = "__all__"
widgets = {
'global_message': forms.Textarea(attrs={'placeholder': 'Message global à afficher sur le site'}),
'active_message': forms.Textarea(attrs={'placeholder': 'Ce message s\'affichera si le site n\'est pas actif'}),
'president': forms.TextInput(attrs={'placeholder': 'Président'}),
'vice_president': forms.TextInput(attrs={'placeholder': 'Vice-président'}),
'secretary': forms.TextInput(attrs={'placeholder': 'Secrétaire'}),
'treasurer': forms.TextInput(attrs={'placeholder': 'Trésorier'}),
'brewer': forms.TextInput(attrs={'placeholder': 'Maître brasseur'}),
'grocer': forms.TextInput(attrs={'placeholder': 'Epic épicier'}),
}

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1 on 2018-08-31 12:45 # Generated by Django 2.1 on 2018-10-04 09:32
from django.db import migrations, models from django.db import migrations, models
@ -11,6 +11,14 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Cotisation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=5, null=True, verbose_name='Montant')),
('duration', models.PositiveIntegerField(verbose_name='Durée de la cotisation (jours)')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='GeneralPreferences', name='GeneralPreferences',
fields=[ fields=[
@ -30,7 +38,10 @@ class Migration(migrations.Migration):
name='PaymentMethod', name='PaymentMethod',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)), ('name', models.CharField(max_length=255, verbose_name='Nom')),
('is_active', models.BooleanField(default=True)),
('is_usable_in_cotisation', models.BooleanField(default=True)),
('affect_balance', models.BooleanField(default=False)),
], ],
), ),
] ]

View file

@ -1,7 +1,10 @@
from django.db import models from django.db import models
class PaymentMethod(models.Model): class PaymentMethod(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255, verbose_name="Nom")
is_active = models.BooleanField(default=True, verbose_name="Actif")
is_usable_in_cotisation = models.BooleanField(default=True, verbose_name="Utilisable pour les cotisations")
affect_balance = models.BooleanField(default=False, verbose_name="Affecte le solde")
def __str__(self): def __str__(self):
return self.name return self.name
@ -16,3 +19,10 @@ class GeneralPreferences(models.Model):
secretary = models.CharField(max_length=255, blank=True) secretary = models.CharField(max_length=255, blank=True)
brewer = models.CharField(max_length=255, blank=True) brewer = models.CharField(max_length=255, blank=True)
grocer = models.CharField(max_length=255, blank=True) grocer = models.CharField(max_length=255, blank=True)
class Cotisation(models.Model):
amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, verbose_name="Montant")
duration = models.PositiveIntegerField(verbose_name="Durée de la cotisation (jours)")
def __str__(self):
return "Cotisation de " + str(self.duration) + " jours pour le prix de " + str(self.amount) + ""

View file

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block entete %}<h1>Gestion des cotisations</h1>{% endblock %}
{% block navbar %}
<ul>
<li><a href="#first">Liste des cotisations</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des cotisations</h2>
</header>
<a class="button" href="{% url 'preferences:addCotisation' %}">Créer une cotisation</a><br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Durée de cotisation</th>
<th>Prix</th>
<th>Administration</th>
</tr>
</thead>
<tbody>
{% for cotisation in cotisations %}
<tr>
<td>{{ cotisation.duration }} jours</td>
<td>{{ cotisation.amount }} €</td>
<td><a class="button small" href="{% url 'preferences:editCotisation' cotisation.pk %}">Modifier</a> <a class="button small" href="{% url 'preferences:deleteCotisation' cotisation.pk %}">Supprimer</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View file

@ -0,0 +1,89 @@
{% extends 'base.html' %}
{% block entete %}
<h1>Administration</h1>
{% endblock %}
{% block nav %}
<ul>
<li><a href="#first" class="active">Message global</a></li>
<li><a href="#second">Site actif</a></li>
<li><a href="#third">Bureau</a></li>
</ul>
{% endblock %}
{% block content %}
<form class="main" method="post" action="">
{% csrf_token %}
<section id="first" class="main">
<div class="spotlight">
<div class="content">
<header class="major">
<h2>Message global</h2>
</header>
<div class="row">
<div class="12u">
{{form.global_message}}
</div>
</div>
</div>
</div>
</section>
<section id="second" class="main">
<div class="spotlight">
<div class="content">
<header class="major">
<h2>Site actif</h2>
</header>
<div class="row uniform">
<div class="12u">
{{form.is_active}}
<label for="{{form.is_active.id_for_label}}">Site actif ?</label>
</div>
</div>
<div class="row uniform">
<div class="12u">
{{form.active_message}}
</div>
</div>
</div>
</div>
</section>
<section id="third" class="main">
<div class="spotlight">
<div class="content">
<header class="major">
<h2>Bureau</h2>
</header>
<div class="row uniform">
<div class="6u">
{{form.president}}
</div>
<div class="6u">
{{form.vice_president}}
</div>
</div>
<div class="row uniform">
<div class="6u">
{{form.secretary}}
</div>
<div class="6u">
{{form.treasurer}}
</div>
</div>
<div class="row uniform">
<div class="6u">
{{form.grocer}}
</div>
<div class="6u">
{{form.brewer}}
</div>
</div>
<div class="row uniform">
<div class="12u">
<button type="submit">Enregistrer</button>
</div>
</div>
</div>
</div>
</section>
</form>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block entete %}<h1>Gestion des moyens de paiement</h1>{% endblock %}
{% block navbar %}
<ul>
<li><a href="#first">Liste des moyens de paiement</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des moyens de paiement</h2>
</header>
<a class="button" href="{% url 'preferences:addPaymentMethod' %}">Créer un moyen de paiement</a><br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Nom</th>
<th>Actif ?</th>
<th>Utilisable dans les cotisations</th>
<th>Affecte le solde</th>
<th>Administration</th>
</tr>
</thead>
<tbody>
{% for pm in paymentMethods %}
<tr>
<td>{{ pm.name }} </td>
<td>{{ pm.is_active | yesno:"Oui, Non"}}</td>
<td>{{ pm.is_usable_in_cotisation | yesno:"Oui, Non" }}</td>
<td>{{ pm.affect_balance | yesno:"Oui, Non" }}</td>
<td><a class="button small" href="{% url 'preferences:editPaymentMethod' pm.pk %}">Modifier</a> <a class="button small" href="{% url 'preferences:deletePaymentMethod' pm.pk %}">Supprimer</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

16
preferences/urls.py Normal file
View file

@ -0,0 +1,16 @@
from django.urls import path
from . import views
app_name="preferences"
urlpatterns = [
path('generalPreferences', views.generalPreferences, name="generalPreferences"),
path('cotisationsIndex', views.cotisationsIndex, name="cotisationsIndex"),
path('addCotisation', views.addCotisation, name="addCotisation"),
path('editCotisation/<int:pk>', views.editCotisation, name="editCotisation"),
path('deleteCotisation/<int:pk>', views.deleteCotisation, name="deleteCotisation"),
path('paymentMethodsIndex', views.paymentMethodsIndex, name="paymentMethodsIndex"),
path('addPaymentMethod', views.addPaymentMethod, name="addPaymentMethod"),
path('editPaymentMethod/<int:pk>', views.editPaymentMethod, name="editPaymentMethod"),
path('deletePaymentMethod/<int:pk>', views.deletePaymentMethod, name="deletePaymentMethod"),
]

View file

@ -1,3 +1,75 @@
from django.shortcuts import render from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.urls import reverse
# Create your views here. from .models import GeneralPreferences, Cotisation, PaymentMethod
from .forms import CotisationForm, PaymentMethodForm, GeneralPreferencesForm
def generalPreferences(request):
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
form = GeneralPreferencesForm(request.POST or None, instance=gp)
if(form.is_valid()):
form.save()
return render(request, "preferences/general_preferences.html", {"form": form})
########## Cotisations ##########
def cotisationsIndex(request):
cotisations = Cotisation.objects.all()
return render(request, "preferences/cotisations_index.html", {"cotisations": cotisations})
def addCotisation(request):
form = CotisationForm(request.POST or None)
if(form.is_valid()):
cotisation = form.save()
messages.success(request, "La cotisation (" + str(cotisation.duration) + " jours, " + str(cotisation.amount) + "€) a bien été créée")
return redirect(reverse('preferences:cotisationsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Création d'une cotisation", "form_button": "Créer"})
def editCotisation(request, pk):
cotisation = get_object_or_404(Cotisation, pk=pk)
form = CotisationForm(request.POST or None, instance=cotisation)
if(form.is_valid()):
cotisation = form.save()
messages.success(request, "La cotisation (" + str(cotisation.duration) + " jours, " + str(cotisation.amount) + "€) a bien été modifiée")
return redirect(reverse('preferences:cotisationsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Modification d'une cotisation", "form_button": "Modifier"})
def deleteCotisation(request,pk):
cotisation = get_object_or_404(Cotisation, pk=pk)
message = "La cotisation (" + str(cotisation.duration) + " jours, " + str(cotisation.amount) + "€) a bien été supprimée"
cotisation.delete()
messages.success(request, message)
return redirect(reverse('preferences:cotisationsIndex'))
########## Payment Methods ##########
def paymentMethodsIndex(request):
paymentMethods = PaymentMethod.objects.all()
return render(request, "preferences/payment_methods_index.html", {"paymentMethods": paymentMethods})
def addPaymentMethod(request):
form = PaymentMethodForm(request.POST or None)
if(form.is_valid()):
paymentMethod = form.save()
messages.success(request, "Le moyen de paiement " + paymentMethod.name + " a bien été crée")
return redirect(reverse('preferences:paymentMethodsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Création d'un moyen de paiement", "form_button": "Créer"})
def editPaymentMethod(request, pk):
paymentMethod = get_object_or_404(PaymentMethod, pk=pk)
form = PaymentMethodForm(request.POST or None, instance=paymentMethod)
if(form.is_valid()):
paymentMethod = form.save()
messages.success(request, "Le moyen de paiment " + paymentMethod.name + " a bien été modifié")
return redirect(reverse('preferences:paymentMethodsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Modification d'un moyen de paiement", "form_button": "Modifier"})
def deletePaymentMethod(request,pk):
paymentMethod = get_object_or_404(PaymentMethod, pk=pk)
message = "Le moyen de paiement " + paymentMethod.name + " a bien été supprimé"
paymentMethod.delete()
messages.success(request, message)
return redirect(reverse('preferences:paymentMethodsIndex'))

View file

@ -1,2 +1,3 @@
Django==2.1 Django==2.1
django-autocomplete-light==3.3.2
pytz==2018.5 pytz==2018.5

View file

@ -0,0 +1,162 @@
/*
This script garantees that this will be called once in django admin.
However, its the callback's responsability to clean up if the
element was cloned with data - which should be the case.
*/
;(function ($) {
$.fn.getFormPrefix = function() {
/* Get the form prefix for a field.
*
* For example:
*
* $(':input[name$=owner]').getFormsetPrefix()
*
* Would return an empty string for an input with name 'owner' but would return
* 'inline_model-0-' for an input named 'inline_model-0-owner'.
*/
var parts = $(this).attr('name').split('-');
var prefix = '';
for (var i in parts) {
var testPrefix = parts.slice(0, -i).join('-');
if (! testPrefix.length) continue;
testPrefix += '-';
var result = $(':input[name^=' + testPrefix + ']')
if (result.length) {
return testPrefix;
}
}
return '';
}
$.fn.getFormPrefixes = function() {
/*
* Get the form prefixes for a field, from the most specific to the least.
*
* For example:
*
* $(':input[name$=owner]').getFormPrefixes()
*
* Would return:
* - [''] for an input named 'owner'.
* - ['inline_model-0-', ''] for an input named 'inline_model-0-owner' (i.e. nested with a nested inline).
* - ['sections-0-items-0-', 'sections-0-', ''] for an input named 'sections-0-items-0-product'
* (i.e. nested multiple time with django-nested-admin).
*/
var parts = $(this).attr('name').split('-').slice(0, -1);
var prefixes = [];
for (i = 0; i < parts.length; i += 2) {
var testPrefix = parts.slice(0, -i || parts.length).join('-');
if (!testPrefix.length)
continue;
testPrefix += '-';
var result = $(':input[name^=' + testPrefix + ']')
if (result.length)
prefixes.push(testPrefix);
}
prefixes.push('');
return prefixes;
}
var initialized = [];
function initialize(element) {
if (typeof element === 'undefined' || typeof element === 'number') {
element = this;
}
if (window.__dal__initListenerIsSet !== true || initialized.indexOf(element) >= 0) {
return;
}
$(element).trigger('autocompleteLightInitialize');
initialized.push(element);
}
if (!window.__dal__initialize) {
window.__dal__initialize = initialize;
$(document).ready(function () {
$('[data-autocomplete-light-function=select2]:not([id*="__prefix__"])').each(initialize);
});
$(document).bind('DOMNodeInserted', function (e) {
$(e.target).find('[data-autocomplete-light-function=select2]:not([id*="__prefix__"])').each(initialize);
});
}
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = $.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
document.csrftoken = getCookie('csrftoken');
if (document.csrftoken === null) {
// Try to get CSRF token from DOM when cookie is missing
var $csrf = $('form :input[name="csrfmiddlewaretoken"]');
if ($csrf.length > 0) {
document.csrftoken = $csrf[0].value;
}
}
})(yl.jQuery);
// Does the same thing as django's admin/js/autocomplete.js, but uses yl.jQuery.
(function($) {
'use strict';
var init = function($element, options) {
var settings = $.extend({
ajax: {
data: function(params) {
return {
term: params.term,
page: params.page
};
}
}
}, options);
$element.select2(settings);
};
$.fn.djangoAdminSelect2 = function(options) {
var settings = $.extend({}, options);
$.each(this, function(i, element) {
var $element = $(element);
init($element, settings);
});
return this;
};
$(function() {
// Initialize all autocomplete widgets except the one in the template
// form used when a new formset is added.
$('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
});
$(document).on('formset:added', (function() {
return function(event, $newFormset) {
return $newFormset.find('.admin-autocomplete').djangoAdminSelect2();
};
})(this));
}(yl.jQuery));

View file

@ -0,0 +1,183 @@
;(function($, yl) {
yl.forwardHandlerRegistry = yl.forwardHandlerRegistry || {};
yl.registerForwardHandler = function(name, handler) {
yl.forwardHandlerRegistry[name] = handler;
};
yl.getForwardHandler = function(name) {
return yl.forwardHandlerRegistry[name];
};
function getForwardStrategy(element) {
var checkForCheckboxes = function() {
var all = true;
$.each(element, function(ix, e) {
if ($(e).attr("type") !== "checkbox") {
all = false;
}
});
return all;
};
if (element.length === 1 &&
element.attr("type") === "checkbox" &&
element.attr("value") === undefined) {
// Single checkbox without 'value' attribute
// Boolean field
return "exists";
} else if (element.length === 1 &&
element.attr("multiple") !== undefined) {
// Multiple by HTML semantics. E. g. multiple select
// Multiple choice field
return "multiple";
} else if (checkForCheckboxes()) {
// Multiple checkboxes or one checkbox with 'value' attribute.
// Multiple choice field represented by checkboxes
return "multiple";
} else {
// Other cases
return "single";
}
}
/**
* Get fields with name `name` relative to `element` with considering form
* prefixes.
* @param element the element
* @param name name of the field
* @returns jQuery object with found fields or empty jQuery object if no
* field was found
*/
yl.getFieldRelativeTo = function(element, name) {
var prefixes = $(element).getFormPrefixes();
for (var i = 0; i < prefixes.length; i++) {
var fieldSelector = "[name=" + prefixes[i] + name + "]";
var field = $(fieldSelector);
if (field.length) {
return field;
}
}
return $();
};
/**
* Get field value which is put to forwarded dictionary
* @param field the field
* @returns forwarded value
*/
yl.getValueFromField = function(field) {
var strategy = getForwardStrategy(field);
var serializedField = $(field).serializeArray();
var getSerializedFieldElementAt = function (index) {
// Return serializedField[index]
// or null if something went wrong
if (serializedField.length > index) {
return serializedField[index];
} else {
return null;
}
};
var getValueOf = function (elem) {
// Return elem.value
// or null if something went wrong
if (elem.hasOwnProperty("value") &&
elem.value !== undefined
) {
return elem.value;
} else {
return null;
}
};
var getSerializedFieldValueAt = function (index) {
// Return serializedField[index].value
// or null if something went wrong
var elem = getSerializedFieldElementAt(index);
if (elem !== null) {
return getValueOf(elem);
} else {
return null;
}
};
if (strategy === "multiple") {
return serializedField.map(
function (item) {
return getValueOf(item);
}
);
} else if (strategy === "exists") {
return serializedField.length > 0;
} else {
return getSerializedFieldValueAt(0);
}
};
yl.getForwards = function(element) {
var forwardElem,
forwardList,
forwardedData,
divSelector,
form;
divSelector = "div.dal-forward-conf#dal-forward-conf-for-" +
element.attr("id");
form = element.length > 0 ? $(element[0].form) : $();
forwardElem =
form.find(divSelector).find('script');
if (forwardElem.length === 0) {
return;
}
try {
forwardList = JSON.parse(forwardElem.text());
} catch (e) {
return;
}
if (!Array.isArray(forwardList)) {
return;
}
forwardedData = {};
$.each(forwardList, function(ix, field) {
var srcName, dstName;
if (field.type === "const") {
forwardedData[field.dst] = field.val;
} else if (field.type === "self") {
if (field.hasOwnProperty("dst")) {
dstName = field.dst;
} else {
dstName = "self";
}
forwardedData[dstName] = yl.getValueFromField(element);
} else if (field.type === "field") {
srcName = field.src;
if (field.hasOwnProperty("dst")) {
dstName = field.dst;
} else {
dstName = srcName;
}
var forwardedField = yl.getFieldRelativeTo(element, srcName);
if (!forwardedField.length) {
return;
}
forwardedData[dstName] = yl.getValueFromField(forwardedField);
} else if (field.type === "javascript") {
var handler = yl.getForwardHandler(field.handler);
forwardedData[field.dst || field.handler] = handler(element);
}
});
return JSON.stringify(forwardedData);
};
})(yl.jQuery, yl);

View file

@ -0,0 +1,36 @@
var yl = yl || {};
if (typeof django !== 'undefined' && typeof django.jQuery !== 'undefined') {
// If django.jQuery is already defined, use it.
yl.jQuery = django.jQuery;
}
else {
// We include jquery itself in our widget's media, because we need it.
// Normally, we expect our widget's reference to admin/js/vendor/jquery/jquery.js
// to be skipped, because django's own code has already included it.
// However, if django.jQuery is NOT defined, we know that jquery was not
// included before we did it ourselves. This can happen if we're not being
// rendered in a django admin form.
// However, someone ELSE'S jQuery may have been included before ours, in
// which case we must ensure that our jquery doesn't override theirs, since
// it might be a newer version that other code on the page relies on.
// Thus, we must run jQuery.noConflict(true) here to move our jQuery out of
// the way.
yl.jQuery = jQuery.noConflict(true);
}
// In addition to all of this, we must ensure that the global jQuery and $ are
// defined, because Select2 requires that. jQuery will only be undefined at
// this point if only we or django included it.
if (typeof jQuery === 'undefined') {
jQuery = yl.jQuery;
$ = yl.jQuery;
}
else {
// jQuery IS still defined, which means someone else also included jQuery.
// In this situation, we need to store the old jQuery in a
// temp variable, set the global jQuery to our yl.jQuery, then let select2
// set itself up. We restore the global jQuery to its original value in
// jquery.post-setup.js.
dal_jquery_backup = jQuery.noConflict(true);
jQuery = yl.jQuery;
}

View file

@ -0,0 +1,7 @@
if (typeof dal_jquery_backup !== 'undefined') {
// We made a backup of the original global jQuery before forcing it to our
// yl.jQuery value. Now that select2 has been set up, we need to restore
// our backup to its rightful place.
jQuery = dal_jquery_backup;
$ = dal_jquery_backup;
}

View file

@ -0,0 +1,9 @@
.select2-container {
min-width: 20em;
}
ul li.select2-selection__choice,
ul li.select2-search {
/* Cancel out django's style */
list-style-type: none;
}

View file

@ -0,0 +1,116 @@
;(function ($) {
if (window.__dal__initListenerIsSet)
return;
$(document).on('autocompleteLightInitialize', '[data-autocomplete-light-function=select2]', function() {
var element = $(this);
// Templating helper
function template(text, is_html) {
if (is_html) {
var $result = $('<span>');
$result.html(text);
return $result;
} else {
return text;
}
}
function result_template(item) {
return template(item.text,
element.attr('data-html') !== undefined || element.attr('data-result-html') !== undefined
);
}
function selected_template(item) {
if (item.selected_text !== undefined) {
return template(item.selected_text,
element.attr('data-html') !== undefined || element.attr('data-selected-html') !== undefined
);
} else {
return result_template(item);
}
return
}
var ajax = null;
if ($(this).attr('data-autocomplete-light-url')) {
ajax = {
url: $(this).attr('data-autocomplete-light-url'),
dataType: 'json',
delay: 250,
data: function (params) {
var data = {
q: params.term, // search term
page: params.page,
create: element.attr('data-autocomplete-light-create') && !element.attr('data-tags'),
forward: yl.getForwards(element)
};
return data;
},
processResults: function (data, page) {
if (element.attr('data-tags')) {
$.each(data.results, function(index, value) {
value.id = value.text;
});
}
return data;
},
cache: true
};
}
$(this).select2({
tokenSeparators: element.attr('data-tags') ? [','] : null,
debug: true,
containerCssClass: ':all:',
placeholder: element.attr('data-placeholder') || '',
language: element.attr('data-autocomplete-light-language'),
minimumInputLength: element.attr('data-minimum-input-length') || 0,
allowClear: ! $(this).is('[required]'),
templateResult: result_template,
templateSelection: selected_template,
ajax: ajax,
tags: Boolean(element.attr('data-tags')),
});
$(this).on('select2:selecting', function (e) {
var data = e.params.args.data;
if (data.create_id !== true)
return;
e.preventDefault();
var select = $(this);
$.ajax({
url: $(this).attr('data-autocomplete-light-url'),
type: 'POST',
dataType: 'json',
data: {
text: data.id,
forward: yl.getForwards($(this))
},
beforeSend: function(xhr, settings) {
xhr.setRequestHeader("X-CSRFToken", document.csrftoken);
},
success: function(data, textStatus, jqXHR ) {
select.append(
$('<option>', {value: data.id, text: data.text, selected: true})
);
select.trigger('change');
select.select2('close');
}
});
});
});
window.__dal__initListenerIsSet = true;
$('[data-autocomplete-light-function=select2]:not([id*="__prefix__"])').each(function() {
window.__dal__initialize(this);
});
})(yl.jQuery);

View file

@ -1,17 +1,3 @@
#listePseudoTransaction { .select2-container{
} color: !black;
#listePseudoTransaction .item {
padding: 3px;
font-family: Helvetica;
border: 1px solid #c0c0c0;
}
#istePseudoTransaction .item:hover {
background-color: #f2f2f2;
cursor: pointer;
}
.itemSelected {
background-color: #dcdcdc;
} }

View file

@ -1,3 +1,4 @@
@import url(font-awesome.min.css); @import url(font-awesome.min.css);
@import url(font.css); @import url(font.css);
@ -6,6 +7,12 @@
html5up.net | @ajlkn html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/ */
.select2-container{
color: black;
}
.select2-search__field{
color: black;
}
/* Reset */ /* Reset */
@ -2072,6 +2079,7 @@
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="email"], input[type="email"],
input[type="number"],
select, select,
textarea { textarea {
-moz-appearance: none; -moz-appearance: none;
@ -2091,6 +2099,7 @@
input[type="text"]:invalid, input[type="text"]:invalid,
input[type="password"]:invalid, input[type="password"]:invalid,
input[type="email"]:invalid, input[type="email"]:invalid,
input[type="number"]:invalid
select:invalid, select:invalid,
textarea:invalid { textarea:invalid {
box-shadow: none; box-shadow: none;
@ -2131,6 +2140,7 @@
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="email"], input[type="email"],
input[type="number"],
select { select {
height: 2.75em; height: 2.75em;
} }
@ -2139,7 +2149,7 @@
padding: 0.75em 1em; padding: 0.75em 1em;
} }
input[type="checkbox"], /*input[type="checkbox"],*/
input[type="radio"] { input[type="radio"] {
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none; -ms-appearance: none;
@ -2151,7 +2161,7 @@
z-index: -1; z-index: -1;
} }
input[type="checkbox"] + label, /*input[type="checkbox"] + label,*/
input[type="radio"] + label { input[type="radio"] + label {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@ -2163,7 +2173,7 @@
position: relative; position: relative;
} }
input[type="checkbox"] + label:before, /*input[type="checkbox"] + label:before,*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -2173,7 +2183,7 @@
text-transform: none !important; text-transform: none !important;
} }
input[type="checkbox"] + label:before, /*input[type="checkbox"] + label:before,*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
border-radius: 8px; border-radius: 8px;
border: solid 1px; border: solid 1px;
@ -2188,14 +2198,14 @@
width: 1.65em; width: 1.65em;
} }
input[type="checkbox"]:checked + label:before, /*input[type="checkbox"]:checked + label:before,*/
input[type="radio"]:checked + label:before { input[type="radio"]:checked + label:before {
content: '\f00c'; content: '\f00c';
} }
input[type="checkbox"] + label:before { /*input[type="checkbox"] + label:before {
border-radius: 8px; border-radius: 8px;
} }*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
border-radius: 100%; border-radius: 100%;
@ -2228,6 +2238,7 @@
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="email"], input[type="email"],
input[type="number"],
select, select,
textarea { textarea {
background: rgba(255, 255, 255, 0.075); background: rgba(255, 255, 255, 0.075);
@ -2237,6 +2248,7 @@
input[type="text"]:focus, input[type="text"]:focus,
input[type="password"]:focus, input[type="password"]:focus,
input[type="email"]:focus, input[type="email"]:focus,
input[type="number"]:focus,
select:focus, select:focus,
textarea:focus { textarea:focus {
border-color: #8cc9f0; border-color: #8cc9f0;
@ -2247,25 +2259,25 @@
color: rgba(255, 255, 255, 0.35); color: rgba(255, 255, 255, 0.35);
} }
input[type="checkbox"] + label, /*input[type="checkbox"] + label,*/
input[type="radio"] + label { input[type="radio"] + label {
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.65);
} }
input[type="checkbox"] + label:before, /*input[type="checkbox"] + label:before,*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
background: rgba(255, 255, 255, 0.075); background: rgba(255, 255, 255, 0.075);
border-color: rgba(255, 255, 255, 0.35); border-color: rgba(255, 255, 255, 0.35);
} }
input[type="checkbox"]:checked + label:before, /*input[type="checkbox"]:checked + label:before,*/
input[type="radio"]:checked + label:before { input[type="radio"]:checked + label:before {
background-color: #ffffff; background-color: #ffffff;
border-color: #ffffff; border-color: #ffffff;
color: #935d8c; color: #935d8c;
} }
input[type="checkbox"]:focus + label:before, /*input[type="checkbox"]:focus + label:before,*/
input[type="radio"]:focus + label:before { input[type="radio"]:focus + label:before {
border-color: #8cc9f0; border-color: #8cc9f0;
box-shadow: 0 0 0 1px #8cc9f0; box-shadow: 0 0 0 1px #8cc9f0;
@ -3475,6 +3487,7 @@
#main input[type="text"], #main input[type="text"],
#main input[type="password"], #main input[type="password"],
#main input[type="email"], #main input[type="email"],
#main input[type="number"],
#main select, #main select,
#main textarea { #main textarea {
background: rgba(222, 222, 222, 0.25); background: rgba(222, 222, 222, 0.25);
@ -3484,6 +3497,7 @@
#main input[type="text"]:focus, #main input[type="text"]:focus,
#main input[type="password"]:focus, #main input[type="password"]:focus,
#main input[type="email"]:focus, #main input[type="email"]:focus,
#main input[type="number"]:focus,
#main select:focus, #main select:focus,
#main textarea:focus { #main textarea:focus {
border-color: #8cc9f0; border-color: #8cc9f0;
@ -3494,25 +3508,25 @@
color: #dddddd; color: #dddddd;
} }
#main input[type="checkbox"] + label, /*#main input[type="checkbox"] + label,*/
#main input[type="radio"] + label { #main input[type="radio"] + label {
color: #636363; color: #636363;
} }
#main input[type="checkbox"] + label:before, /*#main input[type="checkbox"] + label:before,*/
#main input[type="radio"] + label:before { #main input[type="radio"] + label:before {
background: rgba(222, 222, 222, 0.25); background: rgba(222, 222, 222, 0.25);
border-color: #dddddd; border-color: #dddddd;
} }
#main input[type="checkbox"]:checked + label:before, /*#main input[type="checkbox"]:checked + label:before,*/
#main input[type="radio"]:checked + label:before { #main input[type="radio"]:checked + label:before {
background-color: #636363; background-color: #636363;
border-color: #636363; border-color: #636363;
color: #ffffff; color: #ffffff;
} }
#main input[type="checkbox"]:focus + label:before, /*#main input[type="checkbox"]:focus + label:before,*/
#main input[type="radio"]:focus + label:before { #main input[type="radio"]:focus + label:before {
border-color: #8cc9f0; border-color: #8cc9f0;
box-shadow: 0 0 0 1px #8cc9f0; box-shadow: 0 0 0 1px #8cc9f0;

2
static/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

58
static/manage.js Normal file
View file

@ -0,0 +1,58 @@
totalAmount = 0
products = []
paymentMethod = null
solde = 0
function get_product(barcode){
res = $.get("getProduct/" + barcode, function(data){
add_product(data.pk, data.barcode, data.name, data.amount);
});
}
function add_product(pk, barcode, name, amount){
exist = false
index = -1
for(k=0;k < products.length; k++){
if(products[k].pk == pk){
exist = true
index = k
}
}
if(exist){
products[index].quantity += 1;
}else{
products.push({"pk": pk, "barcode": barcode, "name": name, "amount": amount, "quantity": 1});
}
generate_html()
}
function generate_html(){
html =""
for(k=0;k<products.length;k++){
product = products[k]
html += "<tr><td>" + product.barcode + "</td><td>" + product.name + "</td><td>" + String(product.amount) + "</td><td>" + String(product.quantity) + "</td><td>" + String(product.quantity * product.amount) + "</td></tr>"
}
$("#items").html(html)
updateTotal()
}
function updateTotal(){
total = 0
for(k=0;k<products.length;k++){
total += products[k].quantity * products[k].amount
}
$("#totalAmount").text(String(total) + "€")
if(paymentMethod == "compte"){
totalAfter = solde - total
$("#totalAfter").text(totalAfter + "€")
}
}
$(document).ready(function(){
$(".product").click(function(){
product = get_product($(this).attr('target'));
});
$("#id_paymentMethod").on('change', function(){
alert('lol')
});
});

View file

@ -1,3 +1,4 @@
@import url(font-awesome.min.css); @import url(font-awesome.min.css);
@import url(font.css); @import url(font.css);
@ -6,6 +7,12 @@
html5up.net | @ajlkn html5up.net | @ajlkn
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
*/ */
.select2-container{
color: black;
}
.select2-search__field{
color: black;
}
/* Reset */ /* Reset */
@ -2072,6 +2079,7 @@
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="email"], input[type="email"],
input[type="number"],
select, select,
textarea { textarea {
-moz-appearance: none; -moz-appearance: none;
@ -2091,6 +2099,7 @@
input[type="text"]:invalid, input[type="text"]:invalid,
input[type="password"]:invalid, input[type="password"]:invalid,
input[type="email"]:invalid, input[type="email"]:invalid,
input[type="number"]:invalid
select:invalid, select:invalid,
textarea:invalid { textarea:invalid {
box-shadow: none; box-shadow: none;
@ -2131,6 +2140,7 @@
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="email"], input[type="email"],
input[type="number"],
select { select {
height: 2.75em; height: 2.75em;
} }
@ -2139,7 +2149,7 @@
padding: 0.75em 1em; padding: 0.75em 1em;
} }
input[type="checkbox"], /*input[type="checkbox"],*/
input[type="radio"] { input[type="radio"] {
-webkit-appearance: none; -webkit-appearance: none;
-ms-appearance: none; -ms-appearance: none;
@ -2151,7 +2161,7 @@
z-index: -1; z-index: -1;
} }
input[type="checkbox"] + label, /*input[type="checkbox"] + label,*/
input[type="radio"] + label { input[type="radio"] + label {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@ -2163,7 +2173,7 @@
position: relative; position: relative;
} }
input[type="checkbox"] + label:before, /*input[type="checkbox"] + label:before,*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -2173,7 +2183,7 @@
text-transform: none !important; text-transform: none !important;
} }
input[type="checkbox"] + label:before, /*input[type="checkbox"] + label:before,*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
border-radius: 8px; border-radius: 8px;
border: solid 1px; border: solid 1px;
@ -2188,14 +2198,14 @@
width: 1.65em; width: 1.65em;
} }
input[type="checkbox"]:checked + label:before, /*input[type="checkbox"]:checked + label:before,*/
input[type="radio"]:checked + label:before { input[type="radio"]:checked + label:before {
content: '\f00c'; content: '\f00c';
} }
input[type="checkbox"] + label:before { /*input[type="checkbox"] + label:before {
border-radius: 8px; border-radius: 8px;
} }*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
border-radius: 100%; border-radius: 100%;
@ -2228,6 +2238,7 @@
input[type="text"], input[type="text"],
input[type="password"], input[type="password"],
input[type="email"], input[type="email"],
input[type="number"],
select, select,
textarea { textarea {
background: rgba(255, 255, 255, 0.075); background: rgba(255, 255, 255, 0.075);
@ -2237,6 +2248,7 @@
input[type="text"]:focus, input[type="text"]:focus,
input[type="password"]:focus, input[type="password"]:focus,
input[type="email"]:focus, input[type="email"]:focus,
input[type="number"]:focus,
select:focus, select:focus,
textarea:focus { textarea:focus {
border-color: #8cc9f0; border-color: #8cc9f0;
@ -2247,25 +2259,25 @@
color: rgba(255, 255, 255, 0.35); color: rgba(255, 255, 255, 0.35);
} }
input[type="checkbox"] + label, /*input[type="checkbox"] + label,*/
input[type="radio"] + label { input[type="radio"] + label {
color: rgba(255, 255, 255, 0.65); color: rgba(255, 255, 255, 0.65);
} }
input[type="checkbox"] + label:before, /*input[type="checkbox"] + label:before,*/
input[type="radio"] + label:before { input[type="radio"] + label:before {
background: rgba(255, 255, 255, 0.075); background: rgba(255, 255, 255, 0.075);
border-color: rgba(255, 255, 255, 0.35); border-color: rgba(255, 255, 255, 0.35);
} }
input[type="checkbox"]:checked + label:before, /*input[type="checkbox"]:checked + label:before,*/
input[type="radio"]:checked + label:before { input[type="radio"]:checked + label:before {
background-color: #ffffff; background-color: #ffffff;
border-color: #ffffff; border-color: #ffffff;
color: #935d8c; color: #935d8c;
} }
input[type="checkbox"]:focus + label:before, /*input[type="checkbox"]:focus + label:before,*/
input[type="radio"]:focus + label:before { input[type="radio"]:focus + label:before {
border-color: #8cc9f0; border-color: #8cc9f0;
box-shadow: 0 0 0 1px #8cc9f0; box-shadow: 0 0 0 1px #8cc9f0;
@ -3475,6 +3487,7 @@
#main input[type="text"], #main input[type="text"],
#main input[type="password"], #main input[type="password"],
#main input[type="email"], #main input[type="email"],
#main input[type="number"],
#main select, #main select,
#main textarea { #main textarea {
background: rgba(222, 222, 222, 0.25); background: rgba(222, 222, 222, 0.25);
@ -3484,6 +3497,7 @@
#main input[type="text"]:focus, #main input[type="text"]:focus,
#main input[type="password"]:focus, #main input[type="password"]:focus,
#main input[type="email"]:focus, #main input[type="email"]:focus,
#main input[type="number"]:focus,
#main select:focus, #main select:focus,
#main textarea:focus { #main textarea:focus {
border-color: #8cc9f0; border-color: #8cc9f0;
@ -3494,25 +3508,25 @@
color: #dddddd; color: #dddddd;
} }
#main input[type="checkbox"] + label, /*#main input[type="checkbox"] + label,*/
#main input[type="radio"] + label { #main input[type="radio"] + label {
color: #636363; color: #636363;
} }
#main input[type="checkbox"] + label:before, /*#main input[type="checkbox"] + label:before,*/
#main input[type="radio"] + label:before { #main input[type="radio"] + label:before {
background: rgba(222, 222, 222, 0.25); background: rgba(222, 222, 222, 0.25);
border-color: #dddddd; border-color: #dddddd;
} }
#main input[type="checkbox"]:checked + label:before, /*#main input[type="checkbox"]:checked + label:before,*/
#main input[type="radio"]:checked + label:before { #main input[type="radio"]:checked + label:before {
background-color: #636363; background-color: #636363;
border-color: #636363; border-color: #636363;
color: #ffffff; color: #ffffff;
} }
#main input[type="checkbox"]:focus + label:before, /*#main input[type="checkbox"]:focus + label:before,*/
#main input[type="radio"]:focus + label:before { #main input[type="radio"]:focus + label:before {
border-color: #8cc9f0; border-color: #8cc9f0;
box-shadow: 0 0 0 1px #8cc9f0; box-shadow: 0 0 0 1px #8cc9f0;

2
staticfiles/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

67
staticfiles/manage.js Normal file
View file

@ -0,0 +1,67 @@
totalAmount = 0
products = []
paymentMethod = null
balance = 0
username = ""
id = 0
function get_product(barcode){
res = $.get("getProduct/" + barcode, function(data){
add_product(data.pk, data.barcode, data.name, data.amount);
});
}
function add_product(pk, barcode, name, amount){
exist = false
index = -1
for(k=0;k < products.length; k++){
if(products[k].pk == pk){
exist = true
index = k
}
}
if(exist){
products[index].quantity += 1;
}else{
products.push({"pk": pk, "barcode": barcode, "name": name, "amount": amount, "quantity": 1});
}
generate_html()
}
function generate_html(){
html =""
for(k=0;k<products.length;k++){
product = products[k]
html += "<tr><td>" + product.barcode + "</td><td>" + product.name + "</td><td>" + String(product.amount) + "</td><td>" + String(product.quantity) + "</td><td>" + String(product.quantity * product.amount) + "</td></tr>"
}
$("#items").html(html)
updateTotal()
}
function updateTotal(){
total = 0
for(k=0;k<products.length;k++){
total += products[k].quantity * products[k].amount
}
$("#totalAmount").text(String(total) + "€")
totalAfter = balance - total
$("#totalAfter").text(totalAfter + "€")
}
$(document).ready(function(){
$(".product").click(function(){
product = get_product($(this).attr('target'));
});
$("#id_client").on('change', function(){
id = $("#id_client").val();
$.get("/users/getUser/" + id, function(data){
balance = data.balance;
username = data.username;
$("#balance").html(balance + "€");
updateTotal();
}).fail(function(){
alert("Une erreur inconnue est survenue");
window.location.reload()
});
});
});

View file

@ -1,4 +1,5 @@
{% load static %} {% load static %}
{% load vip %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -16,7 +17,7 @@
<header id="header" class="alt"> <header id="header" class="alt">
<span class="logo"><img src="{%static 'Images/coope.png' %}" alt="" /></span> <span class="logo"><img src="{%static 'Images/coope.png' %}" alt="" /></span>
<h1>{% block entete %}{% endblock %}</h1> <h1>{% block entete %}{% endblock %}</h1>
<h3></h3> <h3>{% global_message %}</h3>
<nav> <nav>
{% include 'nav.html' %} {% include 'nav.html' %}
</nav> </nav>

View file

@ -0,0 +1,48 @@
{% load vip %}
<section>
<h2>A propos</h2>
<p>{% lorem %}</p>
<ul class="actions">
<li>
<a class="button" href="">En savoir plus</a>
</li>
</ul>
</section>
<section>
<h2>Contacts</h2>
<table>
<tr>
<td>Email</td>
<td>coopemetz@gmail.com</td>
</tr>
<tr>
<td>Prez</td>
<td>{% president %}</td>
</tr>
<tr>
<td>V-Prez</td>
<td>{% vice_president %}</td>
</tr>
<tr>
<td>Trésorier</td>
<td>{% treasurer %}</td>
</tr>
<tr>
<td>Secrétaire</td>
<td>{% secretary %}</td>
</tr>
<tr>
<td>Maitre brasseur</td>
<td>{% brewer %}</td>
</tr>
<tr>
<td>Epic Epicier</td>
<td>{% grocer %}</td>
</tr>
</table>
<ul class="icons">
<li><a href="https://www.facebook.com/coopesmetz/" class="icon fa-facebook alt"><span class="label">Facebook</span></a></li>
</ul>
</section>
<p class="copyright">coopeV3 &copy; 2016 - 2018. Remi Delannoy - Guillaume Goessel - Yoann Pietri. Design: <a href="https://html5up.net">HTML5 UP</a>.</p>

View file

@ -25,4 +25,5 @@
{{extra_css}} {{extra_css}}
</style> </style>
{% endif %} {% endif %}
{{form.media}}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,37 @@
{% if request.user.is_authenticated %}
<span class="tabulation2">
<a href="{% url 'users:profile' request.user.pk %}">Mon profil</a>
</span>
<span class="tabulation2">
<a href="{% url 'gestion:manage' %}">Caisse</a>
</span>
<span class="tabulation2">
<a href="{% url 'users:index' %}">Gestion des clients</a>
</span>
<span class="tabulation2">
<a href="{% url 'gestion:productsIndex' %}">Gestion des produits</a>
</span>
<span class="tabulation2">
<a href="">Comptabilité</a>
</span>
<span class="tabulation2">
<a href="">Classement</a>
</span>
<span class="tabulation2">
<a href="">Classement sur l'année</a>
</span>
<span class="tabulation2">
<a href="{% url 'preferences:generalPreferences' %}">Admin</a>
</span>
<span class="tabulation2">
<a href="{% url 'preferences:cotisationsIndex' %}">Cotisations</a>
</span>
<span class="tabulation2">
<a href="{% url 'preferences:paymentMethodsIndex' %}">Moyens de paiement</a>
</span>
<span class="tabulation2">
<a href="{% url 'users:logout' %}">Deconnexion</a>
</span>
{% else %}
<a href="{% url 'users:login' %}">Connexion</a>
{% endif %}

View file

@ -0,0 +1,8 @@
<input {{attrs}} name="{{name}}" type="text" class="form-control" placeholder="{{placeholder}}"/>
<script>
$(document).ready(function(){
$("#" + {{attrs.id}}).click(function(){
alert('lol')
});
};
</script>

View file

@ -1,7 +1,8 @@
from django.contrib import admin from django.contrib import admin
from .models import School, Profile from .models import School, Profile, CotisationHistory
admin.site.register(School) admin.site.register(School)
admin.site.register(Profile) admin.site.register(Profile)
admin.site.register(CotisationHistory)
# Register your models here. # Register your models here.

View file

@ -1,7 +1,8 @@
from django import forms from django import forms
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from dal import autocomplete
from .models import School, CotisationHistory, WhiteListHistory
from .models import School
class LoginForm(forms.Form): class LoginForm(forms.Form):
username = forms.CharField(max_length=255, label="Nom d'utitisateur") username = forms.CharField(max_length=255, label="Nom d'utitisateur")
password = forms.CharField(max_length=255, widget=forms.PasswordInput, label="Mot de passe") password = forms.CharField(max_length=255, widget=forms.PasswordInput, label="Mot de passe")
@ -24,14 +25,43 @@ class EditGroupForm(forms.ModelForm):
fields = "__all__" fields = "__all__"
class SelectUserForm(forms.Form): class SelectUserForm(forms.Form):
def __init__(self, *args, **kwargs):
restrictTo = kwargs.pop("restrictTo") or None
if(restrictTo == "non-superusers"):
self.queryset = User.objects.filter(is_superuser=False)
elif(restrictTo == "non-admins"):
self.queryset = User.objects.filter(is_staff=False)
else:
self.queryset = User.objects.all()
super(SelectUserForm, self).__init__(*args, **kwargs)
self.fields['user'].queryset = self.queryset
user = forms.ModelChoiceField(queryset=User.objects.all(), label="Utilisateur") user = forms.ModelChoiceField(queryset=User.objects.all(), label="Utilisateur")
class SelectNonSuperUserForm(forms.Form):
user = forms.ModelChoiceField(queryset=User.objects.filter(is_active=True), required=True, label="Utilisateur", widget=autocomplete.ModelSelect2(url='users:non-super-users-autocomplete', attrs={'data-minimum-input-length':2}))
class SelectNonAdminUserForm(forms.Form):
user = forms.ModelChoiceField(queryset=User.objects.filter(is_active=True), required=True, label="Utilisateur", widget=autocomplete.ModelSelect2(url='users:active-users-autocomplete', attrs={'data-minimum-input-length':2}))
class GroupsEditForm(forms.ModelForm):
class Meta:
model = User
fields = ("groups", )
class EditPasswordForm(forms.Form):
password = forms.CharField(max_length=255, widget=forms.PasswordInput, label="Mot de passe actuel")
password1 = forms.CharField(max_length=255, widget=forms.PasswordInput, label="Nouveau mot de passe")
password2 = forms.CharField(max_length=255, widget=forms.PasswordInput, label="Nouveau mot de passe (répétez)")
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError("Les mots de passe ne sont pas identiques")
return password2
class addCotisationHistoryForm(forms.ModelForm):
class Meta:
model = CotisationHistory
fields = ("cotisation", "paymentMethod")
class addWhiteListHistoryForm(forms.ModelForm):
class Meta:
model = WhiteListHistory
fields = ("duration", )
class SchoolForm(forms.ModelForm):
class Meta:
model = School
fields = "__all__"

View file

@ -1,4 +1,4 @@
# Generated by Django 2.1 on 2018-08-31 12:45 # Generated by Django 2.1 on 2018-10-04 09:32
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@ -10,20 +10,24 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('preferences', '0001_initial'), ('preferences', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Cotisation', name='CotisationHistory',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=5)), ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Montant')),
('paymentDate', models.DateTimeField(auto_now_add=True)), ('duration', models.PositiveIntegerField(verbose_name='Durée')),
('endDate', models.DateTimeField()), ('paymentDate', models.DateTimeField(auto_now_add=True, verbose_name='Date du paiement')),
('paymentMethod', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='preferences.PaymentMethod')), ('endDate', models.DateTimeField(verbose_name='Fin de la cotisation')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ('valid', models.IntegerField(choices=[(0, 'En attente de validation'), (1, 'Validée'), (2, 'Invalidée')], default=0)),
('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='cotisation_made', to=settings.AUTH_USER_MODEL)),
('cotisation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='preferences.Cotisation', verbose_name='Type de cotisation')),
('paymentMethod', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='preferences.PaymentMethod', verbose_name='Moyen de paiement')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Client')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -42,6 +46,17 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=255)), ('name', models.CharField(max_length=255)),
], ],
), ),
migrations.CreateModel(
name='WhiteListHistory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('paymentDate', models.DateTimeField(auto_now_add=True)),
('endDate', models.DateTimeField()),
('duration', models.PositiveIntegerField(help_text="Durée de l'accès gracieux en jour", verbose_name='Durée')),
('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='whitelist_made', to=settings.AUTH_USER_MODEL)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField( migrations.AddField(
model_name='profile', model_name='profile',
name='school', name='school',

View file

@ -3,20 +3,39 @@ from django.contrib.auth.models import User
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from preferences.models import PaymentMethod from preferences.models import PaymentMethod, Cotisation
class School(models.Model): class School(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255, verbose_name="Nom")
def __str__(self): def __str__(self):
return self.name return self.name
class Cotisation(models.Model): class CotisationHistory(models.Model):
WAITING = 0
VALID = 1
INVALID = 2
VALIDATION_CHOICES = (
(WAITING, 'En attente de validation'),
(VALID, 'Validée'),
(INVALID, 'Invalidée'),
)
user = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="Client")
amount = models.DecimalField(max_digits=5, decimal_places=2, verbose_name="Montant")
duration = models.PositiveIntegerField(verbose_name="Durée")
paymentDate = models.DateTimeField(auto_now_add=True, verbose_name="Date du paiement")
endDate = models.DateTimeField(verbose_name="Fin de la cotisation")
paymentMethod = models.ForeignKey(PaymentMethod, on_delete=models.PROTECT, verbose_name="Moyen de paiement")
cotisation = models.ForeignKey(Cotisation, on_delete=models.PROTECT, verbose_name="Type de cotisation")
coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="cotisation_made")
valid = models.IntegerField(choices=VALIDATION_CHOICES, default=WAITING)
class WhiteListHistory(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.PROTECT)
amount = models.DecimalField(max_digits=5, decimal_places=2)
paymentDate = models.DateTimeField(auto_now_add=True) paymentDate = models.DateTimeField(auto_now_add=True)
endDate = models.DateTimeField() endDate = models.DateTimeField()
paymentMethod = models.ForeignKey(PaymentMethod, on_delete=models.PROTECT) duration = models.PositiveIntegerField(verbose_name="Durée", help_text="Durée de l'accès gracieux en jour")
coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="whitelist_made")
class Profile(models.Model): class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
@ -30,7 +49,7 @@ class Profile(models.Model):
return self.credit - self.debit return self.credit - self.debit
def positiveBalance(self): def positiveBalance(self):
return self.solde() >= 0 return self.balance >= 0
@property @property
def rank(self): def rank(self):
@ -45,6 +64,9 @@ class Profile(models.Model):
#alcool += conso.nombre * float(produit.deg) * produit.volume * 0.79 /10 /1000 #alcool += conso.nombre * float(produit.deg) * produit.volume * 0.79 /10 /1000
return 0 return 0
def __str__(self):
return str(self.user)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs): def create_user_profile(sender, instance, created, **kwargs):
if created: if created:

View file

@ -6,6 +6,7 @@
<li><a href="#second">Groupes</a></li> <li><a href="#second">Groupes</a></li>
<li><a href="#third">Admins</a></li> <li><a href="#third">Admins</a></li>
<li><a href="#fourth">Superusers</a></li> <li><a href="#fourth">Superusers</a></li>
<li><a href="#fifth">Écoles</a></li>
</ul> </ul>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -16,8 +17,8 @@
Actions possibles : Actions possibles :
<ul> <ul>
<li><a href="{% url 'users:createUser' %}">Ajouter un utilisateur</a></li> <li><a href="{% url 'users:createUser' %}">Ajouter un utilisateur</a></li>
<li><a href="">Rechercher un utilisateur</a></li> <li><a href="{% url 'users:searchUser' %}">Rechercher un utilisateur</a></li>
<li><a href="">Lister tous les utilisateurs</a></li> <li><a href="{% url 'users:usersIndex' %}">Lister tous les utilisateurs</a></li>
</ul> </ul>
</section> </section>
<section id="second" class="main"> <section id="second" class="main">
@ -52,4 +53,14 @@
<li><a href="{% url 'users:superusersIndex' %}">Lister les superuser</a></li> <li><a href="{% url 'users:superusersIndex' %}">Lister les superuser</a></li>
</ul> </ul>
</section> </section>
<section id="fifth" class="main">
<header class="major">
<h2>Écoles</h2>
</header>
Actions possibles :
<ul>
<li><a href="{% url 'users:createSchool' %}">Ajouter une école</a></li>
<li><a href="{% url 'users:schoolsIndex' %}">Lister les écoles</a></li>
</ul>
</section>
{% endblock %} {% endblock %}

View file

@ -11,6 +11,7 @@
<li><a href="#third">{% if self %}Mes derniers rechargements{% else %}Derniers rechargements {% endif %}</a> <li><a href="#third">{% if self %}Mes derniers rechargements{% else %}Derniers rechargements {% endif %}</a>
</li> </li>
<li><a href="#fourth">{% if self %} Mes cotisations {% else %} Cotisations {% endif %}</a></li> <li><a href="#fourth">{% if self %} Mes cotisations {% else %} Cotisations {% endif %}</a></li>
<li><a href="#fifth">{% if self %} Mes accès gracieux {% else %} Accès gracieux {% endif %}</a></li>
</ul> </ul>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
@ -24,7 +25,8 @@
<ul class="alt" id="informationsClient"> <ul class="alt" id="informationsClient">
<li> <li>
<b>Nom : </b>{{user.last_name}}<span class="tabulation"> <b>Nom : </b>{{user.last_name}}<span class="tabulation">
<b>Prénom : </b>{{user.first_name}}</span> <b>Prénom : </b>{{user.first_name}}</span><span class="tabulation">
<b>École : </b>{{user.profile.school}}</span>
</li> </li>
<li><b>Pseudo : </b>{{user.username}}<span class="tabulation"> <li><b>Pseudo : </b>{{user.username}}<span class="tabulation">
<b>Mail: </b> {{user.email}}</span> <b>Mail: </b> {{user.email}}</span>
@ -37,7 +39,7 @@
<b>Crédit : </b>{{user.profile.credit}}</span><span class="tabulation"> <b>Crédit : </b>{{user.profile.credit}}</span><span class="tabulation">
<b>Débit : </b>{{user.profile.debit}}</span> <b>Débit : </b>{{user.profile.debit}}</span>
</li> </li>
<li><b>Groupes : </b>{{user.group_set|join:", "}}</li> <li><b>Groupe(s) : </b>{{user.groups.all|join:", "}}</li>
<li> <li>
<b>Position au classement : </b>{{user.profile.rank}}<span class="tabulation"> <b>Position au classement : </b>{{user.profile.rank}}<span class="tabulation">
<b>Quantité d'alcool ingérée : </b>{{user.profile.alcohol}} kg</span> <b>Quantité d'alcool ingérée : </b>{{user.profile.alcohol}} kg</span>
@ -48,14 +50,30 @@
<ul class="alt"> <ul class="alt">
<li> <li>
{% if self or perms.users.can_change_user %} {% if self or perms.users.can_change_user %}
<span><a href="">Modifier {{self | yesno:"mes,les"}} informations</a></span> <span><a href="{% url 'users:editUser' user.pk %}">Modifier {{self | yesno:"mes,les"}} informations</a></span>
<span class="tabulation"><a href="">Changer {{self | yesno:"mon,le"}} mot de passe</a></span> {% endif %}
{% if self %}
<span class="tabulation"><a href="{% url 'users:editPassword' user.pk %}">Changer mon mot de passe</a></span>
{% endif %} {% endif %}
{% if perms.users.can_reset_password %} {% if perms.users.can_reset_password %}
<span class="tabulation"><a href="">Réinitialiser le mot de passe</a></span> <span class="tabulation"><a href="{% url 'users:resetPassword' user.pk %}">Réinitialiser le mot de passe</a></span>
{% endif %} {% endif %}
{% if perms.users.can_change_user_perm %} {% if perms.users.can_change_user_perm %}
<span class="tabulation"><a href="">Changer les groupes</a></span> <span class="tabulation"><a href="{% url 'users:editGroups' user.pk %}">Changer les groupes</a></span>
{% endif %}
{% if request.user.is_staff %}
{% if user.is_staff %}
<span class="tabulation"><a href="">Retirer des admins</a></span>
{% else %}
<span class="tabulation"><a href="">Ajouter aux admins</a></span>
{% endif %}
{% endif %}
{% if request.user.is_superuser %}
{% if user.is_superuser %}
<span class="tabulation"><a href="">Retirer des superusers</a></span>
{% else %}
<span class="tabulation"><a href="">Ajouter aux superusers</a></span>
{% endif %}
{% endif %} {% endif %}
</li> </li>
</ul> </ul>
@ -65,7 +83,7 @@
<section class="row uniform"> <section class="row uniform">
</section> </section>
</section> </section>
<section id="second" class="main special"> <section id="second" class="main">
<header class="major"> <header class="major">
<h2>{{self | yesno:"Mes dernières,Dernières"}} consommations</h2> <h2>{{self | yesno:"Mes dernières,Dernières"}} consommations</h2>
<p>(Affichage des 10 dernières entrées)</p> <p>(Affichage des 10 dernières entrées)</p>
@ -96,7 +114,7 @@
</div> </div>
</section> </section>
</section> </section>
<section id="third" class="main special"> <section id="third" class="main">
<header class="major"> <header class="major">
<h2>{{self | yesno:"Mes derniers,Derniers"}} rechargements</h2> <h2>{{self | yesno:"Mes derniers,Derniers"}} rechargements</h2>
<p>(Affichage des 5 dernières entrées)</p> <p>(Affichage des 5 dernières entrées)</p>
@ -125,31 +143,67 @@
</section> </section>
</section> </section>
<section id="fourth" class="main special"> <section id="fourth" class="main">
<header class="major"> <header class="major">
<h2>{{ self | yesno:"Mes cotisations,Cotisations"}}</h2> <h2>{{ self | yesno:"Mes cotisations,Cotisations"}}</h2>
</header> </header>
<section> <section>
<a class="button" href="{% url 'users:addCotisationHistory' user.pk %}">Ajouter une cotisation</a><br><br>
<div class="table-wrapper"> <div class="table-wrapper">
<table> <table>
<thead> <thead>
<tr> <tr>
<th>Montant</th> <th>Montant</th>
<th>Durée</th>
<th>Date de paiement</th> <th>Date de paiement</th>
<th>Moyen de paimement</th> <th>Moyen de paiement</th>
<th>Date de fin</th> <th>Date de fin</th>
<th>Etat</th>
<th>Modération</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for cotisation in cotisations %} {% for cotisation in cotisations %}
<tr> <tr>
<td>{{cotisation.amount}}</td> <td>{{cotisation.amount}}€</td>
<td>{{cotisation.duration}} jours</td>
<td>{{cotisation.paymentDate}}</td> <td>{{cotisation.paymentDate}}</td>
<td>{{cotisation.paymentMethod}}</td> <td>{{cotisation.paymentMethod}}</td>
<td>{{cotisation.endDate}}</td> <td>{{cotisation.endDate}}</td>
<td>{{cotisation.valid}}</td>
<td><a class="button small" href="{% url 'users:validateCotisationHistory' cotisation.pk %}">Valider</a> <a class="button small" href="{% url 'users:invalidateCotisationHistory' cotisation.pk %}">Invalider</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbdoy> </tbody>
</table>
</div>
</section>
</section>
<section id="fifth" class="main">
<header class="major">
<h2>{{ self | yesno:"Mes accès gracieux,Accès gracieux"}}</h2>
</header>
<section>
<a class="button" href="{% url 'users:addWhiteListHistory' user.pk %}">Ajouter un accès à titre gracieux</a><br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Date de l'ajout</th>
<th>Date de fin</th>
<th>Durée</th>
</tr>
</thead>
<tbody>
{% for whitelist in whitelists %}
<tr>
<td>{{whitelist.paymentDate}}</td>
<td>{{whitelist.endDate}}</td>
<td>{{whitelist.duration}} jours</td>
</tr>
{% endfor %}
</tbody>
</table> </table>
</div> </div>
</section> </section>

View file

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block entete %}<h1>Gestion des écoles</h1>{% endblock %}
{% block navbar %}
<ul>
<li><a href="#first">Liste des écoles</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des écoles</h2>
</header>
<a class="button" href="{% url 'users:createSchool' %}">Créer une école</a><br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Ecole</th>
<th>Administration</th>
</tr>
</thead>
<tbody>
{% for school in schools %}
<tr>
<td>{{ school }}</td>
<td><a class="button small" href="{% url 'users:editSchool' school.pk %}">Modifier</a> <a class="button small" href="{% url 'users:deleteSchool' school.pk %}">Supprimer</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View file

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block entete %}<h1>Gestion des utilisateurs</h1>{% endblock %}
{% block navbar %}
<ul>
<li><a href="#first">Liste des utilisateurs</a></li>
</ul>
{% endblock %}
{% block content %}
<section id="first" class="main">
<header class="major">
<h2>Liste des utilisateurs</h2>
</header>
<a class="button" href="{% url 'users:createUser' %}">Créer un utilisateur</a><br><br>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>Utilisateur</th>
<th>Profil</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user }}</td>
<td><a class="button small" href="{% url 'users:profile' user.pk %}">Profil</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
{% endblock %}

View file

@ -8,6 +8,12 @@ urlpatterns = [
path('index', views.index, name="index"), path('index', views.index, name="index"),
path('profile/<int:pk>', views.profile, name="profile"), path('profile/<int:pk>', views.profile, name="profile"),
path('createUser', views.createUser, name="createUser"), path('createUser', views.createUser, name="createUser"),
path('searchUser', views.searchUser, name="searchUser"),
path('usersIndex', views.usersIndex, name="usersIndex"),
path('editGroups/<int:pk>', views.editGroups, name="editGroups"),
path('editPassword/<int:pk>', views.editPassword, name="editPassword"),
path('editUser/<int:pk>', views.editUser, name="editUser"),
path('resetPassword/<int:pk>', views.resetPassword, name="resetPassword"),
path('groupsIndex', views.groupsIndex, name="groupsIndex"), path('groupsIndex', views.groupsIndex, name="groupsIndex"),
path('groupProfile/<int:pk>', views.groupProfile, name="groupProfile"), path('groupProfile/<int:pk>', views.groupProfile, name="groupProfile"),
path('createGroup', views.createGroup, name="createGroup"), path('createGroup', views.createGroup, name="createGroup"),
@ -21,4 +27,16 @@ urlpatterns = [
path('superusersIndex', views.superusersIndex, name="superusersIndex"), path('superusersIndex', views.superusersIndex, name="superusersIndex"),
path('addSuperuser', views.addSuperuser, name="addSuperuser"), path('addSuperuser', views.addSuperuser, name="addSuperuser"),
path('removeSuperuser/<int:pk>', views.removeSuperuser, name="removeSuperuser"), path('removeSuperuser/<int:pk>', views.removeSuperuser, name="removeSuperuser"),
path('all-users-autocomplete', views.AllUsersAutocomplete.as_view(), name="all-users-autocomplete"),
path('active-users-autcocomplete', views.ActiveUsersAutocomplete.as_view(), name="active-users-autocomplete"),
path('non-super-users-autocomplete', views.NonSuperUserAutocomplete.as_view(), name="non-super-users-autocomplete"),
path('getUser/<int:pk>', views.getUser, name="getUser"),
path('addCotisationHistory/<int:pk>', views.addCotisationHistory, name="addCotisationHistory"),
path('validateCotisationHistory/<int:pk>', views.validateCotisationHistory, name="validateCotisationHistory"),
path('invalidateCotisationHistory/<int:pk>', views.invalidateCotisationHistory, name="invalidateCotisationHistory"),
path('addWhiteListHistory/<int:pk>', views.addWhiteListHistory, name="addWhiteListHistory"),
path('schoolsIndex', views.schoolsIndex, name="schoolsIndex"),
path('createSchool', views.createSchool, name="createSchool"),
path('editSchool/<int:pk>', views.editSchool, name="editSchool"),
path('deleteSchool/<int:pk>', views.deleteSchool, name="deleteSchool"),
] ]

View file

@ -3,8 +3,16 @@ from django.urls import reverse
from django.contrib.auth.models import User, Group, Permission from django.contrib.auth.models import User, Group, Permission
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from django.contrib import messages from django.contrib import messages
from django.db.models import Q
from django.http import HttpResponse, HttpResponseRedirect
from .forms import CreateUserForm, LoginForm, CreateGroupForm, EditGroupForm, SelectUserForm import json
from datetime import datetime, timedelta
from dal import autocomplete
from .models import CotisationHistory, WhiteListHistory, School
from .forms import CreateUserForm, LoginForm, CreateGroupForm, EditGroupForm, SelectUserForm, GroupsEditForm, EditPasswordForm, addCotisationHistoryForm, addCotisationHistoryForm, addWhiteListHistoryForm, SelectNonAdminUserForm, SelectNonSuperUserForm, SchoolForm
def loginView(request): def loginView(request):
form = LoginForm(request.POST or None) form = LoginForm(request.POST or None)
@ -29,10 +37,16 @@ def logoutView(request):
def index(request): def index(request):
return render(request, "users/index.html") return render(request, "users/index.html")
########## schools ##########
########## users ##########
def profile(request, pk): def profile(request, pk):
user = get_object_or_404(User, pk=pk) user = get_object_or_404(User, pk=pk)
self = request.user == user self = request.user == user
return render(request, "users/profile.html", {"user":user, "self":self}) cotisations = CotisationHistory.objects.filter(user=user)
whitelists = WhiteListHistory.objects.filter(user=user)
return render(request, "users/profile.html", {"user":user, "self":self, "cotisations":cotisations, "whitelists": whitelists})
def createUser(request): def createUser(request):
form = CreateUserForm(request.POST or None) form = CreateUserForm(request.POST or None)
@ -44,6 +58,68 @@ def createUser(request):
user.save() user.save()
return render(request, "form.html", {"form_entete": "Gestion des utilisateurs", "form":form, "form_title":"Création d'un nouvel utilisateur", "form_button":"Créer l'utilisateur"}) return render(request, "form.html", {"form_entete": "Gestion des utilisateurs", "form":form, "form_title":"Création d'un nouvel utilisateur", "form_button":"Créer l'utilisateur"})
def searchUser(request):
form = SelectUserForm(request.POST or None)
if(form.is_valid()):
return redirect(reverse('users:profile', kwargs={"pk":form.cleaned_data['user'].pk}))
return render(request, "form.html", {"form_entete": "Gestion des utilisateurs", "form": form, "form_title": "Rechercher un utilisateur", "form_button": "Afficher le profil"})
def usersIndex(request):
users = User.objects.all()
return render(request, "users/users_index.html", {"users":users})
def editGroups(request, pk):
user = get_object_or_404(User, pk=pk)
form = GroupsEditForm(request.POST or None, instance=user)
if(form.is_valid()):
form.save()
messages.success(request, "Les groupes de l'utilisateur " + user.username + " ont bien été enregistrés.")
return redirect(reverse('users:profile', kwargs={'pk':pk}))
extra_css = "#id_groups{height:200px;}"
return render(request, "form.html", {"form_entete": "Gestion de l'utilisateur " + user.username, "form": form, "form_title": "Modification des groupes", "form_button": "Enregistrer", "extra_css": extra_css})
def editPassword(request, pk):
user = get_object_or_404(User, pk=pk)
if user != request.user:
messages.error(request, "Vous ne pouvez modifier le mot de passe d'un autre utilisateur")
return redirect(reverse('home'))
else:
form = EditPasswordForm(request.POST or None)
if(form.is_valid()):
if authenticate(username=user.username, password = form.cleaned_data['password']) is not None:
user.set_password(form.cleaned_data['password2'])
user.save()
messages.success(request, "Votre mot de passe a bien été mis à jour")
return redirect(reverse('users:profile', kwargs={'pk':pk}))
else:
messages.error(request, "Le mot de passe actuel est incorrect")
return render(request, "form.html", {"form_entete": "Modification de mon compte", "form": form, "form_title": "Modification de mon mot de passe", "form_button": "Modifier mon mot de passe"})
def editUser(request, pk):
user = get_object_or_404(User, pk=pk)
form = CreateUserForm(request.POST or None, instance=user, initial = {'school': user.profile.school})
if(form.is_valid()):
user.profile.school = form.cleaned_data['school']
user.save()
messages.success(request, "Les modifications ont bien été enregistrées")
return redirect(reverse('users:profile', kwargs={'pk': pk}))
return render(request, "form.html", {"form_entete":"Modification du compte " + user.username, "form": form, "form_title": "Modification des informations", "form_button": "Modifier"})
def resetPassword(request, pk):
user = get_object_or_404(User, pk=pk)
if user.is_superuser:
messages.error(request, "Impossible de réinitialiser le mot de passe de " + user.username + " : il est superuser.")
return redirect(reverse('users:profile', kwargs={'pk': pk}))
else:
user.set_password(user.username)
user.save()
messages.success(request, "Le mot de passe de " + user.username + " a bien été réinitialisé.")
return redirect(reverse('users:profile', kwargs={'pk': pk}))
def getUser(request, pk):
user = get_object_or_404(User, pk=pk)
data = json.dumps({"username": user.username, "balance": float(user.profile.balance)})
return HttpResponse(data, content_type='application/json')
########## Groups ########## ########## Groups ##########
@ -111,7 +187,7 @@ def adminsIndex(request):
return render(request, "users/admins_index.html", {"admins": admins}) return render(request, "users/admins_index.html", {"admins": admins})
def addAdmin(request): def addAdmin(request):
form = SelectUserForm(request.POST or None, restrictTo="non-admins") form = SelectNonAdminUserForm(request.POST or None)
if(form.is_valid()): if(form.is_valid()):
user = form.cleaned_data['user'] user = form.cleaned_data['user']
user.is_staff = True user.is_staff = True
@ -143,7 +219,7 @@ def superusersIndex(request):
return render(request, "users/superusers_index.html", {"superusers": superusers}) return render(request, "users/superusers_index.html", {"superusers": superusers})
def addSuperuser(request): def addSuperuser(request):
form = SelectUserForm(request.POST or None, restrictTo="non-superusers") form = SelectNonSuperUserForm(request.POST or None)
if(form.is_valid()): if(form.is_valid()):
user = form.cleaned_data['user'] user = form.cleaned_data['user']
user.is_admin = True user.is_admin = True
@ -165,3 +241,120 @@ def removeSuperuser(request, pk):
else: else:
messages.error(request, "Impossible de retirer l'utilisateur " + user.username + " des superusers : il n'en fait pas partie.") messages.error(request, "Impossible de retirer l'utilisateur " + user.username + " des superusers : il n'en fait pas partie.")
return redirect(reverse('users:superusersIndex')) return redirect(reverse('users:superusersIndex'))
########## Cotisations ##########
def addCotisationHistory(request, pk):
user = get_object_or_404(User, pk=pk)
form = addCotisationHistoryForm(request.POST or None)
if(form.is_valid()):
cotisation = form.save(commit=False)
cotisation.user = user
cotisation.coopeman = request.user
cotisation.amount = cotisation.cotisation.amount
cotisation.duration = cotisation.cotisation.duration
if(user.profile.cotisationEnd):
cotisation.endDate = user.profile.cotisationEnd + timedelta(days=cotisation.cotisation.duration)
else:
cotisation.endDate = datetime.now() + timedelta(days=cotisation.cotisation.duration)
user.profile.cotisationEnd = cotisation.endDate
user.save()
cotisation.save()
messages.success(request, "La cotisation a bien été ajoutée")
return redirect(reverse('users:profile',kwargs={'pk':user.pk}))
return render(request, "form.html",{"form": form, "form_title": "Ajout d'une cotisation pour l'utilisateur " + str(user), "form_button": "Ajouter"})
def validateCotisationHistory(request, pk):
cotisationHistory = get_object_or_404(CotisationHistory, pk=pk)
cotisationHistory.valid = CotisationHistory.VALID
cotisationHistory.save()
messages.success(request, "La cotisation a bien été validée")
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
def invalidateCotisationHistory(request, pk):
cotisationHistory = get_object_or_404(CotisationHistory, pk=pk)
cotisationHistory.valid = CotisationHistory.INVALID
cotisationHistory.save()
user = cotisationHistory.user
user.profile.cotisationEnd = user.profile.cotisationEnd - timedelta(days=cotisationHistory.duration)
user.save()
messages.success(request, "La cotisation a bien été invalidée")
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
########## Whitelist ##########
def addWhiteListHistory(request, pk):
user = get_object_or_404(User, pk=pk)
form = addWhiteListHistoryForm(request.POST or None)
if(form.is_valid()):
whiteList = form.save(commit=False)
whiteList.user = user
whiteList.coopeman = request.user
if(user.profile.cotisationEnd):
whiteList.endDate = user.profile.cotisationEnd + timedelta(days=whiteList.duration)
else:
whiteList = datetime.now() + timedelta(days=whiteList.duration)
user.profile.cotisationEnd = whiteList.endDate
user.save()
whiteList.save()
messages.success(request, "L'accès gracieux a bien été ajouté")
return redirect(reverse('users:profile', kwargs={'pk':user.pk}))
return render(request, "form.html", {"form": form, "form_title": "Ajout d'un accès gracieux pour " + user.username, "form_button": "Ajouter"})
########## Schools ##########
def schoolsIndex(request):
schools = School.objects.all()
return render(request, "users/schools_index.html", {"schools": schools})
def createSchool(request):
form = SchoolForm(request.POST or None)
if(form.is_valid()):
form.save()
messages.success(request, "L'école a bien été créée")
return redirect(reverse('users:schoolsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Création d'une école", "form_button": "Créer"})
def editSchool(request, pk):
school = get_object_or_404(School, pk=pk)
form = SchoolForm(request.POST or None, instance=school)
if(form.is_valid()):
form.save()
messages.success(request, "L'école a bien été modifiée")
return redirect(reverse('users:schoolsIndex'))
return render(request, "form.html", {"form": form, "form_title": "Modification de l'école " + str(school), "form_button": "Modifier"})
def deleteSchool(request, pk):
school = get_object_or_404(School, pk=pk)
message = "L'école " + str(school) + " a bien été supprimée"
school.delete()
messages.success(request, message)
return redirect(reverse('users:schoolsIndex'))
########## Autocomplete searchs ##########
class AllUsersAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = User.objects.all()
if self.q:
qs = qs.filter(Q(username__istartswith=self.q) | Q(first_name__istartswith=self.q) | Q(last_name__istartswith=self.q))
return qs
class ActiveUsersAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = User.objects.filter(is_active=True)
if self.q:
qs = qs.filter(Q(username__istartswith=self.q) | Q(first_name__istartswith=self.q) | Q(last_name__istartswith=self.q))
return qs
class AdherentAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = User.objects.all()
return qs
class NonSuperUserAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = User.objects.filter(is_superuser=False)
if self.q:
qs = qs.filter(Q(username__istartswith=self.q) | Q(first_name__istartswith=self.q) | Q(last_name__istartswith=self.q))
return qs