mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-09-28 20:33:32 +02:00
Merge branch 'small_features' into 'main'
Small features See merge request bde/nk20!352
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base_search.html" %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
@@ -44,6 +44,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<h3 class="card-header text-center">
|
<h3 class="card-header text-center">
|
||||||
{% trans "All activities" %}
|
{% trans "All activities" %}
|
||||||
</h3>
|
</h3>
|
||||||
{% render_table table %}
|
{% render_table all %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{% comment %}
|
{% comment %}
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
{% load i18n perms pretty_money %}
|
{% load i18n perms pretty_money dict_get %}
|
||||||
{% url 'activity:activity_detail' activity.pk as activity_detail_url %}
|
{% url 'activity:activity_detail' activity.pk as activity_detail_url %}
|
||||||
|
|
||||||
<div id="activity_info" class="card bg-light shadow mb-3">
|
<div id="activity_info" class="card bg-light shadow mb-3">
|
||||||
@@ -53,6 +53,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
|
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
{% if show_entries|dict_get:activity %}
|
||||||
|
<h2 class="text-center">
|
||||||
|
{{ entries_count|dict_get:activity }}
|
||||||
|
{% if entries_count|dict_get:activity >= 2 %}{% trans "entries" %}{% else %}{% trans "entry" %}{% endif %}
|
||||||
|
</h2>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
|
0
apps/activity/templatetags/__init__.py
Normal file
0
apps/activity/templatetags/__init__.py
Normal file
12
apps/activity/templatetags/dict_get.py
Normal file
12
apps/activity/templatetags/dict_get.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
|
||||||
|
def dict_get(d, key):
|
||||||
|
return d.get(key)
|
||||||
|
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
register.filter('dict_get', dict_get)
|
@@ -67,32 +67,65 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin
|
|||||||
tables = [
|
tables = [
|
||||||
lambda data: ActivityTable(data, prefix="all-"),
|
lambda data: ActivityTable(data, prefix="all-"),
|
||||||
lambda data: ActivityTable(data, prefix="upcoming-"),
|
lambda data: ActivityTable(data, prefix="upcoming-"),
|
||||||
|
lambda data: ActivityTable(data, prefix="search-"),
|
||||||
]
|
]
|
||||||
extra_context = {"title": _("Activities")}
|
extra_context = {"title": _("Activities")}
|
||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
return super().get_queryset(**kwargs).distinct()
|
"""
|
||||||
|
Filter the user list with the given pattern.
|
||||||
|
"""
|
||||||
|
return super().get_queryset().distinct()
|
||||||
|
|
||||||
def get_tables_data(self):
|
def get_tables_data(self):
|
||||||
# first table = all activities, second table = upcoming
|
# first table = all activities, second table = upcoming, third table = search
|
||||||
|
|
||||||
|
# table search
|
||||||
|
qs = self.get_queryset().order_by('-date_start')
|
||||||
|
if "search" in self.request.GET and self.request.GET['search']:
|
||||||
|
pattern = self.request.GET['search']
|
||||||
|
|
||||||
|
# check regex
|
||||||
|
valid_regex = is_regex(pattern)
|
||||||
|
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||||
|
prefix = '^' if valid_regex else ''
|
||||||
|
qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})
|
||||||
|
| Q(**{f'organizer__name{suffix}': prefix + pattern})
|
||||||
|
| Q(**{f'organizer__note__alias__name{suffix}': prefix + pattern}))
|
||||||
|
else:
|
||||||
|
qs = qs.none()
|
||||||
|
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Activity, 'view'))
|
||||||
|
|
||||||
return [
|
return [
|
||||||
self.get_queryset().order_by("-date_start"),
|
self.get_queryset().order_by("-date_start"),
|
||||||
Activity.objects.filter(date_end__gt=timezone.now())
|
Activity.objects.filter(date_end__gt=timezone.now())
|
||||||
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
|
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
|
||||||
.distinct()
|
.distinct()
|
||||||
.order_by("date_start")
|
.order_by("date_start"),
|
||||||
|
search_table,
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
tables = context["tables"]
|
tables = context["tables"]
|
||||||
for name, table in zip(["table", "upcoming"], tables):
|
for name, table in zip(["all", "upcoming", "table"], tables):
|
||||||
context[name] = table
|
context[name] = table
|
||||||
|
|
||||||
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
|
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
|
||||||
context["started_activities"] = started_activities
|
context["started_activities"] = started_activities
|
||||||
|
|
||||||
|
entries_count = {}
|
||||||
|
show_entries = {}
|
||||||
|
for activity in started_activities:
|
||||||
|
if activity.activity_type.manage_entries:
|
||||||
|
entries = Entry.objects.filter(activity=activity)
|
||||||
|
entries_count[activity] = entries.count()
|
||||||
|
|
||||||
|
show_entries[activity] = True
|
||||||
|
context["entries_count"] = entries_count
|
||||||
|
context["show_entries"] = show_entries
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@@ -137,6 +170,14 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMix
|
|||||||
"placeholder": ""
|
"placeholder": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.object.activity_type.manage_entries:
|
||||||
|
entries = Entry.objects.filter(activity=self.object)
|
||||||
|
context["entries_count"] = {self.object: entries.count()}
|
||||||
|
|
||||||
|
context["show_entries"] = {self.object: timezone.now() > timezone.localtime(self.object.date_start)}
|
||||||
|
else:
|
||||||
|
context["entries_count"] = {self.object: 0}
|
||||||
|
context["show_entries"] = {self.object: False}
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Food
|
from .models import Food
|
||||||
|
|
||||||
@@ -10,10 +11,25 @@ class FoodTable(tables.Table):
|
|||||||
"""
|
"""
|
||||||
List all foods.
|
List all foods.
|
||||||
"""
|
"""
|
||||||
|
qr_code_numbers = tables.Column(empty_values=(), verbose_name=_("QR Codes"), orderable=False)
|
||||||
|
|
||||||
|
date = tables.Column(empty_values=(), verbose_name=_("Arrival/creation date"), orderable=False)
|
||||||
|
|
||||||
|
def render_date(self, record):
|
||||||
|
if record.__class__.__name__ == "BasicFood":
|
||||||
|
return record.arrival_date.strftime("%d/%m/%Y %H:%M")
|
||||||
|
elif record.__class__.__name__ == "TransformedFood":
|
||||||
|
return record.creation_date.strftime("%d/%m/%Y %H:%M")
|
||||||
|
else:
|
||||||
|
return "--"
|
||||||
|
|
||||||
|
def render_qr_code_numbers(self, record):
|
||||||
|
return ", ".join(str(q.qr_code_number) for q in record.QR_code.all())
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
fields = ('name', 'owner', 'allergens', 'expiry_date')
|
fields = ('name', 'owner', 'qr_code_numbers', 'allergens', 'date', 'expiry_date')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': 'table-row',
|
'class': 'table-row',
|
||||||
'data-href': lambda record: 'detail/' + str(record.pk),
|
'data-href': lambda record: 'detail/' + str(record.pk),
|
||||||
|
@@ -34,6 +34,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="form-check">
|
||||||
|
<label for="stock_only" class="form-check-label">
|
||||||
|
<input id="stock_only" name="stock_only" type="checkbox" class="checkboxinput form-check-input" checked>
|
||||||
|
{% trans "Filter with only food in stock" %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<input id="searchbar" type="text" class="form-control"
|
<input id="searchbar" type="text" class="form-control"
|
||||||
placeholder="{% trans "Search by attribute such as name..." %}">
|
placeholder="{% trans "Search by attribute such as name..." %}">
|
||||||
</div>
|
</div>
|
||||||
@@ -114,7 +120,26 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
let old_pattern = null;
|
||||||
|
let searchbar_obj = $("#searchbar");
|
||||||
|
let stock_only_obj = $("#stock_only");
|
||||||
|
|
||||||
|
function reloadTable() {
|
||||||
|
let pattern = searchbar_obj.val();
|
||||||
|
|
||||||
|
$("#dynamic-table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + (
|
||||||
|
stock_only_obj.is(':checked') ? "" : "&stock=1") + " #dynamic-table");
|
||||||
|
}
|
||||||
|
|
||||||
|
searchbar_obj.keyup(reloadTable);
|
||||||
|
stock_only_obj.change(reloadTable);
|
||||||
|
|
||||||
|
$(document).on("click", ".table-row", function () {
|
||||||
|
window.document.location = $(this).data("href");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.getElementById('goButton').addEventListener('click', function(event) {
|
document.getElementById('goButton').addEventListener('click', function(event) {
|
||||||
|
@@ -65,9 +65,13 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
|
|||||||
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}))
|
| Q(**{f'owner__name{suffix}': prefix + pattern})
|
||||||
|
| Q(**{f'owner__note__alias__name{suffix}': prefix + pattern}))
|
||||||
else:
|
else:
|
||||||
qs = qs.none()
|
qs = qs.none()
|
||||||
|
if "stock" not in self.request.GET or not self.request.GET["stock"] == '1':
|
||||||
|
qs = qs.filter(end_of_life='')
|
||||||
|
|
||||||
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
|
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
|
||||||
# table open
|
# table open
|
||||||
open_table = self.get_queryset().order_by('expiry_date').filter(
|
open_table = self.get_queryset().order_by('expiry_date').filter(
|
||||||
@@ -95,6 +99,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
|
|||||||
owner=club, end_of_life='').filter(
|
owner=club, end_of_life='').filter(
|
||||||
PermissionBackend.filter_queryset(self.request, Food, 'view')
|
PermissionBackend.filter_queryset(self.request, Food, 'view')
|
||||||
))
|
))
|
||||||
|
|
||||||
return [search_table, open_table, served_table] + club_table
|
return [search_table, open_table, served_table] + club_table
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
@@ -218,7 +223,7 @@ class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
copy = self.request.GET.get('copy', None)
|
copy = self.request.GET.get('copy', None)
|
||||||
if copy is not None:
|
if copy is not None:
|
||||||
food = BasicFood.objects.get(pk=copy)
|
food = BasicFood.objects.get(pk=copy)
|
||||||
print(context['form'].fields)
|
|
||||||
for field in context['form'].fields:
|
for field in context['form'].fields:
|
||||||
if field == 'allergens':
|
if field == 'allergens':
|
||||||
context['form'].fields[field].initial = getattr(food, field).all()
|
context['form'].fields[field].initial = getattr(food, field).all()
|
||||||
|
@@ -29,8 +29,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
const input = document.querySelector("input[name='phone_number']");
|
const input = document.querySelector("input[name='phone_number']");
|
||||||
const form = document.querySelector("#profile-form");
|
const form = document.querySelector("#profile-form");
|
||||||
|
|
||||||
if (!input || !form) {
|
if (!input || !form || input.type === "hidden" || input.disabled || input.readOnly) {
|
||||||
console.error("Input phone_number ou form introuvable.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iti = window.intlTelInput(input, {
|
const iti = window.intlTelInput(input, {
|
||||||
|
@@ -67,6 +67,8 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
last.quantity = 1
|
last.quantity = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (last.note.club) {
|
if (last.note.club) {
|
||||||
$('#last_name').val(last.note.name)
|
$('#last_name').val(last.note.name)
|
||||||
$('#first_name').val(last.note.name)
|
$('#first_name').val(last.note.name)
|
||||||
@@ -111,6 +113,7 @@ $(document).ready(function () {
|
|||||||
dest.removeClass('d-none')
|
dest.removeClass('d-none')
|
||||||
$('#dest_note_list').removeClass('d-none')
|
$('#dest_note_list').removeClass('d-none')
|
||||||
$('#debit_type').addClass('d-none')
|
$('#debit_type').addClass('d-none')
|
||||||
|
$('#reason').val('')
|
||||||
|
|
||||||
$('#source_note_label').text(select_emitters_label)
|
$('#source_note_label').text(select_emitters_label)
|
||||||
$('#dest_note_label').text(select_receveirs_label)
|
$('#dest_note_label').text(select_receveirs_label)
|
||||||
@@ -134,6 +137,7 @@ $(document).ready(function () {
|
|||||||
dest.val('')
|
dest.val('')
|
||||||
dest.tooltip('hide')
|
dest.tooltip('hide')
|
||||||
$('#debit_type').addClass('d-none')
|
$('#debit_type').addClass('d-none')
|
||||||
|
$('#reason').val('Rechargement note')
|
||||||
|
|
||||||
$('#source_note_label').text(transfer_type_label)
|
$('#source_note_label').text(transfer_type_label)
|
||||||
$('#dest_note_label').text(select_receveir_label)
|
$('#dest_note_label').text(select_receveir_label)
|
||||||
@@ -162,6 +166,7 @@ $(document).ready(function () {
|
|||||||
dest.addClass('d-none')
|
dest.addClass('d-none')
|
||||||
dest.tooltip('hide')
|
dest.tooltip('hide')
|
||||||
$('#debit_type').removeClass('d-none')
|
$('#debit_type').removeClass('d-none')
|
||||||
|
$('#reason').val('')
|
||||||
|
|
||||||
$('#source_note_label').text(select_emitter_label)
|
$('#source_note_label').text(select_emitter_label)
|
||||||
$('#dest_note_label').text(transfer_type_label)
|
$('#dest_note_label').text(transfer_type_label)
|
||||||
|
@@ -4430,6 +4430,22 @@
|
|||||||
"description": "Modifier le type de caution de mon inscription WEI tant qu'elle n'est pas validée"
|
"description": "Modifier le type de caution de mon inscription WEI tant qu'elle n'est pas validée"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 298,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"wei",
|
||||||
|
"bus"
|
||||||
|
],
|
||||||
|
"query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}",
|
||||||
|
"type": "change",
|
||||||
|
"mask": 2,
|
||||||
|
"field": "information_json",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Modifier les informations du bus"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "permission.permission",
|
"model": "permission.permission",
|
||||||
"pk": 311,
|
"pk": 311,
|
||||||
@@ -4833,7 +4849,10 @@
|
|||||||
221,
|
221,
|
||||||
247,
|
247,
|
||||||
258,
|
258,
|
||||||
259
|
259,
|
||||||
|
260,
|
||||||
|
263,
|
||||||
|
265
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -4845,7 +4864,6 @@
|
|||||||
"name": "Pr\u00e9sident\u22c5e de club",
|
"name": "Pr\u00e9sident\u22c5e de club",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
62,
|
62,
|
||||||
135,
|
|
||||||
142
|
142
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -5122,7 +5140,8 @@
|
|||||||
289,
|
289,
|
||||||
290,
|
290,
|
||||||
291,
|
291,
|
||||||
293
|
293,
|
||||||
|
298
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5233,7 +5252,9 @@
|
|||||||
168,
|
168,
|
||||||
176,
|
176,
|
||||||
177,
|
177,
|
||||||
197
|
197,
|
||||||
|
311,
|
||||||
|
319
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5313,7 +5334,8 @@
|
|||||||
289,
|
289,
|
||||||
290,
|
290,
|
||||||
291,
|
291,
|
||||||
293
|
293,
|
||||||
|
298
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -28,8 +28,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
const input = document.querySelector("input[name='emergency_contact_phone']");
|
const input = document.querySelector("input[name='emergency_contact_phone']");
|
||||||
const form = document.querySelector("#registration-form");
|
const form = document.querySelector("#registration-form");
|
||||||
|
|
||||||
if (!input || !form) {
|
if (!input || !form || input.type === "hidden" || input.disabled || input.readOnly) {
|
||||||
console.error("Input phone_number ou form introuvable.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iti = window.intlTelInput(input, {
|
const iti = window.intlTelInput(input, {
|
||||||
|
@@ -39,8 +39,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
const input = document.querySelector("input[name='phone_number']");
|
const input = document.querySelector("input[name='phone_number']");
|
||||||
const form = document.querySelector("#profile_form");
|
const form = document.querySelector("#profile_form");
|
||||||
|
|
||||||
if (!input || !form) {
|
if (!input || !form || input.type === "hidden" || input.disabled || input.readOnly) {
|
||||||
console.error("Input phone_number ou form introuvable.");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iti = window.intlTelInput(input, {
|
const iti = window.intlTelInput(input, {
|
||||||
|
Reference in New Issue
Block a user