1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-21 18:08:21 +02:00

Compare commits

..

28 Commits

Author SHA1 Message Date
81c12436a8 Revert "missing comma"
This reverts commit 1ca4246cbd.
2023-03-31 16:51:48 +02:00
8eee14075c test6 2023-03-31 16:37:22 +02:00
ab9ba62af1 test5 2023-03-31 16:31:53 +02:00
8ca0648e75 test4 2023-03-31 16:23:41 +02:00
77ecfd6ed5 test3 2023-03-31 16:14:29 +02:00
0ab1367e55 test2 2023-03-31 15:56:34 +02:00
524f0e098a test 2023-03-31 15:48:45 +02:00
1ca4246cbd missing comma 2023-03-31 14:58:46 +02:00
6a9021ec14 Merge branch 'couleur_totalist_spies' into 'main'
Couleur totalist spies

See merge request bde/nk20!208
2023-03-31 12:37:24 +02:00
cb74311e7b Commit migration, j'étais triggered 2023-03-30 19:14:52 +02:00
9d7dd566c9 Ignore /tmp/ 2023-03-30 17:26:06 +02:00
9944ebcaad changement des couleurs de la note vers les couleurs totalist spies 2023-03-25 02:13:16 +01:00
8537f043f7 changement des couleurs de la note vers les couleurs totalist spies 2023-03-25 00:57:19 +01:00
c89a95f8d2 Merge branch 'invoice-logo-totalist' into 'main'
changement du fond des factures

See merge request bde/nk20!207
2023-01-30 13:06:39 +01:00
73640b1dfa changement du fond des factures 2023-01-30 00:06:45 +01:00
84b16ab603 Merge branch 'SogeCreditDate' into 'main'
link SogeCredit to WEI by creation date instead of civil year

See merge request bde/nk20!206
2023-01-17 15:58:52 +01:00
6a1b51dbbf Merge branch 'api_pagination' into 'main'
Add custom pagination size as an API parameter

See merge request bde/nk20!205
2023-01-11 22:46:13 +01:00
c441a43a8b link SogeCredit to WEI by creation date instead of civil year 2023-01-10 21:40:03 +01:00
87f3b51b04 Add custom pagination size as an API parameter 2022-12-14 18:37:13 +01:00
0a853fd3e6 Merge branch 'permission_trez' into 'main'
fix trez perm

See merge request bde/nk20!204
2022-12-10 14:41:57 +01:00
c429734810 fix bug 2022-11-12 14:51:22 +01:00
5d759111b6 Merge branch 'weiWords' into 'main'
change wei words

See merge request bde/nk20!203
2022-09-05 13:24:24 +02:00
70baf7566c change wei words 2022-09-05 13:20:00 +02:00
eb355f547c Merge branch 'SogeNotForMembership' into 'main'
Soge not for membership

See merge request bde/nk20!202
2022-09-04 22:56:07 +02:00
7068170f18 fixing grammar in comments 2022-09-04 13:24:39 +02:00
45ee9a8941 Soge only payd WEI (not bde/kfet membership) 2022-09-04 12:52:40 +02:00
454ea19603 hide Soge during registration 2022-09-04 12:31:08 +02:00
5a77a66391 Merge branch 'beta' into 'main'
Friendships

See merge request bde/nk20!200
2022-04-13 12:45:06 +02:00
43 changed files with 189 additions and 2222 deletions

1
.gitignore vendored
View File

@ -42,6 +42,7 @@ map.json
backups/
/static/
/media/
/tmp/
# Virtualenv
env/

5
apps/api/pagination.py Normal file
View File

@ -0,0 +1,5 @@
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
page_size_query_param = 'page_size'

View File

@ -26,10 +26,6 @@ if "note" in settings.INSTALLED_APPS:
from note.api.urls import register_note_urls
register_note_urls(router, 'note')
if "sheets" in settings.INSTALLED_APPS:
from sheets.api.urls import register_sheets_urls
register_sheets_urls(router, 'sheets')
if "treasury" in settings.INSTALLED_APPS:
from treasury.api.urls import register_treasury_urls
register_treasury_urls(router, 'treasury')

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.27 on 2022-08-18 11:01
# Generated by Django 2.2.26 on 2022-09-04 21:25
from django.db import migrations, models

View File

@ -1,17 +0,0 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
{% crispy form %}
</div>
</div>
{% endblock %}

View File

@ -1,88 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header text-center">
<h1>{{ food.name }}</h1>
</div>
<div class="card-body">
<div class="row">
<div class="card col-xl-6">
<div class="card-header text-center">
<h2>{% trans "queued"|capfirst %}{% if queue %} ({{ queue|length }}){% endif %}</h2>
</div>
<div class="card-body">
<ul>
{% for ordered_food in queue %}
<li>
{{ ordered_food.order.note }}
{% if ordered_food.priority %}
<span class="badge badge-secondary">{{ ordered_food.priority }}</span>
{% endif %}
</li>
{% empty %}
<div class="alert alert-warning">
{% trans "There is no queued order." %}
</div>
{% endfor %}
</ul>
</div>
</div>
<div class="card col-xl-6">
<div class="card-header text-center">
<h2>{% trans "ready"|capfirst %}</h2>
</div>
<div class="card-body">
<ul>
{% for ordered_food in ready %}
<li>{{ ordered_food.order.note }}</li>
{% empty %}
<div class="alert alert-warning">
{% trans "There is no ready order." %}
</div>
{% endfor %}
</ul>
</div>
</div>
</div>
<hr>
<h3>{% trans "Other waiting lists:" %}</h3>
<ul>
{% for other_food in food.sheet.food_set.all %}
{% if other_food != food %}
<li>
<a href="{% url 'sheets:waiting_list' pk=other_food.pk %}">{{ other_food }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="card-footer text-center">
<a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Queued orders" %}
</a>
<a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Ready orders" %}
</a>
<a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary">
{% trans "Back to note sheet detail" %}
</a>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
function reload() {
reloadWithTurbolinks()
timeout = setTimeout(reload, 15000)
}
if (timeout === undefined)
var timeout = setTimeout(reload, 15000)
</script>
{% endblock %}

View File

@ -1,152 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header text-center">
<h1>{{ title }}</h1>
</div>
<div class="card-body">
{% for of in orders %}
<div class="card mb-4">
<div class="card-header text-center">
<h3>{{ of.order.note }}</h3>
</div>
<div class="card-body">
<dl class="row">
<dt class="col-xl-3">{% trans 'date'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.order.date }}</dd>
{% if of.number > 1 %}
<dt class="col-xl-3">{% trans 'order number'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.number }}</dd>
{% endif %}
{% if of.priority %}
<dt class="col-xl-3">{% trans 'priority request'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.priority }}</dd>
{% endif %}
{% if of.remark %}
<dt class="col-xl-3">{% trans 'remark'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.remark }}</dd>
{% endif %}
{% if of.options.count %}
<dt class="col-xl-3">{% trans 'options'|capfirst %}</dt>
<dd class="col-xl-9">{{ of.options.all|join:', ' }}</dd>
{% endif %}
</dl>
</div>
<div class="card-footer text-center">
{% if list_type != 'READY' %}
<a href="#" class="btn btn-success" onclick="setOrderStatus({{ of.pk }}, 'READY')">
{% trans "Mark as ready" %}
</a>
{% endif %}
{% if list_type != 'SERVED' %}
<a href="#" class="btn btn-primary" onclick="setOrderStatus({{ of.pk }}, 'SERVED')">
{% trans "Mark as served" %}
</a>
{% endif %}
{% if list_type != 'QUEUED' %}
<a href="#" class="btn btn-warning" onclick="setOrderStatus({{ of.pk }}, 'QUEUED')">
{% trans "Re-queue" %}
</a>
{% endif %}
{% if list_type != 'CANCELED' %}
<a href="#" class="btn btn-danger" onclick="setOrderStatus({{ of.pk }}, 'CANCELED')">
{% trans "Cancel" %}
</a>
{% endif %}
</div>
</div>
{% empty %}
<div class="alert alert-warning">
{% trans "There is no queued order." %}
</div>
{% endfor %}
</div>
</div>
<div class="card mt-5">
<div class="card-body">
<h3>{% trans "Other waiting lists:" %}</h3>
<ul>
{% for other_food in food.sheet.food_set.all %}
{% if other_food != food %}
<li>
{% if list_type == 'QUEUED' %}
<a href="{% url 'sheets:queued_list' pk=other_food.pk %}">{{ other_food }}</a>
{% else %}
<a href="{% url 'sheets:ready_list' pk=other_food.pk %}">{{ other_food }}</a>
{% endif %}
</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="card-footer text-center">
{% if list_type != 'QUEUED' %}
<a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Queued orders" %}
</a>
{% endif %}
{% if list_type != 'READY' %}
<a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-success">
{% trans "Ready orders" %}
</a>
{% endif %}
{% if list_type != 'SERVED' %}
<a href="{% url 'sheets:served_list' pk=food.pk %}" class="btn btn-secondary">
{% trans "Served orders" %}
</a>
{% endif %}
{% if list_type != 'CANCELED' %}
<a href="{% url 'sheets:canceled_list' pk=food.pk %}" class="btn btn-danger">
{% trans "Canceled orders" %}
</a>
{% endif %}
<a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="btn btn-primary">
{% trans "Waiting list" %}
</a>
<a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary">
{% trans "Back to note sheet detail" %}
</a>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
function reload() {
reloadWithTurbolinks()
timeout = setTimeout(reload, 15000)
}
if (timeout === undefined)
var timeout = setTimeout(reload, 15000)
function setOrderStatus(ordered_food_id, status) {
fetch('/api/sheets/orderedfood/' + ordered_food_id + '/', {
method: 'PATCH',
body: JSON.stringify({
status: status,
served_date: status === 'QUEUED' ? null : new Date().toISOString(),
}),
headers: {
'Content-Type': "application/json; charset=UTF-8",
'X-CSRFTOKEN': "{{ csrf_token }}"
}
}).then(response => response.json()).then(response => {
if ('detail' in response)
addMsg("{% trans "An error occurred" %}" + " : " + response['detail'], "danger")
else {
clearTimeout(timeout)
reload()
}
})
}
</script>
{% endblock %}

