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

Compare commits

..

11 Commits

Author SHA1 Message Date
033ee010f8 Merge branch 'qrcode' into 'main'
Draft: Qrcode

See merge request bde/nk20!196
2025-04-30 20:58:53 +02:00
d58a299a8b Merge branch 'food_traceability' into 'main'
Add manage ingredient feature, fix some bug

See merge request bde/nk20!310
2025-04-30 12:38:32 +02:00
c4404ef995 Merge branch 'food_traceability' into 'main'
fix bug

See merge request bde/nk20!309
2025-04-28 13:35:17 +02:00
f0e9a7d3dc Merge branch 'food_traceability' into 'main'
Food traceability

See merge request bde/nk20!308
2025-04-27 09:36:46 +02:00
e6f3084588 Added a first pass for automatically entering an activity with a qrcode 2023-10-11 18:01:51 +02:00
145e55da75 remove useless comment 2022-03-22 15:06:04 +01:00
d3ba95cdca Insecable space for more clarity 2022-03-22 15:04:41 +01:00
8ffb0ebb56 Use DetailView 2022-03-22 14:59:01 +01:00
5038af9e34 Final html template 2022-03-22 14:58:26 +01:00
819b4214c9 Add QRCode View, URL and test template 2022-03-22 12:26:44 +01:00
b8a93b0b75 Add link to QR code 2022-03-19 16:25:15 +01:00
9 changed files with 92 additions and 15 deletions

View File

@ -38,6 +38,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</a> </a>
<input id="alias" type="text" class="form-control" placeholder="Nom/note ..."> <input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
<button id="trigger" class="btn btn-secondary">Click me !</button>
<hr> <hr>
@ -63,15 +64,46 @@ SPDX-License-Identifier: GPL-3.0-or-later
refreshBalance(); refreshBalance();
} }
function process_qrcode() {
let name = alias_obj.val();
$.get("/api/note/note?search=" + name + "&format=json").done(
function (res) {
let note = res.results[0];
$.post("/api/activity/entry/?format=json", {
csrfmiddlewaretoken: CSRF_TOKEN,
activity: {{ activity.id }},
note: note.id,
guest: null
}).done(function () {
addMsg(interpolate(gettext(
"Entry made for %s whose balance is %s €"),
[note.name, note.balance / 100]), "success", 4000);
reloadTable(true);
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}
alias_obj.keyup(function(event) { alias_obj.keyup(function(event) {
let code = event.originalEvent.keyCode let code = event.originalEvent.keyCode
if (65 <= code <= 122 || code === 13) { if (65 <= code <= 122 || code === 13) {
debounce(reloadTable)() debounce(reloadTable)()
} }
if (code === 0)
process_qrcode();
}); });
$(document).ready(init); $(document).ready(init);
alias_obj2 = document.getElementById("alias");
$("#trigger").click(function (e) {
addMsg("Clicked", "success", 1000);
alias_obj.val(alias_obj.val() + "\0");
alias_obj2.dispatchEvent(new KeyboardEvent('keyup'));
})
function init() { function init() {
$(".table-row").click(function (e) { $(".table-row").click(function (e) {
let target = e.target.parentElement; let target = e.target.parentElement;
@ -168,4 +200,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
}); });
} }
</script> </script>
{% endblock %} {% endblock %}

View File

@ -7,7 +7,7 @@ from api.viewsets import is_regex
from django_tables2.views import MultiTableMixin from django_tables2.views import MultiTableMixin
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseRedirect, Http404 from django.http import HttpResponseRedirect
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
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -63,8 +63,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
valid_regex = is_regex(pattern) valid_regex = is_regex(pattern)
suffix = '__iregex' if valid_regex else '__istartswith' suffix = '__iregex' if valid_regex else '__istartswith'
prefix = '^' if valid_regex else '' prefix = '^' if valid_regex else ''
qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}) qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}))
| Q(**{f'owner__name{suffix}': prefix + pattern}))
else: else:
qs = qs.none() qs = qs.none()
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view')) search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
@ -72,7 +71,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
open_table = self.get_queryset().order_by('expiry_date').filter( open_table = self.get_queryset().order_by('expiry_date').filter(
Q(polymorphic_ctype__model='transformedfood') Q(polymorphic_ctype__model='transformedfood')
| Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC')).filter( | Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC')).filter(
expiry_date__lt=timezone.now(), end_of_life='').filter( expiry_date__lt=timezone.now()).filter(
PermissionBackend.filter_queryset(self.request, Food, 'view')) PermissionBackend.filter_queryset(self.request, Food, 'view'))
# table served # table served
served_table = self.get_queryset().order_by('-pk').filter( served_table = self.get_queryset().order_by('-pk').filter(
@ -241,6 +240,11 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
form.instance.is_ready = False form.instance.is_ready = False
return super().form_valid(form) return super().form_valid(form)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['title'] += ' ' + self.object.name
return context
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
self.object.refresh_from_db() self.object.refresh_from_db()
return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})
@ -434,8 +438,6 @@ class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
return context return context
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
if Food.objects.filter(pk=kwargs['pk']).count() != 1:
return Http404
model = Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model model = Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model
if 'stop_redirect' in kwargs and kwargs['stop_redirect']: if 'stop_redirect' in kwargs and kwargs['stop_redirect']:
return super().get(*args, **kwargs) return super().get(*args, **kwargs)

