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

Compare commits

..

12 Commits

Author SHA1 Message Date
9b8caa7fa1 Merge branch 'beta' into 'master'
Add animated profile picture support

See merge request bde/nk20!116
2020-09-07 21:48:28 +02:00
f1dac73c08 Merge branch 'beta' into 'master'
First release

See merge request bde/nk20!115
2020-09-06 17:03:51 +02:00
1ed74021a2 Merge branch 'beta' into 'master'
v1.0.0

See merge request bde/nk20!114
2020-09-06 16:06:26 +02:00
81ad38927d Merge branch 'beta' into 'master'
Fix note pictures, better ansible

See merge request bde/nk20!113
2020-09-06 13:09:28 +02:00
2f6c7ed156 Merge branch 'beta' into 'master'
Remove padding around note picture

See merge request bde/nk20!111
2020-09-05 19:41:28 +02:00
2b2dde85dc Merge branch 'beta' into 'master'
Few fixes

Closes #61

See merge request bde/nk20!110
2020-09-05 14:52:29 +02:00
863150d200 Merge branch 'beta' into 'master'
Last deployment fixes

See merge request bde/nk20!109
2020-09-05 11:21:07 +02:00
ebe6ce61e4 Merge branch 'beta' into 'master'
Fix Ansible script for production

See merge request bde/nk20!108
2020-09-04 22:42:42 +02:00
3c636e9f71 Merge branch 'beta' into 'master'
Beta

Closes #59

See merge request bde/nk20!107
2020-09-04 22:32:14 +02:00
d054d58661 Merge branch 'beta' into 'master'
Better CI

See merge request bde/nk20!105
2020-09-02 13:17:37 +02:00
9f42ecb97a Merge branch 'beta' into 'master'
Beta

Closes #52, #54, #55, #56 et #57

See merge request bde/nk20!104
2020-09-02 10:00:22 +02:00
1fd7d76412 Merge branch 'beta' into 'master'
Fix import

See merge request bde/nk20!88
2020-08-01 18:11:01 +02:00
40 changed files with 640 additions and 4095 deletions

View File

@ -7,7 +7,7 @@ from threading import Thread
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models, transaction
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@ -123,7 +123,6 @@ class Activity(models.Model):
verbose_name=_('open'),
)
@transaction.atomic
def save(self, *args, **kwargs):
"""
Update the activity wiki page each time the activity is updated (validation, change description, ...)
@ -195,8 +194,8 @@ class Entry(models.Model):
else _("Entry for {note} to the activity {activity}").format(
guest=str(self.guest), note=str(self.note), activity=str(self.activity))
@transaction.atomic
def save(self, *args, **kwargs):
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
if qs.exists():
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
@ -261,7 +260,6 @@ class Guest(models.Model):
except AttributeError:
return False
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
one_year = timedelta(days=365)

View File

@ -7,7 +7,6 @@ from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import F, Q
from django.http import HttpResponse
from django.urls import reverse_lazy
@ -45,7 +44,6 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
date_end=timezone.now(),
)
@transaction.atomic
def form_valid(self, form):
form.instance.creater = self.request.user
return super().form_valid(form)
@ -147,7 +145,6 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
form.fields["inviter"].initial = self.request.user.note
return form
@transaction.atomic
def form_valid(self, form):
form.instance.activity = Activity.objects\
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])

View File

@ -8,7 +8,6 @@ from django import forms
from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User
from django.db import transaction
from django.forms import CheckboxSelectMultiple
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@ -58,7 +57,6 @@ class ProfileForm(forms.ModelForm):
self.fields['address'].widget.attrs.update({"placeholder": "4 avenue des Sciences, 91190 GIF-SUR-YVETTE"})
self.fields['promotion'].widget.attrs.update({"max": timezone.now().year})
@transaction.atomic
def save(self, commit=True):
if not self.instance.section or (("department" in self.changed_data
or "promotion" in self.changed_data) and "section" not in self.changed_data):
@ -163,7 +161,7 @@ class MembershipForm(forms.ModelForm):
soge = forms.BooleanField(
label=_("Inscription paid by Société Générale"),
required=False,
help_text=_("Check this case if the Société Générale paid the inscription."),
help_text=_("Check this case is the Société Générale paid the inscription."),
)
credit_type = forms.ModelChoiceField(

View File

@ -7,7 +7,7 @@ import os
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db import models
from django.db.models import Q
from django.template import loader
from django.urls import reverse, reverse_lazy
@ -271,7 +271,6 @@ class Club(models.Model):
self._force_save = True
self.save(force_update=True)
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
if not self.require_memberships:
@ -407,7 +406,6 @@ class Membership(models.Model):
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save()
@transaction.atomic
def save(self, *args, **kwargs):
"""
Calculate fee and end date before saving the membership and creating the transaction if needed.
@ -477,13 +475,8 @@ class Membership(models.Model):
# to treasurers.
transaction.valid = False
from treasury.models import SogeCredit
if SogeCredit.objects.filter(user=self.user).exists():
soge_credit = SogeCredit.objects.get(user=self.user)
else:
soge_credit = SogeCredit(user=self.user)
soge_credit._force_save = True
soge_credit.save(force_insert=True)
soge_credit.refresh_from_db()
soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0]
soge_credit.refresh_from_db()
transaction.save(force_insert=True)
transaction.refresh_from_db()
soge_credit.transactions.add(transaction)