View File

@ -1967,7 +1967,7 @@
"note",
"transaction"
],
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}]]",
"query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}]]",
"type": "change",
"mask": 2,
"field": "valid",
@ -2607,7 +2607,7 @@
"note",
"transaction"
],
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}]",
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}]",
"type": "change",
"mask": 2,
"field": "valid",
@ -2623,7 +2623,7 @@
"note",
"transaction"
],
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}]",
"query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 5000]}, \"valid\": true}]",
"type": "change",
"mask": 2,
"field": "invalidity_reason",
@ -2928,7 +2928,7 @@
"application"
],
"query": "{\"user\": [\"user\"]}",
"type": "add",
"type": "create",
"mask": 1,
"field": "",
"permanent": true,
@ -3114,10 +3114,10 @@
187,
188,
189,
190,
191,
195,
196
190,
191,
195,
196
]
}
},
@ -3159,8 +3159,8 @@
159,
160,
179,
189,
190
189,
190
]
}
},
@ -3310,10 +3310,10 @@
176,
177,
178,
188,
188,
183,
186,
187
186,
187
]
}
},
@ -3508,13 +3508,13 @@
187,
188,
189,
190,
191,
192,
193,
194,
195,
196
190,
191,
192,
193,
194,
195,
196
]
}
},

View File

@ -1,4 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'sheets.apps.SheetsConfig'

View File

@ -1,46 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin
from note_kfet.admin import admin_site
from sheets.models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
@admin.register(Sheet, site=admin_site)
class SheetAdmin(admin.ModelAdmin):
pass
@admin.register(Food, site=admin_site)
class FoodAdmin(admin.ModelAdmin):
pass
@admin.register(FoodOption, site=admin_site)
class FoodOptionAdmin(admin.ModelAdmin):
pass
@admin.register(Meal, site=admin_site)
class MealAdmin(admin.ModelAdmin):
pass
@admin.register(Order, site=admin_site)
class OrderAdmin(admin.ModelAdmin):
pass
@admin.register(OrderedMeal, site=admin_site)
class OrderedMealAdmin(admin.ModelAdmin):
pass
@admin.register(OrderedFood, site=admin_site)
class OrderedFoodAdmin(admin.ModelAdmin):
pass
@admin.register(SheetOrderTransaction, site=admin_site)
class SheetOrderTransactionAdmin(admin.ModelAdmin):
pass

View File

@ -1,55 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers
from ..models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
class SheetSerializer(serializers.ModelSerializer):
class Meta:
model = Sheet
fields = '__all__'
class FoodSerializer(serializers.ModelSerializer):
class Meta:
model = Food
fields = '__all__'
class FoodOptionSerializer(serializers.ModelSerializer):
class Meta:
model = FoodOption
fields = '__all__'
class MealSerializer(serializers.ModelSerializer):
class Meta:
model = Meal
fields = '__all__'
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = '__all__'
class OrderedMealSerializer(serializers.ModelSerializer):
class Meta:
model = OrderedMeal
fields = '__all__'
class OrderedFoodSerializer(serializers.ModelSerializer):
class Meta:
model = OrderedFood
fields = '__all__'
class SheetOrderTransactionSerializer(serializers.ModelSerializer):
class Meta:
model = SheetOrderTransaction
fields = '__all__'

View File

@ -1,19 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from sheets.api.views import SheetViewSet, FoodViewSet, FoodOptionViewSet, MealViewSet, OrderViewSet, \
OrderedMealViewSet, OrderedFoodViewSet, SheetOrderTransactionViewSet
def register_sheets_urls(router, path):
"""
Configure router for Sheets REST API.
"""
router.register(path + '/sheet', SheetViewSet)
router.register(path + '/food', FoodViewSet)
router.register(path + '/foodoption', FoodOptionViewSet)
router.register(path + '/meal', MealViewSet)
router.register(path + '/order', OrderViewSet)
router.register(path + '/orderedmeal', OrderedMealViewSet)
router.register(path + '/orderedfood', OrderedFoodViewSet)
router.register(path + '/sheetordertransaction', SheetOrderTransactionViewSet)

View File

@ -1,78 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from api.viewsets import ReadProtectedModelViewSet
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter, OrderingFilter
from .serializers import SheetSerializer, FoodSerializer, FoodOptionSerializer, MealSerializer, OrderSerializer, \
OrderedMealSerializer, OrderedFoodSerializer, SheetOrderTransactionSerializer
from ..models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
class SheetViewSet(ReadProtectedModelViewSet):
queryset = Sheet.objects.order_by('id')
serializer_class = SheetSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'date', ]
search_fields = ['$name', ]
class FoodViewSet(ReadProtectedModelViewSet):
queryset = Food.objects.order_by('id')
serializer_class = FoodSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'sheet', 'price', 'club', 'available', ]
search_fields = ['$name', ]
class FoodOptionViewSet(ReadProtectedModelViewSet):
queryset = FoodOption.objects.order_by('id')
serializer_class = FoodOptionSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'food', 'extra_cost', 'available', ]
search_fields = ['$name', '$food__name', ]
class MealViewSet(ReadProtectedModelViewSet):
queryset = Meal.objects.order_by('id')
serializer_class = MealSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'content', 'price', 'available', ]
search_fields = ['$name', ]
class OrderViewSet(ReadProtectedModelViewSet):
queryset = Order.objects.order_by('id')
serializer_class = OrderSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['sheet', 'note', 'date', 'gift', ]
search_fields = ['$sheet__name', '$note__alias__name', '$note__alias__normalized_name', ]
class OrderedMealViewSet(ReadProtectedModelViewSet):
queryset = OrderedMeal.objects.order_by('id')
serializer_class = OrderedMealSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['order', 'meal', ]
class OrderedFoodViewSet(ReadProtectedModelViewSet):
queryset = OrderedFood.objects.order_by('id')
serializer_class = OrderedFoodSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['order', 'meal', 'food', 'options', 'number', 'status', 'served_date', ]
class SheetOrderTransactionViewSet(ReadProtectedModelViewSet):
queryset = SheetOrderTransaction.objects.order_by('-created_at')
serializer_class = SheetOrderTransactionSerializer
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
'destination', 'destination_alias', 'destination__alias__name',
'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount',
'created_at', 'valid', 'invalidity_reason', 'ordered_food', ]
search_fields = ['$reason', '$source_alias', '$source__alias__name', '$source__alias__normalized_name',
'$destination_alias', '$destination__alias__name', '$destination__alias__normalized_name',
'$invalidity_reason', ]
ordering_fields = ['created_at', 'amount', ]

View File

@ -1,10 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class SheetsConfig(AppConfig):
name = 'sheets'
verbose_name = _('note sheets')

View File

@ -1,67 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.helper import FormHelper
from django import forms
from member.models import Club
from note_kfet.inputs import AmountInput, Autocomplete, DateTimePickerInput
from .models import Food, FoodOption, Meal, Sheet
class SheetForm(forms.ModelForm):
class Meta:
model = Sheet
fields = '__all__'
widgets = {
'date': DateTimePickerInput(),
}
class FoodForm(forms.ModelForm):
class Meta:
model = Food
exclude = ('sheet', )
widgets = {
'price': AmountInput(),
'club': Autocomplete(
model=Club,
attrs={"api_url": "/api/members/club/"},
),
}
class FoodOptionForm(forms.ModelForm):
class Meta:
model = FoodOption
fields = '__all__'
widgets = {
'extra_cost': AmountInput(),
}
FoodOptionsFormSet = forms.inlineformset_factory(
Food,
FoodOption,
form=FoodOptionForm,
extra=0,
)
class FoodOptionFormSetHelper(FormHelper):
def __init__(self, form=None):
super().__init__(form)
self.form_tag = False
self.form_method = 'POST'
self.form_class = 'form-inline'
self.template = 'bootstrap4/table_inline_formset.html'
class MealForm(forms.ModelForm):
class Meta:
model = Meal
exclude = ('sheet', )
widgets = {
'content': forms.CheckboxSelectMultiple(),
'price': AmountInput(),
}

View File

