mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-11-17 20:07:52 +01:00
Compare commits
5 Commits
6bf21b103f
...
6cffe94bae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cffe94bae | ||
|
|
78372807f8 | ||
|
|
b9bf01f2e3 | ||
|
|
624f94823c | ||
|
|
30a598c0b7 |
@@ -21,9 +21,13 @@ class FoodSerializer(serializers.ModelSerializer):
|
|||||||
REST API Serializer for Food.
|
REST API Serializer for Food.
|
||||||
The djangorestframework plugin will analyse the model `Food` and parse all fields in the API.
|
The djangorestframework plugin will analyse the model `Food` and parse all fields in the API.
|
||||||
"""
|
"""
|
||||||
|
# This fields is used for autocompleting food in ManageIngredientsView
|
||||||
|
# TODO Find a better way to do it
|
||||||
|
owner_name = serializers.CharField(source='owner.name', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
fields = '__all__'
|
fields = ['name', 'owner', 'allergens', 'expiry_date', 'end_of_life', 'is_ready', 'order', 'owner_name']
|
||||||
|
|
||||||
|
|
||||||
class BasicFoodSerializer(serializers.ModelSerializer):
|
class BasicFoodSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
from api.viewsets import ReadProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from django.utils import timezone
|
|
||||||
from rest_framework.filters import SearchFilter
|
from rest_framework.filters import SearchFilter
|
||||||
|
|
||||||
from .serializers import AllergenSerializer, FoodSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer, \
|
from .serializers import AllergenSerializer, FoodSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer, \
|
||||||
@@ -114,12 +113,6 @@ class OrderViewSet(ReadProtectedModelViewSet):
|
|||||||
filterset_fields = ['user', 'activity', 'dish', 'supplements', 'number', ]
|
filterset_fields = ['user', 'activity', 'dish', 'supplements', 'number', ]
|
||||||
search_fields = ['$user', '$activity', '$dish', '$supplements', '$number', ]
|
search_fields = ['$user', '$activity', '$dish', '$supplements', '$number', ]
|
||||||
|
|
||||||
def perform_update(self, serializer):
|
|
||||||
instance = serializer.save()
|
|
||||||
if instance.served and not instance.served_at:
|
|
||||||
instance.served_at = timezone.now()
|
|
||||||
instance.save()
|
|
||||||
|
|
||||||
|
|
||||||
class FoodTransactionViewSet(ReadProtectedModelViewSet):
|
class FoodTransactionViewSet(ReadProtectedModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class ManageIngredientsForm(forms.Form):
|
|||||||
model=Food,
|
model=Food,
|
||||||
resetable=True,
|
resetable=True,
|
||||||
attrs={"api_url": "/api/food/food",
|
attrs={"api_url": "/api/food/food",
|
||||||
"class": "autocomplete"},
|
"class": "autocomplete manageingredients-autocomplete"},
|
||||||
)
|
)
|
||||||
name.label = _('Name')
|
name.label = _('Name')
|
||||||
|
|
||||||
@@ -181,6 +181,11 @@ class ManageIngredientsForm(forms.Form):
|
|||||||
)
|
)
|
||||||
qrcode.label = _('QR code number')
|
qrcode.label = _('QR code number')
|
||||||
|
|
||||||
|
add_all_same_name = forms.BooleanField(
|
||||||
|
required=False,
|
||||||
|
label=_("Add all identical food")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ManageIngredientsFormSet = forms.formset_factory(
|
ManageIngredientsFormSet = forms.formset_factory(
|
||||||
ManageIngredientsForm,
|
ManageIngredientsForm,
|
||||||
@@ -219,7 +224,7 @@ SupplementFormSet = forms.inlineformset_factory(
|
|||||||
Dish,
|
Dish,
|
||||||
Supplement,
|
Supplement,
|
||||||
form=SupplementForm,
|
form=SupplementForm,
|
||||||
extra=0,
|
extra=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
apps/food/migrations/0004_alter_foodtransaction_order.py
Normal file
19
apps/food/migrations/0004_alter_foodtransaction_order.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-10-31 17:46
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('food', '0003_dish_order_foodtransaction_supplement_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='foodtransaction',
|
||||||
|
name='order',
|
||||||
|
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='transaction', to='food.order', verbose_name='order'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -256,7 +256,7 @@ class TransformedFood(Food):
|
|||||||
self.allergens.set(self.allergens.union(child.allergens.all()))
|
self.allergens.set(self.allergens.union(child.allergens.all()))
|
||||||
if not (child.polymorphic_ctype.model == 'basicfood' and child.date_type == 'DDM'):
|
if not (child.polymorphic_ctype.model == 'basicfood' and child.date_type == 'DDM'):
|
||||||
self.expiry_date = min(self.expiry_date, child.expiry_date)
|
self.expiry_date = min(self.expiry_date, child.expiry_date)
|
||||||
return super().save(force_insert, force_update, using, update_fields)
|
return super().save(force_insert=False, force_update=force_update, using=using, update_fields=update_fields)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _('Transformed food')
|
verbose_name = _('Transformed food')
|
||||||
@@ -445,36 +445,29 @@ class Order(models.Model):
|
|||||||
self.number = 1
|
self.number = 1
|
||||||
else:
|
else:
|
||||||
self.number = last_order.number + 1
|
self.number = last_order.number + 1
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
elif self.served:
|
|
||||||
if FoodTransaction.objects.filter(order=self).exists():
|
|
||||||
transaction = FoodTransaction.objects.get(order=self)
|
|
||||||
transaction.valid = True
|
|
||||||
transaction.save()
|
|
||||||
else:
|
|
||||||
transaction = FoodTransaction(
|
transaction = FoodTransaction(
|
||||||
|
order=self,
|
||||||
source=self.user.note,
|
source=self.user.note,
|
||||||
destination=self.activity.organizer.note,
|
destination=self.activity.organizer.note,
|
||||||
amount=self.amount,
|
amount=self.amount,
|
||||||
quantity=1,
|
quantity=1,
|
||||||
valid=True,
|
|
||||||
order=self,
|
|
||||||
)
|
)
|
||||||
transaction.save()
|
transaction.save()
|
||||||
else:
|
else:
|
||||||
if FoodTransaction.objects.filter(order=self).exists():
|
old_object = Order.objects.get(pk=self.pk)
|
||||||
transaction = FoodTransaction.objects.get(order=self)
|
if not old_object.served and self.served:
|
||||||
transaction.valid = False
|
self.served_at = timezone.now()
|
||||||
transaction.save()
|
self.transaction.save()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FoodTransaction(Transaction):
|
class FoodTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`food.Order`.
|
Special type of :model:`note.Transaction` associated to a :model:`food.Order`.
|
||||||
"""
|
"""
|
||||||
order = models.ForeignKey(
|
order = models.OneToOneField(
|
||||||
Order,
|
Order,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='transaction',
|
related_name='transaction',
|
||||||
@@ -484,3 +477,7 @@ class FoodTransaction(Transaction):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("food transaction")
|
verbose_name = _("food transaction")
|
||||||
verbose_name_plural = _("food transactions")
|
verbose_name_plural = _("food transactions")
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.valid = self.order.served
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function delete_button (button_id, table_id) {
|
|||||||
* @param table_id: Id of the table to reload
|
* @param table_id: Id of the table to reload
|
||||||
*/
|
*/
|
||||||
function serve_button(button_id, table_id, current_state) {
|
function serve_button(button_id, table_id, current_state) {
|
||||||
console.log("update")
|
|
||||||
const new_state = !current_state;
|
const new_state = !current_state;
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '/api/food/order/' + button_id + '/',
|
url: '/api/food/order/' + button_id + '/',
|
||||||
|
|||||||
@@ -106,14 +106,10 @@ class OrderTable(tables.Table):
|
|||||||
get_current_request(), "food.change_order_saved",
|
get_current_request(), "food.change_order_saved",
|
||||||
record) else '')}}, verbose_name=_("Serve"), )
|
record) else '')}}, verbose_name=_("Serve"), )
|
||||||
|
|
||||||
request = tables.Column(
|
|
||||||
orderable=False
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
fields = ('ordered_at', 'user', 'dish', 'supplements', 'request', 'serve', 'delete')
|
fields = ('number', 'ordered_at', 'user', 'dish', 'supplements', 'request', 'serve', 'delete')
|
||||||
order_by = ('ordered_at', )
|
order_by = ('ordered_at', )
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': 'table-row',
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% trans "Update" %}
|
{% trans "Update" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a class="btn btn-sm btn-primary" href="{% url "food:dish_list" activity_pk=dish.activity.pk %}">
|
||||||
|
{% trans "Return to dish list" %}
|
||||||
|
</a>
|
||||||
{% if delete %}
|
{% if delete %}
|
||||||
<a class="btn btn-sm btn-danger" href="{% url "food:dish_delete" activity_pk=dish.activity.pk pk=dish.pk %}">
|
<a class="btn btn-sm btn-danger" href="{% url "food:dish_delete" activity_pk=dish.activity.pk pk=dish.pk %}">
|
||||||
{% trans "Delete" %}
|
{% trans "Delete" %}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% crispy form %}
|
{% crispy form %}
|
||||||
</div>
|
</div>
|
||||||
<h3 class="card-header text-center">
|
<h3 class="card-header text-center">
|
||||||
{% trans "Ajouter des suppléments (optionnel)" %}
|
{% trans "Add supplements (optional)" %}
|
||||||
</h3>
|
</h3>
|
||||||
{{ formset.management_form }}
|
{{ formset.management_form }}
|
||||||
<table class="table table-condensed table-striped">
|
<table class="table table-condensed table-striped">
|
||||||
|
|||||||
41
apps/food/templates/food/kitchen.html
Normal file
41
apps/food/templates/food/kitchen.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% comment %}
|
||||||
|
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
{% endcomment %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Colonne de plats -->
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
|
||||||
|
{% for food, quantity in orders.items %}
|
||||||
|
<div class="card bg-white mb-3" style="flex: 1 1 calc(33.333% - 1rem); border: 1px solid #ccc; padding: 1rem; border-radius: 0.5rem; box-sizing: border-box;">
|
||||||
|
|
||||||
|
<h3 class="card-header text-center">
|
||||||
|
<strong>{{ food }}</strong><br>
|
||||||
|
</h3>
|
||||||
|
<h1 class="card-body text-center">
|
||||||
|
{{ quantity }}</h1>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Colonne de la table -->
|
||||||
|
<div class="card bg-white mb-3">
|
||||||
|
<h3 class="card-header text-center">
|
||||||
|
{% trans "Special orders" %}
|
||||||
|
</h3>
|
||||||
|
{% if table.data %}
|
||||||
|
{% render_table table %}
|
||||||
|
{% else %}
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "There are no special orders." %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -22,6 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<th>{{ form.name.label }}</th>
|
<th>{{ form.name.label }}</th>
|
||||||
<th>{{ form.qrcode.label }}</th>
|
<th>{{ form.qrcode.label }}</th>
|
||||||
<th>{{ form.fully_used.label }}</th>
|
<th>{{ form.fully_used.label }}</th>
|
||||||
|
<th>{{ form.add_all_same_name.label }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="form_body">
|
<tbody id="form_body">
|
||||||
@@ -34,6 +35,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<td>{{ form.name }}</td>
|
<td>{{ form.name }}</td>
|
||||||
<td>{{ form.qrcode }}</td>
|
<td>{{ form.qrcode }}</td>
|
||||||
<td>{{ form.fully_used }}</td>
|
<td>{{ form.fully_used }}</td>
|
||||||
|
<td>{{ form.add_all_same_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
0
apps/food/tests/__init__.py
Normal file
0
apps/food/tests/__init__.py
Normal file
@@ -6,9 +6,12 @@ from django.contrib.auth.models import User
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from activity.models import Activity, ActivityType
|
||||||
|
from member.models import Club
|
||||||
|
|
||||||
from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet
|
from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet, \
|
||||||
from ..models import Allergen, BasicFood, TransformedFood, QRCode
|
DishViewSet, SupplementViewSet, OrderViewSet, FoodTransactionViewSet
|
||||||
|
from ..models import Allergen, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order, FoodTransaction
|
||||||
|
|
||||||
|
|
||||||
class TestFood(TestCase):
|
class TestFood(TestCase):
|
||||||
@@ -64,14 +67,14 @@ class TestFood(TestCase):
|
|||||||
"""
|
"""
|
||||||
Display QRCode creation
|
Display QRCode creation
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:qrcode_create'))
|
response = self.client.get(reverse('food:qrcode_create', kwargs={"slug": 2}))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_basicfood_create(self):
|
def test_basicfood_create(self):
|
||||||
"""
|
"""
|
||||||
Display BasicFood creation
|
Display BasicFood creation
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:basicfood_create'))
|
response = self.client.get(reverse('food:basicfood_create', kwargs={"slug": 2}))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_transformedfood_create(self):
|
def test_transformedfood_create(self):
|
||||||
@@ -81,45 +84,265 @@ class TestFood(TestCase):
|
|||||||
response = self.client.get(reverse('food:transformedfood_create'))
|
response = self.client.get(reverse('food:transformedfood_create'))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_food_create(self):
|
def test_food_update(self):
|
||||||
"""
|
"""
|
||||||
Display Food update
|
Display Food update
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:food_update'))
|
response = self.client.get(reverse('food:food_update', args=(self.basicfood.pk,)))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_food_view(self):
|
def test_food_view(self):
|
||||||
"""
|
"""
|
||||||
Display Food detail
|
Display Food detail
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:food_view'))
|
response = self.client.get(reverse('food:food_view', args=(self.basicfood.pk,)))
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
def test_basicfood_view(self):
|
def test_basicfood_view(self):
|
||||||
"""
|
"""
|
||||||
Display BasicFood detail
|
Display BasicFood detail
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:basicfood_view'))
|
response = self.client.get(reverse('food:basicfood_view', args=(self.basicfood.pk,)))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_transformedfood_view(self):
|
def test_transformedfood_view(self):
|
||||||
"""
|
"""
|
||||||
Display TransformedFood detail
|
Display TransformedFood detail
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:transformedfood_view'))
|
response = self.client.get(reverse('food:transformedfood_view', args=(self.transformedfood.pk,)))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_add_ingredient(self):
|
def test_add_ingredient(self):
|
||||||
"""
|
"""
|
||||||
Display add ingredient view
|
Display add ingredient view
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse('food:add_ingredient'))
|
response = self.client.get(reverse('food:add_ingredient', args=(self.transformedfood.pk,)))
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFoodOrder(TestCase):
|
||||||
|
"""
|
||||||
|
Test Food Order
|
||||||
|
"""
|
||||||
|
fixtures = ('initial',)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_superuser(
|
||||||
|
username='admintoto',
|
||||||
|
password='toto1234',
|
||||||
|
email='toto@example.com'
|
||||||
|
)
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
sess = self.client.session
|
||||||
|
sess['permission_mask'] = 42
|
||||||
|
sess.save()
|
||||||
|
|
||||||
|
self.basicfood = BasicFood.objects.create(
|
||||||
|
id=1,
|
||||||
|
name='basicfood',
|
||||||
|
owner=Club.objects.get(name="BDE"),
|
||||||
|
expiry_date=timezone.now(),
|
||||||
|
is_ready=True,
|
||||||
|
date_type='DLC',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.transformedfood = TransformedFood.objects.create(
|
||||||
|
id=2,
|
||||||
|
name='transformedfood',
|
||||||
|
owner=Club.objects.get(name="BDE"),
|
||||||
|
expiry_date=timezone.now(),
|
||||||
|
is_ready=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.second_transformedfood = TransformedFood.objects.create(
|
||||||
|
id=3,
|
||||||
|
name='second transformedfood',
|
||||||
|
owner=Club.objects.get(name="BDE"),
|
||||||
|
expiry_date=timezone.now(),
|
||||||
|
is_ready=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.third_transformedfood = TransformedFood.objects.create(
|
||||||
|
id=4,
|
||||||
|
name='third transformedfood',
|
||||||
|
owner=Club.objects.get(name="BDE"),
|
||||||
|
expiry_date=timezone.now(),
|
||||||
|
is_ready=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.activity = Activity.objects.create(
|
||||||
|
activity_type=ActivityType.objects.get(name="Perm bouffe"),
|
||||||
|
organizer=Club.objects.get(name="BDE"),
|
||||||
|
creater=self.user,
|
||||||
|
attendees_club_id=1,
|
||||||
|
date_start=timezone.now(),
|
||||||
|
date_end=timezone.now(),
|
||||||
|
name="Test activity",
|
||||||
|
open=True,
|
||||||
|
valid=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.dish = Dish.objects.create(
|
||||||
|
main=self.transformedfood,
|
||||||
|
price=500,
|
||||||
|
activity=self.activity,
|
||||||
|
available=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.second_dish = Dish.objects.create(
|
||||||
|
main=self.second_transformedfood,
|
||||||
|
price=1000,
|
||||||
|
activity=self.activity,
|
||||||
|
available=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.supplement = Supplement.objects.create(
|
||||||
|
dish=self.dish,
|
||||||
|
food=self.basicfood,
|
||||||
|
price=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.order = Order.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
activity=self.activity,
|
||||||
|
dish=self.dish,
|
||||||
|
)
|
||||||
|
self.order.supplements.add(self.supplement)
|
||||||
|
self.order.save()
|
||||||
|
|
||||||
|
def test_dish_list(self):
|
||||||
|
"""
|
||||||
|
Try to display dish list
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_dish_create(self):
|
||||||
|
"""
|
||||||
|
Try to create a dish
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:dish_create", kwargs={"activity_pk": self.activity.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.post(reverse("food:dish_create", kwargs={"activity_pk": self.activity.pk}), data={
|
||||||
|
"main": self.third_transformedfood.pk,
|
||||||
|
"price": 4,
|
||||||
|
"activity": self.activity.pk,
|
||||||
|
"supplements-0-food": self.basicfood.pk,
|
||||||
|
"supplements-0-price": 0.5,
|
||||||
|
"supplements-TOTAL_FORMS": 1,
|
||||||
|
"supplements-INITIAL_FORMS": 0,
|
||||||
|
"supplements-MIN_NUM_FORMS": 0,
|
||||||
|
"supplements-MAX_NUM_FORMS": 1000,
|
||||||
|
})
|
||||||
|
self.assertRedirects(response, reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}), 302, 200)
|
||||||
|
self.assertTrue(Dish.objects.filter(main=self.third_transformedfood).exists())
|
||||||
|
self.assertTrue(Supplement.objects.filter(food=self.basicfood, price=50).exists())
|
||||||
|
|
||||||
|
def test_dish_update(self):
|
||||||
|
"""
|
||||||
|
Try to update a dish
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:dish_update", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.post(reverse("food:dish_update", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}), data={
|
||||||
|
"price": 6,
|
||||||
|
"supplements-0-food": self.basicfood.pk,
|
||||||
|
"supplements-0-price": 1,
|
||||||
|
"supplements-1-food": self.basicfood.pk,
|
||||||
|
"supplements-1-price": 0.25,
|
||||||
|
"supplements-TOTAL_FORMS": 2,
|
||||||
|
"supplements-INITIAL_FORMS": 0,
|
||||||
|
"supplements-MIN_NUM_FORMS": 0,
|
||||||
|
"supplements-MAX_NUM_FORMS": 1000,
|
||||||
|
})
|
||||||
|
self.assertRedirects(response, reverse("food:dish_detail", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}), 302, 200)
|
||||||
|
self.dish.refresh_from_db()
|
||||||
|
self.assertTrue(Dish.objects.filter(main=self.transformedfood, price=600).exists())
|
||||||
|
self.assertTrue(Supplement.objects.filter(dish=self.dish, food=self.basicfood, price=25).exists())
|
||||||
|
|
||||||
|
def test_dish_detail(self):
|
||||||
|
"""
|
||||||
|
Try to display dish details
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:dish_detail", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_dish_delete(self):
|
||||||
|
"""
|
||||||
|
Try to delete a dish
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:dish_delete", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# Cannot delete already ordered Dish
|
||||||
|
response = self.client.delete(reverse("food:dish_delete", kwargs={"activity_pk": self.activity.pk, "pk": self.dish.pk}))
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
self.assertTrue(Dish.objects.filter(pk=self.dish.pk).exists())
|
||||||
|
|
||||||
|
# Can delete a Dish with no order
|
||||||
|
response = self.client.delete(reverse("food:dish_delete", kwargs={"activity_pk": self.activity.pk, "pk": self.second_dish.pk}))
|
||||||
|
self.assertRedirects(response, reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}))
|
||||||
|
self.assertFalse(Dish.objects.filter(pk=self.second_dish.pk).exists())
|
||||||
|
|
||||||
|
def test_order_food(self):
|
||||||
|
"""
|
||||||
|
Try to make an order
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:order_create", kwargs={"activity_pk": self.activity.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.post(reverse("food:order_create", kwargs={"activity_pk": self.activity.pk}), data=dict(
|
||||||
|
user=self.user.pk,
|
||||||
|
activity=self.activity.pk,
|
||||||
|
dish=self.second_dish.pk,
|
||||||
|
supplements=self.supplement.pk
|
||||||
|
))
|
||||||
|
self.assertRedirects(response, reverse("food:food_list"))
|
||||||
|
self.assertTrue(Order.objects.filter(user=self.user, dish=self.second_dish, activity=self.activity).exists())
|
||||||
|
|
||||||
|
def test_order_list(self):
|
||||||
|
"""
|
||||||
|
Try to display order list
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:order_list", kwargs={"activity_pk": self.activity.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_served_order_list(self):
|
||||||
|
"""
|
||||||
|
Try to display served order list
|
||||||
|
"""
|
||||||
|
response = self.client.get(reverse("food:served_order_list", kwargs={"activity_pk": self.activity.pk}))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_serve_order(self):
|
||||||
|
"""
|
||||||
|
Try to serve an order, then to unserve it
|
||||||
|
"""
|
||||||
|
response = self.client.patch("/api/food/order/" + str(self.order.pk) + "/", data=dict(
|
||||||
|
served=True
|
||||||
|
), content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.order.refresh_from_db()
|
||||||
|
self.assertTrue(Order.objects.filter(dish=self.dish, user=self.user, served=True).exists())
|
||||||
|
self.assertIsNotNone(self.order.served_at)
|
||||||
|
|
||||||
|
self.assertTrue(FoodTransaction.objects.filter(order=self.order, valid=True).exists())
|
||||||
|
|
||||||
|
response = self.client.patch("/api/food/order/" + str(self.order.pk) + "/", data=dict(
|
||||||
|
served=False
|
||||||
|
), content_type="application/json")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue(Order.objects.filter(dish=self.dish, user=self.user, served=False).exists())
|
||||||
|
|
||||||
|
self.assertTrue(FoodTransaction.objects.filter(order=self.order, valid=False).exists())
|
||||||
|
|
||||||
|
|
||||||
class TestFoodAPI(TestAPI):
|
class TestFoodAPI(TestAPI):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
super().setUP()
|
super().setUp()
|
||||||
|
|
||||||
self.allergen = Allergen.objects.create(
|
self.allergen = Allergen.objects.create(
|
||||||
name='name',
|
name='name',
|
||||||
@@ -145,6 +368,39 @@ class TestFoodAPI(TestAPI):
|
|||||||
food_container=self.basicfood,
|
food_container=self.basicfood,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.activity = Activity.objects.create(
|
||||||
|
activity_type=ActivityType.objects.get(name="Perm bouffe"),
|
||||||
|
organizer=Club.objects.get(name="BDE"),
|
||||||
|
creater=self.user,
|
||||||
|
attendees_club_id=1,
|
||||||
|
date_start=timezone.now(),
|
||||||
|
date_end=timezone.now(),
|
||||||
|
name="Test activity",
|
||||||
|
open=True,
|
||||||
|
valid=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.dish = Dish.objects.create(
|
||||||
|
main=self.transformedfood,
|
||||||
|
price=500,
|
||||||
|
activity=self.activity,
|
||||||
|
available=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.supplement = Supplement.objects.create(
|
||||||
|
dish=self.dish,
|
||||||
|
food=self.basicfood,
|
||||||
|
price=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.order = Order.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
activity=self.activity,
|
||||||
|
dish=self.dish,
|
||||||
|
)
|
||||||
|
self.order.supplements.add(self.supplement)
|
||||||
|
self.order.save()
|
||||||
|
|
||||||
def test_allergen_api(self):
|
def test_allergen_api(self):
|
||||||
"""
|
"""
|
||||||
Load Allergen API page and test all filters and permissions
|
Load Allergen API page and test all filters and permissions
|
||||||
@@ -157,6 +413,7 @@ class TestFoodAPI(TestAPI):
|
|||||||
"""
|
"""
|
||||||
self.check_viewset(BasicFoodViewSet, '/api/food/basicfood/')
|
self.check_viewset(BasicFoodViewSet, '/api/food/basicfood/')
|
||||||
|
|
||||||
|
# TODO Repair and detabulate this test
|
||||||
def test_transformedfood_api(self):
|
def test_transformedfood_api(self):
|
||||||
"""
|
"""
|
||||||
Load TransformedFood API page and test all filters and permissions
|
Load TransformedFood API page and test all filters and permissions
|
||||||
@@ -168,3 +425,27 @@ class TestFoodAPI(TestAPI):
|
|||||||
Load QRCode API page and test all filters and permissions
|
Load QRCode API page and test all filters and permissions
|
||||||
"""
|
"""
|
||||||
self.check_viewset(QRCodeViewSet, '/api/food/qrcode/')
|
self.check_viewset(QRCodeViewSet, '/api/food/qrcode/')
|
||||||
|
|
||||||
|
def test_dish_api(self):
|
||||||
|
"""
|
||||||
|
Load Dish API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(DishViewSet, '/api/food/dish/')
|
||||||
|
|
||||||
|
def test_supplement_api(self):
|
||||||
|
"""
|
||||||
|
Load Supplement API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(SupplementViewSet, '/api/food/supplement/')
|
||||||
|
|
||||||
|
def test_order_api(self):
|
||||||
|
"""
|
||||||
|
Load Order API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(OrderViewSet, '/api/food/order/')
|
||||||
|
|
||||||
|
def test_foodtransaction_api(self):
|
||||||
|
"""
|
||||||
|
Load FoodTransaction API page and test all filters and permissions
|
||||||
|
"""
|
||||||
|
self.check_viewset(FoodTransactionViewSet, '/api/food/foodtransaction/')
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ urlpatterns = [
|
|||||||
path('activity/<int:activity_pk>/order/', views.OrderCreateView.as_view(), name='order_create'),
|
path('activity/<int:activity_pk>/order/', views.OrderCreateView.as_view(), name='order_create'),
|
||||||
path('activity/<int:activity_pk>/orders/', views.OrderListView.as_view(), name='order_list'),
|
path('activity/<int:activity_pk>/orders/', views.OrderListView.as_view(), name='order_list'),
|
||||||
path('activity/<int:activity_pk>/orders/served', views.ServedOrderListView.as_view(), name='served_order_list'),
|
path('activity/<int:activity_pk>/orders/served', views.ServedOrderListView.as_view(), name='served_order_list'),
|
||||||
path('activity/orders/<int:pk>/delete/', views.OrderDeleteView.as_view(), name='order_delete'),
|
path('activity/<int:activity_pk>/kitchen/', views.KitchenView.as_view(), name='kitchen'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from crispy_forms.helper import FormHelper
|
|||||||
from django_tables2.views import SingleTableView, MultiTableMixin
|
from django_tables2.views import SingleTableView, MultiTableMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q, Count
|
||||||
from django.http import HttpResponseRedirect, Http404
|
from django.http import HttpResponseRedirect, Http404
|
||||||
from django.views.generic import DetailView, UpdateView, CreateView
|
from django.views.generic import DetailView, UpdateView, CreateView
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
@@ -22,7 +22,7 @@ from activity.models import Activity
|
|||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin
|
from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin
|
||||||
|
|
||||||
from .models import Food, BasicFood, TransformedFood, QRCode, Order, Dish
|
from .models import Food, BasicFood, TransformedFood, QRCode, Order, Dish, Supplement
|
||||||
from .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \
|
from .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \
|
||||||
ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \
|
ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \
|
||||||
BasicFoodUpdateForms, TransformedFoodUpdateForms, \
|
BasicFoodUpdateForms, TransformedFoodUpdateForms, \
|
||||||
@@ -307,6 +307,14 @@ class ManageIngredientsView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
elif form.data[prefix + 'name'] != '':
|
elif form.data[prefix + 'name'] != '':
|
||||||
ingredient = Food.objects.get(pk=form.data[prefix + 'name'])
|
ingredient = Food.objects.get(pk=form.data[prefix + 'name'])
|
||||||
|
if form.data.get(prefix + 'add_all_same_name') == 'on':
|
||||||
|
ingredients = Food.objects.filter(name=ingredient.name, owner=ingredient.owner, end_of_life='')
|
||||||
|
for ingredient in ingredients:
|
||||||
|
self.object.ingredients.add(ingredient)
|
||||||
|
if form.data.get(prefix + 'fully_used') == 'on':
|
||||||
|
ingredient.end_of_life = _('Fully used in {meal}'.format(meal=self.object.name))
|
||||||
|
ingredient.save()
|
||||||
|
else:
|
||||||
self.object.ingredients.add(ingredient)
|
self.object.ingredients.add(ingredient)
|
||||||
if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
|
if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on':
|
||||||
ingredient.end_of_life = _('Fully used in {meal}'.format(
|
ingredient.end_of_life = _('Fully used in {meal}'.format(
|
||||||
@@ -591,7 +599,8 @@ class DishCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
formset = SupplementFormSet(self.request.POST, instance=form.instance)
|
formset = SupplementFormSet(self.request.POST, instance=form.instance)
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
for f in formset:
|
for f in formset:
|
||||||
if f.is_valid():
|
# We don't save the product if the price is not entered, ie. if the line is empty
|
||||||
|
if f.is_valid() and f.instance.price:
|
||||||
f.save()
|
f.save()
|
||||||
f.instance.save()
|
f.instance.save()
|
||||||
else:
|
else:
|
||||||
@@ -656,12 +665,54 @@ class DishUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
form_class = DishForm
|
form_class = DishForm
|
||||||
extra_context = {"title": _("Update a dish")}
|
extra_context = {"title": _("Update a dish")}
|
||||||
|
|
||||||
def get_form(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
form = super().get_form(**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 supplements
|
||||||
|
form_set = SupplementFormSet(instance=form.instance)
|
||||||
|
context['formset'] = form_set
|
||||||
|
context['helper'] = SupplementFormSetHelper()
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_form(self, form_class=None):
|
||||||
|
form = super().get_form(form_class)
|
||||||
if 'main' in form.fields:
|
if 'main' in form.fields:
|
||||||
del form.fields["main"]
|
del form.fields["main"]
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def form_valid(self, form):
|
||||||
|
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
||||||
|
|
||||||
|
form.instance.activity = activity
|
||||||
|
|
||||||
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
|
# For each supplement, we save it
|
||||||
|
formset = SupplementFormSet(self.request.POST, instance=form.instance)
|
||||||
|
saved = []
|
||||||
|
if formset.is_valid():
|
||||||
|
for f in formset:
|
||||||
|
# We don't save the product if the price is not entered, ie. if the line is empty
|
||||||
|
if f.is_valid() and f.instance.price:
|
||||||
|
f.save()
|
||||||
|
f.instance.save()
|
||||||
|
saved.append(f.instance.pk)
|
||||||
|
else:
|
||||||
|
f.instance = None
|
||||||
|
# Remove old supplements that weren't given in the form
|
||||||
|
Supplement.objects.filter(~Q(pk__in=saved), dish=form.instance).delete()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse_lazy('food:dish_detail', kwargs={"activity_pk": self.kwargs["activity_pk"], "pk": self.kwargs["pk"]})
|
||||||
|
|
||||||
|
|
||||||
class DishDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
|
class DishDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
|
||||||
"""
|
"""
|
||||||
@@ -726,7 +777,7 @@ class OrderListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, L
|
|||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
||||||
return Order.objects.filter(activity=activity)
|
return Order.objects.filter(activity=activity).order_by('number')
|
||||||
|
|
||||||
def get_tables(self):
|
def get_tables(self):
|
||||||
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
||||||
@@ -787,17 +838,32 @@ class ServedOrderListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class OrderDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
|
class KitchenView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
Delete an order
|
The view to display useful information for the kitchen
|
||||||
"""
|
"""
|
||||||
model = Order
|
model = Order
|
||||||
extra_context = {"title": _('Delete dish')}
|
table_class = OrderTable
|
||||||
|
template_name = 'food/kitchen.html'
|
||||||
|
extra_context = {'title': _('Kitchen')}
|
||||||
|
|
||||||
def delete(self, request, *args, **kwargs):
|
def get_queryset(self):
|
||||||
if self.get_object().served:
|
return super().get_queryset().filter(~Q(supplements__isnull=True, request=''), activity__pk=self.kwargs["activity_pk"])
|
||||||
raise PermissionDenied(_("This order cannot be deleted because it has already been served"))
|
|
||||||
return super().delete(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_context_data(self, **kwargs):
|
||||||
return reverse_lazy('food:order_list', kwargs={"activity_pk": self.kwargs["activity_pk"]})
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
orders_count = Order.objects.values('dish__main__name').annotate(quantity=Count('id'))
|
||||||
|
|
||||||
|
context["orders"] = {o['dish__main__name']: o['quantity'] for o in orders_count}
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_table(self, **kwargs):
|
||||||
|
table = super().get_table(**kwargs)
|
||||||
|
|
||||||
|
hide = ["ordered_at", "serve", "delete"]
|
||||||
|
for field in hide:
|
||||||
|
table.columns.hide(field)
|
||||||
|
|
||||||
|
return table
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
|
|
||||||
# For each product, we save it
|
# For each product, we save it
|
||||||
formset = ProductFormSet(self.request.POST, instance=form.instance)
|
formset = ProductFormSet(self.request.POST, instance=form.instance)
|
||||||
print(formset)
|
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
for f in formset:
|
for f in formset:
|
||||||
# We don't save the product if the designation is not entered, ie. if the line is empty
|
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ $(document).ready(function () {
|
|||||||
target.addClass('is-invalid')
|
target.addClass('is-invalid')
|
||||||
target.removeClass('is-valid')
|
target.removeClass('is-valid')
|
||||||
|
|
||||||
|
const isManageIngredients = target.hasClass('manageingredients-autocomplete')
|
||||||
|
|
||||||
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
|
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
|
||||||
let html = '<ul class="list-group list-group-flush" id="' + prefix + '_list">'
|
let html = '<ul class="list-group list-group-flush" id="' + prefix + '_list">'
|
||||||
|
|
||||||
objects.results.forEach(function (obj) {
|
objects.results.forEach(function (obj) {
|
||||||
html += li(prefix + '_' + obj.id, obj[name_field])
|
const extra = isManageIngredients ? ` (${obj.owner_name})` : ''
|
||||||
|
html += li(`${prefix}_${obj.id}`, `${obj[name_field]}${extra}`)
|
||||||
})
|
})
|
||||||
html += '</ul>'
|
html += '</ul>'
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user