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

Merge branch 'master' into import_nk15

This commit is contained in:
Pierre-antoine Comby
2020-02-22 16:34:17 +01:00
68 changed files with 2147 additions and 830 deletions

View File

@ -1,5 +1,4 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'note.apps.NoteConfig'

View File

@ -1,5 +1,4 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin
@ -8,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
from .models.transactions import Transaction, TransactionTemplate
from .models.transactions import Transaction, TransactionCategory, TransactionTemplate
class AliasInlines(admin.TabularInline):
@ -25,7 +24,10 @@ class NoteAdmin(PolymorphicParentModelAdmin):
Parent regrouping all note types as children
"""
child_models = (NoteClub, NoteSpecial, NoteUser)
list_filter = (PolymorphicChildModelFilter, 'is_active',)
list_filter = (
PolymorphicChildModelFilter,
'is_active',
)
# Use a polymorphic list
list_display = ('pretty', 'balance', 'is_active')
@ -44,11 +46,12 @@ class NoteClubAdmin(PolymorphicChildModelAdmin):
"""
Child for a club note, see NoteAdmin
"""
inlines = (AliasInlines,)
inlines = (AliasInlines, )
# We can't change club after creation or the balance
readonly_fields = ('club', 'balance')
search_fields = ('club',)
search_fields = ('club', )
def has_add_permission(self, request):
"""
A club note should not be manually added
@ -67,7 +70,7 @@ class NoteSpecialAdmin(PolymorphicChildModelAdmin):
"""
Child for a special note, see NoteAdmin
"""
readonly_fields = ('balance',)
readonly_fields = ('balance', )
@admin.register(NoteUser)
@ -75,7 +78,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
"""
Child for an user note, see NoteAdmin
"""
inlines = (AliasInlines,)
inlines = (AliasInlines, )
# We can't change user after creation or the balance
readonly_fields = ('user', 'balance')
@ -101,7 +104,10 @@ class TransactionAdmin(admin.ModelAdmin):
list_display = ('created_at', 'poly_source', 'poly_destination',
'quantity', 'amount', 'transaction_type', 'valid')
list_filter = ('transaction_type', 'valid')
autocomplete_fields = ('source', 'destination',)
autocomplete_fields = (
'source',
'destination',
)
def poly_source(self, obj):
"""
@ -136,8 +142,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
Admin customisation for TransactionTemplate
"""
list_display = ('name', 'poly_destination', 'amount', 'template_type')
list_filter = ('template_type',)
autocomplete_fields = ('destination',)
list_filter = ('template_type', )
autocomplete_fields = ('destination', )
def poly_destination(self, obj):
"""
@ -146,3 +152,12 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
return str(obj.destination)
poly_destination.short_description = _('destination')
@admin.register(TransactionCategory)
class TransactionCategoryAdmin(admin.ModelAdmin):
"""
Admin customisation for TransactionTemplate
"""
list_display = ('name', )
list_filter = ('name', )

View File

View File

@ -0,0 +1,103 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
class NoteSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Notes.
The djangorestframework plugin will analyse the model `Note` and parse all fields in the API.
"""
class Meta:
model = Note
fields = '__all__'
extra_kwargs = {
'url': {
'view_name': 'project-detail',
'lookup_field': 'pk'
},
}
class NoteClubSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Club's notes.
The djangorestframework plugin will analyse the model `NoteClub` and parse all fields in the API.
"""
class Meta:
model = NoteClub
fields = '__all__'
class NoteSpecialSerializer(serializers.ModelSerializer):
"""
REST API Serializer for special notes.
The djangorestframework plugin will analyse the model `NoteSpecial` and parse all fields in the API.
"""
class Meta:
model = NoteSpecial
fields = '__all__'
class NoteUserSerializer(serializers.ModelSerializer):
"""
REST API Serializer for User's notes.
The djangorestframework plugin will analyse the model `NoteUser` and parse all fields in the API.
"""
class Meta:
model = NoteUser
fields = '__all__'
class AliasSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Aliases.
The djangorestframework plugin will analyse the model `Alias` and parse all fields in the API.
"""
class Meta:
model = Alias
fields = '__all__'
class NotePolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
Note: NoteSerializer,
NoteUser: NoteUserSerializer,
NoteClub: NoteClubSerializer,
NoteSpecial: NoteSpecialSerializer
}
class TransactionTemplateSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Transaction templates.
The djangorestframework plugin will analyse the model `TransactionTemplate` and parse all fields in the API.
"""
class Meta:
model = TransactionTemplate
fields = '__all__'
class TransactionSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Transactions.
The djangorestframework plugin will analyse the model `Transaction` and parse all fields in the API.
"""
class Meta:
model = Transaction
fields = '__all__'
class MembershipTransactionSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Membership transactions.
The djangorestframework plugin will analyse the model `MembershipTransaction` and parse all fields in the API.
"""
class Meta:
model = MembershipTransaction
fields = '__all__'

17
apps/note/api/urls.py Normal file
View File

@ -0,0 +1,17 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from .views import NotePolymorphicViewSet, AliasViewSet, \
TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet
def register_note_urls(router, path):
"""
Configure router for Note REST API.
"""
router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet)
router.register(path + '/transaction/template', TransactionTemplateViewSet)
router.register(path + '/transaction/membership', MembershipTransactionViewSet)

161
apps/note/api/views.py Normal file
View File

@ -0,0 +1,161 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db.models import Q
from rest_framework import viewsets
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \
NoteUserSerializer, AliasSerializer, \
TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer
class NoteViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `Note` objects, serialize it to JSON with the given serializer,
then render it on /api/note/note/
"""
queryset = Note.objects.all()
serializer_class = NoteSerializer
class NoteClubViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `NoteClub` objects, serialize it to JSON with the given serializer,
then render it on /api/note/club/
"""
queryset = NoteClub.objects.all()
serializer_class = NoteClubSerializer
class NoteSpecialViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `NoteSpecial` objects, serialize it to JSON with the given serializer,
then render it on /api/note/special/
"""
queryset = NoteSpecial.objects.all()
serializer_class = NoteSpecialSerializer
class NoteUserViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `NoteUser` objects, serialize it to JSON with the given serializer,
then render it on /api/note/user/
"""
queryset = NoteUser.objects.all()
serializer_class = NoteUserSerializer
class NotePolymorphicViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `Note` objects (with polymorhism), serialize it to JSON with the given serializer,
then render it on /api/note/note/
"""
queryset = Note.objects.all()
serializer_class = NotePolymorphicSerializer
def get_queryset(self):
"""
Parse query and apply filters.
:return: The filtered set of requested notes
"""
queryset = Note.objects.all()
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter(
Q(alias__name__regex=alias)
| Q(alias__normalized_name__regex=alias.lower()))
note_type = self.request.query_params.get("type", None)
if note_type:
types = str(note_type).lower()
if "user" in types:
queryset = queryset.filter(polymorphic_ctype__model="noteuser")
elif "club" in types:
queryset = queryset.filter(polymorphic_ctype__model="noteclub")
elif "special" in types:
queryset = queryset.filter(
polymorphic_ctype__model="notespecial")
else:
queryset = queryset.none()
return queryset
class AliasViewSet(viewsets.ModelViewSet):
"""
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/aliases/
"""
queryset = Alias.objects.all()
serializer_class = AliasSerializer
def get_queryset(self):
"""
Parse query and apply filters.
:return: The filtered set of requested aliases
"""
queryset = Alias.objects.all()
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.filter(
Q(name__regex=alias) | Q(normalized_name__regex=alias.lower()))
note_id = self.request.query_params.get("note", None)
if note_id:
queryset = queryset.filter(id=note_id)
note_type = self.request.query_params.get("type", None)
if note_type:
types = str(note_type).lower()
if "user" in types:
queryset = queryset.filter(
note__polymorphic_ctype__model="noteuser")
elif "club" in types:
queryset = queryset.filter(
note__polymorphic_ctype__model="noteclub")
elif "special" in types:
queryset = queryset.filter(
note__polymorphic_ctype__model="notespecial")
else:
queryset = queryset.none()
return queryset
class TransactionTemplateViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer,
then render it on /api/note/transaction/template/
"""
queryset = TransactionTemplate.objects.all()
serializer_class = TransactionTemplateSerializer
class TransactionViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer,
then render it on /api/note/transaction/transaction/
"""
queryset = Transaction.objects.all()
serializer_class = TransactionSerializer
class MembershipTransactionViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `MembershipTransaction` objects, serialize it to JSON with the given serializer,
then render it on /api/note/transaction/membership/
"""
queryset = MembershipTransaction.objects.all()
serializer_class = MembershipTransactionSerializer

View File

@ -1,5 +1,4 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig
@ -20,9 +19,9 @@ class NoteConfig(AppConfig):
"""
post_save.connect(
signals.save_user_note,
sender=settings.AUTH_USER_MODEL
sender=settings.AUTH_USER_MODEL,
)
post_save.connect(
signals.save_club_note,
sender='member.Club'
sender='member.Club',
)

