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

Merge remote-tracking branch 'origin/master' into import_nk15

# Conflicts:
#	apps/treasury/signals.py
This commit is contained in:
Yohann D'ANELLO
2020-05-07 19:01:23 +02:00
126 changed files with 11144 additions and 2592 deletions

View File

@ -3,6 +3,8 @@
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
from note_kfet.middlewares import get_current_authenticated_user
from permission.backends import PermissionBackend
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
@ -97,6 +99,35 @@ class NotePolymorphicSerializer(PolymorphicSerializer):
model = Note
class ConsumerSerializer(serializers.ModelSerializer):
"""
REST API Nested Serializer for Consumers.
return Alias, and the note Associated to it in
"""
note = serializers.SerializerMethodField()
email_confirmed = serializers.SerializerMethodField()
class Meta:
model = Alias
fields = '__all__'
def get_note(self, obj):
"""
Display information about the associated note
"""
# If the user has no right to see the note, then we only display the note identifier
if PermissionBackend.check_perm(get_current_authenticated_user(), "note.view_note", obj.note):
print(obj.pk)
return NotePolymorphicSerializer().to_representation(obj.note)
return dict(id=obj.id)
def get_email_confirmed(self, obj):
if isinstance(obj.note, NoteUser):
return obj.note.user.profile.email_confirmed
return True
class TemplateCategorySerializer(serializers.ModelSerializer):
"""
REST API Serializer for Transaction templates.

View File

@ -1,7 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from .views import NotePolymorphicViewSet, AliasViewSet, \
from .views import NotePolymorphicViewSet, AliasViewSet, ConsumerViewSet, \
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet
@ -11,6 +11,7 @@ def register_note_urls(router, path):
"""
router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet)
router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -10,8 +10,8 @@ from rest_framework.response import Response
from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
TransactionTemplateSerializer, TransactionPolymorphicSerializer
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
from ..models.notes import Note, Alias
from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
@ -90,6 +90,30 @@ class AliasViewSet(ReadProtectedModelViewSet):
return queryset
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
queryset = Alias.objects.all()
serializer_class = ConsumerSerializer
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
ordering_fields = ['name', 'normalized_name']
def get_queryset(self):
"""
Parse query and apply filters.
:return: The filtered set of requested aliases
"""
queryset = super().get_queryset()
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter(
Q(name__regex="^" + alias)
| Q(normalized_name__regex="^" + Alias.normalize(alias))
| Q(normalized_name__regex="^" + alias.lower()))
return queryset
class TemplateCategoryViewSet(ReadProtectedModelViewSet):
"""
REST API View set.

View File

@ -4,7 +4,7 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete
from note_kfet.inputs import Autocomplete, AmountInput
from .models import TransactionTemplate, NoteClub
@ -24,11 +24,6 @@ class TransactionTemplateForm(forms.ModelForm):
model = TransactionTemplate
fields = '__all__'
# Le champ de destination est remplacé par un champ d'auto-complétion.
# Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion
# et récupère les aliases valides
# Pour force le type d'une note, il faut rajouter le paramètre :
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
widgets = {
'destination':
Autocomplete(
@ -41,4 +36,5 @@ class TransactionTemplateForm(forms.ModelForm):
'placeholder': 'Note ...',
},
),
'amount': AmountInput(),
}

View File

@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
from polymorphic.models import PolymorphicModel
from .notes import Note, NoteClub, NoteSpecial
from ..templatetags.pretty_money import pretty_money
"""
Defines transactions
@ -198,6 +199,14 @@ class Transaction(PolymorphicModel):
self.source.save()
self.destination.save()
def delete(self, **kwargs):
"""
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
"""
self.valid = False
self.save(**kwargs)
super().delete(**kwargs)
@property
def total(self):
return self.amount * self.quantity
@ -206,6 +215,10 @@ class Transaction(PolymorphicModel):
def type(self):
return _('Transfer')
def __str__(self):
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
class RecurrentTransaction(Transaction):
"""
@ -214,8 +227,7 @@ class RecurrentTransaction(Transaction):
template = models.ForeignKey(
TransactionTemplate,
null=True,
on_delete=models.SET_NULL,
on_delete=models.PROTECT,
)
category = models.ForeignKey(
TemplateCategory,

View File

@ -10,14 +10,14 @@ def save_user_note(instance, raw, **_kwargs):
# When provisionning data, do not try to autocreate
return
if (instance.is_superuser or instance.profile.registration_valid) and instance.is_active:
if instance.is_superuser or instance.profile.registration_valid:
# Create note only when the registration is validated
from note.models import NoteUser
NoteUser.objects.get_or_create(user=instance)
instance.note.save()
def save_club_note(instance, created, raw, **_kwargs):
def save_club_note(instance, raw, **_kwargs):
"""
Hook to create and save a note when a club is updated
"""
@ -25,7 +25,6 @@ def save_club_note(instance, created, raw, **_kwargs):
# When provisionning data, do not try to autocreate
return
if created:
from .models import NoteClub
NoteClub.objects.create(club=instance)
from .models import NoteClub
NoteClub.objects.get_or_create(club=instance)
instance.note.save()

View File

@ -55,7 +55,7 @@ class HistoryTable(tables.Table):
"class": lambda record: str(record.valid).lower() + ' validate',
"data-toggle": "tooltip",
"title": lambda record: _("Click to invalidate") if record.valid else _("Click to validate"),
"onclick": lambda record: 'in_validate(' + str(record.id) + ', ' + str(record.valid).lower() + ')',
"onclick": lambda record: 'de_validate(' + str(record.id) + ', ' + str(record.valid).lower() + ')',
"onmouseover": lambda record: '$("#invalidity_reason_'
+ str(record.id) + '").show();$("#invalidity_reason_'
+ str(record.id) + '").focus();',
@ -129,13 +129,14 @@ class ButtonTable(tables.Table):
'table table-bordered condensed table-hover'
}
row_attrs = {
'class': lambda record: 'table-row ' + 'table-success' if record.display else 'table-danger',
'class': lambda record: 'table-row ' + ('table-success' if record.display else 'table-danger'),
'id': lambda record: "row-" + str(record.pk),
'data-href': lambda record: record.pk
}
model = TransactionTemplate
exclude = ('id',)
order_by = ('type', '-display', 'destination__name', 'name',)
edit = tables.LinkColumn('note:template_update',
args=[A('pk')],

View File

@ -1,5 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import json
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
@ -30,7 +31,7 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
table_class = HistoryTable
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).order_by("-id").all()[:20]
return super().get_queryset(**kwargs).order_by("-created_at", "-id").all()[:20]
def get_context_data(self, **kwargs):
"""
@ -80,6 +81,33 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
form_class = TransactionTemplateForm
success_url = reverse_lazy('note:template_list')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if "logs" in settings.INSTALLED_APPS:
from logs.models import Changelog
update_logs = Changelog.objects.filter(
model=ContentType.objects.get_for_model(TransactionTemplate),
instance_pk=self.object.pk,
action="edit",
)
price_history = []
for log in update_logs.all():
old_dict = json.loads(log.previous)
new_dict = json.loads(log.data)
old_price = old_dict["amount"]
new_price = new_dict["amount"]
if old_price != new_price:
price_history.append(dict(price=old_price, time=log.timestamp))
price_history.append(dict(price=self.object.amount, time=None))
price_history.reverse()
context["price_history"] = price_history
return context
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
@ -93,7 +121,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
table_class = HistoryTable
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).order_by("-id").all()[:20]
return super().get_queryset(**kwargs).order_by("-created_at", "-id").all()[:20]
def get_context_data(self, **kwargs):
"""