@ -1,157 +0,0 @@
# Generated by Django 2.2.27 on 2022-08-18 11:01
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('member', '0009_auto_20220818_1301'),
('note', '0006_trust'),
]
operations = [
migrations.CreateModel(
name='Food',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='food')),
('price', models.IntegerField(verbose_name='price')),
('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')),
('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='destination club')),
],
options={
'verbose_name': 'food',
'verbose_name_plural': 'food',
},
),
migrations.CreateModel(
name='FoodOption',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('extra_cost', models.IntegerField(default=0, verbose_name='extra cost')),
('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')),
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Food', verbose_name='food')),
],
options={
'verbose_name': 'food option',
'verbose_name_plural': 'food options',
},
),
migrations.CreateModel(
name='Meal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('price', models.IntegerField(verbose_name='price')),
('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')),
('content', models.ManyToManyField(to='sheets.Food', verbose_name='content')),
],
options={
'verbose_name': 'meal',
'verbose_name_plural': 'meals',
},
),
migrations.CreateModel(
name='Order',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True, verbose_name='date')),
('gift', models.IntegerField(verbose_name='gift')),
('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note', verbose_name='note')),
],
options={
'verbose_name': 'order',
'verbose_name_plural': 'orders',
},
),
migrations.CreateModel(
name='OrderedFood',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remark', models.TextField(blank=True, default='', verbose_name='remark')),
('priority', models.CharField(blank=True, default='', max_length=64, verbose_name='priority request')),
('number', models.IntegerField(help_text='How many times the user ordered this.', verbose_name='number')),
('status', models.CharField(choices=[('QUEUED', 'queued'), ('READY', 'ready'), ('SERVED', 'served'), ('CANCELED', 'canceled')], max_length=8, verbose_name='status')),
('served_date', models.DateTimeField(default=None, null=True, verbose_name='served date')),
('food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Food', verbose_name='food')),
],
options={
'verbose_name': 'ordered food',
'verbose_name_plural': 'ordered food',
},
),
migrations.CreateModel(
name='Sheet',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='start date')),
('description', models.TextField(verbose_name='description')),
('visible', models.BooleanField(default=False, help_text='the note sheet will be private until this field is checked.', verbose_name='visible')),
],
options={
'verbose_name': 'note sheet',
'verbose_name_plural': 'note sheets',
},
),
migrations.CreateModel(
name='SheetOrderTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
('ordered_food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.OrderedFood', verbose_name='ordered food')),
],
options={
'verbose_name': 'sheet order transaction',
'verbose_name_plural': 'sheet order transactions',
},
bases=('note.transaction',),
),
migrations.CreateModel(
name='OrderedMeal',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('meal', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Meal', verbose_name='meal')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Order', verbose_name='order')),
],
options={
'verbose_name': 'ordered meal',
'verbose_name_plural': 'ordered meals',
},
),
migrations.AddField(
model_name='orderedfood',
name='meal',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sheets.OrderedMeal', verbose_name='ordered meal'),
),
migrations.AddField(
model_name='orderedfood',
name='options',
field=models.ManyToManyField(blank=True, to='sheets.FoodOption', verbose_name='options'),
),
migrations.AddField(
model_name='orderedfood',
name='order',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Order', verbose_name='order'),
),
migrations.AddField(
model_name='order',
name='sheet',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Sheet', verbose_name='note sheet'),
),
migrations.AddField(
model_name='meal',
name='sheet',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Sheet', verbose_name='note sheet'),
),
migrations.AddField(
model_name='food',
name='sheet',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Sheet', verbose_name='note sheet'),
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 2.2.27 on 2022-08-18 15:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sheets', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='order',
name='gift',
),
migrations.AddField(
model_name='orderedfood',
name='gift',
field=models.IntegerField(default=0, verbose_name='gift'),
preserve_default=False,
),
migrations.AddField(
model_name='orderedmeal',
name='gift',
field=models.IntegerField(default=0, verbose_name='gift'),
preserve_default=False,
),
migrations.AlterField(
model_name='orderedfood',
name='status',
field=models.CharField(choices=[('QUEUED', 'queued'), ('READY', 'ready'), ('SERVED', 'served'), ('CANCELED', 'canceled')], default='QUEUED', max_length=8, verbose_name='status'),
),
]

View File

@ -1,289 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db import models
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from member.models import Club
from note.models import Note, Transaction
class Sheet(models.Model):
name = models.CharField(
max_length=255,
verbose_name=_("name"),
)
date = models.DateTimeField(
verbose_name=_("start date"),
default=timezone.now,
)
description = models.TextField(
verbose_name=_("description"),
)
visible = models.BooleanField(
default=False,
verbose_name=_("visible"),
help_text=_("the note sheet will be private until this field is checked."),
)
def get_absolute_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.pk,))
def __str__(self):
return self.name
class Meta:
verbose_name = _("note sheet")
verbose_name_plural = _("note sheets")
class Food(models.Model):
name = models.CharField(
max_length=255,
verbose_name=_("food"),
)
sheet = models.ForeignKey(
Sheet,
on_delete=models.CASCADE,
verbose_name=_("note sheet"),
)
price = models.IntegerField(
verbose_name=_("price"),
)
club = models.ForeignKey(
Club,
on_delete=models.PROTECT,
verbose_name=_("destination club"),
)
available = models.BooleanField(
default=True,
verbose_name=_("available"),
help_text=_("If set to false, this option won't be offered (in case of out of stock)"),
)
def __str__(self):
return self.name
class Meta:
verbose_name = _("food")
verbose_name_plural = _("food")
class FoodOption(models.Model):
name = models.CharField(
max_length=255,
verbose_name=_("name"),
)
food = models.ForeignKey(
Food,
on_delete=models.CASCADE,
verbose_name=_("food"),
)
extra_cost = models.IntegerField(
default=0,
verbose_name=_("extra cost"),
)
available = models.BooleanField(
default=True,
verbose_name=_("available"),
help_text=_("If set to false, this option won't be offered (in case of out of stock)"),
)
def __str__(self):
return self.name
class Meta:
verbose_name = _("food option")
verbose_name_plural = _("food options")
class Meal(models.Model):
sheet = models.ForeignKey(
Sheet,
on_delete=models.CASCADE,
verbose_name=_("note sheet"),
)
name = models.CharField(
max_length=255,
verbose_name=_("name"),
)
content = models.ManyToManyField(
Food,
verbose_name=_("content"),
)
price = models.IntegerField(
verbose_name=_("price"),
)
available = models.BooleanField(
default=True,
verbose_name=_("available"),
help_text=_("If set to false, this option won't be offered (in case of out of stock)"),
)
def __str__(self):
return _("meal").capitalize() + " " + self.name
class Meta:
verbose_name = _("meal")
verbose_name_plural = _("meals")
class Order(models.Model):
sheet = models.ForeignKey(
Sheet,
on_delete=models.PROTECT,
verbose_name=_("note sheet"),
)
note = models.ForeignKey(
Note,
on_delete=models.PROTECT,
verbose_name=_("note"),
)
date = models.DateTimeField(
verbose_name=_("date"),
auto_now_add=True,
)
class Meta:
verbose_name = _("order")
verbose_name_plural = _("orders")
class OrderedMeal(models.Model):
order = models.ForeignKey(
Order,
on_delete=models.PROTECT,
verbose_name=_("order"),
)
meal = models.ForeignKey(
Meal,
on_delete=models.PROTECT,
verbose_name=_("meal"),
)
gift = models.IntegerField(
verbose_name=_("gift"),
)
class Meta:
verbose_name = _("ordered meal")
verbose_name_plural = _("ordered meals")
class OrderedFood(models.Model):
order = models.ForeignKey(
Order,
on_delete=models.PROTECT,
verbose_name=_("order"),
)
meal = models.ForeignKey(
OrderedMeal,
on_delete=models.SET_NULL,
null=True,
default=None,
verbose_name=_("ordered meal"),
)
food = models.ForeignKey(
Food,
on_delete=models.PROTECT,
verbose_name=_("food"),
)
options = models.ManyToManyField(
FoodOption,
blank=True,
verbose_name=_("options"),
)
remark = models.TextField(
blank=True,
default="",
verbose_name=_("remark"),
)
priority = models.CharField(
max_length=64,
blank=True,
default="",
verbose_name=_("priority request"),
)
gift = models.IntegerField(
verbose_name=_("gift"),
)
number = models.IntegerField(
verbose_name=_("number"),
help_text=_("How many times the user ordered this."),
)
status = models.CharField(
max_length=8,
choices=[
('QUEUED', _("queued")),
('READY', _("ready")),
('SERVED', _("served")),
('CANCELED', _("canceled")),
],
default='QUEUED',
verbose_name=_("status"),
)
served_date = models.DateTimeField(
null=True,
default=None,
verbose_name=_("served date")
)
class Meta:
verbose_name = _("ordered food")
verbose_name_plural = _("ordered food")
class SheetOrderTransaction(Transaction):
ordered_food = models.ForeignKey(
OrderedFood,
on_delete=models.PROTECT,
verbose_name=_("ordered food"),
)
@property
def type(self):
return _("note sheet")
@property
def get_price(self):
if self.ordered_food.meal:
return self.ordered_food.meal.meal.price + self.ordered_food.meal.gift + sum(
sum(opt.extra_cost for opt in ordered_food.options.all())
for ordered_food in self.ordered_food.meal.orderedfood_set.exclude(status='CANCELED').all())
elif self.ordered_food.status == 'CANCELED':
return 0
else:
return self.ordered_food.food.price + self.ordered_food.gift \
+ sum(opt.extra_cost for opt in self.ordered_food.options.all())
class Meta:
verbose_name = _("sheet order transaction")
verbose_name_plural = _("sheet order transactions")

