mirror of https://gitlab.crans.org/bde/nk20
Compare commits
9 Commits
6c9cf73848
...
24ac3ce45f
Author | SHA1 | Date |
---|---|---|
Yohann D'ANELLO | 24ac3ce45f | |
Yohann D'ANELLO | 018ca84e2d | |
Yohann D'ANELLO | 2851d7764c | |
Yohann D'ANELLO | c205219d47 | |
Yohann D'ANELLO | b0398e59b8 | |
Yohann D'ANELLO | 9c3e978a41 | |
Yohann D'ANELLO | 21f1347a60 | |
Yohann D'ANELLO | af857d6fae | |
Yohann D'ANELLO | acf7ecc4ae |
|
@ -4,7 +4,7 @@
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_session
|
||||||
|
|
||||||
|
|
||||||
class ReadProtectedModelViewSet(viewsets.ModelViewSet):
|
class ReadProtectedModelViewSet(viewsets.ModelViewSet):
|
||||||
|
@ -17,7 +17,8 @@ class ReadProtectedModelViewSet(viewsets.ModelViewSet):
|
||||||
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = get_current_authenticated_user()
|
user = self.request.user
|
||||||
|
get_current_session().setdefault("permission_mask", 42)
|
||||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,5 +32,6 @@ class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = get_current_authenticated_user()
|
user = self.request.user
|
||||||
|
get_current_session().setdefault("permission_mask", 42)
|
||||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
||||||
|
|
|
@ -11,10 +11,10 @@ from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
from registration.tokens import email_validation_token
|
from registration.tokens import email_validation_token
|
||||||
|
@ -34,7 +34,7 @@ class Profile(models.Model):
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
|
|
||||||
phone_number = models.CharField(
|
phone_number = PhoneNumberField(
|
||||||
verbose_name=_('phone number'),
|
verbose_name=_('phone number'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|
|
@ -69,7 +69,9 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
form.fields['email'].required = True
|
form.fields['email'].required = True
|
||||||
form.fields['email'].help_text = _("This address must be valid.")
|
form.fields['email'].help_text = _("This address must be valid.")
|
||||||
|
|
||||||
context['profile_form'] = self.profile_form(instance=context['user_object'].profile)
|
context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
|
||||||
|
data=self.request.POST if self.request.POST else None)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
@ -86,30 +88,33 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
data=self.request.POST,
|
data=self.request.POST,
|
||||||
instance=self.object.profile,
|
instance=self.object.profile,
|
||||||
)
|
)
|
||||||
if form.is_valid() and profile_form.is_valid():
|
profile_form.full_clean()
|
||||||
new_username = form.data['username']
|
if not profile_form.is_valid():
|
||||||
alias = Alias.objects.filter(name=new_username)
|
return super().form_invalid(form)
|
||||||
# Si le nouveau pseudo n'est pas un de nos alias,
|
|
||||||
# on supprime éventuellement un alias similaire pour le remplacer
|
|
||||||
if not alias.exists():
|
|
||||||
similar = Alias.objects.filter(
|
|
||||||
normalized_name=Alias.normalize(new_username))
|
|
||||||
if similar.exists():
|
|
||||||
similar.delete()
|
|
||||||
|
|
||||||
olduser = User.objects.get(pk=form.instance.pk)
|
new_username = form.data['username']
|
||||||
|
alias = Alias.objects.filter(name=new_username)
|
||||||
|
# Si le nouveau pseudo n'est pas un de nos alias,
|
||||||
|
# on supprime éventuellement un alias similaire pour le remplacer
|
||||||
|
if not alias.exists():
|
||||||
|
similar = Alias.objects.filter(
|
||||||
|
normalized_name=Alias.normalize(new_username))
|
||||||
|
if similar.exists():
|
||||||
|
similar.delete()
|
||||||
|
|
||||||
user = form.save(commit=False)
|
olduser = User.objects.get(pk=form.instance.pk)
|
||||||
profile = profile_form.save(commit=False)
|
|
||||||
profile.user = user
|
|
||||||
profile.save()
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
if olduser.email != user.email:
|
user = form.save(commit=False)
|
||||||
# If the user changed her/his email, then it is unvalidated and a confirmation link is sent.
|
profile = profile_form.save(commit=False)
|
||||||
user.profile.email_confirmed = False
|
profile.user = user
|
||||||
user.profile.save()
|
profile.save()
|
||||||
user.profile.send_email_validation_link()
|
user.save()
|
||||||
|
|
||||||
|
if olduser.email != user.email:
|
||||||
|
# If the user changed her/his email, then it is unvalidated and a confirmation link is sent.
|
||||||
|
user.profile.email_confirmed = False
|
||||||
|
user.profile.save()
|
||||||
|
user.profile.send_email_validation_link()
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from django.utils import timezone
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework.serializers import ListSerializer
|
||||||
from rest_polymorphic.serializers import PolymorphicSerializer
|
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||||
|
|
||||||
|
from member.api.serializers import MembershipSerializer
|
||||||
|
from member.models import Membership
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_authenticated_user
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
from rest_framework.utils import model_meta
|
||||||
|
|
||||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
|
||||||
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
|
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
|
||||||
|
@ -108,6 +113,8 @@ class ConsumerSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
email_confirmed = serializers.SerializerMethodField()
|
email_confirmed = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
membership = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -126,6 +133,17 @@ class ConsumerSerializer(serializers.ModelSerializer):
|
||||||
return obj.note.user.profile.email_confirmed
|
return obj.note.user.profile.email_confirmed
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def get_membership(self, obj):
|
||||||
|
if isinstance(obj.note, NoteUser):
|
||||||
|
memberships = Membership.objects.filter(
|
||||||
|
PermissionBackend.filter_queryset(get_current_authenticated_user(), Membership, "view")).filter(
|
||||||
|
user=obj.note.user,
|
||||||
|
club=2, # Kfet
|
||||||
|
).order_by("-date_start")
|
||||||
|
if memberships.exists():
|
||||||
|
return MembershipSerializer().to_representation(memberships.first())
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class TemplateCategorySerializer(serializers.ModelSerializer):
|
class TemplateCategorySerializer(serializers.ModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
@ -209,5 +227,23 @@ class TransactionPolymorphicSerializer(PolymorphicSerializer):
|
||||||
except ImportError: # Activity app is not loaded
|
except ImportError: # Activity app is not loaded
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
resource_type = attrs.pop(self.resource_type_field_name)
|
||||||
|
serializer = self._get_serializer_from_resource_type(resource_type)
|
||||||
|
if self.instance:
|
||||||
|
instance = self.instance
|
||||||
|
info = model_meta.get_field_info(instance)
|
||||||
|
for attr, value in attrs.items():
|
||||||
|
if attr in info.relations and info.relations[attr].to_many:
|
||||||
|
field = getattr(instance, attr)
|
||||||
|
field.set(value)
|
||||||
|
else:
|
||||||
|
setattr(instance, attr, value)
|
||||||
|
instance.validate(True)
|
||||||
|
else:
|
||||||
|
serializer.Meta.model(**attrs).validate(True)
|
||||||
|
attrs[self.resource_type_field_name] = resource_type
|
||||||
|
return super().validate(attrs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
|
|
|
@ -9,7 +9,7 @@ from rest_framework import viewsets
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||||
from note_kfet.middlewares import get_current_authenticated_user
|
from note_kfet.middlewares import get_current_session
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||||
|
@ -154,5 +154,7 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
||||||
search_fields = ['$reason', ]
|
search_fields = ['$reason', ]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = get_current_authenticated_user()
|
user = self.request.user
|
||||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))
|
get_current_session().setdefault("permission_mask", 42)
|
||||||
|
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))\
|
||||||
|
.order_by("created_at", "id")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
@ -164,28 +164,9 @@ class Transaction(PolymorphicModel):
|
||||||
models.Index(fields=['destination']),
|
models.Index(fields=['destination']),
|
||||||
]
|
]
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def validate(self, reset=False):
|
||||||
"""
|
previous_source_balance = self.source.balance
|
||||||
When saving, also transfer money between two notes
|
previous_dest_balance = self.destination.balance
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.source.is_active or not self.destination.is_active:
|
|
||||||
if 'force_insert' not in kwargs or not kwargs['force_insert']:
|
|
||||||
if 'force_update' not in kwargs or not kwargs['force_update']:
|
|
||||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
|
||||||
"or the destination note is not active."))
|
|
||||||
|
|
||||||
# If the aliases are not entered, we assume that the used alias is the name of the note
|
|
||||||
if not self.source_alias:
|
|
||||||
self.source_alias = str(self.source)
|
|
||||||
|
|
||||||
if not self.destination_alias:
|
|
||||||
self.destination_alias = str(self.destination)
|
|
||||||
|
|
||||||
if self.source.pk == self.destination.pk:
|
|
||||||
# When source == destination, no money is transferred
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
return
|
|
||||||
|
|
||||||
created = self.pk is None
|
created = self.pk is None
|
||||||
to_transfer = self.amount * self.quantity
|
to_transfer = self.amount * self.quantity
|
||||||
|
@ -204,14 +185,63 @@ class Transaction(PolymorphicModel):
|
||||||
# previously invalid
|
# previously invalid
|
||||||
self.invalidity_reason = None
|
self.invalidity_reason = None
|
||||||
|
|
||||||
# We save first the transaction, in case of the user has no right to transfer money
|
source_balance = self.source.balance
|
||||||
super().save(*args, **kwargs)
|
dest_balance = self.destination.balance
|
||||||
|
|
||||||
# Save notes
|
if reset:
|
||||||
self.source._force_save = True
|
self.source.balance = previous_source_balance
|
||||||
self.source.save()
|
self.destination.balance = previous_dest_balance
|
||||||
self.destination._force_save = True
|
|
||||||
self.destination.save()
|
if source_balance > 2147483647 or source_balance < -2147483648\
|
||||||
|
or dest_balance > 2147483647 or dest_balance < -2147483648:
|
||||||
|
raise ValidationError(_("The note balances must be between - 21 474 836.47 € and 21 474 836.47 €."))
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
When saving, also transfer money between two notes
|
||||||
|
"""
|
||||||
|
with transaction.atomic():
|
||||||
|
if self.pk:
|
||||||
|
self.refresh_from_db()
|
||||||
|
self.source.refresh_from_db()
|
||||||
|
self.destination.refresh_from_db()
|
||||||
|
self.validate(False)
|
||||||
|
|
||||||
|
if not self.source.is_active or not self.destination.is_active:
|
||||||
|
if 'force_insert' not in kwargs or not kwargs['force_insert']:
|
||||||
|
if 'force_update' not in kwargs or not kwargs['force_update']:
|
||||||
|
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||||
|
"or the destination note is not active."))
|
||||||
|
|
||||||
|
# If the aliases are not entered, we assume that the used alias is the name of the note
|
||||||
|
if not self.source_alias:
|
||||||
|
self.source_alias = str(self.source)
|
||||||
|
|
||||||
|
if not self.destination_alias:
|
||||||
|
self.destination_alias = str(self.destination)
|
||||||
|
|
||||||
|
if self.source.pk == self.destination.pk:
|
||||||
|
# When source == destination, no money is transferred
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log("Saving")
|
||||||
|
# We save first the transaction, in case of the user has no right to transfer money
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
self.log("Saved")
|
||||||
|
|
||||||
|
# Save notes
|
||||||
|
self.source._force_save = True
|
||||||
|
self.source.save()
|
||||||
|
self.log("Source saved")
|
||||||
|
self.destination._force_save = True
|
||||||
|
self.destination.save()
|
||||||
|
self.log("Destination saved")
|
||||||
|
|
||||||
|
def log(self, msg):
|
||||||
|
with open("/tmp/log", "a") as f:
|
||||||
|
f.write(msg + "\n")
|
||||||
|
|
||||||
def delete(self, **kwargs):
|
def delete(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -10,7 +10,7 @@ from time import sleep
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.mail import mail_admins
|
from django.core.mail import mail_admins
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.db.models import F, Q, Model
|
from django.db.models import F, Q, Model
|
||||||
from django.forms import model_to_dict
|
from django.forms import model_to_dict
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
@ -43,35 +43,28 @@ class InstancedPermission:
|
||||||
|
|
||||||
obj = copy(obj)
|
obj = copy(obj)
|
||||||
obj.pk = 0
|
obj.pk = 0
|
||||||
# Ensure previous models are deleted
|
with transaction.atomic():
|
||||||
for ignored in range(1000):
|
for o in self.model.model_class().objects.filter(pk=0).all():
|
||||||
if self.model.model_class().objects.filter(pk=0).exists():
|
o._force_delete = True
|
||||||
# If the object exists, that means that one permission is currently checked.
|
Model.delete(o)
|
||||||
# We wait before the other permission, at most 1 second.
|
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
|
||||||
sleep(0.001)
|
msg = "Lors de la vérification d'une permission d'ajout, un objet de clé primaire nulle était "\
|
||||||
continue
|
"encore présent.\n"\
|
||||||
break
|
"Type de permission : " + self.type + "\n"\
|
||||||
for o in self.model.model_class().objects.filter(pk=0).all():
|
"Modèle : " + str(self.model) + "\n"\
|
||||||
o._force_delete = True
|
"Objet trouvé : " + str(model_to_dict(o)) + "\n\n"\
|
||||||
Model.delete(o)
|
"--\nLe BDE"
|
||||||
# An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
|
mail_admins("[Note Kfet] Un objet a été supprimé de force", msg)
|
||||||
msg = "Lors de la vérification d'une permission d'ajout, un objet de clé primaire nulle était "\
|
|
||||||
"encore présent.\n"\
|
|
||||||
"Type de permission : " + self.type + "\n"\
|
|
||||||
"Modèle : " + str(self.model) + "\n"\
|
|
||||||
"Objet trouvé : " + str(model_to_dict(o)) + "\n\n"\
|
|
||||||
"--\nLe BDE"
|
|
||||||
mail_admins("[Note Kfet] Un objet a été supprimé de force", msg)
|
|
||||||
|
|
||||||
# Force insertion, no data verification, no trigger
|
# Force insertion, no data verification, no trigger
|
||||||
obj._force_save = True
|
obj._force_save = True
|
||||||
Model.save(obj, force_insert=True)
|
Model.save(obj, force_insert=True)
|
||||||
# We don't want log anything
|
# We don't want log anything
|
||||||
obj._no_log = True
|
obj._no_log = True
|
||||||
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
|
ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
|
||||||
# Delete testing object
|
# Delete testing object
|
||||||
obj._force_delete = True
|
obj._force_delete = True
|
||||||
Model.delete(obj)
|
Model.delete(obj)
|
||||||
|
|
||||||
with open("/tmp/log", "w") as f:
|
with open("/tmp/log", "w") as f:
|
||||||
f.write(str(obj) + ", " + str(obj.pk) + ", " + str(self.model.model_class().objects.filter(pk=0).exists()))
|
f.write(str(obj) + ", " + str(obj.pk) + ", " + str(self.model.model_class().objects.filter(pk=0).exists()))
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import django_tables2 as tables
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
|
from member.models import Membership
|
||||||
|
from note_kfet.middlewares import get_current_authenticated_user
|
||||||
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
|
class RightsTable(tables.Table):
|
||||||
|
"""
|
||||||
|
List managers of a club.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def render_user(self, value):
|
||||||
|
# If the user has the right, link the displayed user with the page of its detail.
|
||||||
|
s = value.username
|
||||||
|
if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value):
|
||||||
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
|
url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def render_club(self, value):
|
||||||
|
# If the user has the right, link the displayed user with the page of its detail.
|
||||||
|
s = value.name
|
||||||
|
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value):
|
||||||
|
s = format_html("<a href={url}>{name}</a>",
|
||||||
|
url=reverse_lazy('member:club_detail', kwargs={"pk": value.pk}), name=s)
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def render_roles(self, record):
|
||||||
|
# If the user has the right to manage the roles, display the link to manage them
|
||||||
|
roles = record.roles.all()
|
||||||
|
s = ", ".join(str(role) for role in roles)
|
||||||
|
if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record):
|
||||||
|
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))
|
||||||
|
+ "'>" + s + "</a>")
|
||||||
|
return s
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {
|
||||||
|
'class': 'table table-condensed table-striped table-hover',
|
||||||
|
'style': 'table-layout: fixed;'
|
||||||
|
}
|
||||||
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
|
fields = ('user.last_name', 'user.first_name', 'user', 'club', 'roles', )
|
||||||
|
model = Membership
|
|
@ -2,13 +2,16 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
from django.forms import HiddenInput
|
from django.forms import HiddenInput
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import UpdateView, TemplateView
|
from django.views.generic import UpdateView, TemplateView
|
||||||
from member.models import Membership
|
from member.models import Membership
|
||||||
|
|
||||||
from .backends import PermissionBackend
|
from .backends import PermissionBackend
|
||||||
from .models import Role
|
from .models import Role
|
||||||
|
from .tables import RightsTable
|
||||||
|
|
||||||
|
|
||||||
class ProtectQuerysetMixin:
|
class ProtectQuerysetMixin:
|
||||||
|
@ -59,4 +62,16 @@ class RightsView(TemplateView):
|
||||||
for role in roles:
|
for role in roles:
|
||||||
role.clubs = [membership.club for membership in active_memberships if role in membership.roles.all()]
|
role.clubs = [membership.club for membership in active_memberships if role in membership.roles.all()]
|
||||||
|
|
||||||
|
if self.request.user.is_authenticated:
|
||||||
|
special_memberships = Membership.objects.filter(
|
||||||
|
date_start__lte=timezone.now().date(),
|
||||||
|
date_end__gte=timezone.now().date(),
|
||||||
|
).filter(roles__in=Role.objects.filter(~(Q(name="Adhérent BDE")
|
||||||
|
| Q(name="Adhérent Kfet")
|
||||||
|
| Q(name="Membre de club")
|
||||||
|
| Q(name="Adhérent WEI")
|
||||||
|
| Q(name="1A")))).order_by("club", "user__last_name")\
|
||||||
|
.distinct().all()
|
||||||
|
context["special_memberships_table"] = RightsTable(special_memberships)
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -39,7 +39,7 @@ class UserCreateView(CreateView):
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["profile_form"] = self.second_form()
|
context["profile_form"] = self.second_form(self.request.POST if self.request.POST else None)
|
||||||
del context["profile_form"].fields["section"]
|
del context["profile_form"].fields["section"]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from crispy_forms.layout import Submit
|
from crispy_forms.layout import Submit
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.inputs import DatePickerInput, AmountInput
|
from note_kfet.inputs import DatePickerInput, AmountInput
|
||||||
|
|
||||||
|
@ -19,12 +18,13 @@ class InvoiceForm(forms.ModelForm):
|
||||||
|
|
||||||
# Django forms don't support date fields. We have to add it manually
|
# Django forms don't support date fields. We have to add it manually
|
||||||
date = forms.DateField(
|
date = forms.DateField(
|
||||||
initial=datetime.date.today,
|
initial=timezone.now,
|
||||||
widget=DatePickerInput()
|
widget=DatePickerInput(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean_date(self):
|
def clean_date(self):
|
||||||
self.instance.date = self.data.get("date")
|
self.instance.date = self.data.get("date")
|
||||||
|
return self.instance.date
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Invoice
|
model = Invoice
|
||||||
|
@ -36,7 +36,11 @@ class ProductForm(forms.ModelForm):
|
||||||
model = Product
|
model = Product
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
widgets = {
|
widgets = {
|
||||||
"amount": AmountInput()
|
"amount": AmountInput(
|
||||||
|
attrs={
|
||||||
|
"negative": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,6 +119,12 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Attach a special transaction to a remittance.
|
Attach a special transaction to a remittance.
|
||||||
"""
|
"""
|
||||||
|
remittance = forms.ModelChoiceField(
|
||||||
|
queryset=Remittance.objects.none(),
|
||||||
|
label=_("Remittance"),
|
||||||
|
empty_label=_("No attached remittance"),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
# Since we use a proxy model for special transactions, we add manually the fields related to the transaction
|
# Since we use a proxy model for special transactions, we add manually the fields related to the transaction
|
||||||
last_name = forms.CharField(label=_("Last name"))
|
last_name = forms.CharField(label=_("Last name"))
|
||||||
|
@ -123,7 +133,7 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
|
||||||
|
|
||||||
bank = forms.Field(label=_("Bank"))
|
bank = forms.Field(label=_("Bank"))
|
||||||
|
|
||||||
amount = forms.IntegerField(label=_("Amount"), min_value=0)
|
amount = forms.IntegerField(label=_("Amount"), min_value=0, widget=AmountInput(), disabled=True, required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -133,33 +143,19 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
|
||||||
|
|
||||||
self.fields["remittance"].queryset = Remittance.objects.filter(closed=False)
|
self.fields["remittance"].queryset = Remittance.objects.filter(closed=False)
|
||||||
|
|
||||||
def clean_last_name(self):
|
def clean(self):
|
||||||
"""
|
cleaned_data = super().clean()
|
||||||
Replace the first name in the information of the transaction.
|
self.instance.transaction.last_name = cleaned_data["last_name"]
|
||||||
"""
|
self.instance.transaction.first_name = cleaned_data["first_name"]
|
||||||
self.instance.transaction.last_name = self.data.get("last_name")
|
self.instance.transaction.bank = cleaned_data["bank"]
|
||||||
self.instance.transaction.clean()
|
return cleaned_data
|
||||||
|
|
||||||
def clean_first_name(self):
|
def save(self, commit=True):
|
||||||
"""
|
"""
|
||||||
Replace the last name in the information of the transaction.
|
Save the transaction and the remittance.
|
||||||
"""
|
"""
|
||||||
self.instance.transaction.first_name = self.data.get("first_name")
|
self.instance.transaction.save()
|
||||||
self.instance.transaction.clean()
|
return super().save(commit)
|
||||||
|
|
||||||
def clean_bank(self):
|
|
||||||
"""
|
|
||||||
Replace the bank in the information of the transaction.
|
|
||||||
"""
|
|
||||||
self.instance.transaction.bank = self.data.get("bank")
|
|
||||||
self.instance.transaction.clean()
|
|
||||||
|
|
||||||
def clean_amount(self):
|
|
||||||
"""
|
|
||||||
Replace the amount of the transaction.
|
|
||||||
"""
|
|
||||||
self.instance.transaction.amount = self.data.get("amount")
|
|
||||||
self.instance.transaction.clean()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SpecialTransactionProxy
|
model = SpecialTransactionProxy
|
||||||
|
|
|
@ -82,7 +82,7 @@ class Product(models.Model):
|
||||||
verbose_name=_("Designation"),
|
verbose_name=_("Designation"),
|
||||||
)
|
)
|
||||||
|
|
||||||
quantity = models.PositiveIntegerField(
|
quantity = models.IntegerField(
|
||||||
verbose_name=_("Quantity")
|
verbose_name=_("Quantity")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ class RemittanceTable(tables.Table):
|
||||||
model = Remittance
|
model = Remittance
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
fields = ('id', 'date', 'remittance_type', 'comment', 'count', 'amount', 'view',)
|
fields = ('id', 'date', 'remittance_type', 'comment', 'count', 'amount', 'view',)
|
||||||
|
order_by = ('-date',)
|
||||||
|
|
||||||
|
|
||||||
class SpecialTransactionTable(tables.Table):
|
class SpecialTransactionTable(tables.Table):
|
||||||
|
@ -100,7 +101,8 @@ class SpecialTransactionTable(tables.Table):
|
||||||
}
|
}
|
||||||
model = SpecialTransaction
|
model = SpecialTransaction
|
||||||
template_name = 'django_tables2/bootstrap4.html'
|
template_name = 'django_tables2/bootstrap4.html'
|
||||||
fields = ('id', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',)
|
fields = ('created_at', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',)
|
||||||
|
order_by = ('-created_at',)
|
||||||
|
|
||||||
|
|
||||||
class SogeCreditTable(tables.Table):
|
class SogeCreditTable(tables.Table):
|
||||||
|
|
|
@ -238,7 +238,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
|
||||||
|
|
||||||
closed_remittances = RemittanceTable(
|
closed_remittances = RemittanceTable(
|
||||||
data=Remittance.objects.filter(closed=True).filter(
|
data=Remittance.objects.filter(closed=True).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).reverse().all(),
|
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all(),
|
||||||
prefix="closed-remittances-",
|
prefix="closed-remittances-",
|
||||||
)
|
)
|
||||||
closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10)
|
closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10)
|
||||||
|
@ -281,8 +281,6 @@ class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView)
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
context["table"] = RemittanceTable(data=Remittance.objects.filter(
|
|
||||||
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all())
|
|
||||||
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
|
data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all()
|
PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all()
|
||||||
context["special_transactions"] = SpecialTransactionTable(
|
context["special_transactions"] = SpecialTransactionTable(
|
||||||
|
|
|
@ -8,6 +8,8 @@ from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
||||||
from member.models import Club, Membership
|
from member.models import Club, Membership
|
||||||
from note.models import MembershipTransaction
|
from note.models import MembershipTransaction
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
|
@ -223,7 +225,7 @@ class WEIRegistration(models.Model):
|
||||||
verbose_name=_("emergency contact name"),
|
verbose_name=_("emergency contact name"),
|
||||||
)
|
)
|
||||||
|
|
||||||
emergency_contact_phone = models.CharField(
|
emergency_contact_phone = PhoneNumberField(
|
||||||
max_length=32,
|
max_length=32,
|
||||||
verbose_name=_("emergency contact phone"),
|
verbose_name=_("emergency contact phone"),
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -37,6 +37,7 @@ INSTALLED_APPS = [
|
||||||
|
|
||||||
# External apps
|
# External apps
|
||||||
'mailer',
|
'mailer',
|
||||||
|
'phonenumber_field',
|
||||||
'polymorphic',
|
'polymorphic',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
|
@ -195,3 +196,7 @@ MEDIA_URL = '/media/'
|
||||||
# Profile Picture Settings
|
# Profile Picture Settings
|
||||||
PIC_WIDTH = 200
|
PIC_WIDTH = 200
|
||||||
PIC_RATIO = 1
|
PIC_RATIO = 1
|
||||||
|
|
||||||
|
|
||||||
|
PHONENUMBER_DB_FORMAT = 'NATIONAL'
|
||||||
|
PHONENUMBER_DEFAULT_REGION = 'FR'
|
||||||
|
|
|
@ -7,11 +7,13 @@ django-crispy-forms==1.7.2
|
||||||
django-extensions==2.1.9
|
django-extensions==2.1.9
|
||||||
django-filter==2.2.0
|
django-filter==2.2.0
|
||||||
django-mailer==2.0.1
|
django-mailer==2.0.1
|
||||||
|
django-phonenumber-field==4.0.0
|
||||||
django-polymorphic==2.0.3
|
django-polymorphic==2.0.3
|
||||||
django-tables2==2.1.0
|
django-tables2==2.1.0
|
||||||
docutils==0.14
|
docutils==0.14
|
||||||
idna==2.8
|
idna==2.8
|
||||||
oauthlib==3.1.0
|
oauthlib==3.1.0
|
||||||
|
phonenumbers==8.12.7
|
||||||
Pillow==7.1.2
|
Pillow==7.1.2
|
||||||
python3-openid==3.1.0
|
python3-openid==3.1.0
|
||||||
pytz==2019.1
|
pytz==2019.1
|
||||||
|
|
|
@ -105,8 +105,10 @@ function displayStyle(note) {
|
||||||
css += " text-danger";
|
css += " text-danger";
|
||||||
else if (balance < 0)
|
else if (balance < 0)
|
||||||
css += " text-warning";
|
css += " text-warning";
|
||||||
if (!note.email_confirmed)
|
else if (!note.email_confirmed)
|
||||||
css += " text-white bg-primary";
|
css += " text-white bg-primary";
|
||||||
|
else if (note.membership && note.membership.date_end < new Date().toISOString())
|
||||||
|
css += "text-white bg-info";
|
||||||
return css;
|
return css;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,13 +133,9 @@ function displayNote(note, alias, user_note_field = null, profile_pic_field = nu
|
||||||
$("#" + user_note_field).text(alias + (note.balance == null ? "" : (" :\n" + pretty_money(note.balance))));
|
$("#" + user_note_field).text(alias + (note.balance == null ? "" : (" :\n" + pretty_money(note.balance))));
|
||||||
if (profile_pic_field != null) {
|
if (profile_pic_field != null) {
|
||||||
$("#" + profile_pic_field).attr('src', img);
|
$("#" + profile_pic_field).attr('src', img);
|
||||||
$("#" + profile_pic_field).click(function () {
|
$("#" + profile_pic_field + "_link").attr('href', note.resourcetype === "NoteUser" ?
|
||||||
if (note.resourcetype === "NoteUser") {
|
"/accounts/user/" + note.user : note.resourcetype === "NoteClub" ?
|
||||||
document.location.href = "/accounts/user/" + note.user;
|
"/accounts/club/" + note.club : "#");
|
||||||
} else if (note.resourcetype === "NoteClub") {
|
|
||||||
document.location.href = "/accounts/club/" + note.club;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,6 +265,7 @@ function autoCompleteNote(field_id, note_list_id, notes, notes_display, alias_pr
|
||||||
consumers.results.forEach(function (consumer) {
|
consumers.results.forEach(function (consumer) {
|
||||||
let note = consumer.note;
|
let note = consumer.note;
|
||||||
note.email_confirmed = consumer.email_confirmed;
|
note.email_confirmed = consumer.email_confirmed;
|
||||||
|
note.membership = consumer.membership;
|
||||||
let extra_css = displayStyle(note);
|
let extra_css = displayStyle(note);
|
||||||
aliases_matched_html += li(alias_prefix + '_' + consumer.id,
|
aliases_matched_html += li(alias_prefix + '_' + consumer.id,
|
||||||
consumer.name,
|
consumer.name,
|
||||||
|
@ -371,8 +370,12 @@ function de_validate(id, validated) {
|
||||||
refreshHistory();
|
refreshHistory();
|
||||||
},
|
},
|
||||||
error: function (err) {
|
error: function (err) {
|
||||||
|
let errObj = JSON.parse(err.responseText);
|
||||||
|
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"];
|
||||||
|
if (!error)
|
||||||
|
error = err.responseText;
|
||||||
addMsg("Une erreur est survenue lors de la validation/dévalidation " +
|
addMsg("Une erreur est survenue lors de la validation/dévalidation " +
|
||||||
"de cette transaction : " + JSON.parse(err.responseText)["detail"], "danger", 10000);
|
"de cette transaction : " + error, "danger");
|
||||||
|
|
||||||
refreshBalance();
|
refreshBalance();
|
||||||
// error if this method doesn't exist. Please define it.
|
// error if this method doesn't exist. Please define it.
|
||||||
|
|
|
@ -145,6 +145,7 @@ function reset() {
|
||||||
$("#consos_list").html("");
|
$("#consos_list").html("");
|
||||||
$("#user_note").text("");
|
$("#user_note").text("");
|
||||||
$("#profile_pic").attr("src", "/media/pic/default.png");
|
$("#profile_pic").attr("src", "/media/pic/default.png");
|
||||||
|
$("#profile_pic_link").attr("href", "#");
|
||||||
refreshHistory();
|
refreshHistory();
|
||||||
refreshBalance();
|
refreshBalance();
|
||||||
}
|
}
|
||||||
|
@ -212,11 +213,14 @@ function consume(source, source_alias, dest, quantity, amount, reason, type, cat
|
||||||
if (newBalance <= -5000)
|
if (newBalance <= -5000)
|
||||||
addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " +
|
addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " +
|
||||||
"succès, mais la note émettrice " + source_alias + " est en négatif sévère.",
|
"succès, mais la note émettrice " + source_alias + " est en négatif sévère.",
|
||||||
"danger", 10000);
|
"danger", 30000);
|
||||||
else if (newBalance < 0)
|
else if (newBalance < 0)
|
||||||
addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " +
|
addMsg("Attention, La transaction depuis la note " + source_alias + " a été réalisée avec " +
|
||||||
"succès, mais la note émettrice " + source_alias + " est en négatif.",
|
"succès, mais la note émettrice " + source_alias + " est en négatif.",
|
||||||
"warning", 10000);
|
"warning", 30000);
|
||||||
|
if (source.note.membership && source.note.membership.date_end > new Date().toISOString())
|
||||||
|
addMsg("Attention : la note émettrice " + source.name + " n'est plus adhérente.",
|
||||||
|
"danger", 30000);
|
||||||
}
|
}
|
||||||
reset();
|
reset();
|
||||||
}).fail(function (e) {
|
}).fail(function (e) {
|
||||||
|
@ -240,7 +244,7 @@ function consume(source, source_alias, dest, quantity, amount, reason, type, cat
|
||||||
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", "danger", 10000);
|
addMsg("La transaction n'a pas pu être validée pour cause de solde insuffisant.", "danger", 10000);
|
||||||
}).fail(function () {
|
}).fail(function () {
|
||||||
reset();
|
reset();
|
||||||
errMsg(e.responseJSON, 10000);
|
errMsg(e.responseJSON);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ function reset(refresh=true) {
|
||||||
$("#bank").val("");
|
$("#bank").val("");
|
||||||
$("#user_note").val("");
|
$("#user_note").val("");
|
||||||
$("#profile_pic").attr("src", "/media/pic/default.png");
|
$("#profile_pic").attr("src", "/media/pic/default.png");
|
||||||
|
$("#profile_pic_link").attr("href", "#");
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
refreshBalance();
|
refreshBalance();
|
||||||
refreshHistory();
|
refreshHistory();
|
||||||
|
@ -213,6 +214,13 @@ $("#btn_transfer").click(function() {
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let amount = Math.floor(100 * amount_field.val());
|
||||||
|
if (amount > 2147483647) {
|
||||||
|
amount_field.addClass('is-invalid');
|
||||||
|
$("#amount-required").html("<strong>Le montant ne doit pas excéder 21474836.47 €.</strong>");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!reason_field.val()) {
|
if (!reason_field.val()) {
|
||||||
reason_field.addClass('is-invalid');
|
reason_field.addClass('is-invalid');
|
||||||
$("#reason-required").html("<strong>Ce champ est requis.</strong>");
|
$("#reason-required").html("<strong>Ce champ est requis.</strong>");
|
||||||
|
@ -232,7 +240,6 @@ $("#btn_transfer").click(function() {
|
||||||
if (error)
|
if (error)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
let amount = 100 * amount_field.val();
|
|
||||||
let reason = reason_field.val();
|
let reason = reason_field.val();
|
||||||
|
|
||||||
if ($("#type_transfer").is(':checked')) {
|
if ($("#type_transfer").is(':checked')) {
|
||||||
|
@ -253,6 +260,13 @@ $("#btn_transfer").click(function() {
|
||||||
"destination": dest.note.id,
|
"destination": dest.note.id,
|
||||||
"destination_alias": dest.name
|
"destination_alias": dest.name
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
|
if (source.note.membership && source.note.membership.date_end > new Date().toISOString())
|
||||||
|
addMsg("Attention : la note émettrice " + source.name + " n'est plus adhérente.",
|
||||||
|
"danger", 30000);
|
||||||
|
if (dest.note.membership && dest.note.membership.date_end > new Date().toISOString())
|
||||||
|
addMsg("Attention : la note destination " + dest.name + " n'est plus adhérente.",
|
||||||
|
"danger", 30000);
|
||||||
|
|
||||||
if (!isNaN(source.note.balance)) {
|
if (!isNaN(source.note.balance)) {
|
||||||
let newBalance = source.note.balance - source.quantity * dest.quantity * amount;
|
let newBalance = source.note.balance - source.quantity * dest.quantity * amount;
|
||||||
if (newBalance <= -5000) {
|
if (newBalance <= -5000) {
|
||||||
|
@ -277,7 +291,15 @@ $("#btn_transfer").click(function() {
|
||||||
+ " vers la note " + dest.name + " a été fait avec succès !", "success", 10000);
|
+ " vers la note " + dest.name + " a été fait avec succès !", "success", 10000);
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
}).fail(function () { // do it again but valid = false
|
}).fail(function (err) { // do it again but valid = false
|
||||||
|
let errObj = JSON.parse(err.responseText);
|
||||||
|
if (errObj["non_field_errors"]) {
|
||||||
|
addMsg("Le transfert de "
|
||||||
|
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
||||||
|
+ " vers la note " + dest.name + " a échoué : " + errObj["non_field_errors"], "danger");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$.post("/api/note/transaction/transaction/",
|
$.post("/api/note/transaction/transaction/",
|
||||||
{
|
{
|
||||||
"csrfmiddlewaretoken": CSRF_TOKEN,
|
"csrfmiddlewaretoken": CSRF_TOKEN,
|
||||||
|
@ -298,9 +320,13 @@ $("#btn_transfer").click(function() {
|
||||||
+ " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger", 10000);
|
+ " vers la note " + dest.name + " a échoué : Solde insuffisant", "danger", 10000);
|
||||||
reset();
|
reset();
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
|
let errObj = JSON.parse(err.responseText);
|
||||||
|
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"]
|
||||||
|
if (!error)
|
||||||
|
error = err.responseText;
|
||||||
addMsg("Le transfert de "
|
addMsg("Le transfert de "
|
||||||
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
+ pretty_money(source.quantity * dest.quantity * amount) + " de la note " + source.name
|
||||||
+ " vers la note " + dest.name + " a échoué : " + err.responseText, "danger");
|
+ " vers la note " + dest.name + " a échoué : " + error, "danger");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -308,19 +334,22 @@ $("#btn_transfer").click(function() {
|
||||||
} else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) {
|
} else if ($("#type_credit").is(':checked') || $("#type_debit").is(':checked')) {
|
||||||
let special_note = $("#credit_type").val();
|
let special_note = $("#credit_type").val();
|
||||||
let user_note;
|
let user_note;
|
||||||
|
let alias;
|
||||||
let given_reason = reason;
|
let given_reason = reason;
|
||||||
let source_id, dest_id;
|
let source_id, dest_id;
|
||||||
if ($("#type_credit").is(':checked')) {
|
if ($("#type_credit").is(':checked')) {
|
||||||
user_note = dests_notes_display[0].note.id;
|
user_note = dests_notes_display[0].note;
|
||||||
|
alias = dests_notes_display[0].name;
|
||||||
source_id = special_note;
|
source_id = special_note;
|
||||||
dest_id = user_note;
|
dest_id = user_note.id;
|
||||||
reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase();
|
reason = "Crédit " + $("#credit_type option:selected").text().toLowerCase();
|
||||||
if (given_reason.length > 0)
|
if (given_reason.length > 0)
|
||||||
reason += " (" + given_reason + ")";
|
reason += " (" + given_reason + ")";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
user_note = sources_notes_display[0].note.id;
|
user_note = sources_notes_display[0].note;
|
||||||
source_id = user_note;
|
alias = sources_notes_display[0].name;
|
||||||
|
source_id = user_note.id;
|
||||||
dest_id = special_note;
|
dest_id = special_note;
|
||||||
reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase();
|
reason = "Retrait " + $("#credit_type option:selected").text().toLowerCase();
|
||||||
if (given_reason.length > 0)
|
if (given_reason.length > 0)
|
||||||
|
@ -336,18 +365,23 @@ $("#btn_transfer").click(function() {
|
||||||
"polymorphic_ctype": SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
"polymorphic_ctype": SPECIAL_TRANSFER_POLYMORPHIC_CTYPE,
|
||||||
"resourcetype": "SpecialTransaction",
|
"resourcetype": "SpecialTransaction",
|
||||||
"source": source_id,
|
"source": source_id,
|
||||||
"source_alias": sources_notes_display.length ? sources_notes_display[0].name : null,
|
"source_alias": sources_notes_display.length ? alias : null,
|
||||||
"destination": dest_id,
|
"destination": dest_id,
|
||||||
"destination_alias": dests_notes_display.length ? dests_notes_display[0].name : null,
|
"destination_alias": dests_notes_display.length ? alias : null,
|
||||||
"last_name": $("#last_name").val(),
|
"last_name": $("#last_name").val(),
|
||||||
"first_name": $("#first_name").val(),
|
"first_name": $("#first_name").val(),
|
||||||
"bank": $("#bank").val()
|
"bank": $("#bank").val()
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
addMsg("Le crédit/retrait a bien été effectué !", "success", 10000);
|
addMsg("Le crédit/retrait a bien été effectué !", "success", 10000);
|
||||||
|
if (user_note.membership && user_note.membership.date_end > new Date().toISOString())
|
||||||
|
addMsg("Attention : la note " + alias + " n'est plus adhérente.", "danger", 10000);
|
||||||
reset();
|
reset();
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
addMsg("Le crédit/retrait a échoué : " + JSON.parse(err.responseText)["detail"],
|
let errObj = JSON.parse(err.responseText);
|
||||||
"danger", 10000);
|
let error = errObj["detail"] ? errObj["detail"] : errObj["non_field_errors"]
|
||||||
|
if (!error)
|
||||||
|
error = err.responseText;
|
||||||
|
addMsg("Le crédit/retrait a échoué : " + error, "danger", 10000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input class="form-control mx-auto d-block" type="number" min="0" step="0.01"
|
<input class="form-control mx-auto d-block" type="number" {% if not widget.attrs.negative %}min="0"{% endif %} step="0.01"
|
||||||
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
||||||
name="{{ widget.name }}"
|
name="{{ widget.name }}"
|
||||||
{% for name, value in widget.attrs.items %}
|
{% for name, value in widget.attrs.items %}
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
{# User details column #}
|
{# User details column #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card border-success shadow mb-4 text-center">
|
<div class="card border-success shadow mb-4 text-center">
|
||||||
<img src="/media/pic/default.png"
|
<a id="profile_pic_link" href="#">
|
||||||
id="profile_pic" alt="" class="card-img-top">
|
<img src="/media/pic/default.png"
|
||||||
|
id="profile_pic" alt="" class="card-img-top">
|
||||||
|
</a>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<span id="user_note"></span>
|
<span id="user_note"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -36,8 +36,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3" id="note_infos_div">
|
<div class="col-md-3" id="note_infos_div">
|
||||||
<div class="card border-success shadow mb-4">
|
<div class="card border-success shadow mb-4">
|
||||||
<img src="/media/pic/default.png"
|
<a id="profile_pic_link" href="#"><img src="/media/pic/default.png"
|
||||||
id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block">
|
id="profile_pic" alt="" class="img-fluid rounded mx-auto d-block"></a>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<span id="user_note"></span>
|
<span id="user_note"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<h2>{% trans "Users that have surnormal rights" %}</h2>
|
||||||
|
{% render_table special_memberships_table %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h2>{% trans "Roles description" %}</h2>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label for="owned_only" class="form-check-label">
|
<label for="owned_only" class="form-check-label">
|
||||||
|
@ -11,6 +20,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% regroup active_memberships by roles as memberships_per_role %}
|
{% regroup active_memberships by roles as memberships_per_role %}
|
||||||
{% for role in roles %}
|
{% for role in roles %}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Account activation" %}</h2>
|
<h2>{% trans "Account activation" %}</h2>
|
||||||
|
|
Loading…
Reference in New Issue