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

Compare commits

..

8 Commits

Author SHA1 Message Date
92b2c4690f Merge branch 'qrcode' into 'main'
Draft: Qrcode

See merge request bde/nk20!196
2024-08-30 15:24:47 +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
17 changed files with 4479 additions and 4575 deletions

View File

@ -7,6 +7,21 @@ stages:
variables: variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Debian Bullseye
py39-django42:
stage: test
image: debian:bullseye
before_script:
- >
apt-get update &&
apt-get install --no-install-recommends -y
python3-django python3-django-crispy-forms
python3-django-extensions python3-django-filters python3-django-polymorphic
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py39-django42
# Ubuntu 22.04 # Ubuntu 22.04
py310-django42: py310-django42:
stage: test stage: test
@ -39,6 +54,8 @@ py311-django42:
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py311-django42 script: tox -e py311-django42
linters: linters:
stage: quality-assurance stage: quality-assurance
image: debian:bookworm image: debian:bookworm

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;

View File

@ -265,11 +265,12 @@ class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
# Keep only users that have a note # Keep only users that have a note
note_qs = note_qs.filter(note__noteuser__isnull=False) note_qs = note_qs.filter(note__noteuser__isnull=False)
# Keep only valid members # Keep only members
note_qs = note_qs.filter( note_qs = note_qs.filter(
note__noteuser__user__memberships__club=activity.attendees_club, note__noteuser__user__memberships__club=activity.attendees_club,
note__noteuser__user__memberships__date_start__lte=timezone.now(), note__noteuser__user__memberships__date_start__lte=timezone.now(),
note__noteuser__user__memberships__date_end__gte=timezone.now()).exclude(note__inactivity_reason='forced') note__noteuser__user__memberships__date_end__gte=timezone.now(),
)
# Filter with permission backend # Filter with permission backend
note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")) note_qs = note_qs.filter(PermissionBackend.filter_queryset(self.request, Alias, "view"))
@ -329,7 +330,7 @@ class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
activities_open = Activity.objects.filter(open=True, activity_type__manage_entries=True).filter( activities_open = Activity.objects.filter(open=True).filter(
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all() PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
context["activities_open"] = [a for a in activities_open context["activities_open"] = [a for a in activities_open
if PermissionBackend.check_perm(self.request, if PermissionBackend.check_perm(self.request,

View File

@ -44,7 +44,6 @@ class ProfileForm(forms.ModelForm):
""" """
A form for the extras field provided by the :model:`member.Profile` model. A form for the extras field provided by the :model:`member.Profile` model.
""" """
# Remove widget=forms.HiddenInput() if you want to use report frequency.
report_frequency = forms.IntegerField(required=False, initial=0, label=_("Report frequency")) report_frequency = forms.IntegerField(required=False, initial=0, label=_("Report frequency"))
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date")) last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
@ -77,8 +76,7 @@ class ProfileForm(forms.ModelForm):
class Meta: class Meta:
model = Profile model = Profile
fields = '__all__' fields = '__all__'
# Remove ml_[asso]_registration from exclude if the concerned association uses nk20 to manage its mailing list. exclude = ('user', 'email_confirmed', 'registration_valid', )
exclude = ('user', 'email_confirmed', 'registration_valid', 'ml_sport_registration', )
class ImageForm(forms.Form): class ImageForm(forms.Form):

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

@ -26,7 +26,6 @@ from note_kfet.middlewares import _set_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from django import forms
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
CustomAuthenticationForm, MembershipRolesForm CustomAuthenticationForm, MembershipRolesForm
@ -73,24 +72,11 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
form.fields['email'].required = True form.fields['email'].required = True
form.fields['email'].help_text = _("This address must be valid.") form.fields['email'].help_text = _("This address must be valid.")
profile_form = self.profile_form(instance=context['user_object'].profile, if PermissionBackend.check_perm(self.request, "member.change_profile", context['user_object'].profile):
data=self.request.POST if self.request.POST else None) context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
data=self.request.POST if self.request.POST else None)
if not self.object.profile.report_frequency: if not self.object.profile.report_frequency:
del profile_form.fields["last_report"] del context['profile_form'].fields["last_report"]
fields_to_check = list(profile_form.fields.keys())
fields_modifiable = False
# Delete the fields for which the user does not have the permission to modify
for field_name in fields_to_check:
if not PermissionBackend.check_perm(self.request, f"member.change_profile_{field_name}", context['user_object'].profile):
profile_form.fields[field_name].widget = forms.HiddenInput()
else:
fields_modifiable = True
if fields_modifiable:
context['profile_form'] = profile_form
return context return context
@ -402,6 +388,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

@ -31,4 +31,3 @@ class RoleAdmin(admin.ModelAdmin):
Admin customisation for Role Admin customisation for Role
""" """
list_display = ('name', ) list_display = ('name', )
filter_horizontal = ('permissions',)

File diff suppressed because it is too large Load Diff

View File

@ -300,13 +300,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
# join_bde = True # join_bde = True
# join_kfet = True # join_kfet = True
if not (join_bde or any(b for _, b in join_clubs)): if not join_bde:
# This software belongs to the BDE. # This software belongs to the BDE.
form.add_error('join_bde', _("You must join a club.")) form.add_error('join_bde', _("You must join the BDE."))
return super().form_invalid(form)
if join_kfet and not join_bde:
form.add_error('join_bde', _("You must also join the parent club BDE."))
return super().form_invalid(form) return super().form_invalid(form)
# Calculate required registration fee # Calculate required registration fee

View File

@ -276,22 +276,11 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys
surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys
# Don't manage hardcoded people # Don't manage hardcoded people
# surveys = [s for s in surveys if s.bus_id != None] surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
# surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
# surveys = [s for s in surveys if s.registration.user_id in free_users]
# hardcoded_first_year_mb = WEIMembership.objects.filter(bus != None,registration__first_year=True)
# hardcoded_first_year = hardcoded_first_year_mb.values_list('user__id', 'bus__id')
hardcoded_first_year_mb = WEIMembership.objects.filter(registration__first_year=True)
hardcoded_first_year = {mb.user.id if mb.bus else None: mb.bus.id if mb.bus else None for mb in hardcoded_first_year_mb}
# Reset previous algorithm run # Reset previous algorithm run
for survey in surveys: for survey in surveys:
survey.free() survey.free()
if survey.registration.user_id in hardcoded_first_year.keys():
survey.select_bus(hardcoded_first_year[survey.registration.user_id])
survey.save() survey.save()
non_men = [s for s in surveys if s.registration.gender != 'male'] non_men = [s for s in surveys if s.registration.gender != 'male']
@ -302,7 +291,9 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
non_men_total = registrations.filter(~Q(gender='male')).count() non_men_total = registrations.filter(~Q(gender='male')).count()
for bus in self.get_buses(): for bus in self.get_buses():
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count() free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk) # Remove hardcoded people
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
registration__information_json__icontains="hardcoded").count()
quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats) quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats)
tqdm_obj = None tqdm_obj = None
@ -317,6 +308,9 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
for bus in self.get_buses(): for bus in self.get_buses():
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count() free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk) free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk)
# Remove hardcoded people
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
registration__information_json__icontains="hardcoded").count()
quotas[bus] = free_seats quotas[bus] = free_seats
if display_tqdm: if display_tqdm:

