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

Add interface to create and see note sheets

Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
This commit is contained in:
2022-08-18 14:27:02 +02:00
parent 44994a3ae7
commit 51e5e3669e
15 changed files with 591 additions and 49 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.27 on 2022-08-18 11:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0008_auto_20211005_1544'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2022, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

41
apps/sheets/forms.py Normal file
View File

@ -0,0 +1,41 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django import forms
from member.models import Club
from note_kfet.inputs import AmountInput, Autocomplete, DateTimePickerInput
from .models import Food, 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 MealForm(forms.ModelForm):
class Meta:
model = Meal
exclude = ('sheet', )
widgets = {
'content': forms.CheckboxSelectMultiple(),
'price': AmountInput(),
}

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.27 on 2022-08-18 10:25
# Generated by Django 2.2.27 on 2022-08-18 11:01
from django.db import migrations, models
import django.db.models.deletion
@ -10,8 +10,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('member', '0009_auto_20220818_1301'),
('note', '0006_trust'),
('member', '0009_auto_20220818_1225'),
]
operations = [
@ -93,6 +93,7 @@ class Migration(migrations.Migration):
('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',

View File

@ -2,6 +2,7 @@
# 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 _
@ -24,6 +25,18 @@ class Sheet(models.Model):
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")
@ -57,6 +70,9 @@ class Food(models.Model):
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")
@ -85,6 +101,9 @@ class FoodOption(models.Model):
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")
@ -117,6 +136,9 @@ class Meal(models.Model):
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 = _("meal")
verbose_name_plural = _("meals")

22
apps/sheets/tables.py Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,83 @@
{% 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;"{% 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;"{% endif %}>
{{ food }} ({{ food.price|pretty_money }})
{% 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.option_set.all %}
<ul>
{% for option in food.available_options %}
<li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;"{% endif %}>
{{ option }}{% if option.extra_price %} ({{ option.extra_price|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 class="btn btn-success">
{% trans "Order now" %}
</a>
</div>
</div>
{% endblock %}

View File

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

View File

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

View File

@ -3,7 +3,18 @@
from django.urls import path
from sheets.views import FoodCreateView, FoodUpdateView, MealCreateView, MealUpdateView, \
SheetCreateView, SheetDetailView, SheetListView, SheetUpdateView
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"),
]

View File

@ -1,2 +1,136 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.mixins import LoginRequiredMixin
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
from django_tables2 import SingleTableView
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import FoodForm, MealForm, SheetForm
from .models import Sheet, Food, Meal
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, UpdateView):
model = Sheet
form_class = SheetForm
extra_context = {"title": _("Update note sheet")}
class SheetDetailView(ProtectQuerysetMixin, 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 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.kwargs['pk'],))
class FoodUpdateView(ProtectQuerysetMixin, UpdateView):
model = Food
form_class = FoodForm
extra_context = {"title": _("Update food")}
def get_success_url(self):
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],))
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, 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,))