View File

@ -13,29 +13,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if additional_fee_renewal %}
<div class="alert alert-warning">
{% if renewal %}
{% if club.name == "Kfet" %} {# Auto-renewal #}
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
will be charged to renew automatically the membership in this/these club·s.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
The user is not a member of the club·s {{ clubs }}. Please create the required memberships,
otherwise it will fail.
{% endblocktrans %}
{% endif %}
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
will be charged to renew automatically the membership in this/these club·s.
{% endblocktrans %}
{% else %}
{% if club.name == "Kfet" %}
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
will be charged to adhere automatically to this/these club·s.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
This club has parents {{ clubs }}. Please make sure that the user is a member of this or these club·s,
otherwise the creation of this membership will fail.
{% endblocktrans %}
{% endif %}
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
will be charged to adhere automatically to this/these club·s.
{% endblocktrans %}
{% endif %}
</div>
{% endif %}

View File

@ -38,7 +38,7 @@
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}
{% if "note.view_note"|has_perm:user_object.note %}
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
@ -47,7 +47,7 @@
{% endif %}
</dl>
{% if user_object.pk == user.pk %}
{% if user_object.pk == user_object.pk %}
<div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<i class="fa fa-cogs"></i>{% trans 'API token' %}

View File

@ -38,7 +38,6 @@ class CustomLoginView(LoginView):
"""
form_class = CustomAuthenticationForm
@transaction.atomic
def form_valid(self, form):
logout(self.request)
_set_current_user_and_ip(form.get_user(), self.request.session, None)
@ -77,7 +76,6 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
return context
@transaction.atomic
def form_valid(self, form):
"""
Check if ProfileForm is correct
@ -271,7 +269,6 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
self.object = self.get_object()
return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
@transaction.atomic
def form_valid(self, form):
"""Save image to note"""
image = form.cleaned_data['image']
@ -610,9 +607,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
bank = form.cleaned_data["bank"]
soge = form.cleaned_data["soge"] and not user.profile.soge and (club.name == "BDE" or club.name == "Kfet")
if not credit_type:
credit_amount = 0
if not soge and user.note.balance + credit_amount < fee and not Membership.objects.filter(
club__name="Kfet",
user=user,
@ -634,16 +628,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
form.add_error('user', _('User is already a member of the club'))
error = True
# Must join the parent club before joining this club, except for the Kfet club where it can be at the same time.
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
user=form.instance.user,
club=club.parent_club,
date_start__lte=club.parent_club.membership_start,
date_end__gte=club.parent_club.membership_end,
).exists():
form.add_error('user', _('User is not a member of the parent club') + ' ' + club.parent_club.name)
error = True
if club.membership_start and form.instance.date_start < club.membership_start:
form.add_error('user', _("The membership must start after {:%m-%d-%Y}.")
.format(form.instance.club.membership_start))
@ -666,7 +650,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
return not error
@transaction.atomic
def form_valid(self, form):
"""
Create membership, check that all is good, make transactions

View File

@ -1,6 +1,5 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.db.models import Q
from django.core.exceptions import ValidationError
@ -57,9 +56,8 @@ class AliasViewSet(ReadProtectedModelViewSet):
"""
queryset = Alias.objects.all()
serializer_class = AliasSerializer
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
filterset_fields = ['note']
ordering_fields = ['name', 'normalized_name']
def get_serializer_class(self):
@ -108,9 +106,8 @@ class AliasViewSet(ReadProtectedModelViewSet):
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
queryset = Alias.objects.all()
serializer_class = ConsumerSerializer
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
filterset_fields = ['note']
ordering_fields = ['name', 'normalized_name']
def get_queryset(self):
@ -119,31 +116,29 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
:return: The filtered set of requested aliases
"""
queryset = super().get_queryset().distinct()
queryset = super().get_queryset()
# Sqlite doesn't support ORDER BY in subqueries
queryset = queryset.order_by("name") \
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
alias = self.request.query_params.get("alias", None)
alias = self.request.query_params.get("alias", ".*")
queryset = queryset.prefetch_related('note')
if alias:
# We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias.
queryset = queryset.filter(
name__iregex="^" + alias
).union(
queryset.filter(
Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True).union(
queryset.filter(
Q(normalized_name__iregex="^" + alias.lower())
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True)
# We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias.
queryset = queryset.filter(
name__iregex="^" + alias
).union(
queryset.filter(
Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True).union(
queryset.filter(
Q(normalized_name__iregex="^" + alias.lower())
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
& ~Q(name__iregex="^" + alias)
),
all=True)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name")
@ -184,11 +179,8 @@ class TransactionViewSet(ReadProtectedModelViewSet):
"""
queryset = Transaction.objects.order_by("-created_at").all()
serializer_class = TransactionPolymorphicSerializer
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
filterset_fields = ["source", "source_alias", "destination", "destination_alias", "quantity",
"polymorphic_ctype", "amount", "created_at", ]
filter_backends = [SearchFilter]
search_fields = ['$reason', ]
ordering_fields = ['created_at', 'amount']
def get_queryset(self):
user = self.request.user

