mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 18:08:21 +02:00
Compare commits
28 Commits
sheets
...
fix_pipeli
Author | SHA1 | Date | |
---|---|---|---|
81c12436a8 | |||
8eee14075c | |||
ab9ba62af1 | |||
8ca0648e75 | |||
77ecfd6ed5 | |||
0ab1367e55 | |||
524f0e098a | |||
1ca4246cbd | |||
6a9021ec14 | |||
cb74311e7b | |||
9d7dd566c9 | |||
9944ebcaad | |||
8537f043f7 | |||
c89a95f8d2 | |||
73640b1dfa | |||
84b16ab603 | |||
6a1b51dbbf | |||
c441a43a8b | |||
87f3b51b04 | |||
0a853fd3e6 | |||
c429734810 | |||
5d759111b6 | |||
70baf7566c | |||
eb355f547c | |||
7068170f18 | |||
45ee9a8941 | |||
454ea19603 | |||
5a77a66391 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,6 +42,7 @@ map.json
|
||||
backups/
|
||||
/static/
|
||||
/media/
|
||||
/tmp/
|
||||
|
||||
# Virtualenv
|
||||
env/
|
||||
|
5
apps/api/pagination.py
Normal file
5
apps/api/pagination.py
Normal file
@ -0,0 +1,5 @@
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
class CustomPagination(PageNumberPagination):
|
||||
page_size_query_param = 'page_size'
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -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'
|
@ -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
|
@ -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__'
|
@ -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)
|
@ -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', ]
|
@ -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')
|
@ -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(),
|
||||
}
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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")
|
@ -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,))
|
||||
}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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"),
|
||||
]
|
@ -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
|
18
apps/treasury/migrations/0005_auto_20230129_2348.py
Normal file
18
apps/treasury/migrations/0005_auto_20230129_2348.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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
|
||||
|
BIN
apps/treasury/static/img/TotalistSpies.png
Normal file
BIN
apps/treasury/static/img/TotalistSpies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
apps/treasury/static/img/TotalistSpies_bg.jpg
Normal file
BIN
apps/treasury/static/img/TotalistSpies_bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -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'
|
||||
]
|
||||
|
||||
|
||||
|
18
apps/wei/migrations/0004_auto_20220904_2325.py
Normal file
18
apps/wei/migrations/0004_auto_20220904_2325.py
Normal 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'),
|
||||
),
|
||||
]
|
18
apps/wei/migrations/0005_auto_20230128_1850.py
Normal file
18
apps/wei/migrations/0005_auto_20230128_1850.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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"
|
||||
|
@ -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
79
note_kfet/static/css/custom.css
Normal file → Executable 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%);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 %}
|
||||
|
@ -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')),
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user