View File

@ -795,11 +795,11 @@ msgstr "Masque de permissions"
#: apps/member/forms.py:46 #: apps/member/forms.py:46
msgid "Report frequency" msgid "Report frequency"
msgstr "Fréquence des relevés (en jours)" msgstr "Fréquence des rapports (en jours)"
#: apps/member/forms.py:48 #: apps/member/forms.py:48
msgid "Last report date" msgid "Last report date"
msgstr "Date de dernier relevé" msgstr "Date de dernier rapport"
#: apps/member/forms.py:52 #: apps/member/forms.py:52
msgid "" msgid ""
@ -1032,7 +1032,6 @@ msgid ""
"Register on the mailing list to stay informed of the sport events of the " "Register on the mailing list to stay informed of the sport events of the "
"campus (1 mail/week)" "campus (1 mail/week)"
msgstr "" msgstr ""
"S'inscrire sur la liste de diffusion pour rester informé·e des actualités "
"sportives sur le campus (1 mail par semaine)" "sportives sur le campus (1 mail par semaine)"
#: apps/member/models.py:113 #: apps/member/models.py:113
@ -1045,11 +1044,11 @@ msgstr ""
#: apps/member/models.py:117 #: apps/member/models.py:117
msgid "report frequency (in days)" msgid "report frequency (in days)"
msgstr "fréquence des relevés (en jours)" msgstr "fréquence des rapports (en jours)"
#: apps/member/models.py:122 #: apps/member/models.py:122
msgid "last report date" msgid "last report date"
msgstr "date de dernier relevé" msgstr "date de dernier rapport"
#: apps/member/models.py:127 #: apps/member/models.py:127
msgid "email confirmed" msgid "email confirmed"

