mirror of
https://github.com/nanoy42/coope
synced 2024-12-23 15:33:45 +00:00
commit
f2c6ae8ab0
68 changed files with 1745 additions and 238 deletions
|
@ -1,3 +1,12 @@
|
|||
## v3.7
|
||||
* Corrections de bugs mineurs et d'erreur d'affichage
|
||||
* Mise en place des rechargements directs sur les transactions
|
||||
* Réinitialisation de mot de passe
|
||||
* Mise en place de l'initialisation de mot de passe et de l'envoi des statuts et du RI par mail à l'inscritpion
|
||||
* Mise en place d'une barre de recherche globale
|
||||
* Mise à jour de stellar (pour la navigation en particulier)
|
||||
* Passage sous django_tex par pip
|
||||
* Ajout des propositions d'améliorations
|
||||
## v3.6.4
|
||||
* Ajout d'un champ use_stocks
|
||||
* Séparation des formulaires de fût
|
||||
|
|
|
@ -34,11 +34,12 @@ INSTALLED_APPS = [
|
|||
'users',
|
||||
'preferences',
|
||||
'coopeV3',
|
||||
'search',
|
||||
'dal',
|
||||
'dal_select2',
|
||||
'simple_history',
|
||||
'django_tex',
|
||||
'debug_toolbar'
|
||||
'debug_toolbar',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -130,3 +131,5 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'mediafiles')
|
|||
MEDIA_URL = '/media/'
|
||||
|
||||
INTERNAL_IPS = ["127.0.0.1"]
|
||||
|
||||
EMAIL_SUBJECT_PREFIX = "[Coopé Technopôle Metz] "
|
|
@ -31,7 +31,8 @@ urlpatterns = [
|
|||
path('users/', include('users.urls')),
|
||||
path('gestion/', include('gestion.urls')),
|
||||
path('preferences/', include('preferences.urls')),
|
||||
|
||||
path('search/', include('search.urls')),
|
||||
path('users/', include('django.contrib.auth.urls')),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Martin Bierbaum
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,50 +0,0 @@
|
|||
|
||||
import os
|
||||
from subprocess import PIPE, run
|
||||
import tempfile
|
||||
|
||||
from django.template.loader import get_template
|
||||
|
||||
from django_tex.exceptions import TexError
|
||||
from django.conf import settings
|
||||
|
||||
DEFAULT_INTERPRETER = 'pdflatex'
|
||||
|
||||
def run_tex(source):
|
||||
"""
|
||||
Copy the source to temp dict and run latex.
|
||||
"""
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
filename = os.path.join(tempdir, 'texput.tex')
|
||||
with open(filename, 'x', encoding='utf-8') as f:
|
||||
f.write(source)
|
||||
print(source)
|
||||
latex_interpreter = getattr(settings, 'LATEX_INTERPRETER', DEFAULT_INTERPRETER)
|
||||
latex_command = 'cd "{tempdir}" && {latex_interpreter} -interaction=batchmode {path}'.format(tempdir=tempdir, latex_interpreter=latex_interpreter, path=os.path.basename(filename))
|
||||
process = run(latex_command, shell=True, stdout=PIPE, stderr=PIPE)
|
||||
try:
|
||||
if process.returncode == 1:
|
||||
with open(os.path.join(tempdir, 'texput.log'), encoding='utf8') as f:
|
||||
log = f.read()
|
||||
raise TexError(log=log, source=source)
|
||||
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as pdf_file:
|
||||
pdf = pdf_file.read()
|
||||
except FileNotFoundError:
|
||||
if process.stderr:
|
||||
raise Exception(process.stderr.decode('utf-8'))
|
||||
raise
|
||||
return pdf
|
||||
|
||||
def compile_template_to_pdf(template_name, context):
|
||||
"""
|
||||
Compile the source with :func:`~django_tex.core.render_template_with_context` and :func:`~django_tex.core.run_tex`.
|
||||
"""
|
||||
source = render_template_with_context(template_name, context)
|
||||
return run_tex(source)
|
||||
|
||||
def render_template_with_context(template_name, context):
|
||||
"""
|
||||
Render the template
|
||||
"""
|
||||
template = get_template(template_name, using='tex')
|
||||
return template.render(context)
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
from django.template.backends.jinja2 import Jinja2
|
||||
|
||||
class TeXEngine(Jinja2):
|
||||
app_dirname = 'templates'
|
||||
|
||||
def __init__(self, params):
|
||||
default_environment = 'django_tex.environment.environment'
|
||||
if 'environment' not in params['OPTIONS'] or not params['OPTIONS']['environment']:
|
||||
params['OPTIONS']['environment'] = default_environment
|
||||
super().__init__(params)
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
from jinja2 import Environment
|
||||
|
||||
from django.template.defaultfilters import register
|
||||
|
||||
from django_tex.filters import FILTERS as tex_specific_filters
|
||||
|
||||
# Django's built-in filters ...
|
||||
filters = register.filters
|
||||
# ... updated with tex specific filters
|
||||
filters.update(tex_specific_filters)
|
||||
|
||||
def environment(**options):
|
||||
env = Environment(**options)
|
||||
env.filters = filters
|
||||
return env
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
import re
|
||||
|
||||
def prettify_message(message):
|
||||
'''
|
||||
Helper methods that removes consecutive whitespaces and newline characters
|
||||
'''
|
||||
# Replace consecutive whitespaces with a single whitespace
|
||||
message = re.sub(r'[ ]{2,}', ' ', message)
|
||||
# Replace consecutive newline characters, optionally separated by whitespace, with a single newline
|
||||
message = re.sub(r'([\r\n][ \t]*)+', '\n', message)
|
||||
return message
|
||||
|
||||
def tokenizer(code):
|
||||
token_specification = [
|
||||
('ERROR', r'\! (?:.+[\r\n])+[\r\n]+'),
|
||||
('WARNING', r'latex warning.*'),
|
||||
('NOFILE', r'no file.*')
|
||||
]
|
||||
token_regex = '|'.join('(?P<{}>{})'.format(label, regex) for label, regex in token_specification)
|
||||
for m in re.finditer(token_regex, code, re.IGNORECASE):
|
||||
token_dict = dict(type=m.lastgroup, message=prettify_message(m.group()))
|
||||
yield token_dict
|
||||
|
||||
class TexError(Exception):
|
||||
|
||||
def __init__(self, log, source):
|
||||
self.log = log
|
||||
self.source = source
|
||||
self.tokens = list(tokenizer(self.log))
|
||||
self.message = self.get_message()
|
||||
|
||||
def get_message(self):
|
||||
for token in self.tokens:
|
||||
if token['type'] == 'ERROR':
|
||||
return token['message']
|
||||
return 'No error message found in log'
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
|
@ -1,9 +0,0 @@
|
|||
from django.utils.formats import localize_input
|
||||
|
||||
def do_linebreaks(value):
|
||||
return value.replace('\n', '\\\\\n')
|
||||
|
||||
FILTERS = {
|
||||
'localize': localize_input,
|
||||
'linebreaks': do_linebreaks
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.template.loader import get_template
|
||||
|
||||
def validate_template_path(name):
|
||||
try:
|
||||
get_template(name, using='tex')
|
||||
except TemplateDoesNotExist:
|
||||
raise ValidationError(_('Template not found.'))
|
||||
|
||||
class TeXTemplateFile(models.Model):
|
||||
|
||||
title = models.CharField(max_length=255)
|
||||
name = models.CharField(max_length=255, validators=[validate_template_path,])
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
from django.http import HttpResponse
|
||||
|
||||
from django_tex.core import compile_template_to_pdf
|
||||
|
||||
class PDFResponse(HttpResponse):
|
||||
|
||||
def __init__(self, content, filename=None):
|
||||
super(PDFResponse, self).__init__(content_type='application/pdf')
|
||||
self['Content-Disposition'] = 'filename="{}"'.format(filename)
|
||||
self.write(content)
|
||||
|
||||
|
||||
def render_to_pdf(request, template_name, context=None, filename=None):
|
||||
# Request is not needed and only included to make the signature conform to django's render function
|
||||
pdf = compile_template_to_pdf(template_name, context)
|
||||
return PDFResponse(pdf, filename=filename)
|
|
@ -49,11 +49,10 @@ class CreateKegForm(forms.ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = Keg
|
||||
fields = ["name", "stockHold", "amount", "capacity"]
|
||||
fields = ["name", "stockHold", "amount", "capacity", "deg"]
|
||||
widgets = {'amount': forms.TextInput}
|
||||
|
||||
category = forms.ModelChoiceField(queryset=Category.objects.all(), label="Catégorie", help_text="Catégorie dans laquelle placer les produits pinte, demi (et galopin si besoin).")
|
||||
deg = forms.DecimalField(max_digits=5, decimal_places=2, label="Degré", validators=[MinValueValidator(0)])
|
||||
create_galopin = forms.BooleanField(required=False, label="Créer le produit galopin ?")
|
||||
|
||||
def clean(self):
|
||||
|
@ -68,7 +67,7 @@ class EditKegForm(forms.ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = Keg
|
||||
fields = ["name", "stockHold", "amount", "capacity", "pinte", "demi", "galopin"]
|
||||
fields = ["name", "stockHold", "amount", "capacity", "pinte", "demi", "galopin", "deg"]
|
||||
widgets = {'amount': forms.TextInput}
|
||||
|
||||
def clean(self):
|
||||
|
|
33
gestion/migrations/0014_auto_20190912_0951.py
Normal file
33
gestion/migrations/0014_auto_20190912_0951.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.1 on 2019-09-12 07:51
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
def update(apps, schema_editor):
|
||||
Keg = apps.get_model('gestion', 'Keg')
|
||||
for keg in Keg.objects.all():
|
||||
keg.deg = keg.pinte.deg
|
||||
keg.save()
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestion', '0013_auto_20190829_1219'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='historicalkeg',
|
||||
name='deg',
|
||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=5, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Degré'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keg',
|
||||
name='deg',
|
||||
field=models.DecimalField(decimal_places=2, default=0, max_digits=5, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Degré'),
|
||||
),
|
||||
migrations.RunPython(update, reverse)
|
||||
]
|
|
@ -194,6 +194,7 @@ class Keg(models.Model):
|
|||
"""
|
||||
If True, will be displayed on :func:`~gestion.views.manage` view
|
||||
"""
|
||||
deg = models.DecimalField(default=0,max_digits=5, decimal_places=2, verbose_name="Degré", validators=[MinValueValidator(0)])
|
||||
history = HistoricalRecords()
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -88,9 +88,9 @@ Facture FE\FactureNum
|
|||
À régler par chèque, espèces ou par virement bancaire :
|
||||
\begin{center}
|
||||
\begin{tabular}{|c c c c|}
|
||||
\hline \textbf{Code banque} & \textbf{Code guichet}& \textbf{Nº de Compte} & \textbf{Clé RIB} \\
|
||||
\hline \textbf{Code banque} & \textbf{Code guichet}& \textbf{N$^\circ{}$ de Compte} & \textbf{Clé RIB} \\
|
||||
20041 & 01010 & 1074350Z031 & 48 \\
|
||||
\hline \textbf{IBAN Nº} & \multicolumn{3}{|l|}{ FR82 2004 1010 1010 7435 0Z03 148 } \\
|
||||
\hline \textbf{IBAN N$^\circ{}$} & \multicolumn{3}{|l|}{ FR82 2004 1010 1010 7435 0Z03 148 } \\
|
||||
\hline \textbf{BIC} & \multicolumn{3}{|l|}{ PSSTFRPPNCY }\\
|
||||
\hline \textbf{Domiciliation} & \multicolumn{3}{|l|}{La Banque Postale - Centre Financier - 54900 Nancy CEDEX 9}\\
|
||||
\hline \textbf{Titulaire} & \multicolumn{3}{|l|}{ASSO COOPE TECHNOPOLE METZ}\\
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
</style>
|
||||
{% if perms.gestion.add_consumptionhistory %}
|
||||
<section id="intro" class="main">
|
||||
<section id="first" class="main">
|
||||
<div class="spotlight">
|
||||
<div class="content">
|
||||
<header class="major">
|
||||
|
@ -61,7 +61,6 @@
|
|||
</header>
|
||||
<div class="row uniform">
|
||||
<div class="12u$">
|
||||
<a class="button small" href=""><i class="fa fa-times"></i> Annuler</a><br><br>
|
||||
{{gestion_form}}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -84,7 +83,7 @@
|
|||
<td id="balance">0€</td>
|
||||
<td id="totalAmount">0€</td>
|
||||
<td id="totalAfter">0€</td>
|
||||
<td>{% for pm in pay_buttons %}<button class="btn small pay_button" data-payment="{{pm.pk}}"><i class="fa fa-{{pm.icon}}"></i> {{pm.name}}</button> {% endfor %}</td>
|
||||
<td>{% for pm in pay_buttons %}<button class="btn small pay_button" data-payment="{{pm.pk}}"><i class="fa fa-{{pm.icon}}"></i> {{pm.name}}</button> {% endfor %} <a class="button small" href="" tooltip="lol"><i class="fa fa-times"></i> Annuler</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -127,6 +126,25 @@
|
|||
{% if not cotisations|divisibleby:3 %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr style="text-align:center; font-weight:bold;">
|
||||
<td colspan="1">Rechargements</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button onclick="dropdown('myDropdown1')" class="dropbtn small">Rechargement 1€</button>
|
||||
<div id="myDropdown1" class="dropdown-content">
|
||||
{% for pm in pay_buttons %}{% if not pm.affect_balance%}<a class="reload" data-payment="{{pm.pk}}" data-payment-name="{{pm.name}}" target="1"><i class="fa fa-{{pm.icon}}"></i> {{pm.name}}</a> {% endif %}{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button onclick="dropdown('myDropdown2')" class="dropbtn small" target="myDropdown2">Rechargement 10€</button>
|
||||
<div id="myDropdown2" class="dropdown-content">
|
||||
{% for pm in pay_buttons %}{% if not pm.affect_balance%}<a class="reload" data-payment="{{pm.pk}}" data-payment-name="{{pm.name}}" target="10"><i class="fa fa-{{pm.icon}}"></i> {{pm.name}}</a> {% endif %}{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="text-align:center; font-weight:bold;"><td colspan="4">Bières pression</td></tr>
|
||||
{% for product in bieresPression %}
|
||||
{% if forloop.counter0|divisibleby:3 %}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<td>{{ menu.name }}</td>
|
||||
<td>{{ menu.amount}} €</td>
|
||||
<td>{% for art in menu.articles.all %}<a href="{% url 'gestion:productProfile' art.pk %}">{{art}}</a>,{% endfor %}</td>
|
||||
<td>{{ menu.is_active | yesno:"Oui, Non"}}</td>
|
||||
<td><i class="fa fa-{{ menu.is_active | yesno:'check,times'}}"></i></td>
|
||||
<td>{% if perms.gestion.change_menu %}<a href="{% url 'gestion:switchActivateMenu' menu.pk %}" class="button small">{% if menu.is_active %}<i class="fa fa-times-cirlce"></i> Désa{% else %}<i class="fa fa-check-circle"></i> A{% endif %}ctiver</a> <a href="{% url 'gestion:editMenu' menu.pk %}" class="button small"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<td>{{ product.amount}}</td>
|
||||
<td>{{ product.stock }}</td>
|
||||
<td>{{ product.category }}</td>
|
||||
<td>{{ product.is_active | yesno:"Oui, Non"}}</td>
|
||||
<td><i class="fa fa-{{ product.is_active | yesno:'check,times'}}"></i></td>
|
||||
<td>{{ product.deg }}</td>
|
||||
<td>{{ product.volume }} cl</td>
|
||||
<td><a href="{% url 'gestion:productProfile' product.pk %}" class="button small"><i class="fa fa-eye"></i> Profil</a> {% if perms.gestion.change_product %}<a href="{% url 'gestion:switchActivate' product.pk %}" class="button small">{% if product.is_active %}<i class="fa fa-times-circle"></i> Désa{% else %}<i class="fa fa-check-circle"></i> A{% endif %}ctiver</a> <a href="{% url 'gestion:editProduct' product.pk %}" class="button small"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="intro" class="main">
|
||||
<section id="first" class="main">
|
||||
<div class="spotlight">
|
||||
<div class="content">
|
||||
<header class="major">
|
||||
|
|
|
@ -87,14 +87,29 @@ def order(request):
|
|||
menus = json.loads(request.POST["menus"])
|
||||
listPintes = json.loads(request.POST["listPintes"])
|
||||
cotisations = json.loads(request.POST['cotisations'])
|
||||
reloads = json.loads(request.POST['reloads'])
|
||||
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
|
||||
if (not order) and (not menus) and (not cotisations):
|
||||
raise Exception("Pas de commande.")
|
||||
if(reloads):
|
||||
for reload in reloads:
|
||||
reload_amount = Decimal(reload["value"])*Decimal(reload["quantity"])
|
||||
if(reload_amount <= 0):
|
||||
raise Exception("Impossible d'effectuer un rechargement négatif")
|
||||
reload_payment_method = get_object_or_404(PaymentMethod, pk=reload["payment_method"])
|
||||
if not reload_payment_method.is_usable_in_reload:
|
||||
raise Exception("Le moyen de paiement ne peut pas être utilisé pour les rechargements.")
|
||||
reload_entry = Reload(customer=user, amount=reload_amount, PaymentMethod=reload_payment_method, coopeman=request.user)
|
||||
reload_entry.save()
|
||||
user.profile.credit += reload_amount
|
||||
user.save()
|
||||
if(cotisations):
|
||||
for co in cotisations:
|
||||
cotisation = Cotisation.objects.get(pk=co['pk'])
|
||||
for i in range(co['quantity']):
|
||||
cotisation_history = CotisationHistory(cotisation=cotisation)
|
||||
if not paymentMethod.is_usable_in_cotisation:
|
||||
raise Exception("Le moyen de paiement ne peut pas être utilisé pour les cotisations.")
|
||||
if(paymentMethod.affect_balance):
|
||||
if(user.profile.balance >= cotisation_history.cotisation.amount):
|
||||
user.profile.debit += cotisation_history.cotisation.amount
|
||||
|
@ -592,7 +607,29 @@ def editKeg(request, pk):
|
|||
keg = get_object_or_404(Keg, pk=pk)
|
||||
form = EditKegForm(request.POST or None, instance=keg)
|
||||
if(form.is_valid()):
|
||||
form.save()
|
||||
try:
|
||||
price_profile = PriceProfile.objects.get(use_for_draft=True)
|
||||
except:
|
||||
messages.error(request, "Il n'y a pas de profil de prix pour les pressions")
|
||||
return redirect(reverse('preferences:priceProfilesIndex'))
|
||||
keg = form.save()
|
||||
# Update produtcs
|
||||
name = form.cleaned_data["name"][4:]
|
||||
pinte_price = compute_price(keg.amount/(2*keg.capacity), price_profile.a, price_profile.b, price_profile.c, price_profile.alpha)
|
||||
pinte_price = ceil(10*pinte_price)/10
|
||||
keg.pinte.deg = keg.deg
|
||||
keg.pinte.amount = pinte_price
|
||||
keg.pinte.name = "Pinte " + name
|
||||
keg.pinte.save()
|
||||
keg.demi.deg = keg.deg
|
||||
keg.demi.amount = ceil(5*pinte_price)/10
|
||||
keg.demi.name = "Demi " + name
|
||||
keg.demi.save()
|
||||
if(keg.galopin):
|
||||
keg.galopin.deg = deg
|
||||
keg.galopin.amount = ceil(2.5 * pinte_price)/10
|
||||
keg.galopin.name = "Galopin " + name
|
||||
keg.galopin.save()
|
||||
messages.success(request, "Le fût a bien été modifié")
|
||||
return redirect(reverse('gestion:kegsList'))
|
||||
return render(request, "form.html", {"form": form, "form_title": "Modification d'un fût", "form_button": "Modifier", "form_button_icon": "pencil-alt"})
|
||||
|
@ -617,6 +654,15 @@ def openKeg(request):
|
|||
keg.stockHold -= 1
|
||||
keg.is_active = True
|
||||
keg.save()
|
||||
if keg.pinte:
|
||||
keg.pinte.is_active = True
|
||||
keg.pinte.save()
|
||||
if keg.demi:
|
||||
keg.demi.is_active = True
|
||||
keg.demi.save()
|
||||
if keg.galopin:
|
||||
keg.galopin.is_active = True
|
||||
keg.galopin.save()
|
||||
messages.success(request, "Le fut a bien été percuté")
|
||||
return redirect(reverse('gestion:kegsList'))
|
||||
return render(request, "form.html", {"form": form, "form_title":"Percutage d'un fût", "form_button":"Percuter", "form_button_icon": "fill-drip"})
|
||||
|
@ -643,6 +689,15 @@ def openDirectKeg(request, pk):
|
|||
keg.stockHold -= 1
|
||||
keg.is_active = True
|
||||
keg.save()
|
||||
if keg.pinte:
|
||||
keg.pinte.is_active = True
|
||||
keg.pinte.save()
|
||||
if keg.demi:
|
||||
keg.demi.is_active = True
|
||||
keg.demi.save()
|
||||
if keg.galopin:
|
||||
keg.galopin.is_active = True
|
||||
keg.galopin.save()
|
||||
messages.success(request, "Le fût a bien été percuté")
|
||||
else:
|
||||
messages.error(request, "Il n'y a pas de fût en stock")
|
||||
|
@ -664,6 +719,15 @@ def closeKeg(request):
|
|||
kegHistory.save()
|
||||
keg.is_active = False
|
||||
keg.save()
|
||||
if keg.pinte:
|
||||
keg.pinte.is_active = False
|
||||
keg.pinte.save()
|
||||
if keg.demi:
|
||||
keg.demi.is_active = False
|
||||
keg.demi.save()
|
||||
if keg.galopin:
|
||||
keg.galopin.is_active = False
|
||||
keg.galopin.save()
|
||||
messages.success(request, "Le fût a bien été fermé")
|
||||
return redirect(reverse('gestion:kegsList'))
|
||||
return render(request, "form.html", {"form": form, "form_title":"Fermeture d'un fût", "form_button":"Fermer le fût", "form_button_icon": "fill"})
|
||||
|
@ -686,6 +750,15 @@ def closeDirectKeg(request, pk):
|
|||
kegHistory.save()
|
||||
keg.is_active = False
|
||||
keg.save()
|
||||
if keg.pinte:
|
||||
keg.pinte.is_active = False
|
||||
keg.pinte.save()
|
||||
if keg.demi:
|
||||
keg.demi.is_active = False
|
||||
keg.demi.save()
|
||||
if keg.galopin:
|
||||
keg.galopin.is_active = False
|
||||
keg.galopin.save()
|
||||
messages.success(request, "Le fût a bien été fermé")
|
||||
else:
|
||||
messages.error(request, "Le fût n'est pas ouvert")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.contrib import admin
|
||||
from simple_history.admin import SimpleHistoryAdmin
|
||||
from .models import PaymentMethod, GeneralPreferences, Cotisation, DivideHistory, PriceProfile
|
||||
from .models import PaymentMethod, GeneralPreferences, Cotisation, DivideHistory, PriceProfile, Improvement
|
||||
|
||||
class CotisationAdmin(SimpleHistoryAdmin):
|
||||
"""
|
||||
|
@ -40,8 +40,18 @@ class DivideHistoryAdmin(SimpleHistoryAdmin):
|
|||
list_display = ('date', 'total_cotisations', 'total_cotisations_amount', 'total_ptm_amount', 'coopeman')
|
||||
ordering = ('-date',)
|
||||
|
||||
class ImprovementAdmin(SimpleHistoryAdmin):
|
||||
"""
|
||||
The admin class for Improvement.
|
||||
"""
|
||||
list_display = ('title', 'mode', 'seen', 'done', 'date')
|
||||
ordering = ('-date',)
|
||||
search_fields = ('title', 'description')
|
||||
list_filter = ('mode', 'seen', 'done')
|
||||
|
||||
admin.site.register(PaymentMethod, PaymentMethodAdmin)
|
||||
admin.site.register(GeneralPreferences, GeneralPreferencesAdmin)
|
||||
admin.site.register(Cotisation, CotisationAdmin)
|
||||
admin.site.register(PriceProfile, PriceProfileAdmin)
|
||||
admin.site.register(DivideHistory, DivideHistoryAdmin)
|
||||
admin.site.register(Improvement, ImprovementAdmin)
|
|
@ -1,7 +1,7 @@
|
|||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from .models import Cotisation, PaymentMethod, GeneralPreferences, PriceProfile
|
||||
from .models import Cotisation, PaymentMethod, GeneralPreferences, PriceProfile, Improvement
|
||||
|
||||
class CotisationForm(forms.ModelForm):
|
||||
"""
|
||||
|
@ -50,3 +50,11 @@ class GeneralPreferencesForm(forms.ModelForm):
|
|||
'home_text': forms.Textarea(attrs={'placeholder': 'Ce message sera affiché sur la page d\'accueil'})
|
||||
}
|
||||
|
||||
|
||||
class ImprovementForm(forms.ModelForm):
|
||||
"""
|
||||
Form to create an improvement
|
||||
"""
|
||||
class Meta:
|
||||
model = Improvement
|
||||
fields = ["title", "mode", "description"]
|
||||
|
|
31
preferences/migrations/0019_improvement.py
Normal file
31
preferences/migrations/0019_improvement.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 2.1 on 2019-09-08 09:59
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('preferences', '0018_auto_20190627_2302'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Improvement',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255)),
|
||||
('mode', models.IntegerField(choices=[(0, 'Bug'), (1, 'Amélioration'), (2, 'Nouvelle fonctionnalité')])),
|
||||
('description', models.TextField()),
|
||||
('seen', models.BooleanField(default=False)),
|
||||
('done', models.BooleanField(default=False)),
|
||||
('coopeman', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='improvement_submitted', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Amélioration',
|
||||
},
|
||||
),
|
||||
]
|
39
preferences/migrations/0020_auto_20190908_1217.py
Normal file
39
preferences/migrations/0020_auto_20190908_1217.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Generated by Django 2.1 on 2019-09-08 10:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0019_improvement'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='improvement',
|
||||
name='date',
|
||||
field=models.DateTimeField(auto_now_add=True, default='2019-09-08 00:00'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='improvement',
|
||||
name='done',
|
||||
field=models.BooleanField(default=False, verbose_name='Fait ?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='improvement',
|
||||
name='mode',
|
||||
field=models.IntegerField(choices=[(0, 'Bug'), (1, 'Amélioration'), (2, 'Nouvelle fonctionnalité')], verbose_name='Type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='improvement',
|
||||
name='seen',
|
||||
field=models.BooleanField(default=False, verbose_name='Vu ?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='improvement',
|
||||
name='title',
|
||||
field=models.CharField(max_length=255, verbose_name='Titre'),
|
||||
),
|
||||
]
|
|
@ -202,3 +202,31 @@ class PriceProfile(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Improvement(models.Model):
|
||||
"""
|
||||
Stores bugs and amelioration proposals.
|
||||
"""
|
||||
|
||||
BUG = 0
|
||||
AMELIORATION = 1
|
||||
NEWFEATURE = 2
|
||||
|
||||
MODES = (
|
||||
(BUG, "Bug"),
|
||||
(AMELIORATION, "Amélioration"),
|
||||
(NEWFEATURE, "Nouvelle fonctionnalité")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Amélioration"
|
||||
|
||||
title = models.CharField(max_length=255, verbose_name="Titre")
|
||||
mode = models.IntegerField(choices=MODES, verbose_name="Type")
|
||||
description = models.TextField()
|
||||
seen = models.BooleanField(default=False, verbose_name="Vu ?")
|
||||
done = models.BooleanField(default=False, verbose_name="Fait ?")
|
||||
coopeman = models.ForeignKey(User, on_delete=models.PROTECT, related_name="improvement_submitted")
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
|
21
preferences/templates/preferences/improvement_profile.html
Normal file
21
preferences/templates/preferences/improvement_profile.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
{% extends "base.html" %}
|
||||
{% block entete %}Amélioration {{improvement.title}}{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">{{improvement.title}}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>{{improvement.title}}</h2>
|
||||
</header>
|
||||
<a href="{% url 'preferences:improvementsIndex' %}" class="button">Retour à la liste des améliorations</a><br><br>
|
||||
<strong>Titre : </strong> {{improvement.title}}<br>
|
||||
<strong>Type : </strong> {{improvement.get_mode_display}}<br>
|
||||
<strong>Date : </strong> {{improvement.date}}<br>
|
||||
<strong>Fait : </strong> {{improvement.done|yesno:"Oui,Non"}}<br>
|
||||
<strong>Coopeman : </strong> {{improvement.coopeman}}<br>
|
||||
<strong>Description : </strong> {{improvement.description}}<br>
|
||||
</section>
|
||||
{% endblock %}
|
68
preferences/templates/preferences/improvements_index.html
Normal file
68
preferences/templates/preferences/improvements_index.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
{% extends "base.html" %}
|
||||
{% block entete %}Améliorations{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">Liste des améliorations à faire</a></li>
|
||||
<li><a href="#seconde">Liste des améliorations faîtes</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>Liste des améliorations à faire</h2>
|
||||
</header>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Type</th>
|
||||
<th>Vu ?</th>
|
||||
<th>Date</th>
|
||||
<th>Administration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for improvement in todo_improvements %}
|
||||
<tr>
|
||||
<td>{{improvement.title}}</td>
|
||||
<td>{{improvement.get_mode_display}}</td>
|
||||
<td><i class="fa fa-{{improvement.seen|yesno:'check,times'}}"></i></td>
|
||||
<td>{{improvement.date}}</td>
|
||||
<td><a href="{% url 'preferences:improvementProfile' improvement.pk %}" class="button small"><i class="fa fa-eye"></i> Voir</a> <a href="{% url 'preferences:changeImprovementState' improvement.pk %}" class="button small"><i class="fa fa-check"></i> Passer en fait</a> <a href="{% url 'preferences:deleteImprovement' improvement.pk %}" class="button small"><i class="fa fa-trash"></i> Supprimer</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
<section id="second" class="main">
|
||||
<header class="major">
|
||||
<h2>Liste des améliorations faîtes</h2>
|
||||
</header>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Titre</th>
|
||||
<th>Type</th>
|
||||
<th>Vu ?</th>
|
||||
<th>Date</th>
|
||||
<th>Administration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for improvement in done_improvements %}
|
||||
<tr>
|
||||
<td>{{improvement.title}}</td>
|
||||
<td>{{improvement.get_mode_display}}</td>
|
||||
<td><i class="fa fa-{{improvement.seen|yesno:'check,times'}}"></i></td>
|
||||
<td>{{improvement.date}}</td>
|
||||
<td><a href="{% url 'preferences:improvementProfile' improvement.pk %}" class="button small"><i class="fa fa-eye"></i> Voir</a> <a href="{% url 'preferences:changeImprovementState' improvement.pk %}" class="button small"><i class="fa fa-check"></i> Passer en non fait</a> <a href="{% url 'preferences:deleteImprovement' improvement.pk %}" class="button small"><i class="fa fa-trash"></i> Supprimer</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -30,10 +30,10 @@
|
|||
{% 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.is_usable_in_reload | yesno:"Oui, Non" }}</td>
|
||||
<td>{{ pm.affect_balance | yesno:"Oui, Non" }}</td>
|
||||
<td><i class="fa fa-{{ pm.is_active | yesno:'check,times'}}"></i></td>
|
||||
<td><i class="fa fa-{{ pm.is_usable_in_cotisation | yesno:'check,times' }}"></i></td>
|
||||
<td><i class="fa fa-{{ pm.is_usable_in_reload | yesno:'check,times' }}"></i></td>
|
||||
<td><i class="fa fa-{{ pm.affect_balance | yesno:'check,times' }}"></i></td>
|
||||
<td><i class="fa fa-{{ pm.icon }}"></i></td>
|
||||
<td>{% if perms.preferences.change_paymentmethod %}<a class="button small" href="{% url 'preferences:editPaymentMethod' pm.pk %}"><i class="fa fa-pencil-alt"></i> Modifier</a> {% endif %}{% if perms.preferences.delete_paymentmethod %}<a class="button small" href="{% url 'preferences:deletePaymentMethod' pm.pk %}"><i class="fa fa-trash"></i> Supprimer</a>{% endif %}</td>
|
||||
</tr>
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
<td>{{ pp.b }}</td>
|
||||
<td>{{ pp.c }}</td>
|
||||
<td>{{ pp.alpha }}</td>
|
||||
<td>{{ pp.use_for_draft | yesno:"Oui,Non"}}</td>
|
||||
<td><i class="fa fa-{{ pp.use_for_draft | yesno:'check,times'}}"></i></td>
|
||||
<td>{% if perms.preferences.change_priceprofile %}<a class="button small" href="{% url 'preferences:editPriceProfile' pp.pk %}"><i class="fa fa-pencil-alt"></i> Modifier</a> {% endif %}{% if perms.preferences.delete_priceprofile %}<a class="button small" href="{% url 'preferences:deletePriceProfile' pp.pk %}"><i class="fa fa-trash"></i> Supprimer</a>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -19,5 +19,10 @@ urlpatterns = [
|
|||
path('deletePriceProfile/<int:pk>', views.delete_price_profile, name="deletePriceProfile"),
|
||||
path('inactive', views.inactive, name="inactive"),
|
||||
path('getConfig', views.get_config, name="getConfig"),
|
||||
path('getCotisation/<int:pk>', views.get_cotisation, name="getCotisation")
|
||||
,]
|
||||
path('getCotisation/<int:pk>', views.get_cotisation, name="getCotisation"),
|
||||
path('addImprovement', views.add_improvement, name="addImprovement"),
|
||||
path('improvementsIndex', views.improvements_index, name="improvementsIndex"),
|
||||
path('improvementProfile/<int:pk>', views.improvement_profile, name="improvementProfile"),
|
||||
path('deleteImprovement/<int:pk>', views.delete_improvement, name="deleteImprovement"),
|
||||
path('changeImprovementState/<int:pk>', views.change_improvement_state, name="changeImprovementState"),
|
||||
]
|
||||
|
|
|
@ -7,12 +7,13 @@ from django.contrib.auth.decorators import login_required, permission_required
|
|||
from django.http import HttpResponse
|
||||
from django.forms.models import model_to_dict
|
||||
from django.http import Http404
|
||||
from django.core.mail import mail_admins
|
||||
|
||||
from coopeV3.acl import active_required
|
||||
|
||||
from .models import GeneralPreferences, Cotisation, PaymentMethod, PriceProfile
|
||||
from .models import GeneralPreferences, Cotisation, PaymentMethod, PriceProfile, Improvement
|
||||
|
||||
from .forms import CotisationForm, PaymentMethodForm, GeneralPreferencesForm, PriceProfileForm
|
||||
from .forms import CotisationForm, PaymentMethodForm, GeneralPreferencesForm, PriceProfileForm, ImprovementForm
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
|
@ -245,3 +246,73 @@ def delete_price_profile(request,pk):
|
|||
price_profile.delete()
|
||||
messages.success(request, message)
|
||||
return redirect(reverse('preferences:priceProfilesIndex'))
|
||||
|
||||
|
||||
########## Improvements ##########
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
def add_improvement(request):
|
||||
"""
|
||||
Display a form to create an improvement. Any logged user can access it
|
||||
"""
|
||||
form = ImprovementForm(request.POST or None)
|
||||
if form.is_valid():
|
||||
improvement = form.save(commit=False)
|
||||
improvement.coopeman = request.user
|
||||
improvement.save()
|
||||
mail_admins("Nouvelle proposition d'amélioration", "Une nouvelle proposition d'amélioration a été postée (" + improvement.title + ", " + improvement.get_mode_display() + "). Le corps est le suivant : " + improvement.description)
|
||||
messages.success(request, "Votre proposition a bien été envoyée")
|
||||
return redirect(reverse('home'))
|
||||
return render(request, "form.html", {"form": form, "form_title": "Proposition d'amélioration", "form_button": "Envoyer", "form_button_icon": "bug"})
|
||||
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
@permission_required('preferences.view_improvement')
|
||||
def improvements_index(request):
|
||||
"""
|
||||
Display all improvements
|
||||
"""
|
||||
todo_improvements = Improvement.objects.filter(done=False).order_by('-date')
|
||||
done_improvements = Improvement.objects.filter(done=True).order_by('-date')
|
||||
return render(request, "preferences/improvements_index.html", {"todo_improvements": todo_improvements, "done_improvements": done_improvements})
|
||||
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
@permission_required('preferences.view_improvement')
|
||||
@permission_required('preferences.change_improvement')
|
||||
def improvement_profile(request, pk):
|
||||
"""
|
||||
Display an improvement
|
||||
"""
|
||||
improvement = get_object_or_404(Improvement, pk=pk)
|
||||
improvement.seen = 1
|
||||
improvement.save()
|
||||
return render(request, "preferences/improvement_profile.html", {"improvement": improvement})
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
@permission_required('preferences.change_improvement')
|
||||
def change_improvement_state(request, pk):
|
||||
"""
|
||||
Change done state of an improvement
|
||||
"""
|
||||
improvement = get_object_or_404(Improvement, pk=pk)
|
||||
improvement.done = 1 - improvement.done
|
||||
improvement.save()
|
||||
messages.success(request, "L'état a bien été changé")
|
||||
return redirect(reverse('preferences:improvementsIndex'))
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
@permission_required('preferences.delete_improvement')
|
||||
def delete_improvement(request, pk):
|
||||
"""
|
||||
Delete an improvement
|
||||
"""
|
||||
improvement = get_object_or_404(Improvement, pk=pk)
|
||||
improvement.delete()
|
||||
messages.success(request, "L'amélioration a bien été supprimée.")
|
||||
return redirect(reverse('preferences:improvementsIndex'))
|
|
@ -6,3 +6,4 @@ docutils==0.14
|
|||
django-simple-history==2.5.1
|
||||
jinja2==2.10
|
||||
Sphinx==1.8.4
|
||||
django-tex==1.1.7
|
||||
|
|
3
search/admin.py
Normal file
3
search/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
5
search/apps.py
Normal file
5
search/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SearchConfig(AppConfig):
|
||||
name = 'search'
|
0
search/migrations/__init__.py
Normal file
0
search/migrations/__init__.py
Normal file
3
search/models.py
Normal file
3
search/models.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
224
search/templates/search/search.html
Normal file
224
search/templates/search/search.html
Normal file
|
@ -0,0 +1,224 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block entete %}Recherche{% endblock %}
|
||||
{% block navbar%}
|
||||
<ul>
|
||||
{% if perms.auth.view_user %}
|
||||
<li><a href="#first">Utilisateurs ({{users.count}})</a></li>
|
||||
{% endif %}
|
||||
{% if perms.gestion.view_product %}
|
||||
<li><a href="#second">Produits ({{products.count}})</a></li>
|
||||
{% endif %}
|
||||
{% if perms.gestion.view_keg %}
|
||||
<li><a href="#third">Fûts ({{kegs.count}})</a></li>
|
||||
{% endif %}
|
||||
{% if perms.gestion.view_menu %}
|
||||
<li><a href="#fourth">Menus ({{menus.count}})</a></li>
|
||||
{% endif %}
|
||||
{% if perms.auth.view_group %}
|
||||
<li><a href="#fifth">Groupes ({{groups.count}})</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% if perms.auth.view_user %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>Résultats dans les utilisateurs ({{users.count}} résultat{% if users.count != 1 %}s{% endif %})</h2>
|
||||
</header>
|
||||
<section>
|
||||
{% if users.count %}
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom d'utilisateur</th>
|
||||
<th>Prénom Nom</th>
|
||||
<th>Solde</th>
|
||||
<th>Fin d'adhésion</th>
|
||||
<th>Staff</th>
|
||||
<th>Profil</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{user.username}}</td>
|
||||
<td>{{user.first_name}} {{user.last_name}}</td>
|
||||
<td>{{user.profile.balance}} €</td>
|
||||
<td>{% if user.profile.is_adherent %}{{user.profile.cotisationEnd}}{% else %}Non adhérent{% endif%}</td>
|
||||
<td><i class="fa fa-{{user.is_staff|yesno:'check,times'}}"></i></td>
|
||||
<td><a class="button small" href="{% url 'users:profile' user.pk %}"><i class="fa fa-user"></i> Profil</a></td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
Aucun résultat n'a pu être trouvé.
|
||||
{% endif %}
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if perms.gestion.view_product %}
|
||||
<section id="second" class="main">
|
||||
<header class="major">
|
||||
<h2>Résultats dans les produits ({{products.count}} résultat{% if products.count != 1 %}s{% endif %})</h2>
|
||||
</header>
|
||||
<section>
|
||||
{% if products.count %}
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Prix</th>
|
||||
<th>Actif</th>
|
||||
<th>Catégorie</th>
|
||||
<th>Adhérent</th>
|
||||
<th>Stock</th>
|
||||
<th>Volume</th>
|
||||
<th>Degré</th>
|
||||
<th>Administration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for product in products %}
|
||||
<tr>
|
||||
<td>{{product.name}}</td>
|
||||
<td>{{product.amount}} €</td>
|
||||
<td><i class="fa fa-{{product.is_active|yesno:'check,times'}}"></i></td>
|
||||
<td>{{product.category}}</td>
|
||||
<td><i class="fa fa-{{product.adherentRequired|yesno:'check,times'}}"></i></td>
|
||||
<td>{{product.stock}}</td>
|
||||
<td>{{product.volume}} cl</td>
|
||||
<td>{{product.deg}}</td>
|
||||
<td>{% if perms.gestion.change_product %}<a class="button small" href="{% url 'gestion:switchActivate' product.pk %}"><i class="fa fa-check-circle"></i> {{product.is_active|yesno:"Désa,A"}}ctiver</a> <a class="button small" href="{% url 'gestion:editProduct' product.pk %}"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
Aucun résultat n'a pu être trouvé.
|
||||
{% endif %}
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if perms.gestion.view_keg %}
|
||||
<section id="third" class="main">
|
||||
<header class="major">
|
||||
<h2>Résultats dans les fûts ({{kegs.count}} résultat{% if kegs.count != 1 %}s{% endif %})</h2>
|
||||
</header>
|
||||
<section>
|
||||
{% if kegs.count %}
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Stock</th>
|
||||
<th>Capacité</th>
|
||||
<th>Actif</th>
|
||||
<th>Prix du fût</th>
|
||||
<th>Degré</th>
|
||||
<th>Historique</th>
|
||||
<th>Administration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for keg in kegs %}
|
||||
<tr>
|
||||
<td>{{keg.name}}</td>
|
||||
<td>{{keg.stockHold}}</td>
|
||||
<td>{{keg.capacity}} L</td>
|
||||
<td><i class="fa fa-{{keg.is_active|yesno:'check,times'}}"></i></td>
|
||||
<td>{{keg.amount}} €</td>
|
||||
<td>{{keg.deg}}°</td>
|
||||
<td><a href="{% url 'gestion:kegH' keg.pk %}" class="button small"><i class="fa fa-history"></i> Voir</a></td>
|
||||
<td>{% if perms.gestion.change_keg %}<a class="button small" href="{% url 'gestion:editKeg' keg.pk %}"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
Aucun résultat n'a pu être trouvé.
|
||||
{% endif %}
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if perms.gestion.view_menu %}
|
||||
<section id="fourth" class="main">
|
||||
<header class="major">
|
||||
<h2>Résultats dans les menus ({{menus.count}} résultat{% if menus.count != 1 %}s{% endif %})</h2>
|
||||
</header>
|
||||
<section>
|
||||
{% if menus.count %}
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Prix</th>
|
||||
<th>Actif</th>
|
||||
<th>Adhérent</th>
|
||||
<th>Nombre de produit</th>
|
||||
<th>Administration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for menu in menus %}
|
||||
<tr>
|
||||
<td>{{menu.name}}</td>
|
||||
<td>{{menu.amount}} €</td>
|
||||
<td><i class="fa fa-{{menu.is_active|yesno:'check,times'}}"></i></td>
|
||||
<td><i class="fa fa-{{menu.adherentRequired|yesno:'check,times'}}"></i></td>
|
||||
<td>{{menu.articles.count}}</td>
|
||||
<td>{% if perms.gestion.change_menu %}<a class="button small" href="{% url 'gestion:switchActivateMenu' menu.pk %}"><i class="fa fa-check-circle"></i> {{menu_is_active|yesno:"Désa,A"}}ctiver</a> <a class="button small" href="{% url 'gestion:editMenu' menu.pk %}"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
Aucun résultat n'a pu être trouvé.
|
||||
{% endif %}
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if perms.auth.view_group %}
|
||||
<section id="fifth" class="main">
|
||||
<header class="major">
|
||||
<h2>Résultats dans les groupes ({{groups.count}} résultat{% if groups.count != 1 %}s{% endif %})</h2>
|
||||
</header>
|
||||
<section>
|
||||
{% if groups.count %}
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Nombre de droits</th>
|
||||
<th>Nombre d'utilisateurs</th>
|
||||
<th>Administrer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>{{group.name}}</td>
|
||||
<td>{{group.permissions.count}}</td>
|
||||
<td>{{group.user_set.count}}</td>
|
||||
<td><a href="{% url 'users:groupProfile' group.pk %}" class="button small"><i class="fa fa-eye"></i> Voir</a>{% if perms.auth.change_group %}<a href="{% url 'users:editGroup' group.pk %}" class="button small"><i class="fa fa-pencil-alt"></i> Modifier</a>{% endif %}</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
Aucun résultat n'a pu être trouvé.
|
||||
{% endif %}
|
||||
</section>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% endblock %}
|
3
search/tests.py
Normal file
3
search/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
8
search/urls.py
Normal file
8
search/urls.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name="search"
|
||||
urlpatterns = [
|
||||
path('search', views.search, name="search"),
|
||||
]
|
25
search/views.py
Normal file
25
search/views.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from django.shortcuts import render
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from coopeV3.acl import active_required
|
||||
from gestion.models import Product, Menu, Keg
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
def search(request):
|
||||
q = request.GET.get("q")
|
||||
if q:
|
||||
users = User.objects.filter(Q(username__icontains=q) | Q(first_name__icontains=q) | Q(last_name__icontains=q))
|
||||
products = Product.objects.filter(name__icontains=q)
|
||||
kegs = Keg.objects.filter(name__icontains=q)
|
||||
menus = Menu.objects.filter(name__icontains=q)
|
||||
groups = Group.objects.filter(name__icontains=q)
|
||||
else:
|
||||
users = User.objects.none()
|
||||
products = Product.objects.none()
|
||||
kegs = Keg.objects.none()
|
||||
menus = Menu.objects.none()
|
||||
groups = Group.objects.none()
|
||||
return render(request, "search/search.html", {"q": q, "users": users, "products": products, "kegs": kegs, "menus": menus, "groups": groups})
|
28
staticfiles/dropdown.css
Normal file
28
staticfiles/dropdown.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
/* The container <div> - needed to position the dropdown content */
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Dropdown Content (Hidden by Default) */
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f1f1f1;
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Links inside the dropdown */
|
||||
.dropdown-content a {
|
||||
color: black;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* Show the dropdown menu (use JS to add this class to the .dropdown-content container when the user clicks on the dropdown button) */
|
||||
.show {
|
||||
display:block;
|
||||
}
|
27
staticfiles/dropdown.js
Normal file
27
staticfiles/dropdown.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
/* When the user clicks on the button,
|
||||
toggle between hiding and showing the dropdown content */
|
||||
function dropdown(target) {
|
||||
var dropdowns = document.getElementsByClassName("dropdown-content");
|
||||
var i;
|
||||
for (i = 0; i < dropdowns.length; i++) {
|
||||
var openDropdown = dropdowns[i];
|
||||
if (openDropdown.classList.contains('show')) {
|
||||
openDropdown.classList.remove('show');
|
||||
}
|
||||
}
|
||||
document.getElementById(target).classList.toggle("show");
|
||||
}
|
||||
|
||||
// Close the dropdown menu if the user clicks outside of it
|
||||
window.onclick = function(event) {
|
||||
if (!event.target.matches('.dropbtn')) {
|
||||
var dropdowns = document.getElementsByClassName("dropdown-content");
|
||||
var i;
|
||||
for (i = 0; i < dropdowns.length; i++) {
|
||||
var openDropdown = dropdowns[i];
|
||||
if (openDropdown.classList.contains('show')) {
|
||||
openDropdown.classList.remove('show');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
2
staticfiles/js/breakpoints.min.js
vendored
Normal file
2
staticfiles/js/breakpoints.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/* breakpoints.js v1.0 | @ajlkn | MIT licensed */
|
||||
var breakpoints=function(){"use strict";function e(e){t.init(e)}var t={list:null,media:{},events:[],init:function(e){t.list=e,window.addEventListener("resize",t.poll),window.addEventListener("orientationchange",t.poll),window.addEventListener("load",t.poll),window.addEventListener("fullscreenchange",t.poll)},active:function(e){var n,a,s,i,r,d,c;if(!(e in t.media)){if(">="==e.substr(0,2)?(a="gte",n=e.substr(2)):"<="==e.substr(0,2)?(a="lte",n=e.substr(2)):">"==e.substr(0,1)?(a="gt",n=e.substr(1)):"<"==e.substr(0,1)?(a="lt",n=e.substr(1)):"!"==e.substr(0,1)?(a="not",n=e.substr(1)):(a="eq",n=e),n&&n in t.list)if(i=t.list[n],Array.isArray(i)){if(r=parseInt(i[0]),d=parseInt(i[1]),isNaN(r)){if(isNaN(d))return;c=i[1].substr(String(d).length)}else c=i[0].substr(String(r).length);if(isNaN(r))switch(a){case"gte":s="screen";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: -1px)";break;case"not":s="screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (max-width: "+d+c+")"}else if(isNaN(d))switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen";break;case"gt":s="screen and (max-width: -1px)";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+")";break;default:s="screen and (min-width: "+r+c+")"}else switch(a){case"gte":s="screen and (min-width: "+r+c+")";break;case"lte":s="screen and (max-width: "+d+c+")";break;case"gt":s="screen and (min-width: "+(d+1)+c+")";break;case"lt":s="screen and (max-width: "+(r-1)+c+")";break;case"not":s="screen and (max-width: "+(r-1)+c+"), screen and (min-width: "+(d+1)+c+")";break;default:s="screen and (min-width: "+r+c+") and (max-width: "+d+c+")"}}else s="("==i.charAt(0)?"screen and "+i:i;t.media[e]=!!s&&s}return t.media[e]!==!1&&window.matchMedia(t.media[e]).matches},on:function(e,n){t.events.push({query:e,handler:n,state:!1}),t.active(e)&&n()},poll:function(){var e,n;for(e=0;e<t.events.length;e++)n=t.events[e],t.active(n.query)?n.state||(n.state=!0,n.handler()):n.state&&(n.state=!1)}};return e._=t,e.on=function(e,n){t.on(e,n)},e.active=function(e){return t.active(e)},e}();!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.breakpoints=t()}(this,function(){return breakpoints});
|
2
staticfiles/js/browser.min.js
vendored
Normal file
2
staticfiles/js/browser.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/* browser.js v1.0 | @ajlkn | MIT licensed */
|
||||
var browser=function(){"use strict";var e={name:null,version:null,os:null,osVersion:null,touch:null,mobile:null,_canUse:null,canUse:function(n){e._canUse||(e._canUse=document.createElement("div"));var o=e._canUse.style,r=n.charAt(0).toUpperCase()+n.slice(1);return n in o||"Moz"+r in o||"Webkit"+r in o||"O"+r in o||"ms"+r in o},init:function(){var n,o,r,i,t=navigator.userAgent;for(n="other",o=0,r=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],i=0;i<r.length;i++)if(t.match(r[i][1])){n=r[i][0],o=parseFloat(RegExp.$1);break}for(e.name=n,e.version=o,n="other",o=0,r=[["ios",/([0-9_]+) like Mac OS X/,function(e){return e.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(e){return 0}],["wp",/Windows Phone ([0-9\.]+)/,null],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(e){return e.replace("_",".").replace("_","")}],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null],["linux",/Linux/,null],["bsd",/BSD/,null],["unix",/X11/,null]],i=0;i<r.length;i++)if(t.match(r[i][1])){n=r[i][0],o=parseFloat(r[i][2]?r[i][2](RegExp.$1):RegExp.$1);break}e.os=n,e.osVersion=o,e.touch="wp"==e.os?navigator.msMaxTouchPoints>0:!!("ontouchstart"in window),e.mobile="wp"==e.os||"android"==e.os||"ios"==e.os||"bb"==e.os}};return e.init(),e}();!function(e,n){"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?module.exports=n():e.browser=n()}(this,function(){return browser});
|
2
staticfiles/js/jquery.min.js
vendored
Normal file
2
staticfiles/js/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
staticfiles/js/jquery.scrollex.min.js
vendored
Normal file
2
staticfiles/js/jquery.scrollex.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/* jquery.scrollex v0.2.1 | (c) @ajlkn | github.com/ajlkn/jquery.scrollex | MIT licensed */
|
||||
!function(t){function e(t,e,n){return"string"==typeof t&&("%"==t.slice(-1)?t=parseInt(t.substring(0,t.length-1))/100*e:"vh"==t.slice(-2)?t=parseInt(t.substring(0,t.length-2))/100*n:"px"==t.slice(-2)&&(t=parseInt(t.substring(0,t.length-2)))),t}var n=t(window),i=1,o={};n.on("scroll",function(){var e=n.scrollTop();t.map(o,function(t){window.clearTimeout(t.timeoutId),t.timeoutId=window.setTimeout(function(){t.handler(e)},t.options.delay)})}).on("load",function(){n.trigger("scroll")}),jQuery.fn.scrollex=function(l){var s=t(this);if(0==this.length)return s;if(this.length>1){for(var r=0;r<this.length;r++)t(this[r]).scrollex(l);return s}if(s.data("_scrollexId"))return s;var a,u,h,c,p;switch(a=i++,u=jQuery.extend({top:0,bottom:0,delay:0,mode:"default",enter:null,leave:null,initialize:null,terminate:null,scroll:null},l),u.mode){case"top":h=function(t,e,n,i,o){return t>=i&&o>=t};break;case"bottom":h=function(t,e,n,i,o){return n>=i&&o>=n};break;case"middle":h=function(t,e,n,i,o){return e>=i&&o>=e};break;case"top-only":h=function(t,e,n,i,o){return i>=t&&n>=i};break;case"bottom-only":h=function(t,e,n,i,o){return n>=o&&o>=t};break;default:case"default":h=function(t,e,n,i,o){return n>=i&&o>=t}}return c=function(t){var i,o,l,s,r,a,u=this.state,h=!1,c=this.$element.offset();i=n.height(),o=t+i/2,l=t+i,s=this.$element.outerHeight(),r=c.top+e(this.options.top,s,i),a=c.top+s-e(this.options.bottom,s,i),h=this.test(t,o,l,r,a),h!=u&&(this.state=h,h?this.options.enter&&this.options.enter.apply(this.element):this.options.leave&&this.options.leave.apply(this.element)),this.options.scroll&&this.options.scroll.apply(this.element,[(o-r)/(a-r)])},p={id:a,options:u,test:h,handler:c,state:null,element:this,$element:s,timeoutId:null},o[a]=p,s.data("_scrollexId",p.id),p.options.initialize&&p.options.initialize.apply(this),s},jQuery.fn.unscrollex=function(){var e=t(this);if(0==this.length)return e;if(this.length>1){for(var n=0;n<this.length;n++)t(this[n]).unscrollex();return e}var i,l;return(i=e.data("_scrollexId"))?(l=o[i],window.clearTimeout(l.timeoutId),delete o[i],e.removeData("_scrollexId"),l.options.terminate&&l.options.terminate.apply(this),e):e}}(jQuery);
|
2
staticfiles/js/jquery.scrolly.min.js
vendored
Normal file
2
staticfiles/js/jquery.scrolly.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/* jquery.scrolly v1.0.0-dev | (c) @ajlkn | MIT licensed */
|
||||
(function(e){function u(s,o){var u,a,f;if((u=e(s))[t]==0)return n;a=u[i]()[r];switch(o.anchor){case"middle":f=a-(e(window).height()-u.outerHeight())/2;break;default:case r:f=Math.max(a,0)}return typeof o[i]=="function"?f-=o[i]():f-=o[i],f}var t="length",n=null,r="top",i="offset",s="click.scrolly",o=e(window);e.fn.scrolly=function(i){var o,a,f,l,c=e(this);if(this[t]==0)return c;if(this[t]>1){for(o=0;o<this[t];o++)e(this[o]).scrolly(i);return c}l=n,f=c.attr("href");if(f.charAt(0)!="#"||f[t]<2)return c;a=jQuery.extend({anchor:r,easing:"swing",offset:0,parent:e("body,html"),pollOnce:!1,speed:1e3},i),a.pollOnce&&(l=u(f,a)),c.off(s).on(s,function(e){var t=l!==n?l:u(f,a);t!==n&&(e.preventDefault(),a.parent.stop().animate({scrollTop:t},a.speed,a.easing))})}})(jQuery);
|
123
staticfiles/js/main.js
Normal file
123
staticfiles/js/main.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Stellar by HTML5 UP
|
||||
html5up.net | @ajlkn
|
||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||
*/
|
||||
|
||||
(function($) {
|
||||
|
||||
var $window = $(window),
|
||||
$body = $('body'),
|
||||
$main = $('#main');
|
||||
|
||||
// Breakpoints.
|
||||
breakpoints({
|
||||
xlarge: [ '1281px', '1680px' ],
|
||||
large: [ '981px', '1280px' ],
|
||||
medium: [ '737px', '980px' ],
|
||||
small: [ '481px', '736px' ],
|
||||
xsmall: [ '361px', '480px' ],
|
||||
xxsmall: [ null, '360px' ]
|
||||
});
|
||||
|
||||
// Play initial animations on page load.
|
||||
$window.on('load', function() {
|
||||
window.setTimeout(function() {
|
||||
$body.removeClass('is-preload');
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Nav.
|
||||
var $nav = $('#nav');
|
||||
|
||||
if ($nav.length > 0) {
|
||||
|
||||
// Shrink effect.
|
||||
$main
|
||||
.scrollex({
|
||||
mode: 'top',
|
||||
enter: function() {
|
||||
$nav.addClass('alt');
|
||||
},
|
||||
leave: function() {
|
||||
$nav.removeClass('alt');
|
||||
},
|
||||
});
|
||||
|
||||
// Links.
|
||||
var $nav_a = $nav.find('a');
|
||||
|
||||
$nav_a
|
||||
.scrolly({
|
||||
speed: 1000,
|
||||
offset: function() { return $nav.height(); }
|
||||
})
|
||||
.on('click', function() {
|
||||
|
||||
var $this = $(this);
|
||||
|
||||
// External link? Bail.
|
||||
if ($this.attr('href').charAt(0) != '#')
|
||||
return;
|
||||
|
||||
// Deactivate all links.
|
||||
$nav_a
|
||||
.removeClass('active')
|
||||
.removeClass('active-locked');
|
||||
|
||||
// Activate link *and* lock it (so Scrollex doesn't try to activate other links as we're scrolling to this one's section).
|
||||
$this
|
||||
.addClass('active')
|
||||
.addClass('active-locked');
|
||||
|
||||
})
|
||||
.each(function() {
|
||||
|
||||
var $this = $(this),
|
||||
id = $this.attr('href'),
|
||||
$section = $(id);
|
||||
|
||||
// No section for this link? Bail.
|
||||
if ($section.length < 1)
|
||||
return;
|
||||
|
||||
// Scrollex.
|
||||
$section.scrollex({
|
||||
mode: 'middle',
|
||||
initialize: function() {
|
||||
|
||||
// Deactivate section.
|
||||
if (browser.canUse('transition'))
|
||||
$section.addClass('inactive');
|
||||
|
||||
},
|
||||
enter: function() {
|
||||
|
||||
// Activate section.
|
||||
$section.removeClass('inactive');
|
||||
|
||||
// No locked links? Deactivate all links and activate this section's one.
|
||||
if ($nav_a.filter('.active-locked').length == 0) {
|
||||
|
||||
$nav_a.removeClass('active');
|
||||
$this.addClass('active');
|
||||
|
||||
}
|
||||
|
||||
// Otherwise, if this section's link is the one that's locked, unlock it.
|
||||
else if ($this.hasClass('active-locked'))
|
||||
$this.removeClass('active-locked');
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Scrolly.
|
||||
$('.scrolly').scrolly({
|
||||
speed: 1000
|
||||
});
|
||||
|
||||
})(jQuery);
|
587
staticfiles/js/util.js
Normal file
587
staticfiles/js/util.js
Normal file
|
@ -0,0 +1,587 @@
|
|||
(function($) {
|
||||
|
||||
/**
|
||||
* Generate an indented list of links from a nav. Meant for use with panel().
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.navList = function() {
|
||||
|
||||
var $this = $(this);
|
||||
$a = $this.find('a'),
|
||||
b = [];
|
||||
|
||||
$a.each(function() {
|
||||
|
||||
var $this = $(this),
|
||||
indent = Math.max(0, $this.parents('li').length - 1),
|
||||
href = $this.attr('href'),
|
||||
target = $this.attr('target');
|
||||
|
||||
b.push(
|
||||
'<a ' +
|
||||
'class="link depth-' + indent + '"' +
|
||||
( (typeof target !== 'undefined' && target != '') ? ' target="' + target + '"' : '') +
|
||||
( (typeof href !== 'undefined' && href != '') ? ' href="' + href + '"' : '') +
|
||||
'>' +
|
||||
'<span class="indent-' + indent + '"></span>' +
|
||||
$this.text() +
|
||||
'</a>'
|
||||
);
|
||||
|
||||
});
|
||||
|
||||
return b.join('');
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Panel-ify an element.
|
||||
* @param {object} userConfig User config.
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.panel = function(userConfig) {
|
||||
|
||||
// No elements?
|
||||
if (this.length == 0)
|
||||
return $this;
|
||||
|
||||
// Multiple elements?
|
||||
if (this.length > 1) {
|
||||
|
||||
for (var i=0; i < this.length; i++)
|
||||
$(this[i]).panel(userConfig);
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
// Vars.
|
||||
var $this = $(this),
|
||||
$body = $('body'),
|
||||
$window = $(window),
|
||||
id = $this.attr('id'),
|
||||
config;
|
||||
|
||||
// Config.
|
||||
config = $.extend({
|
||||
|
||||
// Delay.
|
||||
delay: 0,
|
||||
|
||||
// Hide panel on link click.
|
||||
hideOnClick: false,
|
||||
|
||||
// Hide panel on escape keypress.
|
||||
hideOnEscape: false,
|
||||
|
||||
// Hide panel on swipe.
|
||||
hideOnSwipe: false,
|
||||
|
||||
// Reset scroll position on hide.
|
||||
resetScroll: false,
|
||||
|
||||
// Reset forms on hide.
|
||||
resetForms: false,
|
||||
|
||||
// Side of viewport the panel will appear.
|
||||
side: null,
|
||||
|
||||
// Target element for "class".
|
||||
target: $this,
|
||||
|
||||
// Class to toggle.
|
||||
visibleClass: 'visible'
|
||||
|
||||
}, userConfig);
|
||||
|
||||
// Expand "target" if it's not a jQuery object already.
|
||||
if (typeof config.target != 'jQuery')
|
||||
config.target = $(config.target);
|
||||
|
||||
// Panel.
|
||||
|
||||
// Methods.
|
||||
$this._hide = function(event) {
|
||||
|
||||
// Already hidden? Bail.
|
||||
if (!config.target.hasClass(config.visibleClass))
|
||||
return;
|
||||
|
||||
// If an event was provided, cancel it.
|
||||
if (event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
}
|
||||
|
||||
// Hide.
|
||||
config.target.removeClass(config.visibleClass);
|
||||
|
||||
// Post-hide stuff.
|
||||
window.setTimeout(function() {
|
||||
|
||||
// Reset scroll position.
|
||||
if (config.resetScroll)
|
||||
$this.scrollTop(0);
|
||||
|
||||
// Reset forms.
|
||||
if (config.resetForms)
|
||||
$this.find('form').each(function() {
|
||||
this.reset();
|
||||
});
|
||||
|
||||
}, config.delay);
|
||||
|
||||
};
|
||||
|
||||
// Vendor fixes.
|
||||
$this
|
||||
.css('-ms-overflow-style', '-ms-autohiding-scrollbar')
|
||||
.css('-webkit-overflow-scrolling', 'touch');
|
||||
|
||||
// Hide on click.
|
||||
if (config.hideOnClick) {
|
||||
|
||||
$this.find('a')
|
||||
.css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)');
|
||||
|
||||
$this
|
||||
.on('click', 'a', function(event) {
|
||||
|
||||
var $a = $(this),
|
||||
href = $a.attr('href'),
|
||||
target = $a.attr('target');
|
||||
|
||||
if (!href || href == '#' || href == '' || href == '#' + id)
|
||||
return;
|
||||
|
||||
// Cancel original event.
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
// Hide panel.
|
||||
$this._hide();
|
||||
|
||||
// Redirect to href.
|
||||
window.setTimeout(function() {
|
||||
|
||||
if (target == '_blank')
|
||||
window.open(href);
|
||||
else
|
||||
window.location.href = href;
|
||||
|
||||
}, config.delay + 10);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Event: Touch stuff.
|
||||
$this.on('touchstart', function(event) {
|
||||
|
||||
$this.touchPosX = event.originalEvent.touches[0].pageX;
|
||||
$this.touchPosY = event.originalEvent.touches[0].pageY;
|
||||
|
||||
})
|
||||
|
||||
$this.on('touchmove', function(event) {
|
||||
|
||||
if ($this.touchPosX === null
|
||||
|| $this.touchPosY === null)
|
||||
return;
|
||||
|
||||
var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX,
|
||||
diffY = $this.touchPosY - event.originalEvent.touches[0].pageY,
|
||||
th = $this.outerHeight(),
|
||||
ts = ($this.get(0).scrollHeight - $this.scrollTop());
|
||||
|
||||
// Hide on swipe?
|
||||
if (config.hideOnSwipe) {
|
||||
|
||||
var result = false,
|
||||
boundary = 20,
|
||||
delta = 50;
|
||||
|
||||
switch (config.side) {
|
||||
|
||||
case 'left':
|
||||
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta);
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta));
|
||||
break;
|
||||
|
||||
case 'top':
|
||||
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta);
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (result) {
|
||||
|
||||
$this.touchPosX = null;
|
||||
$this.touchPosY = null;
|
||||
$this._hide();
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Prevent vertical scrolling past the top or bottom.
|
||||
if (($this.scrollTop() < 0 && diffY < 0)
|
||||
|| (ts > (th - 2) && ts < (th + 2) && diffY > 0)) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Event: Prevent certain events inside the panel from bubbling.
|
||||
$this.on('click touchend touchstart touchmove', function(event) {
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
// Event: Hide panel if a child anchor tag pointing to its ID is clicked.
|
||||
$this.on('click', 'a[href="#' + id + '"]', function(event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
config.target.removeClass(config.visibleClass);
|
||||
|
||||
});
|
||||
|
||||
// Body.
|
||||
|
||||
// Event: Hide panel on body click/tap.
|
||||
$body.on('click touchend', function(event) {
|
||||
$this._hide(event);
|
||||
});
|
||||
|
||||
// Event: Toggle.
|
||||
$body.on('click', 'a[href="#' + id + '"]', function(event) {
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
config.target.toggleClass(config.visibleClass);
|
||||
|
||||
});
|
||||
|
||||
// Window.
|
||||
|
||||
// Event: Hide on ESC.
|
||||
if (config.hideOnEscape)
|
||||
$window.on('keydown', function(event) {
|
||||
|
||||
if (event.keyCode == 27)
|
||||
$this._hide(event);
|
||||
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply "placeholder" attribute polyfill to one or more forms.
|
||||
* @return {jQuery} jQuery object.
|
||||
*/
|
||||
$.fn.placeholder = function() {
|
||||
|
||||
// Browser natively supports placeholders? Bail.
|
||||
if (typeof (document.createElement('input')).placeholder != 'undefined')
|
||||
return $(this);
|
||||
|
||||
// No elements?
|
||||
if (this.length == 0)
|
||||
return $this;
|
||||
|
||||
// Multiple elements?
|
||||
if (this.length > 1) {
|
||||
|
||||
for (var i=0; i < this.length; i++)
|
||||
$(this[i]).placeholder();
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
// Vars.
|
||||
var $this = $(this);
|
||||
|
||||
// Text, TextArea.
|
||||
$this.find('input[type=text],textarea')
|
||||
.each(function() {
|
||||
|
||||
var i = $(this);
|
||||
|
||||
if (i.val() == ''
|
||||
|| i.val() == i.attr('placeholder'))
|
||||
i
|
||||
.addClass('polyfill-placeholder')
|
||||
.val(i.attr('placeholder'));
|
||||
|
||||
})
|
||||
.on('blur', function() {
|
||||
|
||||
var i = $(this);
|
||||
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
return;
|
||||
|
||||
if (i.val() == '')
|
||||
i
|
||||
.addClass('polyfill-placeholder')
|
||||
.val(i.attr('placeholder'));
|
||||
|
||||
})
|
||||
.on('focus', function() {
|
||||
|
||||
var i = $(this);
|
||||
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
return;
|
||||
|
||||
if (i.val() == i.attr('placeholder'))
|
||||
i
|
||||
.removeClass('polyfill-placeholder')
|
||||
.val('');
|
||||
|
||||
});
|
||||
|
||||
// Password.
|
||||
$this.find('input[type=password]')
|
||||
.each(function() {
|
||||
|
||||
var i = $(this);
|
||||
var x = $(
|
||||
$('<div>')
|
||||
.append(i.clone())
|
||||
.remove()
|
||||
.html()
|
||||
.replace(/type="password"/i, 'type="text"')
|
||||
.replace(/type=password/i, 'type=text')
|
||||
);
|
||||
|
||||
if (i.attr('id') != '')
|
||||
x.attr('id', i.attr('id') + '-polyfill-field');
|
||||
|
||||
if (i.attr('name') != '')
|
||||
x.attr('name', i.attr('name') + '-polyfill-field');
|
||||
|
||||
x.addClass('polyfill-placeholder')
|
||||
.val(x.attr('placeholder')).insertAfter(i);
|
||||
|
||||
if (i.val() == '')
|
||||
i.hide();
|
||||
else
|
||||
x.hide();
|
||||
|
||||
i
|
||||
.on('blur', function(event) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
|
||||
|
||||
if (i.val() == '') {
|
||||
|
||||
i.hide();
|
||||
x.show();
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
x
|
||||
.on('focus', function(event) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']');
|
||||
|
||||
x.hide();
|
||||
|
||||
i
|
||||
.show()
|
||||
.focus();
|
||||
|
||||
})
|
||||
.on('keypress', function(event) {
|
||||
|
||||
event.preventDefault();
|
||||
x.val('');
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Events.
|
||||
$this
|
||||
.on('submit', function() {
|
||||
|
||||
$this.find('input[type=text],input[type=password],textarea')
|
||||
.each(function(event) {
|
||||
|
||||
var i = $(this);
|
||||
|
||||
if (i.attr('name').match(/-polyfill-field$/))
|
||||
i.attr('name', '');
|
||||
|
||||
if (i.val() == i.attr('placeholder')) {
|
||||
|
||||
i.removeClass('polyfill-placeholder');
|
||||
i.val('');
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
.on('reset', function(event) {
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
$this.find('select')
|
||||
.val($('option:first').val());
|
||||
|
||||
$this.find('input,textarea')
|
||||
.each(function() {
|
||||
|
||||
var i = $(this),
|
||||
x;
|
||||
|
||||
i.removeClass('polyfill-placeholder');
|
||||
|
||||
switch (this.type) {
|
||||
|
||||
case 'submit':
|
||||
case 'reset':
|
||||
break;
|
||||
|
||||
case 'password':
|
||||
i.val(i.attr('defaultValue'));
|
||||
|
||||
x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]');
|
||||
|
||||
if (i.val() == '') {
|
||||
i.hide();
|
||||
x.show();
|
||||
}
|
||||
else {
|
||||
i.show();
|
||||
x.hide();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'checkbox':
|
||||
case 'radio':
|
||||
i.attr('checked', i.attr('defaultValue'));
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
i.val(i.attr('defaultValue'));
|
||||
|
||||
if (i.val() == '') {
|
||||
i.addClass('polyfill-placeholder');
|
||||
i.val(i.attr('placeholder'));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
i.val(i.attr('defaultValue'));
|
||||
break;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Moves elements to/from the first positions of their respective parents.
|
||||
* @param {jQuery} $elements Elements (or selector) to move.
|
||||
* @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations.
|
||||
*/
|
||||
$.prioritize = function($elements, condition) {
|
||||
|
||||
var key = '__prioritize';
|
||||
|
||||
// Expand $elements if it's not already a jQuery object.
|
||||
if (typeof $elements != 'jQuery')
|
||||
$elements = $($elements);
|
||||
|
||||
// Step through elements.
|
||||
$elements.each(function() {
|
||||
|
||||
var $e = $(this), $p,
|
||||
$parent = $e.parent();
|
||||
|
||||
// No parent? Bail.
|
||||
if ($parent.length == 0)
|
||||
return;
|
||||
|
||||
// Not moved? Move it.
|
||||
if (!$e.data(key)) {
|
||||
|
||||
// Condition is false? Bail.
|
||||
if (!condition)
|
||||
return;
|
||||
|
||||
// Get placeholder (which will serve as our point of reference for when this element needs to move back).
|
||||
$p = $e.prev();
|
||||
|
||||
// Couldn't find anything? Means this element's already at the top, so bail.
|
||||
if ($p.length == 0)
|
||||
return;
|
||||
|
||||
// Move element to top of parent.
|
||||
$e.prependTo($parent);
|
||||
|
||||
// Mark element as moved.
|
||||
$e.data(key, $p);
|
||||
|
||||
}
|
||||
|
||||
// Moved already?
|
||||
else {
|
||||
|
||||
// Condition is true? Bail.
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
$p = $e.data(key);
|
||||
|
||||
// Move element back to its original location (using our placeholder).
|
||||
$e.insertAfter($p);
|
||||
|
||||
// Unmark element as moved.
|
||||
$e.removeData(key);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -2,6 +2,7 @@ total = 0
|
|||
products = []
|
||||
menus = []
|
||||
cotisations = []
|
||||
reloads = []
|
||||
paymentMethod = null
|
||||
balance = 0
|
||||
username = ""
|
||||
|
@ -95,12 +96,33 @@ function add_cotisation(pk, duration, amount){
|
|||
generate_html();
|
||||
}
|
||||
|
||||
function add_reload(value, payment_method, payment_method_name){
|
||||
exist = false;
|
||||
index = -1;
|
||||
for(k=0; k < reloads.length; k++){
|
||||
if(reloads[k].value == value && reloads[k].payment_method == payment_method){
|
||||
exist = true;
|
||||
index = k;
|
||||
}
|
||||
}
|
||||
if(exist){
|
||||
reloads[index].quantity += 1;
|
||||
}else{
|
||||
reloads.push({"value": value, "quantity": 1, "payment_method": payment_method, "payment_method_name": payment_method_name});
|
||||
}
|
||||
generate_html();
|
||||
}
|
||||
|
||||
function generate_html(){
|
||||
html = "";
|
||||
for(k=0;k<cotisations.length;k++){
|
||||
cotisation = cotisations[k];
|
||||
html += '<tr><td></td><td>Cotisation ' + String(cotisation.duration) + ' jours</td><td>' + String(cotisation.amount) + ' €</td><td><input type="number" data-target="' + String(k) + '" onChange="updateCotisationInput(this)" value="' + String(cotisation.quantity) + '"/></td><td>' + String(Number((cotisation.quantity * cotisation.amount).toFixed(2))) + ' €</td></tr>';
|
||||
}
|
||||
for(k=0;k<reloads.length;k++){
|
||||
reload = reloads[k];
|
||||
html += '<tr><td>Rechargement ' + String(reload.payment_method_name) + " " + String(reload.value) + ' €</td><td>-' + String(reload.value) + ' €</td><td><input type="number" data-target="' + String(k) + '" onChange="updateReloadInput(this)" value="' + String(reload.quantity) + '"/></td><td>-' + String(Number((reload.quantity * reload.value).toFixed(2))) + ' €</td></tr>';
|
||||
}
|
||||
for(k=0;k<products.length;k++){
|
||||
product = products[k]
|
||||
html += '<tr><td>' + product.name + '</td><td>' + String(product.amount) + ' €</td><td><input type="number" data-target="' + String(k) + '" onChange="updateInput(this)" value="' + String(product.quantity) + '"/></td><td>' + String(Number((product.quantity * product.amount).toFixed(2))) + ' €</td></tr>';
|
||||
|
@ -109,7 +131,7 @@ function generate_html(){
|
|||
menu = menus[k]
|
||||
html += '<tr><td>' + menu.name + '</td><td>' + String(menu.amount) + ' €</td><td><input type="number" data-target="' + String(k) + '" onChange="updateMenuInput(this)" value="' + String(menu.quantity) + '"/></td><td>' + String(Number((menu.quantity * menu.amount).toFixed(2))) + ' €</td></tr>';
|
||||
}
|
||||
$("#items").html(html)
|
||||
$("#items").html(html);
|
||||
updateTotal();
|
||||
}
|
||||
|
||||
|
@ -124,6 +146,9 @@ function updateTotal(){
|
|||
for(k=0; k<cotisations.length;k++){
|
||||
total += cotisations[k].quantity * cotisations[k].amount;
|
||||
}
|
||||
for(k=0; k<reloads.length;k++){
|
||||
total -= reloads[k].quantity * reloads[k].value;
|
||||
}
|
||||
$("#totalAmount").text(String(Number(total.toFixed(2))) + "€")
|
||||
totalAfter = balance - total
|
||||
$("#totalAfter").text(String(Number(totalAfter.toFixed(2))) + "€")
|
||||
|
@ -150,6 +175,13 @@ function updateCotisationInput(a){
|
|||
generate_html();
|
||||
}
|
||||
|
||||
function updateReloadInput(a){
|
||||
quantity = parseInt(a.value);
|
||||
k = parseInt(a.getAttribute("data-target"));
|
||||
reloads[k].quantity = quantity;
|
||||
generate_html();
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$(".cotisation-hidden").hide();
|
||||
get_config();
|
||||
|
@ -166,6 +198,10 @@ $(document).ready(function(){
|
|||
cotisation = get_cotisation($(this).attr('target'));
|
||||
});
|
||||
|
||||
$(".reload").click(function(){
|
||||
add_reload(parseInt($(this).attr('target')), parseInt($(this).attr('data-payment')), $(this).attr('data-payment-name'));
|
||||
})
|
||||
|
||||
$("#id_client").on('change', function(){
|
||||
id_user = $("#id_client").val();
|
||||
$.get("/users/getUser/" + id_user, function(data){
|
||||
|
@ -206,7 +242,7 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
}
|
||||
$.post("order", {"user":id_user, "paymentMethod": $(this).attr('data-payment'), "order_length": products.length + menus.length + cotisations.length, "order": JSON.stringify(products), "amount": total, "menus": JSON.stringify(menus), "listPintes": JSON.stringify(listPintes), "cotisations": JSON.stringify(cotisations)}, function(data){
|
||||
$.post("order", {"user":id_user, "paymentMethod": $(this).attr('data-payment'), "order_length": products.length + menus.length + cotisations.length + reloads.length, "order": JSON.stringify(products), "amount": total, "menus": JSON.stringify(menus), "listPintes": JSON.stringify(listPintes), "cotisations": JSON.stringify(cotisations), "reloads": JSON.stringify(reloads)}, function(data){
|
||||
alert(data);
|
||||
location.reload();
|
||||
}).fail(function(data){
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<link rel="icon" sizes="32x32" href="{% static 'favicon32.ico' %}" type="image/x-icon">
|
||||
<link rel="icon" sizes="96x96" href="{% static 'favicon96.ico' %}" type="image/x-icon">
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}" />
|
||||
<link rel="stylesheet" href="{% static 'dropdown.css' %}" />
|
||||
{% block extra_css %}{% endblock %}
|
||||
{% block extra_script %}{% endblock %}
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
|
||||
|
@ -17,6 +18,10 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
|
||||
<form method="get" action="/search/search">
|
||||
<input id="search_input" placeholder="Rechercher" name="q" value="{{q}}" style="float:left; color:black;"> <button class="button small" action="submit" style="float:left;background-color:white;color:black;margin-left:10px;min-width:0;"><i class="fa fa-search" style="color:black"></i></button>
|
||||
</form>
|
||||
<header id="header" class="alt">
|
||||
<span class="logo"><img src="{%static 'Images/coope.png' %}" alt="" /></span>
|
||||
<h1>{% block entete %}{% endblock %}</h1>
|
||||
|
@ -55,5 +60,13 @@
|
|||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
<script src="{% static 'dropdown.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.scrollex.min.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.scrolly.min.js' %}"></script>
|
||||
<script src="{% static 'js/browser.min.js' %}"></script>
|
||||
<script src="{% static 'js/breakpoints.min.js' %}"></script>
|
||||
<script src="{% static 'js/util.js' %}"></script>
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -42,6 +42,6 @@
|
|||
<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">coope.rez v3.6.4 (release stable) © 2018-2019 Yoann Pietri. <a href="{% url 'about'%}">À propos du projet</a>.</p>
|
||||
<p class="copyright">coope.rez v3.7.0 (release stable) © 2018-2019 Yoann Pietri. <a href="{% url 'about'%}">À propos du projet</a>.</p>
|
||||
|
||||
|
||||
|
|
|
@ -70,6 +70,14 @@
|
|||
<i class="fa fa-search-dollar"></i> <a href="{% url 'gestion:compute-price' %}">Calcul de prix</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="tabulation2">
|
||||
<i class="fa fa-bug"></i> <a href="{% url 'preferences:addImprovement' %}">Proposition d'amélioration</a>
|
||||
</span>
|
||||
{% if perms.preferences.view_improvement %}
|
||||
<span class="tabulation2">
|
||||
<i class="fa fa-bug"></i> <a href="{% url 'preferences:improvementsIndex' %}">Améliorations</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="tabulation2">
|
||||
<i class="fa fa-bed"></i> <a href="{% url 'users:logout' %}">Deconnexion</a>
|
||||
</span>
|
||||
|
|
17
templates/registration/password_reset_complete.html
Normal file
17
templates/registration/password_reset_complete.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block entete %}Réinitilisation du mot de passe{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">Réinitialisation du mot de passe</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>Réinitialisation du mot de passe</h2>
|
||||
<p>Mot de passe réinitialisé.</p>
|
||||
</header>
|
||||
Vous pouvez vous connecter en vous rendant sur la <a href="{% url 'users:login' %}">page de connexion</a>.
|
||||
</section>
|
||||
{{form.media}}
|
||||
{% endblock %}
|
23
templates/registration/password_reset_confirm.html
Normal file
23
templates/registration/password_reset_confirm.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block entete %}Réinitilisation du mot de passe{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">Réinitialisation du mot de passe</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>Réinitialisation du mot de passe</h2>
|
||||
</header>
|
||||
<section>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<br>
|
||||
<button type="submit"><i class="fa fa-lock"></i> Changer le mot de passe</button>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
||||
{{form.media}}
|
||||
{% endblock %}
|
16
templates/registration/password_reset_done.html
Normal file
16
templates/registration/password_reset_done.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block entete %}Réinitilisation du mot de passe{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">Réinitialisation du mot de passe</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>Réinitialisation du mot de passe</h2>
|
||||
<p>Un mail vous a été envoyé avec un lien pour réinitialiser le mot de passe.</p>
|
||||
</header>
|
||||
</section>
|
||||
{{form.media}}
|
||||
{% endblock %}
|
11
templates/registration/password_reset_email.html
Normal file
11
templates/registration/password_reset_email.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
{% autoescape off %}
|
||||
Bonjour {{user.username}},
|
||||
|
||||
Vous avez demandé une réinitalisation de votre mot de passe sur le site de gestion de la Coopé Technopôle Metz, vous pouvez le faire en cliquant sur le lien ci dessous:
|
||||
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
|
||||
Si le lien ne fonctionne pas en cliquant, vous pouvez le copier-coller dans votre navigateur,
|
||||
|
||||
Le staff Coopé Technopôle Metz
|
||||
{% endautoescape %}
|
24
templates/registration/password_reset_form.html
Normal file
24
templates/registration/password_reset_form.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block entete %}Réinitilisation du mot de passe{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">Réinitialisation du mot de passe</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>Réinitialisation du mot de passe</h2>
|
||||
<p>Vous recevrez un lien pour réinitilisaser votre mot de passe sur votre adresse e-mail.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<br>
|
||||
<button type="submit"><i class="fa fa-lock"></i> Réinitialiser</button>
|
||||
</form>
|
||||
</section>
|
||||
</section>
|
||||
{{form.media}}
|
||||
{% endblock %}
|
1
templates/registration/password_reset_subject.txt
Normal file
1
templates/registration/password_reset_subject.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Réinitialisation du mot de passe Coopé TM
|
|
@ -21,6 +21,12 @@ class CreateUserForm(forms.ModelForm):
|
|||
|
||||
school = forms.ModelChoiceField(queryset=School.objects.all(), label="École")
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
email = cleaned_data.get("email")
|
||||
if User.objects.filter(email=email).count() > 0:
|
||||
raise forms.ValidationError("L'email est déjà utilisé")
|
||||
|
||||
class CreateGroupForm(forms.ModelForm):
|
||||
"""
|
||||
Form to create a new group (:class:`django.contrib.auth.models.Group`).
|
||||
|
|
|
@ -7,6 +7,7 @@ from simple_history.models import HistoricalRecords
|
|||
from preferences.models import PaymentMethod, Cotisation
|
||||
from gestion.models import ConsumptionHistory
|
||||
|
||||
|
||||
class School(models.Model):
|
||||
"""
|
||||
Stores school.
|
||||
|
|
31
users/templates/users/login.html
Normal file
31
users/templates/users/login.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
{% extends 'base.html' %}
|
||||
{% block entete %}{{form_title}}{% endblock %}
|
||||
{% block navbar %}
|
||||
<ul>
|
||||
<li><a href="#first">{{form_title}}</a></li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section id="first" class="main">
|
||||
<header class="major">
|
||||
<h2>{{form_title}}</h2>
|
||||
<p>{{form_p}}</p>
|
||||
</header>
|
||||
<section>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form }}
|
||||
<br>
|
||||
{{ extra_html | safe }}<br><br>
|
||||
<button type="submit"><i class="fa fa-{{form_button_icon}}"></i> {{form_button}}</button>
|
||||
</form>
|
||||
</section>
|
||||
Si vous avez perdu votre mot de passe : <a href="{% url 'password_reset' %}">mot de passe oublié</a>.
|
||||
</section>
|
||||
{% if extra_css %}
|
||||
<style>
|
||||
{{extra_css}}
|
||||
</style>
|
||||
{% endif %}
|
||||
{{form.media}}
|
||||
{% endblock %}
|
|
@ -58,9 +58,6 @@
|
|||
{% if self %}
|
||||
<span class="tabulation"><a href="{% url 'users:editPassword' user.pk %}"><i class="fa fa-user-lock"></i> Changer mon mot de passe</a></span>
|
||||
{% endif %}
|
||||
{% if perms.users.can_reset_password %}
|
||||
<span class="tabulation"><a href="{% url 'users:resetPassword' user.pk %}"><i class="fa fa-lock-open"></i> Réinitialiser le mot de passe</a></span>
|
||||
{% endif %}
|
||||
{% if perms.users.can_change_user_perm %}
|
||||
<span class="tabulation"><a href="{% url 'users:editGroups' user.pk %}"><i class="fa fa-layer-group"></i> Changer les groupes</a></span>
|
||||
{% endif %}
|
||||
|
|
15
users/templates/users/welcome_email.html
Normal file
15
users/templates/users/welcome_email.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
{% autoescape off %}
|
||||
Bonjour {{user.username}},<br>
|
||||
|
||||
Vous venez de créer votre compte sur le logiciel de gestion de l'association Coopé Technopôle Metz. Pour finir vous adhésion à l'association, vous devez
|
||||
<ul>
|
||||
<li>lire et accepter les statuts et le règlement intérieur (disponibles en pièces jointes),</li>
|
||||
<li>vous acquittez d'une cotisation auprès de l'un de nos membres actifs.</li>
|
||||
</ul>
|
||||
|
||||
Vous pouvez acceder à votre compte sur {{protocol}}://{{domain}} après avoir activé votre mot de passe avec le lien suivant : <br>
|
||||
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}<br><br>
|
||||
|
||||
Le Staff Coopé Technopôle Metz
|
||||
{% endautoescape %}
|
11
users/templates/users/welcome_email.txt
Normal file
11
users/templates/users/welcome_email.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
Bonjour {{user.username}},
|
||||
|
||||
Vous venez de créer votre compte sur le logiciel de gestion de l'association Coopé Technopôle Metz. Pour finir vous adhésion à l'association, vous devez
|
||||
- lire et accepter les statuts et le règlement intérieur (disponibles en pièces jointes),
|
||||
- vous acquittez d'une cotisation auprès de l'un de nos membres actifs.
|
||||
|
||||
Vous pouvez acceder à votre compte sur {{procotol}}://{{domain}} après avoir activé votre mot de passe avec le lien suivant :
|
||||
|
||||
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
|
||||
|
||||
Le Staff Coopé Technopôle Metz
|
|
@ -1,4 +1,5 @@
|
|||
from django.urls import path
|
||||
from django.urls import path, include
|
||||
|
||||
from . import views
|
||||
|
||||
app_name="users"
|
||||
|
@ -13,7 +14,6 @@ urlpatterns = [
|
|||
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('groupProfile/<int:pk>', views.groupProfile, name="groupProfile"),
|
||||
path('createGroup', views.createGroup, name="createGroup"),
|
||||
|
|
|
@ -2,14 +2,21 @@ from django.shortcuts import render, get_object_or_404, redirect
|
|||
from django.urls import reverse
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
from django.contrib import messages
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template.loader import get_template
|
||||
from django.template import Context
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.forms.models import model_to_dict
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
import simplejson as json
|
||||
from datetime import datetime, timedelta
|
||||
|
@ -23,6 +30,7 @@ from coopeV3.acl import admin_required, superuser_required, self_or_has_perm, ac
|
|||
from .models import CotisationHistory, WhiteListHistory, School
|
||||
from .forms import CreateUserForm, LoginForm, CreateGroupForm, EditGroupForm, SelectUserForm, GroupsEditForm, EditPasswordForm, addCotisationHistoryForm, addCotisationHistoryForm, addWhiteListHistoryForm, SelectNonAdminUserForm, SelectNonSuperUserForm, SchoolForm, ExportForm
|
||||
from gestion.models import Reload, Consumption, ConsumptionHistory, MenuHistory
|
||||
from preferences.models import GeneralPreferences
|
||||
|
||||
@active_required
|
||||
def loginView(request):
|
||||
|
@ -38,7 +46,7 @@ def loginView(request):
|
|||
return redirect(reverse('home'))
|
||||
else:
|
||||
messages.error(request, "Nom d'utilisateur et/ou mot de passe invalide")
|
||||
return render(request, "form.html", {"form_entete": "Connexion", "form": form, "form_title": "Connexion", "form_button": "Se connecter", "form_button_icon": "sign-in-alt"})
|
||||
return render(request, "users/login.html", {"form_entete": "Connexion", "form": form, "form_title": "Connexion", "form_button": "Se connecter", "form_button_icon": "sign-in-alt"})
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
|
@ -169,6 +177,30 @@ def createUser(request):
|
|||
user.save()
|
||||
user.profile.school = form.cleaned_data['school']
|
||||
user.save()
|
||||
uid = urlsafe_base64_encode(force_bytes(user.pk)).decode('UTF-8')
|
||||
print(uid)
|
||||
token = default_token_generator.make_token(user)
|
||||
plaintext = get_template('users/welcome_email.txt')
|
||||
htmly = get_template('users/welcome_email.html')
|
||||
context = {'user': user, 'uid': uid, 'token': token, 'protocol': "http", 'domain': get_current_site(request).name}
|
||||
text_content = plaintext.render(context)
|
||||
html_content = htmly.render(context)
|
||||
email = EmailMultiAlternatives(
|
||||
"Bienvenue à l'association Coopé Technopôle Metz",
|
||||
text_content,
|
||||
"Coopé Technopôle Metz <no-reply@coope.rezometz.org>",
|
||||
[user.email],
|
||||
reply_to=["coopemetz@gmail.com"]
|
||||
)
|
||||
email.attach_alternative(html_content, "text/html")
|
||||
gp,_ = GeneralPreferences.objects.get_or_create(pk=1)
|
||||
if gp.statutes:
|
||||
#email.attach("statuts.pdf", gp.statutes.read(), "application/pdf")
|
||||
pass
|
||||
if gp.rules:
|
||||
#email.attach("ri.pdf", gp.rules.read(), "application/pdf")
|
||||
pass
|
||||
email.send()
|
||||
messages.success(request, "L'utilisateur a bien été créé")
|
||||
return redirect(reverse('users:profile', kwargs={'pk':user.pk}))
|
||||
return render(request, "form.html", {"form_entete": "Gestion des utilisateurs", "form":form, "form_title":"Création d'un nouvel utilisateur", "form_button":"Créer mon compte", "form_button_icon": "user-plus", 'extra_html': '<strong>En cliquant sur le bouton "Créer mon compte", vous :<ul><li>attestez sur l\'honneur que les informations fournies à l\'association Coopé Technopôle Metz sont correctes et que vous n\'avez jamais été enregistré dans l\'association sous un autre nom / pseudonyme</li><li>joignez l\'association de votre plein gré</li><li>vous engagez à respecter les statuts et le réglement intérieur de l\'association (envoyés par mail)</li><li>reconnaissez le but de l\'assocation Coopé Technopôle Metz et vous attestez avoir pris conaissances des droits et des devoirs des membres de l\'association</li><li>consentez à ce que les données fournies à l\'association, ainsi que vos autres données de compte (débit, crédit, solde et historique des transactions) soient stockées dans le logiciel de gestion et accessibles par tous les membres actifs de l\'association, en particulier par le comité de direction</li></ul></strong>'})
|
||||
|
@ -250,23 +282,6 @@ def editUser(request, pk):
|
|||
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", "form_button_icon": "pencil-alt"})
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
@permission_required('auth.change_user')
|
||||
def resetPassword(request, pk):
|
||||
"""
|
||||
Reset the password of a user (:class:`django.contrib.auth.models.User`).
|
||||
"""
|
||||
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}))
|
||||
|
||||
@active_required
|
||||
@login_required
|
||||
@permission_required('auth.view_user')
|
||||
|
@ -343,7 +358,7 @@ def gen_user_infos(request, pk):
|
|||
"""
|
||||
Generates a latex document include adhesion certificate and list of `cotisations <users.models.CotisationHistory>`.
|
||||
"""
|
||||
user= get_object_or_404(User, pk=pk)
|
||||
user = get_object_or_404(User, pk=pk)
|
||||
cotisations = CotisationHistory.objects.filter(user=user).order_by('-paymentDate')
|
||||
now = datetime.now()
|
||||
path = os.path.join(settings.BASE_DIR, "templates/coope.png")
|
||||
|
|
Loading…
Reference in a new issue