mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 09:58:23 +02:00
Merge remote-tracking branch 'origin/master' into import_nk15
# Conflicts: # apps/treasury/signals.py
This commit is contained in:
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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(),
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
|
@ -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')],
|
||||
|
@ -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):
|
||||
"""
|
||||
|
Reference in New Issue
Block a user