View File

@ -1,22 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import django_tables2 as tables
from django.urls import reverse_lazy
from sheets.models import Sheet
class SheetTable(tables.Table):
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
model = Sheet
template_name = 'django_tables2/bootstrap4.html'
fields = ('name', 'date', )
row_attrs = {
'class': 'table-row',
'id': lambda record: "row-" + str(record.pk),
'data-href': lambda record: reverse_lazy('sheets:sheet_detail', args=(record.pk,))
}

View File

@ -1,86 +0,0 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{# The next part concerns the option formset #}
{# Generate some hidden fields that manage the number of options, and make easier the parsing #}
{{ formset.management_form }}
<table class="table table-condensed table-striped">
{# Fill initial data #}
{% for form in formset %}
{% if forloop.first %}
<thead>
<tr>
<th>{{ form.name.label }}<span class="asteriskField">*</span></th>
<th>{{ form.extra_cost.label }}<span class="asteriskField">*</span></th>
<th>{{ form.available.label }}<span class="asteriskField">*</span></th>
</tr>
</thead>
<tbody id="form_body">
{% endif %}
<tr class="row-formset">
<td>{{ form.name }}</td>
<td>{{ form.extra_cost }}</td>
<td>{{ form.available }}</td>
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
{{ form.food }}
{{ form.id }}
</tr>
{% endfor %}
</tbody>
</table>
{# Display buttons to add and remove options #}
<div class="card-body">
<button type="button" id="add_more" class="btn btn-success">{% trans "Add option" %}</button>
</div>
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form>
</div>
</div>
{# Hidden div that store an empty product form, to be copied into new forms #}
<div id="empty_form" style="display: none;">
<table class='no_error'>
<tbody id="for_real">
<tr class="row-formset">
<td>{{ formset.empty_form.name }}</td>
<td>{{ formset.empty_form.extra_cost }} </td>
<td>{{ formset.empty_form.available }}</td>
{{ formset.empty_form.food }}
{{ formset.empty_form.id }}
</tr>
</tbody>
</table>
</div>
{% endblock %}
{% block extrajavascript %}
<script>
/* script that handles add and remove lines */
IDS = {};
$("#id_foodoption_set-TOTAL_FORMS").val($(".row-formset").length - 1);
$('#add_more').click(function () {
let form_idx = $('#id_foodoption_set-TOTAL_FORMS').val();
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
$('#id_foodoption_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
$('#id_foodoption_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
});
</script>
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -1,87 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% load pretty_money %}
{% block content %}
<div class="card">
<div class="card-header text-center">
<h1>{{ sheet.name }}</h1>
</div>
<div class="card-body">
<div class="alert alert-secondary">
<div class="row">
<div class="col-sm-11">
{{ sheet.description }}
</div>
{% if can_change_sheet %}
<div class="col-sm-1">
<a class="badge badge-primary" href="{% url 'sheets:sheet_update' pk=sheet.pk %}">
<i class="fa fa-edit"></i>
{% trans "Edit" %}
</a>
</div>
{% endif %}
</div>
</div>
<h3>{% trans "menu"|capfirst %} :</h3>
<ul>
{% for meal in sheet.meal_set.all %}
<li{% if not meal.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
{{ meal }} ({{ meal.price|pretty_money }})
{% if can_change_sheet %}
<a href="{% url 'sheets:meal_update' pk=meal.pk %}" class="badge badge-primary">
<i class="fa fa-edit"></i>
{% trans "Edit" %}
</a>
{% endif %}
</li>
{% endfor %}
<hr>
{% for food in sheet.food_set.all %}
<li{% if not food.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
{{ food }} ({{ food.price|pretty_money }})
<a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="badge badge-primary">
<i class="fa fa-list"></i>
{% trans "Waiting list" %}
</a>
{% if can_change_sheet %}
<a href="{% url 'sheets:food_update' pk=food.pk %}" class="badge badge-primary">
<i class="fa fa-edit"></i>
{% trans "Edit" %}
</a>
{% endif %}
{% if food.foodoption_set.all %}
<ul>
{% for option in food.foodoption_set.all %}
<li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
{{ option }}{% if option.extra_cost %} ({{ option.extra_cost|pretty_money }}){% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% empty %}
<div class="alert alert-warning">
{% trans "The menu is empty for now." %}
</div>
{% endfor %}
</ul>
<div class="text-center">
{% if can_add_food %}
<a href="{% url 'sheets:food_create' pk=sheet.pk %}" class="btn btn-primary">{% trans "Add new food" %}</a>
{% endif %}
{% if can_add_meal %}
<a href="{% url 'sheets:meal_create' pk=sheet.pk %}" class="btn btn-primary">{% trans "Add new meal" %}</a>
{% endif %}
</div>
</div>
<div class="card-footer text-center">
<a href="{% url 'sheets:sheet_order' pk=sheet.pk %}" class="btn btn-success">
{% trans "Order now" %}
</a>
</div>
</div>
{% endblock %}

View File

@ -1,21 +0,0 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -1,74 +0,0 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block content %}
<div class="row justify-content-center mb-4">
<div class="col-md-10 text-center">
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved()" id="search_field"/>
{% if can_create_sheet %}
<hr>
<a class="btn btn-primary text-center my-4" href="{% url 'sheets:sheet_create' %}">{% trans "Create a sheet" %}</a>
{% endif %}
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card card-border shadow">
<div class="card-header text-center">
<h5> {% trans "Note sheet listing" %}</h5>
</div>
<div class="card-body px-0 py-0" id="sheets_table">
{% render_table table %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script type="text/javascript">
function getInfo() {
var asked = $("#search_field").val();
/* on ne fait la requête que si on a au moins un caractère pour chercher */
var sel = $(".table-row");
if (asked.length >= 1) {
$.getJSON("/api/sheets/sheet/?format=json&search="+asked, function(buttons){
let selected_id = buttons.results.map((a => "#row-"+a.id));
if (selected_id.length)
$(".table-row,"+selected_id.join()).show();
$(".table-row").not(selected_id.join()).hide();
});
}else{
// show everything
$('table tr').show();
}
}
var timer;
var timer_on;
/* Fontion appelée quand le texte change (délenche le timer) */
function search_field_moved(secondfield) {
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
clearTimeout(timer);
timer = setTimeout("getInfo(" + secondfield + ")", 300);
}
else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
timer = setTimeout("getInfo(" + secondfield + ")", 300);
timer_on = true;
}
}
// clickable row
$(document).ready(function($) {
$(".table-row").click(function() {
window.document.location = $(this).data("href");
});
});
</script>
{% endblock %}

View File

@ -1,26 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path
from sheets.views import FoodCreateView, FoodUpdateView, MealCreateView, MealUpdateView, OrderView, \
SheetCreateView, SheetDetailView, SheetListView, SheetUpdateView, WaitingListDetailView, WaitingListView
app_name = 'sheets'
urlpatterns = [
path('list/', SheetListView.as_view(), name="sheet_list"),
path('create/', SheetCreateView.as_view(), name="sheet_create"),
path('update/<int:pk>/', SheetUpdateView.as_view(), name="sheet_update"),
path('detail/<int:pk>/', SheetDetailView.as_view(), name="sheet_detail"),
path('food/create/<int:pk>/', FoodCreateView.as_view(), name="food_create"),
path('food/<int:pk>/update/', FoodUpdateView.as_view(), name="food_update"),
path('meal/create/<int:pk>/', MealCreateView.as_view(), name="meal_create"),
path('meal/<int:pk>/update/', MealUpdateView.as_view(), name="meal_update"),
path('order/<int:pk>/', OrderView.as_view(), name="sheet_order"),
path('waiting-list/<int:pk>/', WaitingListView.as_view(), name="waiting_list"),
path('waiting-list/<int:pk>/queued/', WaitingListDetailView.as_view(), name="queued_list"),
path('waiting-list/<int:pk>/ready/', WaitingListDetailView.as_view(), name="ready_list"),
path('waiting-list/<int:pk>/served/', WaitingListDetailView.as_view(), name="served_list"),
path('waiting-list/<int:pk>/canceled/', WaitingListDetailView.as_view(), name="canceled_list"),
]

View File

@ -1,444 +0,0 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta
from crispy_forms.bootstrap import Accordion, AccordionGroup, FormActions
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Fieldset, Submit, Row, Field
from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.forms import Form
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, UpdateView, FormView
from django_tables2 import SingleTableView
from note.models import Alias, Note
from note.templatetags.pretty_money import pretty_money
from note_kfet.inputs import AmountInput, Autocomplete
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import FoodForm, MealForm, SheetForm, FoodOptionsFormSet, FoodOptionFormSetHelper
from .models import Sheet, Food, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
from .tables import SheetTable
class SheetListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
model = Sheet
table_class = SheetTable
ordering = '-date'
extra_context = {"title": _("Search note sheet")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["can_create_sheet"] = PermissionBackend.check_perm(self.request, "sheets.add_sheet", Sheet(
name="Test",
date=timezone.now(),
description="Test sheet",
))
return context
class SheetCreateView(ProtectQuerysetMixin, ProtectedCreateView):
model = Sheet
form_class = SheetForm
extra_context = {"title": _("Create note sheet")}
def get_sample_object(self):
return Sheet(
name="Test",
date=timezone.now(),
description="Test",
)
class SheetUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = Sheet
form_class = SheetForm
extra_context = {"title": _("Update note sheet")}
class SheetDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
model = Sheet
def get_context_data(self, **kwargs):
context = super().get_context_data()
context['can_change_sheet'] = PermissionBackend.check_perm(self.request, 'sheets.change_sheet', self.object)
context['can_add_meal'] = PermissionBackend.check_perm(self.request,
'sheets.add_meal',
Meal(sheet=self.object, name="Test", price=500))
context['can_add_food'] = PermissionBackend.check_perm(self.request,
'sheets.add_food',
Food(sheet=self.object, name="Test", price=500))
return context
class FoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
model = Food
form_class = FoodForm
extra_context = {"title": _("Create new food")}
def get_sample_object(self):
return Food(
sheet_id=self.kwargs['pk'],
name="Test",
price=500,
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = context['form']
form.helper = FormHelper()
# Remove form tag on the generation of the form in the template (already present on the template)
form.helper.form_tag = False
# The formset handles the set of the products
form_set = FoodOptionsFormSet(instance=form.instance)
context['formset'] = form_set
context['helper'] = FoodOptionFormSetHelper()
return context
def form_valid(self, form):
form.instance.sheet_id = self.kwargs['pk']
# For each product, we save it
formset = FoodOptionsFormSet(self.request.POST, instance=form.instance)
if formset.is_valid():
for f in formset:
# We don't save the product if the designation is not entered, ie. if the line is empty
if f.is_valid() and f.instance.name:
f.save()
f.instance.save()
else:
f.instance = None
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],))
class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = Food
form_class = FoodForm
extra_context = {"title": _("Update food")}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
form = context['form']
form.helper = FormHelper()
# Remove form tag on the generation of the form in the template (already present on the template)
form.helper.form_tag = False
# The formset handles the set of the products
form_set = FoodOptionsFormSet(instance=form.instance)
context['formset'] = form_set
context['helper'] = FoodOptionFormSetHelper()
return context
def form_valid(self, form):
# For each product, we save it
formset = FoodOptionsFormSet(self.request.POST, instance=form.instance)
if formset.is_valid():
for f in formset:
# We don't save the product if the designation is not entered, ie. if the line is empty
if f.is_valid() and f.instance.name:
f.save()
f.instance.save()
else:
f.instance = None
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
class MealCreateView(ProtectQuerysetMixin, ProtectedCreateView):
model = Meal
form_class = MealForm
extra_context = {"title": _("Create new meal")}
def get_sample_object(self):
return Meal(
sheet_id=self.kwargs['pk'],
name="Test",
price=500,
)
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['content'].queryset = form.fields['content'].queryset.filter(sheet_id=self.kwargs['pk'])
return form
def form_valid(self, form):
form.instance.sheet_id = self.kwargs['pk']
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
class MealUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
model = Meal
form_class = MealForm
extra_context = {"title": _("Update meal")}
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['content'].queryset = form.fields['content'].queryset.filter(sheet=self.object.sheet)
return form
def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
class OrderView(LoginRequiredMixin, FormView, DetailView):
model = Sheet
template_name = 'sheets/order.html'
extra_context = {'title': _("Order now")}
def get_form(self, form_class=None):
form = Form()
form.helper = FormHelper()
layout_fields = []
self.object = self.get_object()
form.fields['note'] = forms.ModelChoiceField(
queryset=Note.objects.filter(PermissionBackend.filter_queryset(self.request, Note, 'note.view_note')),
label=_("Orderer"),
initial=self.request.user.note,
widget=Autocomplete(
model=Note,
attrs={
"api_url": "/api/note/note/",
'placeholder': _("Who orders")
},
),
)
layout_fields.append(Field('note', css_class='is-valid'))
for meal in self.object.meal_set.filter(available=True).all():
form.fields[f'meal_{meal.id}_quantity'] = forms.IntegerField(
label=_("Quantity"),
initial=0,
)
form.fields[f'meal_{meal.id}_gift'] = forms.IntegerField(
label=_("gift").capitalize(),
initial=0,
widget=AmountInput(),
help_text=_("Be careful: this gift will be multiplied for each order."),
)
form.fields[f'meal_{meal.id}_remark'] = forms.CharField(
max_length=255,
required=False,
label=_("remark").capitalize(),
help_text=_("Allergies,…"),
)
form.fields[f'meal_{meal.id}_priority'] = forms.CharField(
max_length=64,
required=False,
label=_("priority request").capitalize(),
help_text=_("Lesson at 13h30,…"),
)
ag = AccordionGroup(f"{meal} ({pretty_money(meal.price)})",
Row(Field(f'meal_{meal.id}_quantity', wrapper_class='col-sm-9'),
Field(f'meal_{meal.id}_gift', wrapper_class='col-sm-3')),
Row(Field(f'meal_{meal.id}_remark', wrapper_class='col-sm-9'),
Field(f'meal_{meal.id}_priority', wrapper_class='col-sm-3')))
for food in meal.content.filter(available=True).all():
if food.foodoption_set.count():
options_fieldset = Fieldset(_("Options for ") + str(food))
options_row = Row(css_class='ml-0')
for option in food.foodoption_set.filter(available=True).all():
form.fields[f'meal_{meal.id}_food_{food.id}_option_{option.id}'] = forms.BooleanField(
label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}",
required=False,
)
options_row.fields.append(
Field(f'meal_{meal.id}_food_{food.id}_option_{option.id}', wrapper_class='col-sm-12'))
options_fieldset.fields.append(options_row)
ag.fields.append(options_fieldset)
layout_fields.append(ag)
for food in self.object.food_set.filter(available=True).all():
form.fields[f'food_{food.id}_quantity'] = forms.IntegerField(
label=_("quantity").capitalize(),
initial=0,
)
form.fields[f'food_{food.id}_gift'] = forms.IntegerField(
label=_("gift").capitalize(),
initial=0,
widget=AmountInput(),
help_text=_("Be careful: this gift will be multiplied for each order."),
)
form.fields[f'food_{food.id}_remark'] = forms.CharField(
max_length=255,
required=False,
label=_("remark").capitalize(),
help_text=_("Allergies,…"),
)
form.fields[f'food_{food.id}_priority'] = forms.CharField(
max_length=255,
required=False,
label=_("priority request").capitalize(),
help_text=_("Lesson at 13h30,…"),
)
ag = AccordionGroup(f"{food} ({pretty_money(food.price)})",
Row(Field(f'food_{food.id}_quantity', wrapper_class='col-sm-9'),
Field(f'food_{food.id}_gift', wrapper_class='col-sm-3')),
Row(Field(f'food_{food.id}_remark', wrapper_class='col-sm-9'),
Field(f'food_{food.id}_priority', wrapper_class='col-sm-3')))
if food.foodoption_set.count():
options_fieldset = Fieldset(_("Options"))
options_row = Row(css_class='ml-0')
for option in food.foodoption_set.all():
form.fields[f'food_{food.id}_option_{option.id}'] = forms.BooleanField(
label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}",
required=False,
)
options_row.fields.append(Field(f'food_{food.id}_option_{option.id}', wrapper_class='col-sm-12'))
options_fieldset.fields.append(options_row)
ag.fields.append(options_fieldset)
layout_fields.append(ag)
layout_fields.append(FormActions(Submit('submit', _("Order now"))))
form.helper.layout = Accordion(*layout_fields)
if self.request.method in ['PUT', 'POST']:
form.data = self.request.POST
form.files = self.request.FILES
form.is_bound = not form.data or not form.files
return form
def form_valid(self, form):
data = form.cleaned_data
sheet = self.get_object()
with transaction.atomic():
order = Order.objects.create(sheet_id=self.kwargs['pk'], note=data['note'])
total_quantity = 0
for meal in sheet.meal_set.filter(available=True).all():
quantity = data[f'meal_{meal.id}_quantity']
if not quantity:
continue
total_quantity += quantity
gift = data[f'meal_{meal.id}_gift']
remark = data[f'meal_{meal.id}_remark'] or ''
priority = data[f'meal_{meal.id}_priority'] or ''
ordered_meal = OrderedMeal.objects.create(order=order, meal=meal, gift=gift)
for ignored in range(quantity):
for food in meal.content.filter(available=True).all():
n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'],
order__note=order.note,
order__date__gte=timezone.now() - timedelta(hours=6),
food=food).exclude(status='CANCELED').count()
of = OrderedFood.objects.create(order=order, meal=ordered_meal, food=food,
remark=remark, priority=priority, number=n + 1, gift=0)
for option in food.foodoption_set.filter(available=True).all():
if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']:
of.options.add(option)
of.save()
first_food = ordered_meal.orderedfood_set.first()
tr = SheetOrderTransaction(source_id=order.note_id, destination=first_food.food.club.note,
source_alias=str(order.note), destination_alias=first_food.food.club.name,
quantity=quantity, ordered_food=first_food,
reason=f"{meal.name} - {sheet.name}")
tr.amount = tr.get_price / tr.quantity
tr.save()
for food in sheet.food_set.filter(available=True).all():
quantity = data[f'food_{food.id}_quantity']
if not quantity:
continue
total_quantity += quantity
gift = data[f'food_{meal.id}_gift']
remark = data[f'food_{meal.id}_remark'] or ''
priority = data[f'food_{meal.id}_priority'] or ''
for ignored in range(quantity):
n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'],
order__note=order.note,
order__date__gte=timezone.now() - timedelta(hours=6),
food=food).exclude(state='CANCELED').count()
of = OrderedFood.objects.create(order=order, food=food, gift=gift,
remark=remark, priority=priority, number=n + 1)
for option in food.foodoption_set.filter(available=True).all():
if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']:
of.options.add(option)
of.options.save()
tr = SheetOrderTransaction(source_id=order.note_id, destination_id=first_food.club.note,
source_alias=str(order.note), destination_alias=first_food.club.name,
quantity=quantity, ordered_food=of,
reason=f"{food.name} - {sheet.name}")
tr.amount = tr.get_price / tr.quantity
tr.save()
if total_quantity == 0:
form.add_error(None, _("You didn't select anything."))
transaction.rollback()
return self.form_invalid(form)
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],))
class WaitingListView(ProtectQuerysetMixin, DetailView):
model = Food
template_name = 'sheets/waiting_list.html'
extra_context = {'title': _("Waiting list")}
def get_context_data(self, **kwargs):
content = super().get_context_data(**kwargs)
content['queue'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status='QUEUED')\
.order_by('-priority', 'number', 'order__date').all()
content['ready'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status='READY')\
.order_by('served_date').all()
return content
class WaitingListDetailView(ProtectQuerysetMixin, DetailView):
model = Food
template_name = 'sheets/waiting_list_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
list_type = 'CANCELED' if 'canceled' in self.request.path else \
'SERVED' if 'served' in self.request.path else \
'READY' if 'ready' in self.request.path else 'QUEUED'
context['list_type'] = list_type
context['orders'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status=list_type)\
.order_by('served_date', '-priority', 'number', 'order__date').all()
context['title'] = self.object.name + " - " + _(list_type.lower()).capitalize()
return context

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2023-01-29 22:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0004_auto_20211005_1544'),
]
operations = [
migrations.AlterField(
model_name='invoice',
name='bde',
field=models.CharField(choices=[('TotalistSpies', 'Tota[list]Spies'), ('Saperlistpopette', 'Saper[list]popette'), ('Finalist', 'Fina[list]'), ('Listorique', '[List]orique'), ('Satellist', 'Satel[list]'), ('Monopolist', 'Monopo[list]'), ('Kataclist', 'Katac[list]')], default='TotalistSpies', max_length=32, verbose_name='BDE'),
),
]