View File

@ -8,7 +8,7 @@ from django.conf.global_settings import DEFAULT_FROM_EMAIL
from django.core.exceptions import ValidationError
from django.core.mail import send_mail
from django.core.validators import RegexValidator
from django.db import models, transaction
from django.db import models
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@ -93,7 +93,6 @@ class Note(PolymorphicModel):
delta = timezone.now() - self.last_negative
return "{:d} jours".format(delta.days)
@transaction.atomic
def save(self, *args, **kwargs):
"""
Save note with it's alias (called in polymorphic children)
@ -109,16 +108,12 @@ class Note(PolymorphicModel):
# Save alias
a.note = self
# Consider that if the name of the note could be changed, then the alias can be created.
# It does not mean that any alias can be created.
a._force_save = True
a.save(force_insert=True)
else:
# Check if the name of the note changed without changing the normalized form of the alias
alias = Alias.objects.get(normalized_name=Alias.normalize(str(self)))
if alias.name != str(self):
alias.name = str(self)
alias._force_save = True
alias.save()
def clean(self, *args, **kwargs):
@ -159,7 +154,6 @@ class NoteUser(Note):
def pretty(self):
return _("%(user)s's note") % {'user': str(self.user)}
@transaction.atomic
def save(self, *args, **kwargs):
if self.pk and self.balance < 0:
old_note = NoteUser.objects.get(pk=self.pk)
@ -201,7 +195,6 @@ class NoteClub(Note):
def pretty(self):
return _("Note of %(club)s club") % {'club': str(self.club)}
@transaction.atomic
def save(self, *args, **kwargs):
if self.pk and self.balance < 0:
old_note = NoteClub.objects.get(pk=self.pk)
@ -317,7 +310,6 @@ class Alias(models.Model):
pass
self.normalized_name = normalized_name
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)

View File

@ -170,21 +170,19 @@ class Transaction(PolymorphicModel):
previous_source_balance = self.source.balance
previous_dest_balance = self.destination.balance
source_balance = previous_source_balance
dest_balance = previous_dest_balance
source_balance = self.source.balance
dest_balance = self.destination.balance
created = self.pk is None
to_transfer = self.total
if not created:
to_transfer = self.amount * self.quantity
if not created and not self.valid and not hasattr(self, "_force_save"):
# Revert old transaction
# We make a select for update to avoid concurrency issues
old_transaction = Transaction.objects.select_for_update().get(pk=self.pk)
old_transaction = Transaction.objects.get(pk=self.pk)
# Check that nothing important changed
if not hasattr(self, "_force_save"):
for field_name in ["source_id", "destination_id", "quantity", "amount"]:
if getattr(self, field_name) != getattr(old_transaction, field_name):
raise ValidationError(_("You can't update the {field} on a Transaction. "
"Please invalidate it and create one other.").format(field=field_name))
for field_name in ["source_id", "destination_id", "quantity", "amount"]:
if getattr(self, field_name) != getattr(old_transaction, field_name):
raise ValidationError(_("You can't update the {field} on a Transaction. "
"Please invalidate it and create one other.").format(field=field_name))
if old_transaction.valid == self.valid:
# Don't change anything
@ -217,6 +215,10 @@ class Transaction(PolymorphicModel):
# When source == destination, no money is transferred and no transaction is created
return
# We refresh the notes with the "select for update" tag to avoid concurrency issues
self.source = Note.objects.filter(pk=self.source_id).select_for_update().get()
self.destination = Note.objects.filter(pk=self.destination_id).select_for_update().get()
# Check that the amounts stay between big integer bounds
diff_source, diff_dest = self.validate()
@ -235,11 +237,9 @@ class Transaction(PolymorphicModel):
super().save(*args, **kwargs)
# Save notes
self.source.refresh_from_db()
self.source.balance += diff_source
self.source._force_save = True
self.source.save()
self.destination.refresh_from_db()
self.destination.balance += diff_dest
self.destination._force_save = True
self.destination.save()
@ -273,7 +273,6 @@ class RecurrentTransaction(Transaction):
_("The destination of this transaction must equal to the destination of the template."))
return super().clean()
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
return super().save(*args, **kwargs)
@ -324,7 +323,6 @@ class SpecialTransaction(Transaction):
raise(ValidationError(_("A special transaction is only possible between a"
" Note associated to a payment method and a User or a Club")))
@transaction.atomic
def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)

View File

@ -10,22 +10,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block content %}
<div class="row mt-4">
<div class="col-sm-5 col-md-4" id="infos_div">
<div class="row justify-content-center justify-content-md-end">
<div class="row">
{# User details column #}
<div class="col picture-col">
<div class="card bg-light mb-4 text-center">
<div class="col">
<div class="card bg-light border-success mb-4 text-center">
<a id="profile_pic_link" href="#">
<img src="{% static "member/img/default_picture.png" %}"
id="profile_pic" alt="" class="card-img-top d-none d-sm-block">
id="profile_pic" alt="" class="card-img-top">
</a>
<div class="card-body text-center text-break p-2">
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
<div class="card-body text-center text-break">
<span id="user_note"></span>
</div>
</div>
</div>
{# User selection column #}
<div class="col-xl" id="user_select_div">
<div class="col-xl-7" id="user_select_div">
<div class="card bg-light border-success mb-4">
<div class="card-header">
<p class="card-text font-weight-bold">
@ -44,7 +44,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div>
</div>
</div>
{# Summary of consumption and consume button #}
<div class="col-xl-5 d-none" id="consos_list_div">
<div class="card bg-light border-info mb-4">
@ -66,6 +65,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div>
</div>
{# Show last used buttons #}
<div class="card bg-light mb-4">
<div class="card-header">
@ -159,7 +159,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblock %}
{% block extrajavascript %}
<script type="text/javascript" src="{% static "note/js/consos.js" 'javascript-catalog' %}"></script>
<script type="text/javascript" src="{% static "js/consos.js" %}"></script>
<script type="text/javascript">
{% for button in highlighted %}
{% if button.display %}

View File

@ -34,21 +34,21 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div>
</div>
<hr>
<div class="row justify-content-center">
<div class="row">
{# Preview note profile (picture, username and balance) #}
<div class="col-md picture-col" id="note_infos_div">
<div class="card bg-light mb-4 text-center">
<div class="col-md-3" id="note_infos_div">
<div class="card bg-light border-success shadow mb-4 pt-4 text-center">
<a id="profile_pic_link" href="#"><img src="{% static "member/img/default_picture.png" %}"
id="profile_pic" alt="" class="img-fluid rounded mx-auto"></a>
<div class="card-body text-center p-2">
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
<div class="card-body text-center">
<span id="user_note"></span>
</div>
</div>
</div>
{# list of emitters #}
<div class="col-md-3" id="emitters_div">
<div class="card bg-light mb-4">
<div class="card bg-light border-success shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold">
<label for="source_note" id="source_note_label">{% trans "Select emitters" %}</label>
@ -75,7 +75,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
{# list of receiver #}
<div class="col-md-3" id="dests_div">
<div class="card bg-light mb-4">
<div class="card bg-light border-info shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold" id="dest_title">
<label for="dest_note" id="dest_note_label">{% trans "Select receivers" %}</label>
@ -97,8 +97,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div>
{# Information on transaction (amount, reason, name,...) #}
<div class="col-md" id="external_div">
<div class="card bg-light mb-4">
<div class="col-md-3" id="external_div">
<div class="card bg-light border-warning shadow mb-4">
<div class="card-header">
<p class="card-text font-weight-bold">
{% trans "Action" %}
@ -153,7 +153,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div>
</div>
{# transaction history #}
<div class="card mb-4" id="history">
<div class="card shadow mb-4" id="history">
<div class="card-header">
<p class="card-text font-weight-bold">
{% trans "Recent transactions history" %}
@ -176,5 +176,5 @@ SPDX-License-Identifier: GPL-2.0-or-later
select_receveirs_label = "{% trans "Select receivers" %}";
transfer_type_label = "{% trans "Transfer type" %}";
</script>
<script src="{% static "note/js/transfer.js" %}"></script>
<script src="/static/js/transfer.js"></script>
{% endblock %}

View File

@ -144,7 +144,7 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
The Magic View that make people pay their beer and burgers.
(Most of the magic happens in the dark world of Javascript see `static/note/js/consos.js`)
(Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`)
"""
model = Transaction
template_name = "note/conso_form.html"

