mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-12-14 07:15:16 +01:00
Prepare for first irl test
This commit is contained in:
@@ -65,11 +65,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if activity.open and activity.activity_type.manage_entries and ".change__open"|has_perm:activity %}
|
||||
<a class="btn btn-warning btn-sm my-1" href="{% url 'activity:activity_entry' pk=activity.pk %}"> {% trans "Entry page" %}</a>
|
||||
{% endif %}
|
||||
{% if false %}
|
||||
{% if activity.activity_type.name == "Perm bouffe" %}
|
||||
<a class="btn btn-warning btn-sm my-1" href="{% url 'food:dish_list' activity_pk=activity.pk %}"> {% trans "Dish page" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if request.path_info == activity_detail_url %}
|
||||
{% if activity.valid and ".change__open"|has_perm:activity %}
|
||||
|
||||
@@ -13,6 +13,7 @@ from member.models import Club
|
||||
from note_kfet.inputs import Autocomplete, AmountInput
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from activity.models import Activity
|
||||
|
||||
from .models import Food, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order, Recipe
|
||||
|
||||
@@ -201,6 +202,17 @@ class DishForm(forms.ModelForm):
|
||||
"""
|
||||
Form to create a dish
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# TODO find a better way to get pk (be not url scheme dependant)
|
||||
pk = get_current_request().path.split('/')[3]
|
||||
club = Activity.objects.get(pk=pk).organizer
|
||||
qs = self.fields['main'].queryset.filter(
|
||||
owner=club,
|
||||
end_of_life=''
|
||||
).filter(PermissionBackend.filter_queryset(get_current_request(), Food, "change"))
|
||||
self.fields['main'].queryset = qs
|
||||
|
||||
class Meta:
|
||||
model = Dish
|
||||
fields = ('main', 'price', 'available')
|
||||
@@ -213,6 +225,17 @@ class SupplementForm(forms.ModelForm):
|
||||
"""
|
||||
Form to create a dish
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# TODO find a better way to get pk (be not url scheme dependant)
|
||||
pk = get_current_request().path.split('/')[3]
|
||||
club = Activity.objects.get(pk=pk).organizer
|
||||
qs = self.fields['food'].queryset.filter(
|
||||
owner=club,
|
||||
end_of_life=''
|
||||
).filter(PermissionBackend.filter_queryset(get_current_request(), Food, "change"))
|
||||
self.fields['food'].queryset = qs
|
||||
|
||||
class Meta:
|
||||
model = Supplement
|
||||
fields = '__all__'
|
||||
@@ -249,6 +272,20 @@ class OrderForm(forms.ModelForm):
|
||||
"""
|
||||
Form to order food
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# TODO find a better way to get pk (be not url scheme dependant)
|
||||
pk = get_current_request().path.split('/')[3]
|
||||
qs = self.fields['supplements'].queryset.filter(
|
||||
available=True
|
||||
).filter(PermissionBackend.filter_queryset(get_current_request(), Supplement, "view"))
|
||||
self.fields['supplements'].queryset = qs
|
||||
qs = self.fields['dish'].queryset.filter(
|
||||
activity__pk=pk,
|
||||
available=True
|
||||
).filter(PermissionBackend.filter_queryset(get_current_request(), Dish, "view"))
|
||||
self.fields['dish'].queryset = qs
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
exclude = ("activity", "number", "ordered_at", "served", "served_at")
|
||||
|
||||
18
apps/food/migrations/0007_supplement_available.py
Normal file
18
apps/food/migrations/0007_supplement_available.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.9 on 2025-12-13 23:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('food', '0006_recipe'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='supplement',
|
||||
name='available',
|
||||
field=models.BooleanField(default=True, verbose_name='available'),
|
||||
),
|
||||
]
|
||||
@@ -388,6 +388,11 @@ class Supplement(models.Model):
|
||||
verbose_name=_('price')
|
||||
)
|
||||
|
||||
available = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_('available'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Supplement')
|
||||
verbose_name_plural = _('Supplements')
|
||||
@@ -476,7 +481,7 @@ class Order(models.Model):
|
||||
dish=str(self.dish),
|
||||
user=str(self.user))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
def save(self, supplements=None, *args, **kwargs):
|
||||
if self.activity != self.dish.activity:
|
||||
raise ValidationError(_('Activities must be the same.'))
|
||||
created = self.pk is None
|
||||
@@ -487,12 +492,19 @@ class Order(models.Model):
|
||||
else:
|
||||
self.number = last_order.number + 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
amount = self.dish.price
|
||||
if supplements:
|
||||
self.supplements.set(supplements)
|
||||
super().save(*args, **kwargs)
|
||||
for supplement in supplements:
|
||||
if supplement.dish != self.dish:
|
||||
raise ValidationError(_('(You cannot select these supplements for this dish.'))
|
||||
amount += supplement.price
|
||||
transaction = FoodTransaction(
|
||||
order=self,
|
||||
source=self.user.note,
|
||||
destination=self.activity.organizer.note,
|
||||
amount=self.amount,
|
||||
amount=amount,
|
||||
quantity=1,
|
||||
reason=str(self.dish),
|
||||
)
|
||||
@@ -504,6 +516,12 @@ class Order(models.Model):
|
||||
self.transaction.save()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
if self.transaction.valid:
|
||||
raise ValidationError(_('You cannot delete this order because the transaction is valid'))
|
||||
else:
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
class FoodTransaction(Transaction):
|
||||
"""
|
||||
@@ -511,7 +529,7 @@ class FoodTransaction(Transaction):
|
||||
"""
|
||||
order = models.OneToOneField(
|
||||
Order,
|
||||
on_delete=models.PROTECT,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='transaction',
|
||||
verbose_name=_('order')
|
||||
)
|
||||
|
||||
@@ -32,10 +32,13 @@ function serve_button(button_id, table_id, current_state) {
|
||||
})
|
||||
})
|
||||
.done(function () {
|
||||
console.log('done');
|
||||
if (current_state) {
|
||||
console.log('true');
|
||||
$('table').load(location.pathname + ' table')
|
||||
}
|
||||
else {
|
||||
console.log('false');
|
||||
$('#' + table_id).load(location.pathname + ' #' + table_id + ' > *');
|
||||
}
|
||||
})
|
||||
|
||||
@@ -47,7 +47,7 @@ class DishTable(tables.Table):
|
||||
supplements = tables.Column(empty_values=(), verbose_name=_('Available supplements'), orderable=False)
|
||||
|
||||
def render_supplements(self, record):
|
||||
return ", ".join(str(q.food) for q in record.supplements.all())
|
||||
return ", ".join(str(q.food) for q in record.supplements.filter(available=True))
|
||||
|
||||
def render_price(self, value):
|
||||
return pretty_money(value)
|
||||
|
||||
@@ -22,7 +22,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<li> {% trans "Available" %} : {{ dish.available|yesno }}</li>
|
||||
<li> {% trans "Possible supplements" %} :
|
||||
{% for supp in supplements %}
|
||||
<a href="{% url "food:food_view" pk=supp.food.pk %}">{{ supp.food.name }} ({{ supp.price|pretty_money }})</a>{% if not forloop.last %},{% endif %}
|
||||
<a href="{% url "food:food_view" pk=supp.food.pk %}">{% if not supp.food.available %}<s>{% endif %}{{ supp.food.name }} ({{ supp.price|pretty_money }}) {% if not supp.food.available %}</s>{% endif %}</a>{% if not forloop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -26,6 +26,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<tr>
|
||||
<th>{{ form.food.label }}<span class="asteriskField">*</span></th>
|
||||
<th>{{ form.price.label }}<span class="asteriskField">*</span></th>
|
||||
<th>{{ form.available.label }}<span class="asteriskField">*</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="form_body">
|
||||
@@ -33,6 +34,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<tr class="row-formset">
|
||||
<td>{{ form.food }}</td>
|
||||
<td>{{ form.price }}</td>
|
||||
<td>{{ form.available }}</td>
|
||||
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
|
||||
{{ form.dish }}
|
||||
{{ form.id }}
|
||||
|
||||
@@ -17,6 +17,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<a class="btn btn-sm btn-success" href="{% url 'food:dish_create' activity_pk=activity.pk %}">{% trans "New dish" %}</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'activity:activity_detail' pk=activity.pk %}">{% trans "Activity page" %}</a>
|
||||
<a class="btn btn-sm btn-success" href="{% url 'food:order_create' activity_pk=activity.pk %}">{% trans "Order food" %}</a>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'food:order_list' activity_pk=activity.pk %}">{% trans "Order list" %}</a>
|
||||
<a class="btn btn-sm btn-primary" href="{% url "food:food_list" %}">
|
||||
{% trans "Return to the food list" %}
|
||||
</a>
|
||||
|
||||
@@ -47,11 +47,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<a class="btn btn-sm btn-secondary" href="{% url "food:manage_ingredients" pk=food.pk %}">
|
||||
{% trans "Manage ingredients" %}
|
||||
</a>
|
||||
{% if false %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url "food:recipe_use" pk=food.pk %}">
|
||||
{% trans "Use a recipe" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-primary" href="{% url "food:food_list" %}">
|
||||
{% trans "Return to the food list" %}
|
||||
|
||||
@@ -70,7 +70,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% trans "New meal" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if false %}
|
||||
{% if can_view_recipes %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'food:recipe_list' %}">
|
||||
{% trans "View recipes" %}
|
||||
@@ -86,7 +85,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% trans "View" %} {{ activity.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if served.data %}
|
||||
|
||||
@@ -21,6 +21,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% render_table table %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if forloop.last %}<a class="btn btn-primary" href="{% url 'food:dish_list' activity_pk=activity.pk %}">{% trans "Return to dish list" %}</a> {% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -11,7 +11,7 @@ from member.models import Club
|
||||
|
||||
from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet, \
|
||||
DishViewSet, SupplementViewSet, OrderViewSet, FoodTransactionViewSet
|
||||
from ..models import Allergen, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order # TODO FoodTransaction
|
||||
from ..models import Allergen, BasicFood, TransformedFood, QRCode, Dish, Supplement, Order, FoodTransaction
|
||||
|
||||
|
||||
class TestFood(TestCase):
|
||||
@@ -120,7 +120,7 @@ class TestFood(TestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
'''class TestFoodOrder(TestCase):
|
||||
class TestFoodOrder(TestCase):
|
||||
"""
|
||||
Test Food Order
|
||||
"""
|
||||
@@ -198,7 +198,7 @@ class TestFood(TestCase):
|
||||
)
|
||||
|
||||
self.supplement = Supplement.objects.create(
|
||||
dish=self.dish,
|
||||
dish=self.second_dish,
|
||||
food=self.basicfood,
|
||||
price=100,
|
||||
)
|
||||
@@ -208,8 +208,6 @@ class TestFood(TestCase):
|
||||
activity=self.activity,
|
||||
dish=self.dish,
|
||||
)
|
||||
self.order.supplements.add(self.supplement)
|
||||
self.order.save()
|
||||
|
||||
def test_dish_list(self):
|
||||
"""
|
||||
@@ -300,8 +298,10 @@ class TestFood(TestCase):
|
||||
dish=self.second_dish.pk,
|
||||
supplements=self.supplement.pk
|
||||
))
|
||||
self.assertRedirects(response, reverse("food:food_list"))
|
||||
self.assertRedirects(response, reverse("food:dish_list", kwargs={"activity_pk": self.activity.pk}))
|
||||
self.assertTrue(Order.objects.filter(user=self.user, dish=self.second_dish, activity=self.activity).exists())
|
||||
order = Order.objects.get(user=self.user, dish=self.second_dish, activity=self.activity, supplements=self.supplement)
|
||||
self.assertTrue(FoodTransaction.objects.filter(order=order, amount=order.amount, valid=False, source=order.user.note).exists())
|
||||
|
||||
def test_order_list(self):
|
||||
"""
|
||||
@@ -337,7 +337,7 @@ class TestFood(TestCase):
|
||||
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())'''
|
||||
self.assertTrue(FoodTransaction.objects.filter(order=self.order, valid=False).exists())
|
||||
|
||||
|
||||
class TestFoodAPI(TestAPI):
|
||||
|
||||
@@ -20,19 +20,19 @@ urlpatterns = [
|
||||
path('add/ingredient/<int:pk>/', views.AddIngredientView.as_view(), name='add_ingredient'),
|
||||
path('redirect/', views.QRCodeRedirectView.as_view(), name='redirect_view'),
|
||||
# TODO not always store activity_pk in url
|
||||
# path('activity/<int:activity_pk>/dishes/add/', views.DishCreateView.as_view(), name='dish_create'),
|
||||
# path('activity/<int:activity_pk>/dishes/', views.DishListView.as_view(), name='dish_list'),
|
||||
# path('activity/<int:activity_pk>/dishes/<int:pk>/', views.DishDetailView.as_view(), name='dish_detail'),
|
||||
# path('activity/<int:activity_pk>/dishes/<int:pk>/update/', views.DishUpdateView.as_view(), name='dish_update'),
|
||||
# path('activity/<int:activity_pk>/dishes/<int:pk>/delete/', views.DishDeleteView.as_view(), name='dish_delete'),
|
||||
# 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/served', views.ServedOrderListView.as_view(), name='served_order_list'),
|
||||
# path('activity/<int:activity_pk>/kitchen/', views.KitchenView.as_view(), name='kitchen'),
|
||||
# path('recipe/add/', views.RecipeCreateView.as_view(), name='recipe_create'),
|
||||
# path('recipe/', views.RecipeListView.as_view(), name='recipe_list'),
|
||||
# path('recipe/<int:pk>/', views.RecipeDetailView.as_view(), name='recipe_detail'),
|
||||
# path('recipe/<int:pk>/update/', views.RecipeUpdateView.as_view(), name='recipe_update'),
|
||||
# path('update/ingredients/<int:pk>/recipe/', views.UseRecipeView.as_view(), name='recipe_use'),
|
||||
# path('ajax/get_ingredients/', views.get_ingredients_for_recipe, name='get_ingredients'),
|
||||
path('activity/<int:activity_pk>/dishes/add/', views.DishCreateView.as_view(), name='dish_create'),
|
||||
path('activity/<int:activity_pk>/dishes/', views.DishListView.as_view(), name='dish_list'),
|
||||
path('activity/<int:activity_pk>/dishes/<int:pk>/', views.DishDetailView.as_view(), name='dish_detail'),
|
||||
path('activity/<int:activity_pk>/dishes/<int:pk>/update/', views.DishUpdateView.as_view(), name='dish_update'),
|
||||
path('activity/<int:activity_pk>/dishes/<int:pk>/delete/', views.DishDeleteView.as_view(), name='dish_delete'),
|
||||
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/served', views.ServedOrderListView.as_view(), name='served_order_list'),
|
||||
path('activity/<int:activity_pk>/kitchen/', views.KitchenView.as_view(), name='kitchen'),
|
||||
path('recipe/add/', views.RecipeCreateView.as_view(), name='recipe_create'),
|
||||
path('recipe/', views.RecipeListView.as_view(), name='recipe_list'),
|
||||
path('recipe/<int:pk>/', views.RecipeDetailView.as_view(), name='recipe_detail'),
|
||||
path('recipe/<int:pk>/update/', views.RecipeUpdateView.as_view(), name='recipe_update'),
|
||||
path('update/ingredients/<int:pk>/recipe/', views.UseRecipeView.as_view(), name='recipe_use'),
|
||||
path('ajax/get_ingredients/', views.get_ingredients_for_recipe, name='get_ingredients'),
|
||||
]
|
||||
|
||||
@@ -777,11 +777,13 @@ class OrderCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
activity = Activity.objects.get(pk=self.kwargs["activity_pk"])
|
||||
|
||||
form.instance.activity = activity
|
||||
supplements = Supplement.objects.filter(pk__in=form.data.getlist('supplements')).all()
|
||||
|
||||
return super().form_valid(form)
|
||||
form.instance.save(supplements=supplements)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('food:food_list')
|
||||
return reverse_lazy('food:dish_list', kwargs={"activity_pk": self.kwargs["activity_pk"]})
|
||||
|
||||
|
||||
class OrderListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
|
||||
|
||||
Reference in New Issue
Block a user