View File

@ -1,9 +1,94 @@
#!/usr/bin/env python
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from dal import autocomplete
from django import forms
from .models import TransactionTemplate
from .models import Transaction, TransactionTemplate
class TransactionTemplateForm(forms.ModelForm):
class Meta:
model = TransactionTemplate
fields ='__all__'
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.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
}
class TransactionForm(forms.ModelForm):
def save(self, commit=True):
self.instance.transaction_type = 'transfert'
super().save(commit)
class Meta:
model = Transaction
fields = (
'source',
'destination',
'reason',
'amount',
)
# Voir ci-dessus
widgets = {
'source':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
'destination':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
}
class ConsoForm(forms.ModelForm):
def save(self, commit=True):
button: TransactionTemplate = TransactionTemplate.objects.filter(
name=self.data['button']).get()
self.instance.destination = button.destination
self.instance.amount = button.amount
self.instance.transaction_type = 'bouton'
self.instance.reason = button.name
super().save(commit)
class Meta:
model = Transaction
fields = ('source', )
# Le champ d'utilisateur 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 de note valides
widgets = {
'source':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={
'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
}

View File

@ -1,14 +1,13 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
from .transactions import MembershipTransaction, Transaction, \
TransactionTemplate
TransactionCategory, TransactionTemplate
__all__ = [
# Notes
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
# Transactions
'MembershipTransaction', 'Transaction', 'TransactionTemplate',
'MembershipTransaction', 'Transaction', 'TransactionCategory', 'TransactionTemplate',
]

View File

@ -1,5 +1,4 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import unicodedata
@ -10,7 +9,6 @@ from django.core.validators import RegexValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from polymorphic.models import PolymorphicModel
"""
Defines each note types
"""
@ -34,8 +32,7 @@ class Note(PolymorphicModel):
default=True,
help_text=_(
'Designates whether this note should be treated as active. '
'Unselect this instead of deleting notes.'
),
'Unselect this instead of deleting notes.'),
)
display_image = models.ImageField(
verbose_name=_('display image'),
@ -85,7 +82,8 @@ class Note(PolymorphicModel):
"""
Verify alias (simulate save)
"""
aliases = Alias.objects.filter(name=str(self))
aliases = Alias.objects.filter(
normalized_name=Alias.normalize(str(self)))
if aliases.exists():
# Alias exists, so check if it is linked to this note
if aliases.first().note != self:
@ -181,15 +179,15 @@ class Alias(models.Model):
validators=[
RegexValidator(
regex=settings.ALIAS_VALIDATOR_REGEX,
message=_('Invalid alias')
message=_('Invalid alias'),
)
] if settings.ALIAS_VALIDATOR_REGEX else []
] if settings.ALIAS_VALIDATOR_REGEX else [],
)
normalized_name = models.CharField(
max_length=255,
unique=True,
default='',
editable=False
editable=False,
)
note = models.ForeignKey(
Note,
@ -209,11 +207,9 @@ class Alias(models.Model):
Normalizes a string: removes most diacritics and does casefolding
"""
return ''.join(
char
for char in unicodedata.normalize('NFKD', string.casefold())
char for char in unicodedata.normalize('NFKD', string.casefold())
if all(not unicodedata.category(char).startswith(cat)
for cat in {'M', 'P', 'Z', 'C'})
).casefold()
for cat in {'M', 'P', 'Z', 'C'})).casefold()
def save(self, *args, **kwargs):
"""
@ -229,7 +225,13 @@ class Alias(models.Model):
raise ValidationError(_('Alias too long.'))
try:
if self != Alias.objects.get(normalized_name=normalized_name):
raise ValidationError(_('An alias with a similar name '
'already exists.'))
raise ValidationError(
_('An alias with a similar name '
'already exists.'))
except Alias.DoesNotExist:
pass
def delete(self, using=None, keep_parents=False):
if self.name == str(self.note):
raise ValidationError(_("You can't delete your main alias."))
return super().delete(using, keep_parents)

View File

@ -1,5 +1,4 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db import models
@ -7,16 +6,36 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.urls import reverse
from .notes import Note,NoteClub
from .notes import Note, NoteClub
"""
Defines transactions
"""
class TransactionCategory(models.Model):
"""
Defined a recurrent transaction category
Example: food, softs, ...
"""
name = models.CharField(
verbose_name=_("name"),
max_length=31,
unique=True,
)
class Meta:
verbose_name = _("transaction category")
verbose_name_plural = _("transaction categories")
def __str__(self):
return str(self.name)
class TransactionTemplate(models.Model):
"""
Defined a reccurent transaction
Defined a recurrent transaction
associated to selling something (a burger, a beer, ...)
"""
@ -35,9 +54,11 @@ class TransactionTemplate(models.Model):
verbose_name=_('amount'),
help_text=_('in centimes'),
)
template_type = models.CharField(
template_type = models.ForeignKey(
TransactionCategory,
on_delete=models.PROTECT,
verbose_name=_('type'),
max_length=31
max_length=31,
)
description = models.CharField(
@ -50,7 +71,7 @@ class TransactionTemplate(models.Model):
verbose_name_plural = _("transaction templates")
def get_absolute_url(self):
return reverse('note:template_update',args=(self.pk,))
return reverse('note:template_update', args=(self.pk, ))
class Transaction(models.Model):
@ -83,9 +104,7 @@ class Transaction(models.Model):
verbose_name=_('quantity'),
default=1,
)
amount = models.PositiveIntegerField(
verbose_name=_('amount'),
)
amount = models.PositiveIntegerField(verbose_name=_('amount'), )
transaction_type = models.CharField(
verbose_name=_('type'),
max_length=31,
@ -127,7 +146,7 @@ class Transaction(models.Model):
@property
def total(self):
return self.amount*self.quantity
return self.amount * self.quantity
class MembershipTransaction(Transaction):

View File

@ -1,5 +1,4 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@ -1,20 +1,26 @@
#!/usr/bin/env python
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import django_tables2 as tables
from django.db.models import F
from .models.transactions import Transaction
class HistoryTable(tables.Table):
class Meta:
attrs = {'class':'table table-bordered table-condensed table-striped table-hover'}
attrs = {
'class':
'table table-condensed table-striped table-hover'
}
model = Transaction
template_name = 'django_tables2/bootstrap.html'
sequence = ('...','total','valid')
template_name = 'django_tables2/bootstrap4.html'
sequence = ('...', 'total', 'valid')
total = tables.Column() #will use Transaction.total() !!
total = tables.Column() # will use Transaction.total() !!
def order_total(self, QuerySet, is_descending):
def order_total(self, queryset, is_descending):
# needed for rendering
QuerySet = QuerySet.annotate(
total=F('amount') * F('quantity')
).order_by(('-' if is_descending else '') + 'total')
return (QuerySet, True)
queryset = queryset.annotate(total=F('amount') * F('quantity')) \
.order_by(('-' if is_descending else '') + 'total')
return (queryset, True)

View File

@ -1,11 +1,21 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django import template
def pretty_money(value):
if value%100 == 0:
return "{:s}{:d}".format("- " if value < 0 else "", abs(value) // 100)
if value % 100 == 0:
return "{:s}{:d}".format(
"- " if value < 0 else "",
abs(value) // 100,
)
else:
return "{:s}{:d}{:02d}".format("- " if value < 0 else "", abs(value) // 100, abs(value) % 100)
return "{:s}{:d}{:02d}".format(
"- " if value < 0 else "",
abs(value) // 100,
abs(value) % 100,
)
register = template.Library()

View File

@ -1,15 +1,19 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path
from . import views
from .models import Note
app_name = 'note'
urlpatterns = [
path('transfer/', views.TransactionCreate.as_view(), name='transfer'),
path('buttons/create/',views.TransactionTemplateCreateView.as_view(),name='template_create'),
path('buttons/update/<int:pk>/',views.TransactionTemplateUpdateView.as_view(),name='template_update'),
path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list')
path('buttons/create/', views.TransactionTemplateCreateView.as_view(), name='template_create'),
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
path('consos/', views.ConsoView.as_view(), name='consos'),
# API for the note autocompleter
path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note), name='note_autocomplete'),
]

View File

@ -1,13 +1,16 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from dal import autocomplete
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, ListView, DetailView, UpdateView
from django.views.generic import CreateView, ListView, UpdateView
from .models import Transaction, TransactionTemplate, Alias
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
from .models import Transaction,TransactionTemplate
from .forms import TransactionTemplateForm
class TransactionCreate(LoginRequiredMixin, CreateView):
"""
@ -16,7 +19,7 @@ class TransactionCreate(LoginRequiredMixin, CreateView):
TODO: If user have sufficient rights, they can transfer from an other note
"""
model = Transaction
fields = ('destination', 'amount', 'reason')
form_class = TransactionForm
def get_context_data(self, **kwargs):
"""
@ -25,24 +28,127 @@ class TransactionCreate(LoginRequiredMixin, CreateView):
context = super().get_context_data(**kwargs)
context['title'] = _('Transfer money from your account '
'to one or others')
context['no_cache'] = True
return context
class TransactionTemplateCreateView(LoginRequiredMixin,CreateView):
def get_form(self, form_class=None):
"""
If the user has no right to transfer funds, then it won't have the choice of the source of the transfer.
"""
form = super().get_form(form_class)
if False: # TODO: fix it with "if %user has no right to transfer funds"
del form.fields['source']
return form
def form_valid(self, form):
"""
If the user has no right to transfer funds, then it will be the source of the transfer by default.
"""
if False: # TODO: fix it with "if %user has no right to transfer funds"
form.instance.source = self.request.user.note
return super().form_valid(form)
class NoteAutocomplete(autocomplete.Select2QuerySetView):
"""
Auto complete note by aliases
"""
def get_queryset(self):
"""
Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion.
Cette fonction récupère la requête, et renvoie la liste filtrée des aliases.
"""
# Un utilisateur non connecté n'a accès à aucune information
if not self.request.user.is_authenticated:
return Alias.objects.none()
qs = Alias.objects.all()
# self.q est le paramètre de la recherche
if self.q:
qs = qs.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q)))\
.order_by('normalized_name').distinct()
# Filtrage par type de note (user, club, special)
note_type = self.forwarded.get("note_type", None)
if note_type:
types = str(note_type).lower()
if "user" in types:
qs = qs.filter(note__polymorphic_ctype__model="noteuser")
elif "club" in types:
qs = qs.filter(note__polymorphic_ctype__model="noteclub")
elif "special" in types:
qs = qs.filter(note__polymorphic_ctype__model="notespecial")
else:
qs = qs.none()
return qs
def get_result_label(self, result):
# Gère l'affichage de l'alias dans la recherche
res = result.name
note_name = str(result.note)
if res != note_name:
res += " (aka. " + note_name + ")"
return res
def get_result_value(self, result):
# Le résultat renvoyé doit être l'identifiant de la note, et non de l'alias
return str(result.note.pk)
class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
"""
Create TransactionTemplate
"""
model = TransactionTemplate
form_class = TransactionTemplateForm
class TransactionTemplateListView(LoginRequiredMixin,ListView):
class TransactionTemplateListView(LoginRequiredMixin, ListView):
"""
List TransactionsTemplates
"""
model = TransactionTemplate
form_class = TransactionTemplateForm
class TransactionTemplateUpdateView(LoginRequiredMixin,UpdateView):
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
"""
"""
model = TransactionTemplate
form_class=TransactionTemplateForm
form_class = TransactionTemplateForm
class ConsoView(LoginRequiredMixin, CreateView):
"""
Consume
"""
model = Transaction
template_name = "note/conso_form.html"
form_class = ConsoForm
def get_context_data(self, **kwargs):
"""
Add some context variables in template such as page title
"""
context = super().get_context_data(**kwargs)
context['transaction_templates'] = TransactionTemplate.objects.all() \
.order_by('template_type')
context['title'] = _("Consommations")
# select2 compatibility
context['no_cache'] = True
return context
def get_success_url(self):
"""
When clicking a button, reload the same page
"""
return reverse('note:consos')