View File

@ -4,6 +4,7 @@
from functools import lru_cache
from time import time
from django.conf import settings
from django.contrib.sessions.models import Session
from note_kfet.middlewares import get_current_session
@ -32,9 +33,9 @@ def memoize(f):
sess_funs = new_sess_funs
def func(*args, **kwargs):
# if settings.DEBUG:
# # Don't memoize in DEBUG mode
# return f(*args, **kwargs)
if settings.DEBUG:
# Don't memoize in DEBUG mode
return f(*args, **kwargs)
nonlocal last_collect

View File

@ -1103,7 +1103,7 @@
"treasury",
"sogecredit"
],
"query": "{}",
"query": "{\"credit_transaction\": null}",
"type": "add",
"mask": 1,
"field": "",
@ -2647,150 +2647,6 @@
"description": "Changer l'image de la note de son club"
}
},
{
"model": "permission.permission",
"pk": 170,
"fields": {
"model": [
"note",
"alias"
],
"query": "{\"note__is_active\": true}",
"type": "add",
"mask": 1,
"field": "",
"permanent": false,
"description": "Ajouter n'importe quel alias à une note non bloquée"
}
},
{
"model": "permission.permission",
"pk": 171,
"fields": {
"model": [
"note",
"alias"
],
"query": "{\"note__is_active\": true}",
"type": "delete",
"mask": 3,
"field": "",
"permanent": false,
"description": "Supprimer n'importe quel alias à une note non bloquée"
}
},
{
"model": "permission.permission",
"pk": 172,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir toutes les remises"
}
},
{
"model": "permission.permission",
"pk": 173,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "add",
"mask": 3,
"field": "",
"permanent": false,
"description": "Ajouter une remise"
}
},
{
"model": "permission.permission",
"pk": 174,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier une remise"
}
},
{
"model": "permission.permission",
"pk": 175,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "delete",
"mask": 3,
"field": "",
"permanent": false,
"description": "Supprimer une remise"
}
},
{
"model": "permission.permission",
"pk": 176,
"fields": {
"model": [
"auth",
"user"
],
"query": "{\"profile__registration_valid\": false}",
"type": "change",
"mask": 1,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel utilisateur non encore inscrit"
}
},
{
"model": "permission.permission",
"pk": 177,
"fields": {
"model": [
"member",
"profile"
],
"query": "{\"registration_valid\": false}",
"type": "change",
"mask": 1,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel profil non encore inscrit"
}
},
{
"model": "permission.permission",
"pk": 178,
"fields": {
"model": [
"note",
"alias"
],
"query": "{}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir tous les alias, y compris ceux des non adhérents"
}
},
{
"model": "permission.role",
"pk": 1,
@ -2994,16 +2850,7 @@
150,
151,
163,
164,
170,
171,
172,
173,
174,
175,
176,
177,
178
164
]
}
},
@ -3177,16 +3024,7 @@
166,
167,
168,
169,
170,
171,
172,
173,
174,
175,
176,
177,
178
169
]
}
},
@ -3212,15 +3050,10 @@
29,
30,
31,
70,
143,
166,
167,
168,
170,
171,
176,
177
168
]
}
},
@ -3386,13 +3219,10 @@
138,
139,
140,
143,
145,
146,
147,
150,
176,
177
150
]
}
},

