mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 09:58:23 +02:00
Merge branch 'beta' into migration-django-4-2
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'note.apps.NoteConfig'
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
|
||||
|
@ -1,16 +1,16 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import Q
|
||||
from django.core.exceptions import ValidationError
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||
from api.filters import RegexSafeSearchFilter
|
||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet, \
|
||||
is_regex
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
|
||||
@ -29,7 +29,7 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
queryset = Note.objects.order_by('id')
|
||||
serializer_class = NotePolymorphicSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter, OrderingFilter]
|
||||
filterset_fields = ['alias__name', 'polymorphic_ctype', 'is_active', 'balance', 'last_negative', 'created_at', ]
|
||||
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model',
|
||||
'$noteuser__user__last_name', '$noteuser__user__first_name', '$noteuser__user__email',
|
||||
@ -48,10 +48,14 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet):
|
||||
.distinct()
|
||||
|
||||
alias = self.request.query_params.get("alias", ".*")
|
||||
# Check if this is a valid regex. If not, we won't check regex
|
||||
valid_regex = is_regex(alias)
|
||||
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||
alias_prefix = '^' if valid_regex else ''
|
||||
queryset = queryset.filter(
|
||||
Q(alias__name__iregex="^" + alias)
|
||||
| Q(alias__normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
| Q(alias__normalized_name__iregex="^" + alias.lower())
|
||||
Q(**{f"alias__name{suffix}": alias_prefix + alias})
|
||||
| Q(**{f"alias__normalized_name{suffix}": alias_prefix + Alias.normalize(alias)})
|
||||
| Q(**{f"alias__normalized_name{suffix}": alias_prefix + alias.lower()})
|
||||
)
|
||||
|
||||
return queryset.order_by("id")
|
||||
@ -65,7 +69,7 @@ class TrustViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
queryset = Trust.objects
|
||||
serializer_class = TrustSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
search_fields = ['$trusting__alias__name', '$trusting__alias__normalized_name',
|
||||
'$trusted__alias__name', '$trusted__alias__normalized_name']
|
||||
filterset_fields = ['trusting', 'trusting__noteuser__user', 'trusted', 'trusted__noteuser__user']
|
||||
@ -91,11 +95,11 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
REST API View set.
|
||||
The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/note/aliases/
|
||||
then render it on /api/note/alias/
|
||||
"""
|
||||
queryset = Alias.objects
|
||||
serializer_class = AliasSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user',
|
||||
'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
@ -126,18 +130,22 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
|
||||
alias = self.request.query_params.get("alias", None)
|
||||
if alias:
|
||||
# Check if this is a valid regex. If not, we won't check regex
|
||||
valid_regex = is_regex(alias)
|
||||
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||
alias_prefix = '^' if valid_regex else ''
|
||||
queryset = queryset.filter(
|
||||
name__iregex="^" + alias
|
||||
**{f"name{suffix}": alias_prefix + alias}
|
||||
).union(
|
||||
queryset.filter(
|
||||
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
Q(**{f"normalized_name{suffix}": alias_prefix + Alias.normalize(alias)})
|
||||
& ~Q(**{f"name{suffix}": alias_prefix + alias})
|
||||
),
|
||||
all=True).union(
|
||||
queryset.filter(
|
||||
Q(normalized_name__iregex="^" + alias.lower())
|
||||
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||
& ~Q(name__iregex="^" + alias)
|
||||
Q(**{f"normalized_name{suffix}": "^" + alias.lower()})
|
||||
& ~Q(**{f"normalized_name{suffix}": "^" + Alias.normalize(alias)})
|
||||
& ~Q(**{f"name{suffix}": "^" + alias})
|
||||
),
|
||||
all=True)
|
||||
|
||||
@ -147,7 +155,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
queryset = Alias.objects
|
||||
serializer_class = ConsumerSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||
filter_backends = [RegexSafeSearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||
filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user',
|
||||
'note__noteclub__club', 'note__polymorphic_ctype__model', ]
|
||||
@ -166,11 +174,7 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||
|
||||
alias = self.request.query_params.get("alias", None)
|
||||
# Check if this is a valid regex. If not, we won't check regex
|
||||
try:
|
||||
re.compile(alias)
|
||||
valid_regex = True
|
||||
except (re.error, TypeError):
|
||||
valid_regex = False
|
||||
valid_regex = is_regex(alias)
|
||||
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||
alias_prefix = '^' if valid_regex else ''
|
||||
queryset = queryset.prefetch_related('note')
|
||||
@ -198,7 +202,7 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
queryset = TemplateCategory.objects.order_by('name')
|
||||
serializer_class = TemplateCategorySerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
|
||||
filterset_fields = ['name', 'templates', 'templates__name']
|
||||
search_fields = ['$name', '$templates__name', ]
|
||||
|
||||
@ -211,7 +215,7 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
queryset = TransactionTemplate.objects.order_by('name')
|
||||
serializer_class = TransactionTemplateSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['name', 'amount', 'display', 'category', 'category__name', ]
|
||||
search_fields = ['$name', '$category__name', ]
|
||||
ordering_fields = ['amount', ]
|
||||
@ -225,7 +229,7 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
||||
"""
|
||||
queryset = Transaction.objects.order_by('-created_at')
|
||||
serializer_class = TransactionPolymorphicSerializer
|
||||
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||
filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
|
||||
'destination', 'destination_alias', 'destination__alias__name',
|
||||
'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount',
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from datetime import datetime
|
||||
|
||||
|
@ -18,6 +18,7 @@ def create_special_notes(apps, schema_editor):
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('note', '0001_initial'),
|
||||
('logs', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.utils import timezone
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
// Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// When a transaction is performed, lock the interface to prevent spam clicks.
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import html
|
||||
|
@ -22,8 +22,8 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
|
||||
est inférieur à 0 € depuis plus de 24h.
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérent⋅es dont le solde
|
||||
est inférieur à 0 €.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@ -43,4 +43,4 @@
|
||||
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -9,7 +9,7 @@ Ce mail t'a été envoyé parce que le solde de ta Note Kfet
|
||||
|
||||
Ton solde actuel est de {{ note.balance|pretty_money }}.
|
||||
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde
|
||||
Par ailleurs, le BDE ne sert pas d'alcool aux adhérent·e·s dont le solde
|
||||
est inférieur à 0 € depuis plus de 24h.
|
||||
|
||||
Si tu ne comprends pas ton solde, tu peux consulter ton historique
|
||||
@ -22,4 +22,4 @@ virement bancaire.
|
||||
--
|
||||
Le BDE
|
||||
|
||||
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import template
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import template
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.tests import TestAPI
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
@ -13,6 +13,7 @@ from django.views.generic import CreateView, UpdateView, DetailView
|
||||
from django.urls import reverse_lazy
|
||||
from django_tables2 import SingleTableView
|
||||
from activity.models import Entry
|
||||
from api.viewsets import is_regex
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
from note_kfet.inputs import AmountInput
|
||||
@ -89,11 +90,15 @@ class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, Sing
|
||||
qs = super().get_queryset().distinct()
|
||||
if "search" in self.request.GET:
|
||||
pattern = self.request.GET["search"]
|
||||
|
||||
# Check if this is a valid regex. If not, we won't check regex
|
||||
valid_regex = is_regex(pattern)
|
||||
suffix = "__iregex" if valid_regex else "__icontains"
|
||||
qs = qs.filter(
|
||||
Q(name__iregex=pattern)
|
||||
| Q(destination__club__name__iregex=pattern)
|
||||
| Q(category__name__iregex=pattern)
|
||||
| Q(description__iregex=pattern)
|
||||
Q(**{f"name{suffix}": pattern})
|
||||
| Q(**{f"destination__club__name{suffix}": pattern})
|
||||
| Q(**{f"category__name{suffix}": pattern})
|
||||
| Q(**{f"description{suffix}": pattern})
|
||||
)
|
||||
|
||||
qs = qs.order_by('-display', 'category__name', 'destination__club__name', 'name')
|
||||
@ -223,7 +228,10 @@ class TransactionSearchView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView
|
||||
if "type" in data and data["type"]:
|
||||
transactions = transactions.filter(polymorphic_ctype__in=data["type"])
|
||||
if "reason" in data and data["reason"]:
|
||||
transactions = transactions.filter(reason__iregex=data["reason"])
|
||||
# Check if this is a valid regex. If not, we won't check regex
|
||||
valid_regex = is_regex(data["reason"])
|
||||
suffix = "__iregex" if valid_regex else "__istartswith"
|
||||
transactions = transactions.filter(Q(**{f"reason{suffix}": data["reason"]}))
|
||||
if "valid" in data and data["valid"]:
|
||||
transactions = transactions.filter(valid=data["valid"])
|
||||
if "amount_gte" in data and data["amount_gte"]:
|
||||
|
Reference in New Issue
Block a user