View File

@ -9,7 +9,6 @@ MAILTO=notekfet2020@lists.crans.org
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -c 1 -v 0 * * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -c 1 -v 0
* * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -c 1 -v 0 * * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -c 1 -v 0
00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -v 0 00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -v 0
00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log -r failure 30 -v 0
# Faire une sauvegarde de la base de données # Faire une sauvegarde de la base de données
00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db 00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db
# Vérifier la cohérence de la base et mailer en cas de problème # Vérifier la cohérence de la base et mailer en cas de problème
@ -26,5 +25,3 @@ MAILTO=notekfet2020@lists.crans.org
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons -v 0 00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons -v 0
# Vider les tokens Oauth2 # Vider les tokens Oauth2
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0 00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0
# Envoyer la liste des abonnés à la NL BDA
00 8 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art -v 0

View File

@ -225,7 +225,6 @@ MEDIA_URL = '/media/'
# Use mailer in production to place emails in a queue before sending them to avoid spam # Use mailer in production to place emails in a queue before sending them to avoid spam
EMAIL_BACKEND = 'mailer.backend.DbBackend' EMAIL_BACKEND = 'mailer.backend.DbBackend'
MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
MAILER_EMAIL_MAX_BATCH = 10
EMAIL_USE_SSL = os.getenv('EMAIL_USE_SSL', False) EMAIL_USE_SSL = os.getenv('EMAIL_USE_SSL', False)
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.org') EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.org')
EMAIL_PORT = os.getenv('EMAIL_PORT', 25) EMAIL_PORT = os.getenv('EMAIL_PORT', 25)

View File

@ -66,13 +66,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a> <a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
</li> </li>
{% endif %} {% endif %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
{% url 'food:food_list' as url %} {% url 'food:food_list' as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-cutlery"></i> {% trans 'Food' %}</a> <a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-cutlery"></i> {% trans 'Food' %}</a>
</li> </li>
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated and user|is_member:"Kfet" %}
<li class="nav-item"> <li class="nav-item">
{% url 'note:transfer' as url %} {% url 'note:transfer' as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a> <a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a>
@ -158,7 +158,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</nav> </nav>
<div class="{% block containertype %}container{% endblock %} my-3"> <div class="{% block containertype %}container{% endblock %} my-3">
<div id="messages"> <div id="messages">
{% if user.is_authenticated %} {% if user.is_authenticated %}
{% if not user|is_member:"BDE" %} {% if not user|is_member:"BDE" %}
<div class="alert alert-danger"> <div class="alert alert-danger">

View File

@ -1,14 +1,14 @@
[tox] [tox]
envlist = envlist =
# Debian Bullseye Python
py39-django42
# Ubuntu 22.04 Python # Ubuntu 22.04 Python
py310-django42 py310-django42
# Debian Bookworm Python # Debian Bookworm Python
py311-django42 py311-django42
# Ubuntu 24.04 Python
py312-django42
linters linters
skipsdist = True skipsdist = True