View File

@ -199,7 +199,6 @@ class Permission(models.Model):
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
@transaction.atomic
def save(self, **kwargs):
self.full_clean()
super().save()

View File

@ -14,7 +14,6 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
This is a simple patch of this class that controls view access.
"""
# The queryset is filtered, and permissions are more powerful than a simple check than just "can view this model"
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': [],

View File

@ -6,7 +6,6 @@ from datetime import date
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q
from django.forms import HiddenInput
from django.http import Http404
@ -57,7 +56,6 @@ class ProtectQuerysetMixin:
return form
@transaction.atomic
def form_valid(self, form):
"""
Submit the form, if the page is a FormView.

View File

@ -60,7 +60,7 @@ class ValidationForm(forms.Form):
soge = forms.BooleanField(
label=_("Inscription paid by Société Générale"),
required=False,
help_text=_("Check this case if the Société Générale paid the inscription."),
help_text=_("Check this case is the Société Générale paid the inscription."),
)
credit_type = forms.ModelChoiceField(

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block content %}
<div class="row mt-4">
<div class="col-xl-5 mb-4">
<div class="col-md-3 mb-4">
<div class="card bg-light shadow">
<div class="card-header text-center" >
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
@ -50,7 +50,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div>
</div>
</div>
<div class="col-md-7">
<div class="col-md-9">
<div class="card bg-light shadow">
<form method="post">
<div class="card-header text-center" >