View File

@ -60,7 +60,10 @@
{% if user_object.pk == user.pk %} {% if user_object.pk == user.pk %}
<div class="text-center"> <div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}"> <a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<i class="fa fa-cogs"></i>{% trans 'API token' %} <i class="fa fa-cogs"></i>&nbsp;{% trans 'API token' %}
</a>
<a class="small badge badge-secondary" href="{% url 'member:qr_code' user_object.pk %}">
<i class="fa fa-qrcode"></i>&nbsp;{% trans 'QR Code' %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -0,0 +1,36 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block content %}
<div class="card bg-light">
<h3 class="card-header text-center">
{% trans "QR Code for" %} {{ user_object.username }} ({{ user_object.first_name }} {{user_object.last_name }})
</h3>
<div class="text-center" id="qrcode">
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
var qrc = new QRCode(document.getElementById("qrcode"), {
text: "{{ user_object.pk }}\0",
width: 1024,
height: 1024
});
</script>
{% endblock %}
{% block extracss %}
<style>
img {
width: 100%
}
</style>
{% endblock %}

View File

@ -25,4 +25,5 @@ urlpatterns = [
path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"), path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"),
path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"), path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
path('user/<int:pk>/qr_code/', views.QRCodeView.as_view(), name='qr_code'),
] ]

View File

@ -402,6 +402,14 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
context['token'] = Token.objects.get_or_create(user=self.request.user)[0] context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
return context return context
class QRCodeView(LoginRequiredMixin, DetailView):
"""
Affiche le QR Code
"""
model = User
context_object_name = "user_object"
template_name = "member/qr_code.html"
extra_context = {"title": _("QR Code")}
# ******************************* # # ******************************* #
# CLUB # # CLUB #

View File

@ -19,9 +19,8 @@ Le modèle regroupe :
* Propriétaire (doit-être un Club) * Propriétaire (doit-être un Club)
* Allergènes (ManyToManyField) * Allergènes (ManyToManyField)
* date d'expiration * date d'expiration
* fin de vie * a été mangé (booléen)
* est prêt (booléen) * est prêt (booléen)
* consigne (pour les GCKs)
BasicFood BasicFood
~~~~~~~~~ ~~~~~~~~~
@ -41,7 +40,7 @@ Les TransformedFood correspondent aux produits préparés à la Kfet. Ils peuven
Le modèle regroupe : Le modèle regroupe :
* Durée de conservation (par défaut 3 jours) * Durée de consommation (par défaut 3 jours)
* Ingrédients (ManyToManyField vers Food) * Ingrédients (ManyToManyField vers Food)
* Date de création * Date de création
* Champs de Food * Champs de Food

View File

@ -12,7 +12,6 @@ Applications de la Note Kfet 2020
../api/index ../api/index
registration registration
logs logs
food
treasury treasury
wei wei
wrapped wrapped
@ -67,8 +66,6 @@ Applications facultatives
Serveur central d'authentification, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client. Serveur central d'authentification, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client.
* `Scripts <https://gitlab.crans.org/bde/nk20-scripts>`_ * `Scripts <https://gitlab.crans.org/bde/nk20-scripts>`_
Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc... Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc...
* `Food <food>`_ :
Gestion de la nourriture dans Kfet pour les clubs.
* `Treasury <treasury>`_ : * `Treasury <treasury>`_ :
Interface de gestion pour les trésorièr⋅es, émission de factures, remises de chèque, statistiques... Interface de gestion pour les trésorièr⋅es, émission de factures, remises de chèque, statistiques...
* `WEI <wei>`_ : * `WEI <wei>`_ :

View File

@ -183,7 +183,6 @@ Contributeur⋅rices
* korenst1 * korenst1
* nicomarg * nicomarg
* PAC * PAC
* Quark
* ÿnérant * ÿnérant