View File

@ -28,8 +28,9 @@ class Invoice(models.Model):
bde = models.CharField(
max_length=32,
default='Saperlistpopette',
default='TotalistSpies',
choices=(
('TotalistSpies', 'Tota[list]Spies'),
('Saperlistpopette', 'Saper[list]popette'),
('Finalist', 'Fina[list]'),
('Listorique', '[List]orique'),
@ -95,7 +96,7 @@ class Invoice(models.Model):
products = self.products.all()
self.place = "Gif-sur-Yvette"
self.my_name = "BDE ENS Cachan"
self.my_name = "BDE ENS Paris Saclay"
self.my_address_street = "4 avenue des Sciences"
self.my_city = "91190 Gif-sur-Yvette"
self.bank_code = 30003
@ -310,8 +311,8 @@ class SogeCredit(models.Model):
amount = sum(transaction.total for transaction in self.transactions.all())
if 'wei' in settings.INSTALLED_APPS:
from wei.models import WEIMembership
if not WEIMembership.objects.filter(club__weiclub__year=datetime.date.today().year, user=self.user)\
.exists():
if not WEIMembership.objects\
.filter(club__weiclub__year=self.credit_transaction.created_at.year, user=self.user).exists():
# 80 € for people that don't go to WEI
amount += 8000
return amount
@ -329,17 +330,18 @@ class SogeCredit(models.Model):
bde_qs = Membership.objects.filter(user=self.user, club=bde, date_start__gte=bde.membership_start)
kfet_qs = Membership.objects.filter(user=self.user, club=kfet, date_start__gte=kfet.membership_start)
if bde_qs.exists():
m = bde_qs.get()
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
if m.transaction not in self.transactions.all():
self.transactions.add(m.transaction)
if kfet_qs.exists():
m = kfet_qs.get()
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
if m.transaction not in self.transactions.all():
self.transactions.add(m.transaction)
## Soge do not pay BDE and kfet memberships this year (2022-2023)
# if bde_qs.exists():
# m = bde_qs.get()
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
# if m.transaction not in self.transactions.all():
# self.transactions.add(m.transaction)
#
# if kfet_qs.exists():
# m = kfet_qs.get()
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
# if m.transaction not in self.transactions.all():
# self.transactions.add(m.transaction)
if 'wei' in settings.INSTALLED_APPS:
from wei.models import WEIClub

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -14,14 +14,17 @@ from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInf
from ...models import WEIMembership
WORDS = [
'13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant',
'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill',
'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial',
'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno',
'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit',
'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic',
'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane',
'ABBA', 'After', 'Alcoolique anonyme', 'Ambiance festive', 'Années 2000', 'Apéro', 'Art',
'Baby foot billard biere pong', 'BBQ', 'Before', 'Bière pong', 'Bon enfant', 'Calme', 'Canapé',
'Chanson paillarde', 'Chanson populaire', 'Chartreuse', 'Cheerleader', 'Chill', 'Choré',
'Cinéma', 'Cocktail', 'Comédie musicle', 'Commercial', 'Copaing', 'Danse', 'Dancefloor',
'Electro', 'Fanfare', 'Gin tonic', 'Inclusif', 'Jazz', "Jeux d'alcool", 'Jeux de carte',
'Jeux de rôle', 'Jeux de société', 'JUL', 'Jus de fruit', 'Kfet', 'Kleptomanie assurée',
'LGBTQ+', 'Livre', 'Morning beer', 'Musique', 'NAPS', 'Paillettes', 'Pastis', 'Paté Hénaff',
'Peluche', 'Pena baiona', "Peu d'alcool", 'Pilier de bar', 'PMU', 'Poulpe', 'Punch', 'Rap',
'Réveil', 'Rock', 'Rugby', 'Sandwich', 'Serge', 'Shot', 'Sociable', 'Spectacle', 'Techno',
'Techno house', 'Thérapie Taxi', 'Tradition kchanaises', 'Troisième mi-temps', 'Turn up',
'Vodka', 'Vodka pomme', 'Volley', 'Vomi stratégique'
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.26 on 2022-09-04 21:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0003_bus_size'),
]
operations = [
migrations.AlterField(
model_name='weiclub',
name='year',
field=models.PositiveIntegerField(default=2022, unique=True, verbose_name='year'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.28 on 2023-01-28 17:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0004_auto_20220904_2325'),
]
operations = [
migrations.AlterField(
model_name='weiclub',
name='year',
field=models.PositiveIntegerField(default=2023, unique=True, verbose_name='year'),
),
]

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-18 22:51+0200\n"
"POT-Creation-Date: 2022-04-10 22:34+0200\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -60,7 +60,6 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
#: apps/note/models/transactions.py:46 apps/note/models/transactions.py:301
#: apps/permission/models.py:330
#: apps/registration/templates/registration/future_profile_detail.html:16
#: apps/sheets/models.py:16 apps/sheets/models.py:84 apps/sheets/models.py:121
#: apps/wei/models.py:67 apps/wei/models.py:131 apps/wei/tables.py:282
#: apps/wei/templates/wei/base.html:26
#: apps/wei/templates/wei/weimembership_form.html:14
@ -96,8 +95,7 @@ msgstr "types d'activité"
#: apps/activity/models.py:68
#: apps/activity/templates/activity/includes/activity_info.html:19
#: apps/note/models/transactions.py:81 apps/permission/models.py:110
#: apps/permission/models.py:189 apps/sheets/models.py:25 apps/wei/models.py:78
#: apps/wei/models.py:142
#: apps/permission/models.py:189 apps/wei/models.py:78 apps/wei/models.py:142
msgid "description"
msgstr "description"
@ -145,7 +143,6 @@ msgstr ""
#: apps/activity/models.py:109
#: apps/activity/templates/activity/includes/activity_info.html:25
#: apps/sheets/models.py:20
msgid "start date"
msgstr "date de début"
@ -174,7 +171,7 @@ msgid "entry time"
msgstr "heure d'entrée"
#: apps/activity/models.py:178 apps/note/apps.py:14
#: apps/note/models/notes.py:77 apps/sheets/models.py:157
#: apps/note/models/notes.py:77
msgid "note"
msgstr "note"
@ -337,10 +334,7 @@ msgstr "Entrée effectuée !"
#: apps/member/templates/member/add_members.html:46
#: apps/member/templates/member/club_form.html:16
#: apps/note/templates/note/transactiontemplate_form.html:18
#: apps/sheets/templates/sheets/food_form.html:51
#: apps/sheets/templates/sheets/meal_form.html:17
#: apps/sheets/templates/sheets/sheet_form.html:17 apps/treasury/forms.py:89
#: apps/treasury/forms.py:143
#: apps/treasury/forms.py:89 apps/treasury/forms.py:143
#: apps/treasury/templates/treasury/invoice_form.html:74
#: apps/wei/templates/wei/bus_form.html:17
#: apps/wei/templates/wei/busteam_form.html:17
@ -1476,7 +1470,7 @@ msgstr "modèles de transaction"
msgid "used alias"
msgstr "alias utilisé"
#: apps/note/models/transactions.py:136 apps/sheets/views.py:277
#: apps/note/models/transactions.py:136
msgid "quantity"
msgstr "quantité"
@ -1598,10 +1592,8 @@ msgid "Delete"
msgstr "Supprimer"
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
#: apps/sheets/templates/sheets/sheet_detail.html:21
#: apps/sheets/templates/sheets/sheet_detail.html:36
#: apps/sheets/templates/sheets/sheet_detail.html:52 apps/wei/tables.py:49
#: apps/wei/tables.py:50 apps/wei/templates/wei/base.html:89
#: apps/wei/tables.py:49 apps/wei/tables.py:50
#: apps/wei/templates/wei/base.html:89
#: apps/wei/templates/wei/bus_detail.html:20
#: apps/wei/templates/wei/busteam_detail.html:20
#: apps/wei/templates/wei/busteam_detail.html:40
@ -1754,98 +1746,6 @@ msgstr "Bouton affiché"
msgid "An error occured"
msgstr "Une erreur s'est produite"
#: apps/note/templates/sheets/waiting_list.html:14 apps/sheets/models.py:244
msgid "queued"
msgstr "en attente"
#: apps/note/templates/sheets/waiting_list.html:27
#: apps/note/templates/sheets/waiting_list_detail.html:67
msgid "There is no queued order."
msgstr "Il n'y a pas de commande en attente."
#: apps/note/templates/sheets/waiting_list.html:35 apps/sheets/models.py:245
msgid "ready"
msgstr "prêt"
#: apps/note/templates/sheets/waiting_list.html:43
msgid "There is no ready order."
msgstr "Il n'y a pas de commande prête."
#: apps/note/templates/sheets/waiting_list.html:53
#: apps/note/templates/sheets/waiting_list_detail.html:75
msgid "Other waiting lists:"
msgstr "Autres listes d'attente :"
#: apps/note/templates/sheets/waiting_list.html:66
#: apps/note/templates/sheets/waiting_list_detail.html:93
msgid "Queued orders"
msgstr "Commandes en attente"
#: apps/note/templates/sheets/waiting_list.html:69
#: apps/note/templates/sheets/waiting_list_detail.html:98
msgid "Ready orders"
msgstr "Commandes prêtes"
#: apps/note/templates/sheets/waiting_list.html:72
#: apps/note/templates/sheets/waiting_list_detail.html:115
msgid "Back to note sheet detail"
msgstr "Retour aux détails de la feuille de note"
#: apps/note/templates/sheets/waiting_list_detail.html:18
#: apps/sheets/models.py:161
msgid "date"
msgstr "date"
#: apps/note/templates/sheets/waiting_list_detail.html:22
msgid "order number"
msgstr "nombre de commandes"
#: apps/note/templates/sheets/waiting_list_detail.html:27
#: apps/sheets/models.py:229 apps/sheets/views.py:249 apps/sheets/views.py:295
msgid "priority request"
msgstr "demande de priorité"
#: apps/note/templates/sheets/waiting_list_detail.html:32
#: apps/sheets/models.py:222 apps/sheets/views.py:243 apps/sheets/views.py:289
msgid "remark"
msgstr "remarques"
#: apps/note/templates/sheets/waiting_list_detail.html:37
#: apps/sheets/models.py:216
msgid "options"
msgstr "options"
#: apps/note/templates/sheets/waiting_list_detail.html:45
msgid "Mark as ready"
msgstr "Marquer comme prêt"
#: apps/note/templates/sheets/waiting_list_detail.html:50
msgid "Mark as served"
msgstr "Marquer comme servi"
#: apps/note/templates/sheets/waiting_list_detail.html:55
msgid "Re-queue"
msgstr "Remettre en attente"
#: apps/note/templates/sheets/waiting_list_detail.html:60
#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17
#: note_kfet/templates/oauth2_provider/authorize.html:28
msgid "Cancel"
msgstr "Annuler"
#: apps/note/templates/sheets/waiting_list_detail.html:103
msgid "Served orders"
msgstr "Commandes servies"
#: apps/note/templates/sheets/waiting_list_detail.html:108
msgid "Canceled orders"
msgstr "Commandes annulées"
#: apps/note/templates/sheets/waiting_list_detail.html:112
#: apps/sheets/templates/sheets/sheet_detail.html:47 apps/sheets/views.py:416
msgid "Waiting list"
msgstr "Liste d'attente"
#: apps/note/views.py:36
msgid "Transfer money"
msgstr "Transférer de l'argent"
@ -2267,237 +2167,6 @@ msgstr ""
msgid "Invalidate pre-registration"
msgstr "Invalider l'inscription"
#: apps/sheets/apps.py:10 apps/sheets/models.py:42
msgid "note sheets"
msgstr "feuilles de notes"
#: apps/sheets/models.py:30
msgid "visible"
msgstr "visible"
#: apps/sheets/models.py:31
msgid "the note sheet will be private until this field is checked."
msgstr "la feuille de note restera privée tant que ce champ n'est pas coché."
#: apps/sheets/models.py:41 apps/sheets/models.py:54 apps/sheets/models.py:116
#: apps/sheets/models.py:151 apps/sheets/models.py:273
msgid "note sheet"
msgstr "feuille de note"
#: apps/sheets/models.py:48 apps/sheets/models.py:77 apps/sheets/models.py:78
#: apps/sheets/models.py:90 apps/sheets/models.py:210
msgid "food"
msgstr "nourriture"
#: apps/sheets/models.py:58 apps/sheets/models.py:130
msgid "price"
msgstr "prix"
#: apps/sheets/models.py:64
msgid "destination club"
msgstr "club de destination"
#: apps/sheets/models.py:69 apps/sheets/models.py:100 apps/sheets/models.py:135
msgid "available"
msgstr "disponible"
#: apps/sheets/models.py:70 apps/sheets/models.py:101 apps/sheets/models.py:136
msgid "If set to false, this option won't be offered (in case of out of stock)"
msgstr ""
"Si mis à faux, cette option ne sera pas présentée (en cas de rupture de "
"stock)"
#: apps/sheets/models.py:95
msgid "extra cost"
msgstr "surcoût"
#: apps/sheets/models.py:108
msgid "food option"
msgstr "option de nourriture"
#: apps/sheets/models.py:109
msgid "food options"
msgstr "options de nourriture"
#: apps/sheets/models.py:126
msgid "content"
msgstr "contenu"
#: apps/sheets/models.py:140 apps/sheets/models.py:143
#: apps/sheets/models.py:180
msgid "meal"
msgstr "menu"
#: apps/sheets/models.py:144
msgid "meals"
msgstr "menus"
#: apps/sheets/models.py:166 apps/sheets/models.py:174
#: apps/sheets/models.py:196
msgid "order"
msgstr "commande"
#: apps/sheets/models.py:167
msgid "orders"
msgstr "commandes"
#: apps/sheets/models.py:184 apps/sheets/models.py:233 apps/sheets/views.py:235
#: apps/sheets/views.py:281
msgid "gift"
msgstr "don"
#: apps/sheets/models.py:188 apps/sheets/models.py:204
msgid "ordered meal"
msgstr "menu commandé"
#: apps/sheets/models.py:189
msgid "ordered meals"
msgstr "menus commandés"
#: apps/sheets/models.py:237
msgid "number"
msgstr "numéro"
#: apps/sheets/models.py:238
msgid "How many times the user ordered this."
msgstr "Combien de fois cet⋅te utilisateur⋅rice a commandé ceci."
#: apps/sheets/models.py:246
msgid "served"
msgstr "servi"
#: apps/sheets/models.py:247
msgid "canceled"
msgstr "annulé"
#: apps/sheets/models.py:250
msgid "status"
msgstr "statut"
#: apps/sheets/models.py:256
msgid "served date"
msgstr "date de service"
#: apps/sheets/models.py:260 apps/sheets/models.py:261
#: apps/sheets/models.py:268
msgid "ordered food"
msgstr "nourriture commandée"
#: apps/sheets/models.py:288
msgid "sheet order transaction"
msgstr "transaction de commande sur feuille de note"
#: apps/sheets/models.py:289
msgid "sheet order transactions"
msgstr "transactions de commande sur feuille de note"
#: apps/sheets/templates/sheets/food_form.html:48
msgid "Add option"
msgstr "Ajouter une option"
#: apps/sheets/templates/sheets/sheet_detail.html:28
msgid "menu"
msgstr "menu"
#: apps/sheets/templates/sheets/sheet_detail.html:31
#: apps/sheets/templates/sheets/sheet_detail.html:43
#: apps/sheets/templates/sheets/sheet_detail.html:58
msgid "This product is unavailable."
msgstr "Ce produit est indisponible."
#: apps/sheets/templates/sheets/sheet_detail.html:67
msgid "The menu is empty for now."
msgstr "Le menu est vide pour le moment."
#: apps/sheets/templates/sheets/sheet_detail.html:74
msgid "Add new food"
msgstr "Ajouter un plat"
#: apps/sheets/templates/sheets/sheet_detail.html:77
msgid "Add new meal"
msgstr "Ajouter un menu"
#: apps/sheets/templates/sheets/sheet_detail.html:83 apps/sheets/views.py:206
#: apps/sheets/views.py:319
msgid "Order now"
msgstr "Commander maintenant"
#: apps/sheets/templates/sheets/sheet_list.html:14
msgid "Create a sheet"
msgstr "Créer une feuille de note"
#: apps/sheets/templates/sheets/sheet_list.html:22
msgid "Note sheet listing"
msgstr "Liste des feuilles de notes"
#: apps/sheets/views.py:33
msgid "Search note sheet"
msgstr "Chercher une feuille de note"
#: apps/sheets/views.py:48
msgid "Create note sheet"
msgstr "Créer une feuille de note"
#: apps/sheets/views.py:61
msgid "Update note sheet"
msgstr "Modifier une feuille de note"
#: apps/sheets/views.py:84
msgid "Create new food"
msgstr "Créer un plat"
#: apps/sheets/views.py:130
msgid "Update food"
msgstr "Modifier un plat"
#: apps/sheets/views.py:167
msgid "Create new meal"
msgstr "Créer un menu"
#: apps/sheets/views.py:192
msgid "Update meal"
msgstr "Modifier un menu"
#: apps/sheets/views.py:217
msgid "Orderer"
msgstr "Commanditaire"
#: apps/sheets/views.py:223
msgid "Who orders"
msgstr "Qui commande"
#: apps/sheets/views.py:231 apps/treasury/models.py:140
msgid "Quantity"
msgstr "Quantité"
#: apps/sheets/views.py:238 apps/sheets/views.py:284
msgid "Be careful: this gift will be multiplied for each order."
msgstr "Attention : ce don sera multiplié pour chaque commande."
#: apps/sheets/views.py:244 apps/sheets/views.py:290
msgid "Allergies,…"
msgstr "Allergies,…"
#: apps/sheets/views.py:250 apps/sheets/views.py:296
msgid "Lesson at 13h30,…"
msgstr "Cours à 13h30,…"
#: apps/sheets/views.py:261
msgid "Options for "
msgstr "Options pour "
#: apps/sheets/views.py:306
msgid "Options"
msgstr "Options"
#: apps/sheets/views.py:403
msgid "You didn't select anything."
msgstr "Vous n'avez rien sélectionné."
#: apps/sheets/views.py:432
msgid "Detailed waiting list"
msgstr "Liste d'attente détaillée"
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:96
msgid "Treasury"
msgstr "Trésorerie"
@ -2584,6 +2253,10 @@ msgstr "Facture n°{id}"
msgid "Designation"
msgstr "Désignation"
#: apps/treasury/models.py:140
msgid "Quantity"
msgstr "Quantité"
#: apps/treasury/models.py:145
msgid "Unit price"
msgstr "Prix unitaire"
@ -3512,19 +3185,19 @@ msgstr "Répartir les 1A dans les bus"
msgid "Attribute bus"
msgstr "Attribuer un bus"
#: note_kfet/settings/base.py:173
#: note_kfet/settings/base.py:172
msgid "German"
msgstr "Allemand"
#: note_kfet/settings/base.py:174
#: note_kfet/settings/base.py:173
msgid "English"
msgstr "Anglais"
#: note_kfet/settings/base.py:175
#: note_kfet/settings/base.py:174
msgid "Spanish"
msgstr "Espagnol"
#: note_kfet/settings/base.py:176
#: note_kfet/settings/base.py:175
msgid "French"
msgstr "Français"
@ -3674,6 +3347,11 @@ msgstr "Il n'y a pas de résultat."
msgid "Are you sure to delete the application"
msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application"
#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17
#: note_kfet/templates/oauth2_provider/authorize.html:28
msgid "Cancel"
msgstr "Annuler"
#: note_kfet/templates/oauth2_provider/application_detail.html:11
msgid "Client id"
msgstr "ID client"
@ -3884,6 +3562,3 @@ msgstr ""
"vous connecter. Vous devez vous rendre à la Kfet et payer les frais "
"d'adhésion. Vous devez également valider votre adresse email en suivant le "
"lien que vous avez reçu."
#~ msgid "Detailed view"
#~ msgstr "Vue détaillée"

View File

@ -75,7 +75,6 @@ INSTALLED_APPS = [
'permission',
'registration',
'scripts',
'sheets',
'treasury',
'wei',
]
@ -253,7 +252,7 @@ REST_FRAMEWORK = {
'rest_framework.authentication.TokenAuthentication',
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'DEFAULT_PAGINATION_CLASS': 'apps.api.pagination.CustomPagination',
'PAGE_SIZE': 20,
}

79
note_kfet/static/css/custom.css Normal file → Executable file
View File

@ -65,7 +65,10 @@ mark {
/* Last BDE colors */
.bg-primary {
background-color: rgb(102, 83, 105) !important;
/* background-color: rgb(18, 67, 4) !important; */
/* MODE VIEUXCON=ON */
/* background-color: rgb(166, 0, 2) !important; */
background-color: rgb(0, 0, 0) !important;
}
html {
@ -80,15 +83,15 @@ body {
.btn-outline-primary:hover,
.btn-outline-primary:not(:disabled):not(.disabled).active,
.btn-outline-primary:not(:disabled):not(.disabled):active {
color: #fff;
background-color: rgb(102, 83, 105);
border-color: rgb(102, 83, 105);
color: rgb(241, 229, 52);
background-color: rgb(228, 35, 132);
border-color: rgb(228, 35, 132);
}
.btn-outline-primary {
color: rgb(102, 83, 105);
background-color: rgba(248, 249, 250, 0.9);
border-color: rgb(102, 83, 105);
color: #fff;
background-color: #000;
border-color: #464647;
}
.turbolinks-progress-bar {
@ -97,40 +100,64 @@ body {
.btn-primary:hover,
.btn-primary:not(:disabled):not(.disabled).active,
.btn-primary:not(:disabled):not(.disabled):active,
a.badge-primary:hover,
a.badge-primary:not(:disabled):not(.disabled).active,
a.badge-primary:not(:disabled):not(.disabled):active {
color: #fff;
background-color: rgb(102, 83, 105);
border-color: rgb(102, 83, 105);
.btn-primary:not(:disabled):not(.disabled):active {
color: rgb(241, 229, 52);
background-color: rgb(228, 35, 132);
border-color: rgb(228, 35, 132);
}
.btn-primary, a.badge-primary {
color: rgba(248, 249, 250, 0.9);
background-color: rgb(102, 83, 105);
border-color: rgb(102, 83, 105);
.btn-primary {
color: #fff;
background-color: #000;
border-color: #adb5bd;
}
.border-primary {
border-color: rgb(115, 15, 115) !important;
border-color: rgb(228, 35, 132) !important;
}
.btn-secondary {
color: #fff;
background-color: #000;
border-color: #adb5bd;
}
.btn-secondary:hover,
.btn-secondary:not(:disabled):not(.disabled).active,
.btn-secondary:not(:disabled):not(.disabled):active {
color: rgb(241, 229, 52);
background-color: rgb(228, 35, 132);
border-color: rgb(228, 35, 132);
}
.btn-outline-dark {
color: #343a40;
border-color: #343a40;
}
.btn-outline-dark:hover,
.btn-outline-dark:not(:disabled):not(.disabled).active,
.btn-outline-dark:not(:disabled):not(.disabled):active {
color: rgb(241, 229, 52);
background-color: rgb(228, 35, 132);
border-color: rgb(228, 35, 132);
}
a {
color: rgb(102, 83, 105);
color: rgb(228, 35, 132);
}
a:hover {
color: rgb(200, 30, 200);
color: rgb(228, 35, 132);
}
.form-control:focus {
box-shadow: 0 0 0 0.25rem rgba(200, 30, 200, 0.25);
border-color: rgb(200, 30, 200);
box-shadow: 0 0 0 0.25rem rgb(228 35 132 / 50%);
border-color: rgb(228, 35, 132);
}
.btn-outline-primary.focus {
box-shadow: 0 0 0 0.25rem rgba(200, 30, 200, 0.5);
box-shadow: 0 0 0 0.25rem rgb(228 35 132 / 10%);
}

View File

@ -23,11 +23,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% csrf_token %}
{{ form|crispy }}
{{ profile_form|crispy }}
{{ soge_form|crispy }}
{% comment "Soge not for membership (only WEI)" %} {{ soge_form|crispy }} {% endcomment %}
<button class="btn btn-success" type="submit">
{% trans "Sign up" %}
</button>
</form>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -21,7 +21,6 @@ urlpatterns = [
path('activity/', include('activity.urls')),
path('treasury/', include('treasury.urls')),
path('wei/', include('wei.urls')),
path('sheets/', include('sheets.urls')),
# Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')),

View File

@ -17,3 +17,6 @@ django-tables2~=2.3.1
python-memcached~=1.59
phonenumbers~=8.9.10
Pillow>=5.4.1
oauthlib<3.2.1
lxml<4.9.2
zipp>=2.0.0,<2.0.1

View File

@ -16,7 +16,7 @@ skipsdist = True
sitepackages = True
deps =
-r{toxinidir}/requirements.txt
coverage
coverage<6.5.0
commands =
coverage run --omit='apps/scripts*,*_example.py,note_kfet/wsgi.py' --source=apps,note_kfet ./manage.py test apps/
coverage report -m