View File

@ -5,7 +5,6 @@ from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q
from django.shortcuts import resolve_url, redirect
from django.urls import reverse_lazy
@ -48,7 +47,6 @@ class UserCreateView(CreateView):
return context
@transaction.atomic
def form_valid(self, form):
"""
If the form is valid, then the user is created with is_active set to False
@ -236,7 +234,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
form.fields["first_name"].initial = user.first_name
return form
@transaction.atomic
def form_valid(self, form):
user = self.get_object()

View File

@ -4,7 +4,6 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import AmountInput
@ -150,7 +149,6 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
self.instance.transaction.bank = cleaned_data["bank"]
return cleaned_data
@transaction.atomic
def save(self, commit=True):
"""
Save the transaction and the remittance.

View File

@ -5,7 +5,7 @@ from datetime import date
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.db import models
from django.db.models import Q
from django.template.loader import render_to_string
from django.utils import timezone
@ -76,7 +76,6 @@ class Invoice(models.Model):
verbose_name=_("tex source"),
)
@transaction.atomic
def save(self, *args, **kwargs):
"""
When an invoice is generated, we store the tex source.
@ -229,7 +228,6 @@ class Remittance(models.Model):
"""
return sum(transaction.total for transaction in self.transactions.all())
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
# Check if all transactions have the right type.
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
@ -293,12 +291,11 @@ class SogeCredit(models.Model):
@property
def valid(self):
return self.credit_transaction and self.credit_transaction.valid
return self.credit_transaction.valid
@property
def amount(self):
return self.credit_transaction.total if self.valid \
else sum(transaction.total for transaction in self.transactions.all()) + 8000
return sum(transaction.total for transaction in self.transactions.all()) + 8000
def invalidate(self):
"""
@ -308,10 +305,10 @@ class SogeCredit(models.Model):
if self.valid:
self.credit_transaction.valid = False
self.credit_transaction.save()
for tr in self.transactions.all():
tr.valid = False
tr._force_save = True
tr.save()
for transaction in self.transactions.all():
transaction.valid = False
transaction._force_save = True
transaction.save()
def validate(self, force=False):
if self.valid and not force:
@ -323,20 +320,18 @@ class SogeCredit(models.Model):
# Refresh credit amount
self.save()
self.credit_transaction.valid = True
self.credit_transaction._force_save = True
self.credit_transaction.save()
self.save()
for tr in self.transactions.all():
tr.valid = True
tr._force_save = True
tr.created_at = timezone.now()
tr.save()
for transaction in self.transactions.all():
transaction.valid = True
transaction._force_save = True
transaction.created_at = timezone.now()
transaction.save()
@transaction.atomic
def save(self, *args, **kwargs):
if not self.credit_transaction:
credit_transaction = SpecialTransaction(
self.credit_transaction = SpecialTransaction.objects.create(
source=NoteSpecial.objects.get(special_type="Virement bancaire"),
destination=self.user.note,
quantity=1,
@ -347,10 +342,6 @@ class SogeCredit(models.Model):
bank="Société générale",
valid=False,
)
credit_transaction._force_save = True
credit_transaction.save()
credit_transaction.refresh_from_db()
self.credit_transaction = credit_transaction
elif not self.valid:
self.credit_transaction.amount = self.amount
self.credit_transaction._force_save = True
@ -370,11 +361,11 @@ class SogeCredit(models.Model):
"Please ask her/him to credit the note before invalidating this credit."))
self.invalidate()
for tr in self.transactions.all():
tr._force_save = True
tr.valid = True
tr.created_at = timezone.now()
tr.save()
for transaction in self.transactions.all():
transaction._force_save = True
transaction.valid = True
transaction.created_at = timezone.now()
transaction.save()
self.credit_transaction.valid = False
self.credit_transaction.reason += " (invalide)"
self.credit_transaction.save()

View File

@ -9,7 +9,6 @@ from tempfile import mkdtemp
from crispy_forms.helper import FormHelper
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError, PermissionDenied
from django.db import transaction
from django.db.models import Q
from django.forms import Form
from django.http import HttpResponse
@ -66,7 +65,6 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
del form.fields["locked"]
return form
@transaction.atomic
def form_valid(self, form):
ret = super().form_valid(form)
@ -146,7 +144,6 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
del form.fields["id"]
return form
@transaction.atomic
def form_valid(self, form):
ret = super().form_valid(form)
@ -442,7 +439,6 @@ class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormVie
form_class = Form
extra_context = {"title": _("Manage credits from the Société générale")}
@transaction.atomic
def form_valid(self, form):
if "validate" in form.data:
self.get_object().validate(True)

View File

@ -4,7 +4,6 @@
from random import choice
from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
@ -89,7 +88,6 @@ class WEISurvey2020(WEISurvey):
"""
form.set_registration(self.registration)
@transaction.atomic
def form_valid(self, form):
word = form.cleaned_data["word"]
self.information.step += 1

View File

@ -0,0 +1,59 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date
from django.core.management import BaseCommand
from django.db.models import Q
from member.models import Membership, Club
from wei.models import WEIClub
class Command(BaseCommand):
help = "Get mailing list registrations from the last wei. " \
"Usage: manage.py extract_ml_registrations -t {events,art,sport}. " \
"You can write this into a file with a pipe, then paste the document into your mail manager."
def add_arguments(self, parser):
parser.add_argument('--type', '-t', choices=["members", "clubs", "events", "art", "sport"], default="members",
help='Select the type of the mailing list (default members)')
parser.add_argument('--year', '-y', type=int, default=None,
help='Select the year of the concerned WEI. Default: last year')
def handle(self, *args, **options):
###########################################################
# WARNING #
###########################################################
#
# This code is obsolete.
# TODO: Improve the mailing list extraction system, and link it automatically with Mailman.
if options["type"] == "members":
for membership in Membership.objects.filter(
club__name="BDE",
date_start__lte=date.today(),
date_end__gte=date.today(),
).all():
self.stdout.write(membership.user.email)
return
if options["type"] == "clubs":
for club in Club.objects.all():
self.stdout.write(club.email)
return
if options["year"] is None:
wei = WEIClub.objects.order_by('-year').first()
else:
wei = WEIClub.objects.filter(year=options["year"])
if wei.exists():
wei = wei.get()
else:
wei = WEIClub.objects.order_by('-year').first()
self.stderr.write(self.style.WARNING("Warning: there was no WEI in year " + str(options["year"]) + ". "
+ "Assuming the last WEI (year " + str(wei.year) + ")"))
q = Q(ml_events_registration=True) if options["type"] == "events" else Q(ml_art_registration=True)\
if options["type"] == "art" else Q(ml_sport_registration=True)
registrations = wei.users.filter(q)
for registration in registrations.all():
self.stdout.write(registration.user.email)

View File

@ -238,7 +238,7 @@ class WEIRegistration(models.Model):
information_json = models.TextField(
default="{}",
verbose_name=_("registration information"),
help_text=_("Information about the registration (buses for old members, survey for the new members), "
help_text=_("Information about the registration (buses for old members, survey fot the new members), "
"encoded in JSON"),
)

View File

@ -10,7 +10,6 @@ from tempfile import mkdtemp
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q, Count
from django.db.models.functions.text import Lower
from django.forms import HiddenInput
@ -85,7 +84,6 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
date_end=date.today(),
)
@transaction.atomic
def form_valid(self, form):
form.instance.requires_membership = True
form.instance.parent_club = Club.objects.get(name="Kfet")
@ -519,7 +517,6 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
del form.fields["information_json"]
return form
@transaction.atomic
def form_valid(self, form):
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
form.instance.first_year = True
@ -600,7 +597,6 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
return form
@transaction.atomic
def form_valid(self, form):
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
form.instance.first_year = False
@ -692,7 +688,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
del form.fields["information_json"]
return form
@transaction.atomic
def form_valid(self, form):
# If the membership is already validated, then we update the bus and the team (and the roles)
if form.instance.is_validated:
@ -871,7 +866,6 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
).all()
return form
@transaction.atomic
def form_valid(self, form):
"""
Create membership, check that all is good, make transactions
@ -1022,7 +1016,6 @@ class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
context["club"] = self.object.wei
return context
@transaction.atomic
def form_valid(self, form):
"""
Update the survey with the data of the form.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
"model": "sites.site",
"pk": 1,
"fields": {
"domain": "note.crans.org",
"domain": "localhost",
"name": "La Note Kfet \ud83c\udf7b"
}
}

View File

@ -50,20 +50,6 @@ class SessionMiddleware(object):
def __call__(self, request):
user = request.user
# If we authenticate through a token to connect to the API, then we query the good user
if 'HTTP_AUTHORIZATION' in request.META and request.path.startswith("/api"):
token = request.META.get('HTTP_AUTHORIZATION')
if token.startswith("Token "):
token = token[6:]
from rest_framework.authtoken.models import Token
if Token.objects.filter(key=token).exists():
token_obj = Token.objects.get(key=token)
user = token_obj.user
session = request.session
session["permission_mask"] = 42
session.save()
if 'HTTP_X_REAL_IP' in request.META:
ip = request.META.get('HTTP_X_REAL_IP')
elif 'HTTP_X_FORWARDED_FOR' in request.META:

View File

@ -154,7 +154,6 @@ from django.utils.translation import gettext_lazy as _
LANGUAGES = [
('de', _('German')),
('en', _('English')),
('es', _('Spanish')),
('fr', _('French')),
]

View File

@ -22,11 +22,6 @@
border-bottom-color: rgba(0, 0, 0, .250);
}
/* Fixed width picture column */
.picture-col {
max-width: 202px;
}
/* Limit fluid container to a max size */
.container-fluid {
max-width: 1600px;

View File

@ -29,6 +29,7 @@ $(document).ready(function () {
// Switching in double consumptions mode should update the layout
$('#double_conso').change(function () {
$('#consos_list_div').removeClass('d-none')
$('#user_select_div').attr('class', 'col-xl-4')
$('#infos_div').attr('class', 'col-sm-5 col-xl-6')
const note_list_obj = $('#note_list')
@ -47,6 +48,7 @@ $(document).ready(function () {
$('#single_conso').change(function () {
$('#consos_list_div').addClass('d-none')
$('#user_select_div').attr('class', 'col-xl-7')
$('#infos_div').attr('class', 'col-sm-5 col-md-4')
const consos_list_obj = $('#consos_list')
@ -253,7 +255,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
template: template
}).done(function () {
reset()
addMsg(gettext("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 () {
reset()
errMsg(e.responseJSON)

View File

@ -177,11 +177,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
onchange="this.form.submit()">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% for lang_code, lang_name in LANGUAGES %}
<option value="{{ lang_code }}"
{% if lang_code == LANGUAGE_CODE %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"
{% if language.code == LANGUAGE_CODE %}
selected{% endif %}>
{{ lang_name }} ({{ lang_code }})
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>

View File

@ -6,7 +6,6 @@ from django.conf.urls.static import static
from django.urls import path, include
from django.views.defaults import bad_request, permission_denied, page_not_found, server_error
from django.views.generic import RedirectView
from django.views.i18n import JavaScriptCatalog
from member.views import CustomLoginView
@ -35,14 +34,10 @@ urlpatterns = [
# Make coffee
path('coffee/', include('django_htcpcp_tea.urls')),
# Translate js
path('jsi18n/', JavaScriptCatalog.as_view(), name='javascript-catalog'),
]
# During development, serve media files
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
if "cas_server" in settings.INSTALLED_APPS: