mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-21 18:08:21 +02:00
Compare commits
100 Commits
Author | SHA1 | Date | |
---|---|---|---|
1c5e951c2f | |||
beb1853aef | |||
0078eb8f90 | |||
e5e758f9d9 | |||
4a78328717 | |||
65a2e8c08c | |||
b5fa428bad | |||
fb72385773 | |||
2f68601e8b | |||
0b1bed8048 | |||
8ada0e51f2 | |||
c3d613947f | |||
36b8157372 | |||
992cfe8e23 | |||
18a8ff1b8a | |||
c61bb2e90d | |||
4b12e3ed08 | |||
af07ed9807 | |||
bbe53b3b63 | |||
536f0ec226 | |||
541ed59f40 | |||
e172b4f4bb | |||
d666179037 | |||
f22e92132c | |||
ca7ad05746 | |||
f55ca2f725 | |||
d4e4ed580f | |||
8756751344 | |||
fd83fe19bf | |||
a00d95608b | |||
3303edd01f | |||
e48ef92137 | |||
919d0b7e85 | |||
439bf35b62 | |||
74b26335d1 | |||
3d733ed6af | |||
d54ab94ceb | |||
4f188ca3e5 | |||
72bac75fbd | |||
6d54aae614 | |||
8052152ea5 | |||
70448db8e5 | |||
ac2d1e8111 | |||
3ba61385a3 | |||
7353348d7a | |||
f63e2e088e | |||
d566def706 | |||
eaf6769e8b | |||
a61ec81cff | |||
60f2a73cc5 | |||
bcd96b2ed8 | |||
5c702187e5 | |||
905d65371f | |||
180cd3e1ec | |||
73ca65aa91 | |||
5ed0560953 | |||
dbc6fbbf71 | |||
872fd8f86d | |||
f89234b69a | |||
36a980555b | |||
826cd4d87f | |||
e8005a6c58 | |||
2270a0aa82 | |||
0f53ac45f7 | |||
670556c59e | |||
5b02ba48e0 | |||
f3f18bc25e | |||
03124e124c | |||
6308964e93 | |||
ed79097288 | |||
d7eaef8cee | |||
01d405e54b | |||
80e3cba4c6 | |||
f190053e84 | |||
218960adb5 | |||
88a1eae631 | |||
2a2ecb2acc | |||
f5486bdb63 | |||
9b090a145c | |||
860c7b50e5 | |||
afdc75c0bd | |||
c6603e8aa7 | |||
72cc1638e6 | |||
6a0dc4cb10 | |||
0f1f3b9560 | |||
c720e5483e | |||
0fd3e9db78 | |||
c34296c923 | |||
ce4c22a4a1 | |||
3e0f665ef8 | |||
be8751c815 | |||
8225445c3e | |||
f333e6a875 | |||
e5835b46a5 | |||
fe937405a6 | |||
0741c8ad2b | |||
3191dba31f | |||
428de69d93 | |||
0888afe439 | |||
3111c30e56 |
@ -16,8 +16,8 @@ py37-django22:
|
|||||||
apt-get install --no-install-recommends -t buster-backports -y
|
apt-get install --no-install-recommends -t buster-backports -y
|
||||||
python3-django python3-django-crispy-forms
|
python3-django python3-django-crispy-forms
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
python3-bs4 python3-setuptools tox texlive-xetex
|
||||||
script: tox -e py37-django22
|
script: tox -e py37-django22
|
||||||
|
|
||||||
@ -33,8 +33,8 @@ py38-django22:
|
|||||||
apt-get install --no-install-recommends -y
|
apt-get install --no-install-recommends -y
|
||||||
python3-django python3-django-crispy-forms
|
python3-django python3-django-crispy-forms
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil
|
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||||
python3-bs4 python3-setuptools tox texlive-xetex
|
python3-bs4 python3-setuptools tox texlive-xetex
|
||||||
script: tox -e py38-django22
|
script: tox -e py38-django22
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ RUN apt-get update && \
|
|||||||
apt-get install --no-install-recommends -t buster-backports -y \
|
apt-get install --no-install-recommends -t buster-backports -y \
|
||||||
python3-django python3-django-crispy-forms \
|
python3-django python3-django-crispy-forms \
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic \
|
python3-django-extensions python3-django-filters python3-django-polymorphic \
|
||||||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil \
|
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil \
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||||
python3-bs4 python3-setuptools \
|
python3-bs4 python3-setuptools \
|
||||||
uwsgi uwsgi-plugin-python3 \
|
uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
||||||
|
@ -93,10 +93,10 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
|||||||
$ sudo apt install --no-install-recommends -t buster-backports -y \
|
$ sudo apt install --no-install-recommends -t buster-backports -y \
|
||||||
python3-django python3-django-crispy-forms \
|
python3-django python3-django-crispy-forms \
|
||||||
python3-django-extensions python3-django-filters python3-django-polymorphic \
|
python3-django-extensions python3-django-filters python3-django-polymorphic \
|
||||||
python3-djangorestframework python3-django-cas-server python3-psycopg2 python3-pil \
|
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil \
|
||||||
python3-babel python3-lockfile python3-pip python3-phonenumbers ipython3 \
|
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||||
python3-bs4 python3-setuptools \
|
python3-bs4 python3-setuptools python3-docutils \
|
||||||
uwsgi uwsgi-plugin-python3 \
|
memcached uwsgi uwsgi-plugin-python3 \
|
||||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
||||||
nginx python3-venv git acl
|
nginx python3-venv git acl
|
||||||
```
|
```
|
||||||
|
@ -23,13 +23,14 @@
|
|||||||
- python3-babel
|
- python3-babel
|
||||||
- python3-bs4
|
- python3-bs4
|
||||||
- python3-django
|
- python3-django
|
||||||
- python3-django-cas-server
|
|
||||||
- python3-django-crispy-forms
|
- python3-django-crispy-forms
|
||||||
- python3-django-extensions
|
- python3-django-extensions
|
||||||
- python3-django-filters
|
- python3-django-filters
|
||||||
|
- python3-django-oauth-toolkit
|
||||||
- python3-django-polymorphic
|
- python3-django-polymorphic
|
||||||
- python3-djangorestframework
|
- python3-djangorestframework
|
||||||
- python3-lockfile
|
- python3-lockfile
|
||||||
|
- python3-memcache
|
||||||
- python3-phonenumbers
|
- python3-phonenumbers
|
||||||
- python3-pil
|
- python3-pil
|
||||||
- python3-pip
|
- python3-pip
|
||||||
@ -40,6 +41,9 @@
|
|||||||
# LaTeX (PDF generation)
|
# LaTeX (PDF generation)
|
||||||
- texlive-xetex
|
- texlive-xetex
|
||||||
|
|
||||||
|
# Cache server
|
||||||
|
- memcached
|
||||||
|
|
||||||
# WSGI server
|
# WSGI server
|
||||||
- uwsgi
|
- uwsgi
|
||||||
- uwsgi-plugin-python3
|
- uwsgi-plugin-python3
|
||||||
|
@ -7,7 +7,7 @@ from threading import Thread
|
|||||||
|
|
||||||
from django.conf import settings
|
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, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
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 _
|
||||||
@ -123,6 +123,7 @@ class Activity(models.Model):
|
|||||||
verbose_name=_('open'),
|
verbose_name=_('open'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Update the activity wiki page each time the activity is updated (validation, change description, ...)
|
Update the activity wiki page each time the activity is updated (validation, change description, ...)
|
||||||
@ -194,8 +195,8 @@ class Entry(models.Model):
|
|||||||
else _("Entry for {note} to the activity {activity}").format(
|
else _("Entry for {note} to the activity {activity}").format(
|
||||||
guest=str(self.guest), note=str(self.note), activity=str(self.activity))
|
guest=str(self.guest), note=str(self.note), activity=str(self.activity))
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
|
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
|
||||||
if qs.exists():
|
if qs.exists():
|
||||||
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
|
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
|
||||||
@ -260,6 +261,7 @@ class Guest(models.Model):
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
one_year = timedelta(days=365)
|
one_year = timedelta(days=365)
|
||||||
|
|
||||||
|
@ -7,12 +7,15 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import F, Q
|
from django.db.models import F, Q
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.generic import DetailView, TemplateView, UpdateView
|
from django.views.generic import DetailView, TemplateView, UpdateView
|
||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
from note.models import Alias, NoteSpecial, NoteUser
|
from note.models import Alias, NoteSpecial, NoteUser
|
||||||
@ -44,6 +47,7 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
date_end=timezone.now(),
|
date_end=timezone.now(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.creater = self.request.user
|
form.instance.creater = self.request.user
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
@ -145,6 +149,7 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
form.fields["inviter"].initial = self.request.user.note
|
form.fields["inviter"].initial = self.request.user.note
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.activity = Activity.objects\
|
form.instance.activity = Activity.objects\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])
|
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])
|
||||||
@ -285,6 +290,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
# Cache for 1 hour
|
||||||
|
@method_decorator(cache_page(60 * 60), name='dispatch')
|
||||||
class CalendarView(View):
|
class CalendarView(View):
|
||||||
"""
|
"""
|
||||||
Render an ICS calendar with all valid activities.
|
Render an ICS calendar with all valid activities.
|
||||||
|
@ -8,6 +8,7 @@ from django import forms
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import transaction
|
||||||
from django.forms import CheckboxSelectMultiple
|
from django.forms import CheckboxSelectMultiple
|
||||||
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 _
|
||||||
@ -57,6 +58,7 @@ class ProfileForm(forms.ModelForm):
|
|||||||
self.fields['address'].widget.attrs.update({"placeholder": "4 avenue des Sciences, 91190 GIF-SUR-YVETTE"})
|
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})
|
self.fields['promotion'].widget.attrs.update({"max": timezone.now().year})
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
if not self.instance.section or (("department" in self.changed_data
|
if not self.instance.section or (("department" in self.changed_data
|
||||||
or "promotion" in self.changed_data) and "section" not in self.changed_data):
|
or "promotion" in self.changed_data) and "section" not in self.changed_data):
|
||||||
@ -148,6 +150,7 @@ class ClubForm(forms.ModelForm):
|
|||||||
"membership_fee_unpaid": AmountInput(),
|
"membership_fee_unpaid": AmountInput(),
|
||||||
"parent_club": Autocomplete(
|
"parent_club": Autocomplete(
|
||||||
Club,
|
Club,
|
||||||
|
resetable=True,
|
||||||
attrs={
|
attrs={
|
||||||
'api_url': '/api/members/club/',
|
'api_url': '/api/members/club/',
|
||||||
}
|
}
|
||||||
@ -161,7 +164,7 @@ class MembershipForm(forms.ModelForm):
|
|||||||
soge = forms.BooleanField(
|
soge = forms.BooleanField(
|
||||||
label=_("Inscription paid by Société Générale"),
|
label=_("Inscription paid by Société Générale"),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Check this case is the Société Générale paid the inscription."),
|
help_text=_("Check this case if the Société Générale paid the inscription."),
|
||||||
)
|
)
|
||||||
|
|
||||||
credit_type = forms.ModelChoiceField(
|
credit_type = forms.ModelChoiceField(
|
||||||
|
@ -7,6 +7,7 @@ def create_bde_and_kfet(apps, schema_editor):
|
|||||||
"""
|
"""
|
||||||
Club = apps.get_model("member", "club")
|
Club = apps.get_model("member", "club")
|
||||||
NoteClub = apps.get_model("note", "noteclub")
|
NoteClub = apps.get_model("note", "noteclub")
|
||||||
|
Alias = apps.get_model("note", "alias")
|
||||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
|
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
|
||||||
|
|
||||||
@ -45,6 +46,19 @@ def create_bde_and_kfet(apps, schema_editor):
|
|||||||
polymorphic_ctype_id=polymorphic_ctype_id,
|
polymorphic_ctype_id=polymorphic_ctype_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Alias.objects.get_or_create(
|
||||||
|
id=5,
|
||||||
|
note_id=5,
|
||||||
|
name="BDE",
|
||||||
|
normalized_name="bde",
|
||||||
|
)
|
||||||
|
Alias.objects.get_or_create(
|
||||||
|
id=6,
|
||||||
|
note_id=6,
|
||||||
|
name="Kfet",
|
||||||
|
normalized_name="kfet",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -7,7 +7,7 @@ import os
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
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.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
|
||||||
@ -271,6 +271,7 @@ class Club(models.Model):
|
|||||||
self._force_save = True
|
self._force_save = True
|
||||||
self.save(force_update=True)
|
self.save(force_update=True)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, force_insert=False, force_update=False, using=None,
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
update_fields=None):
|
update_fields=None):
|
||||||
if not self.require_memberships:
|
if not self.require_memberships:
|
||||||
@ -406,6 +407,7 @@ class Membership(models.Model):
|
|||||||
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
||||||
parent_membership.save()
|
parent_membership.save()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||||
@ -475,8 +477,13 @@ class Membership(models.Model):
|
|||||||
# to treasurers.
|
# to treasurers.
|
||||||
transaction.valid = False
|
transaction.valid = False
|
||||||
from treasury.models import SogeCredit
|
from treasury.models import SogeCredit
|
||||||
soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0]
|
if SogeCredit.objects.filter(user=self.user).exists():
|
||||||
soge_credit.refresh_from_db()
|
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()
|
||||||
transaction.save(force_insert=True)
|
transaction.save(force_insert=True)
|
||||||
transaction.refresh_from_db()
|
transaction.refresh_from_db()
|
||||||
soge_credit.transactions.add(transaction)
|
soge_credit.transactions.add(transaction)
|
||||||
|
@ -112,7 +112,7 @@ class MembershipTable(tables.Table):
|
|||||||
fee=0,
|
fee=0,
|
||||||
)
|
)
|
||||||
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
if PermissionBackend.check_perm(get_current_authenticated_user(),
|
||||||
"member:add_membership", empty_membership): # If the user has right
|
"member.add_membership", empty_membership): # If the user has right
|
||||||
renew_url = reverse_lazy('member:club_renew_membership',
|
renew_url = reverse_lazy('member:club_renew_membership',
|
||||||
kwargs={"pk": record.pk})
|
kwargs={"pk": record.pk})
|
||||||
t = format_html(
|
t = format_html(
|
||||||
|
@ -13,15 +13,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% if additional_fee_renewal %}
|
{% if additional_fee_renewal %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
{% if renewal %}
|
{% if renewal %}
|
||||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
{% if club.name == "Kfet" %} {# Auto-renewal #}
|
||||||
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
|
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||||
will be charged to renew automatically the membership in this/these club·s.
|
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||||
{% endblocktrans %}
|
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 %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
{% if club.name == "Kfet" %}
|
||||||
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
|
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||||
will be charged to adhere automatically to this/these club·s.
|
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||||
{% endblocktrans %}
|
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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
|
||||||
|
|
||||||
{% if "note.view_note"|has_perm:user_object.note %}
|
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}
|
||||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
|
||||||
|
|
||||||
@ -47,7 +47,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% if user_object.pk == user_object.pk %}
|
{% if user_object.pk == user.pk %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
||||||
<i class="fa fa-cogs"></i>{% trans 'API token' %}
|
<i class="fa fa-cogs"></i>{% trans 'API token' %}
|
||||||
|
0
apps/member/templatetags/__init__.py
Normal file
0
apps/member/templatetags/__init__.py
Normal file
22
apps/member/templatetags/memberinfo.py
Normal file
22
apps/member/templatetags/memberinfo.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from ..models import Club, Membership
|
||||||
|
|
||||||
|
|
||||||
|
def is_member(user, club):
|
||||||
|
if isinstance(user, str):
|
||||||
|
club = User.objects.get(username=user)
|
||||||
|
if isinstance(club, str):
|
||||||
|
club = Club.objects.get(name=club)
|
||||||
|
return Membership.objects\
|
||||||
|
.filter(user=user, club=club, date_start__lte=date.today(), date_end__gte=date.today()).exists()
|
||||||
|
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
register.filter("is_member", is_member)
|
@ -41,7 +41,7 @@ class TemplateLoggedInTests(TestCase):
|
|||||||
password="adminadmin",
|
password="adminadmin",
|
||||||
permission_mask=3,
|
permission_mask=3,
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 200)
|
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
|
||||||
|
|
||||||
def test_logout(self):
|
def test_logout(self):
|
||||||
response = self.client.get(reverse("logout"))
|
response = self.client.get(reverse("logout"))
|
||||||
|
@ -205,7 +205,7 @@ class TestMemberships(TestCase):
|
|||||||
first_name="Toto",
|
first_name="Toto",
|
||||||
bank="Le matelas",
|
bank="Le matelas",
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, club.get_absolute_url(), 302, 200)
|
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||||
|
|
||||||
self.assertTrue(Membership.objects.filter(user=user, club=club).exists())
|
self.assertTrue(Membership.objects.filter(user=user, club=club).exists())
|
||||||
|
|
||||||
@ -244,9 +244,9 @@ class TestMemberships(TestCase):
|
|||||||
first_name="Toto",
|
first_name="Toto",
|
||||||
bank="Bank",
|
bank="Bank",
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, club.get_absolute_url(), 302, 200)
|
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||||
|
|
||||||
response = self.client.get(user.profile.get_absolute_url())
|
response = self.client.get(club.get_absolute_url())
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_auto_join_kfet_when_join_bde_with_soge(self):
|
def test_auto_join_kfet_when_join_bde_with_soge(self):
|
||||||
@ -273,7 +273,7 @@ class TestMemberships(TestCase):
|
|||||||
first_name="Toto",
|
first_name="Toto",
|
||||||
bank="Société générale",
|
bank="Société générale",
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, bde.get_absolute_url(), 302, 200)
|
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||||
|
|
||||||
self.assertTrue(Membership.objects.filter(user=user, club=bde).exists())
|
self.assertTrue(Membership.objects.filter(user=user, club=bde).exists())
|
||||||
self.assertTrue(Membership.objects.filter(user=user, club=kfet).exists())
|
self.assertTrue(Membership.objects.filter(user=user, club=kfet).exists())
|
||||||
|
@ -38,6 +38,7 @@ class CustomLoginView(LoginView):
|
|||||||
"""
|
"""
|
||||||
form_class = CustomAuthenticationForm
|
form_class = CustomAuthenticationForm
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
logout(self.request)
|
logout(self.request)
|
||||||
_set_current_user_and_ip(form.get_user(), self.request.session, None)
|
_set_current_user_and_ip(form.get_user(), self.request.session, None)
|
||||||
@ -76,6 +77,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
Check if ProfileForm is correct
|
Check if ProfileForm is correct
|
||||||
@ -155,8 +157,12 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||||
context['history_list'] = history_table
|
context['history_list'] = history_table
|
||||||
|
|
||||||
club_list = Membership.objects.filter(user=user, date_end__gte=date.today())\
|
club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
||||||
|
.order_by("club__name", "-date_start")
|
||||||
|
# Display only the most recent membership
|
||||||
|
club_list = club_list.distinct("club__name")\
|
||||||
|
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_list
|
||||||
membership_table = MembershipTable(data=club_list, prefix='membership-')
|
membership_table = MembershipTable(data=club_list, prefix='membership-')
|
||||||
membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
|
membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
|
||||||
context['club_list'] = membership_table
|
context['club_list'] = membership_table
|
||||||
@ -164,6 +170,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
# Check permissions to see if the authenticated user can lock/unlock the note
|
# Check permissions to see if the authenticated user can lock/unlock the note
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
modified_note = NoteUser.objects.get(pk=user.note.pk)
|
modified_note = NoteUser.objects.get(pk=user.note.pk)
|
||||||
|
# Don't log these tests
|
||||||
|
modified_note._no_signal = True
|
||||||
modified_note.is_active = True
|
modified_note.is_active = True
|
||||||
modified_note.inactivity_reason = 'manual'
|
modified_note.inactivity_reason = 'manual'
|
||||||
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
context["can_lock_note"] = user.note.is_active and PermissionBackend\
|
||||||
@ -176,6 +184,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
context["can_force_lock"] = user.note.is_active and PermissionBackend\
|
||||||
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
.check_perm(self.request.user, "note.change_note_is_active", modified_note)
|
||||||
old_note._force_save = True
|
old_note._force_save = True
|
||||||
|
old_note._no_signal = True
|
||||||
old_note.save()
|
old_note.save()
|
||||||
modified_note.refresh_from_db()
|
modified_note.refresh_from_db()
|
||||||
modified_note.is_active = True
|
modified_note.is_active = True
|
||||||
@ -269,6 +278,7 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
|
|||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
|
return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Save image to note"""
|
"""Save image to note"""
|
||||||
image = form.cleaned_data['image']
|
image = form.cleaned_data['image']
|
||||||
@ -389,7 +399,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
||||||
club.update_membership_dates()
|
club.update_membership_dates()
|
||||||
# managers list
|
# managers list
|
||||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club")\
|
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||||
|
date_start__lte=date.today(), date_end__gte=date.today())\
|
||||||
.order_by('user__last_name').all()
|
.order_by('user__last_name').all()
|
||||||
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
||||||
# transaction history
|
# transaction history
|
||||||
@ -402,8 +413,12 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
|||||||
# member list
|
# member list
|
||||||
club_member = Membership.objects.filter(
|
club_member = Membership.objects.filter(
|
||||||
club=club,
|
club=club,
|
||||||
date_end__gte=date.today(),
|
date_end__gte=date.today() - timedelta(days=15),
|
||||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))\
|
||||||
|
.order_by("user__username", "-date_start")
|
||||||
|
# Display only the most recent membership
|
||||||
|
club_member = club_member.distinct("user__username")\
|
||||||
|
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_member
|
||||||
|
|
||||||
membership_table = MembershipTable(data=club_member, prefix="membership-")
|
membership_table = MembershipTable(data=club_member, prefix="membership-")
|
||||||
membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1))
|
membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1))
|
||||||
@ -607,6 +622,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
bank = form.cleaned_data["bank"]
|
bank = form.cleaned_data["bank"]
|
||||||
soge = form.cleaned_data["soge"] and not user.profile.soge and (club.name == "BDE" or club.name == "Kfet")
|
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(
|
if not soge and user.note.balance + credit_amount < fee and not Membership.objects.filter(
|
||||||
club__name="Kfet",
|
club__name="Kfet",
|
||||||
user=user,
|
user=user,
|
||||||
@ -628,6 +646,16 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
form.add_error('user', _('User is already a member of the club'))
|
form.add_error('user', _('User is already a member of the club'))
|
||||||
error = True
|
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__gte=club.parent_club.membership_start,
|
||||||
|
date_end__lte=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:
|
if club.membership_start and form.instance.date_start < club.membership_start:
|
||||||
form.add_error('user', _("The membership must start after {:%m-%d-%Y}.")
|
form.add_error('user', _("The membership must start after {:%m-%d-%Y}.")
|
||||||
.format(form.instance.club.membership_start))
|
.format(form.instance.club.membership_start))
|
||||||
@ -650,6 +678,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
|
|
||||||
return not error
|
return not error
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
Create membership, check that all is good, make transactions
|
Create membership, check that all is good, make transactions
|
||||||
@ -659,6 +688,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
||||||
.get(pk=self.kwargs["club_pk"])
|
.get(pk=self.kwargs["club_pk"])
|
||||||
user = form.instance.user
|
user = form.instance.user
|
||||||
|
old_membership = None
|
||||||
else: # get from url for renewal
|
else: # get from url for renewal
|
||||||
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
||||||
club = old_membership.club
|
club = old_membership.club
|
||||||
@ -733,6 +763,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \
|
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \
|
||||||
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \
|
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \
|
||||||
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
|
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
|
||||||
|
# Set the same roles as before
|
||||||
|
if old_membership:
|
||||||
|
member_role = member_role.union(old_membership.roles.all())
|
||||||
form.instance.roles.set(member_role)
|
form.instance.roles.set(member_role)
|
||||||
form.instance._force_save = True
|
form.instance._force_save = True
|
||||||
form.instance.save()
|
form.instance.save()
|
||||||
@ -770,7 +803,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy('member:club_detail', kwargs={'pk': self.object.club.id})
|
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.user.id})
|
||||||
|
|
||||||
|
|
||||||
class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# 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.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
@ -56,8 +57,9 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = Alias.objects.all()
|
queryset = Alias.objects.all()
|
||||||
serializer_class = AliasSerializer
|
serializer_class = AliasSerializer
|
||||||
filter_backends = [SearchFilter, OrderingFilter]
|
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
|
filterset_fields = ['note']
|
||||||
ordering_fields = ['name', 'normalized_name']
|
ordering_fields = ['name', 'normalized_name']
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
@ -106,8 +108,9 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
|||||||
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||||
queryset = Alias.objects.all()
|
queryset = Alias.objects.all()
|
||||||
serializer_class = ConsumerSerializer
|
serializer_class = ConsumerSerializer
|
||||||
filter_backends = [SearchFilter, OrderingFilter]
|
filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
|
||||||
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
|
||||||
|
filterset_fields = ['note']
|
||||||
ordering_fields = ['name', 'normalized_name']
|
ordering_fields = ['name', 'normalized_name']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -116,29 +119,31 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
|||||||
:return: The filtered set of requested aliases
|
:return: The filtered set of requested aliases
|
||||||
"""
|
"""
|
||||||
|
|
||||||
queryset = super().get_queryset()
|
queryset = super().get_queryset().distinct()
|
||||||
# Sqlite doesn't support ORDER BY in subqueries
|
# Sqlite doesn't support ORDER BY in subqueries
|
||||||
queryset = queryset.order_by("name") \
|
queryset = queryset.order_by("name") \
|
||||||
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
|
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
|
||||||
|
|
||||||
alias = self.request.query_params.get("alias", ".*")
|
alias = self.request.query_params.get("alias", None)
|
||||||
queryset = queryset.prefetch_related('note')
|
queryset = queryset.prefetch_related('note')
|
||||||
# We match first an alias if it is matched without normalization,
|
|
||||||
# then if the normalized pattern matches a normalized alias.
|
if alias:
|
||||||
queryset = queryset.filter(
|
# We match first an alias if it is matched without normalization,
|
||||||
name__iregex="^" + alias
|
# then if the normalized pattern matches a normalized alias.
|
||||||
).union(
|
queryset = queryset.filter(
|
||||||
queryset.filter(
|
name__iregex="^" + alias
|
||||||
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
).union(
|
||||||
& ~Q(name__iregex="^" + alias)
|
queryset.filter(
|
||||||
),
|
Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||||
all=True).union(
|
& ~Q(name__iregex="^" + alias)
|
||||||
queryset.filter(
|
),
|
||||||
Q(normalized_name__iregex="^" + alias.lower())
|
all=True).union(
|
||||||
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
queryset.filter(
|
||||||
& ~Q(name__iregex="^" + alias)
|
Q(normalized_name__iregex="^" + alias.lower())
|
||||||
),
|
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
|
||||||
all=True)
|
& ~Q(name__iregex="^" + alias)
|
||||||
|
),
|
||||||
|
all=True)
|
||||||
|
|
||||||
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
|
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
|
||||||
else queryset.order_by("name")
|
else queryset.order_by("name")
|
||||||
@ -179,8 +184,11 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = Transaction.objects.order_by("-created_at").all()
|
queryset = Transaction.objects.order_by("-created_at").all()
|
||||||
serializer_class = TransactionPolymorphicSerializer
|
serializer_class = TransactionPolymorphicSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
|
||||||
|
filterset_fields = ["source", "source_alias", "destination", "destination_alias", "quantity",
|
||||||
|
"polymorphic_ctype", "amount", "created_at", ]
|
||||||
search_fields = ['$reason', ]
|
search_fields = ['$reason', ]
|
||||||
|
ordering_fields = ['created_at', 'amount']
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models.signals import post_save, pre_delete
|
from django.db.models.signals import pre_delete, pre_save, post_save
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import signals
|
from . import signals
|
||||||
@ -17,6 +17,15 @@ class NoteConfig(AppConfig):
|
|||||||
"""
|
"""
|
||||||
Define app internal signals to interact with other apps
|
Define app internal signals to interact with other apps
|
||||||
"""
|
"""
|
||||||
|
pre_save.connect(
|
||||||
|
signals.pre_save_note,
|
||||||
|
sender="note.noteuser",
|
||||||
|
)
|
||||||
|
pre_save.connect(
|
||||||
|
signals.pre_save_note,
|
||||||
|
sender="note.noteclub",
|
||||||
|
)
|
||||||
|
|
||||||
post_save.connect(
|
post_save.connect(
|
||||||
signals.save_user_note,
|
signals.save_user_note,
|
||||||
sender=settings.AUTH_USER_MODEL,
|
sender=settings.AUTH_USER_MODEL,
|
||||||
|
@ -8,7 +8,7 @@ from django.conf.global_settings import DEFAULT_FROM_EMAIL
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
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 _
|
||||||
@ -93,6 +93,7 @@ class Note(PolymorphicModel):
|
|||||||
delta = timezone.now() - self.last_negative
|
delta = timezone.now() - self.last_negative
|
||||||
return "{:d} jours".format(delta.days)
|
return "{:d} jours".format(delta.days)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Save note with it's alias (called in polymorphic children)
|
Save note with it's alias (called in polymorphic children)
|
||||||
@ -108,12 +109,16 @@ class Note(PolymorphicModel):
|
|||||||
|
|
||||||
# Save alias
|
# Save alias
|
||||||
a.note = self
|
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)
|
a.save(force_insert=True)
|
||||||
else:
|
else:
|
||||||
# Check if the name of the note changed without changing the normalized form of the alias
|
# 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)))
|
alias = Alias.objects.get(normalized_name=Alias.normalize(str(self)))
|
||||||
if alias.name != str(self):
|
if alias.name != str(self):
|
||||||
alias.name = str(self)
|
alias.name = str(self)
|
||||||
|
alias._force_save = True
|
||||||
alias.save()
|
alias.save()
|
||||||
|
|
||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
@ -154,19 +159,6 @@ class NoteUser(Note):
|
|||||||
def pretty(self):
|
def pretty(self):
|
||||||
return _("%(user)s's note") % {'user': str(self.user)}
|
return _("%(user)s's note") % {'user': str(self.user)}
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if self.pk and self.balance < 0:
|
|
||||||
old_note = NoteUser.objects.get(pk=self.pk)
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
if old_note.balance >= 0:
|
|
||||||
# Passage en négatif
|
|
||||||
self.last_negative = timezone.now()
|
|
||||||
self._force_save = True
|
|
||||||
self.save(*args, **kwargs)
|
|
||||||
self.send_mail_negative_balance()
|
|
||||||
else:
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def send_mail_negative_balance(self):
|
def send_mail_negative_balance(self):
|
||||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||||
@ -195,19 +187,6 @@ class NoteClub(Note):
|
|||||||
def pretty(self):
|
def pretty(self):
|
||||||
return _("Note of %(club)s club") % {'club': str(self.club)}
|
return _("Note of %(club)s club") % {'club': str(self.club)}
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
if self.pk and self.balance < 0:
|
|
||||||
old_note = NoteClub.objects.get(pk=self.pk)
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
if old_note.balance >= 0:
|
|
||||||
# Passage en négatif
|
|
||||||
self.last_negative = timezone.now()
|
|
||||||
self._force_save = True
|
|
||||||
self.save(*args, **kwargs)
|
|
||||||
self.send_mail_negative_balance()
|
|
||||||
else:
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def send_mail_negative_balance(self):
|
def send_mail_negative_balance(self):
|
||||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||||
@ -310,6 +289,7 @@ class Alias(models.Model):
|
|||||||
pass
|
pass
|
||||||
self.normalized_name = normalized_name
|
self.normalized_name = normalized_name
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.clean()
|
self.clean()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
@ -170,19 +170,21 @@ class Transaction(PolymorphicModel):
|
|||||||
previous_source_balance = self.source.balance
|
previous_source_balance = self.source.balance
|
||||||
previous_dest_balance = self.destination.balance
|
previous_dest_balance = self.destination.balance
|
||||||
|
|
||||||
source_balance = self.source.balance
|
source_balance = previous_source_balance
|
||||||
dest_balance = self.destination.balance
|
dest_balance = previous_dest_balance
|
||||||
|
|
||||||
created = self.pk is None
|
created = self.pk is None
|
||||||
to_transfer = self.amount * self.quantity
|
to_transfer = self.total
|
||||||
if not created and not self.valid and not hasattr(self, "_force_save"):
|
if not created:
|
||||||
# Revert old transaction
|
# Revert old transaction
|
||||||
old_transaction = Transaction.objects.get(pk=self.pk)
|
# We make a select for update to avoid concurrency issues
|
||||||
|
old_transaction = Transaction.objects.select_for_update().get(pk=self.pk)
|
||||||
# Check that nothing important changed
|
# Check that nothing important changed
|
||||||
for field_name in ["source_id", "destination_id", "quantity", "amount"]:
|
if not hasattr(self, "_force_save"):
|
||||||
if getattr(self, field_name) != getattr(old_transaction, field_name):
|
for field_name in ["source_id", "destination_id", "quantity", "amount"]:
|
||||||
raise ValidationError(_("You can't update the {field} on a Transaction. "
|
if getattr(self, field_name) != getattr(old_transaction, field_name):
|
||||||
"Please invalidate it and create one other.").format(field=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:
|
if old_transaction.valid == self.valid:
|
||||||
# Don't change anything
|
# Don't change anything
|
||||||
@ -215,9 +217,8 @@ class Transaction(PolymorphicModel):
|
|||||||
# When source == destination, no money is transferred and no transaction is created
|
# When source == destination, no money is transferred and no transaction is created
|
||||||
return
|
return
|
||||||
|
|
||||||
# We refresh the notes with the "select for update" tag to avoid concurrency issues
|
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||||
self.source = Note.objects.filter(pk=self.source_id).select_for_update().get()
|
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||||
self.destination = Note.objects.filter(pk=self.destination_id).select_for_update().get()
|
|
||||||
|
|
||||||
# Check that the amounts stay between big integer bounds
|
# Check that the amounts stay between big integer bounds
|
||||||
diff_source, diff_dest = self.validate()
|
diff_source, diff_dest = self.validate()
|
||||||
@ -237,9 +238,11 @@ class Transaction(PolymorphicModel):
|
|||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
# Save notes
|
# Save notes
|
||||||
|
self.source.refresh_from_db()
|
||||||
self.source.balance += diff_source
|
self.source.balance += diff_source
|
||||||
self.source._force_save = True
|
self.source._force_save = True
|
||||||
self.source.save()
|
self.source.save()
|
||||||
|
self.destination.refresh_from_db()
|
||||||
self.destination.balance += diff_dest
|
self.destination.balance += diff_dest
|
||||||
self.destination._force_save = True
|
self.destination._force_save = True
|
||||||
self.destination.save()
|
self.destination.save()
|
||||||
@ -273,6 +276,7 @@ class RecurrentTransaction(Transaction):
|
|||||||
_("The destination of this transaction must equal to the destination of the template."))
|
_("The destination of this transaction must equal to the destination of the template."))
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.clean()
|
self.clean()
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
@ -323,6 +327,7 @@ class SpecialTransaction(Transaction):
|
|||||||
raise(ValidationError(_("A special transaction is only possible between a"
|
raise(ValidationError(_("A special transaction is only possible between a"
|
||||||
" Note associated to a payment method and a User or a Club")))
|
" Note associated to a payment method and a User or a Club")))
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.clean()
|
self.clean()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
# 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
|
||||||
|
|
||||||
|
|
||||||
def save_user_note(instance, raw, **_kwargs):
|
def save_user_note(instance, raw, **_kwargs):
|
||||||
"""
|
"""
|
||||||
@ -25,6 +27,16 @@ def save_club_note(instance, raw, **_kwargs):
|
|||||||
instance.note.save()
|
instance.note.save()
|
||||||
|
|
||||||
|
|
||||||
|
def pre_save_note(instance, raw, **_kwargs):
|
||||||
|
if not raw and instance.pk and not hasattr(instance, "_no_signal") and instance.balance < 0:
|
||||||
|
from note.models import Note
|
||||||
|
old_note = Note.objects.get(pk=instance.pk)
|
||||||
|
if old_note.balance >= 0:
|
||||||
|
# Passage en négatif
|
||||||
|
instance.last_negative = timezone.now()
|
||||||
|
instance.send_mail_negative_balance()
|
||||||
|
|
||||||
|
|
||||||
def delete_transaction(instance, **_kwargs):
|
def delete_transaction(instance, **_kwargs):
|
||||||
"""
|
"""
|
||||||
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
|
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
|
||||||
|
@ -29,7 +29,6 @@ $(document).ready(function () {
|
|||||||
// Switching in double consumptions mode should update the layout
|
// Switching in double consumptions mode should update the layout
|
||||||
$('#double_conso').change(function () {
|
$('#double_conso').change(function () {
|
||||||
$('#consos_list_div').removeClass('d-none')
|
$('#consos_list_div').removeClass('d-none')
|
||||||
$('#user_select_div').attr('class', 'col-xl-4')
|
|
||||||
$('#infos_div').attr('class', 'col-sm-5 col-xl-6')
|
$('#infos_div').attr('class', 'col-sm-5 col-xl-6')
|
||||||
|
|
||||||
const note_list_obj = $('#note_list')
|
const note_list_obj = $('#note_list')
|
||||||
@ -48,7 +47,6 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
$('#single_conso').change(function () {
|
$('#single_conso').change(function () {
|
||||||
$('#consos_list_div').addClass('d-none')
|
$('#consos_list_div').addClass('d-none')
|
||||||
$('#user_select_div').attr('class', 'col-xl-7')
|
|
||||||
$('#infos_div').attr('class', 'col-sm-5 col-md-4')
|
$('#infos_div').attr('class', 'col-sm-5 col-md-4')
|
||||||
|
|
||||||
const consos_list_obj = $('#consos_list')
|
const consos_list_obj = $('#consos_list')
|
@ -67,7 +67,11 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
last.quantity = 1
|
last.quantity = 1
|
||||||
|
|
||||||
if (!last.note.user) {
|
if (last.note.club) {
|
||||||
|
$('#last_name').val(last.note.name)
|
||||||
|
$('#first_name').val(last.note.name)
|
||||||
|
}
|
||||||
|
else if (!last.note.user) {
|
||||||
$.getJSON('/api/note/note/' + last.note.id + '/?format=json', function (note) {
|
$.getJSON('/api/note/note/' + last.note.id + '/?format=json', function (note) {
|
||||||
last.note.user = note.user
|
last.note.user = note.user
|
||||||
$.getJSON('/api/user/' + last.note.user + '/', function (user) {
|
$.getJSON('/api/user/' + last.note.user + '/', function (user) {
|
||||||
@ -246,7 +250,7 @@ $('#btn_transfer').click(function () {
|
|||||||
error = true
|
error = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reason_field.val()) {
|
if (!reason_field.val() && $('#type_transfer').is(':checked')) {
|
||||||
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>')
|
||||||
error = true
|
error = true
|
||||||
@ -388,7 +392,7 @@ $('#btn_transfer').click(function () {
|
|||||||
alias = sources_notes_display[0].name
|
alias = sources_notes_display[0].name
|
||||||
source_id = user_note.id
|
source_id = user_note.id
|
||||||
dest_id = special_note
|
dest_id = special_note
|
||||||
reason = 'Retrait ' + $('#credit_type option:selected').text().toLowerCase()
|
reason = 'Retrait ' + $('#debit_type option:selected').text().toLowerCase()
|
||||||
if (given_reason.length > 0) { reason += ' (' + given_reason + ')' }
|
if (given_reason.length > 0) { reason += ' (' + given_reason + ')' }
|
||||||
}
|
}
|
||||||
$.post('/api/note/transaction/transaction/',
|
$.post('/api/note/transaction/transaction/',
|
@ -10,22 +10,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-sm-5 col-md-4" id="infos_div">
|
<div class="col-sm-5 col-md-4" id="infos_div">
|
||||||
<div class="row">
|
<div class="row justify-content-center justify-content-md-end">
|
||||||
{# User details column #}
|
{# User details column #}
|
||||||
<div class="col">
|
<div class="col picture-col">
|
||||||
<div class="card bg-light border-success mb-4 text-center">
|
<div class="card bg-light mb-4 text-center">
|
||||||
<a id="profile_pic_link" href="#">
|
<a id="profile_pic_link" href="#">
|
||||||
<img src="{% static "member/img/default_picture.png" %}"
|
<img src="{% static "member/img/default_picture.png" %}"
|
||||||
id="profile_pic" alt="" class="card-img-top">
|
id="profile_pic" alt="" class="card-img-top d-none d-sm-block">
|
||||||
</a>
|
</a>
|
||||||
<div class="card-body text-center text-break">
|
<div class="card-body text-center text-break p-2">
|
||||||
<span id="user_note"></span>
|
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# User selection column #}
|
{# User selection column #}
|
||||||
<div class="col-xl-7" id="user_select_div">
|
<div class="col-xl" id="user_select_div">
|
||||||
<div class="card bg-light border-success mb-4">
|
<div class="card bg-light border-success mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold">
|
<p class="card-text font-weight-bold">
|
||||||
@ -44,6 +44,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Summary of consumption and consume button #}
|
{# Summary of consumption and consume button #}
|
||||||
<div class="col-xl-5 d-none" id="consos_list_div">
|
<div class="col-xl-5 d-none" id="consos_list_div">
|
||||||
<div class="card bg-light border-info mb-4">
|
<div class="card bg-light border-info mb-4">
|
||||||
@ -65,7 +66,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{# Show last used buttons #}
|
{# Show last used buttons #}
|
||||||
<div class="card bg-light mb-4">
|
<div class="card bg-light mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -159,7 +159,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrajavascript %}
|
{% block extrajavascript %}
|
||||||
<script type="text/javascript" src="{% static "js/consos.js" %}"></script>
|
<script type="text/javascript" src="{% static "note/js/consos.js" %}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
{% for button in highlighted %}
|
{% for button in highlighted %}
|
||||||
{% if button.display %}
|
{% if button.display %}
|
||||||
|
@ -34,21 +34,21 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row">
|
<div class="row justify-content-center">
|
||||||
{# Preview note profile (picture, username and balance) #}
|
{# Preview note profile (picture, username and balance) #}
|
||||||
<div class="col-md-3" id="note_infos_div">
|
<div class="col-md picture-col" id="note_infos_div">
|
||||||
<div class="card bg-light border-success shadow mb-4 pt-4 text-center">
|
<div class="card bg-light mb-4 text-center">
|
||||||
<a id="profile_pic_link" href="#"><img src="{% static "member/img/default_picture.png" %}"
|
<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>
|
id="profile_pic" alt="" class="img-fluid rounded mx-auto"></a>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center p-2">
|
||||||
<span id="user_note"></span>
|
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# list of emitters #}
|
{# list of emitters #}
|
||||||
<div class="col-md-3" id="emitters_div">
|
<div class="col-md-3" id="emitters_div">
|
||||||
<div class="card bg-light border-success shadow mb-4">
|
<div class="card bg-light mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold">
|
<p class="card-text font-weight-bold">
|
||||||
<label for="source_note" id="source_note_label">{% trans "Select emitters" %}</label>
|
<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 #}
|
{# list of receiver #}
|
||||||
<div class="col-md-3" id="dests_div">
|
<div class="col-md-3" id="dests_div">
|
||||||
<div class="card bg-light border-info shadow mb-4">
|
<div class="card bg-light mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold" id="dest_title">
|
<p class="card-text font-weight-bold" id="dest_title">
|
||||||
<label for="dest_note" id="dest_note_label">{% trans "Select receivers" %}</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
{# Information on transaction (amount, reason, name,...) #}
|
{# Information on transaction (amount, reason, name,...) #}
|
||||||
<div class="col-md-3" id="external_div">
|
<div class="col-md" id="external_div">
|
||||||
<div class="card bg-light border-warning shadow mb-4">
|
<div class="card bg-light mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold">
|
<p class="card-text font-weight-bold">
|
||||||
{% trans "Action" %}
|
{% trans "Action" %}
|
||||||
@ -153,7 +153,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{# transaction history #}
|
{# transaction history #}
|
||||||
<div class="card shadow mb-4" id="history">
|
<div class="card mb-4" id="history">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold">
|
<p class="card-text font-weight-bold">
|
||||||
{% trans "Recent transactions history" %}
|
{% trans "Recent transactions history" %}
|
||||||
@ -176,5 +176,5 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
select_receveirs_label = "{% trans "Select receivers" %}";
|
select_receveirs_label = "{% trans "Select receivers" %}";
|
||||||
transfer_type_label = "{% trans "Transfer type" %}";
|
transfer_type_label = "{% trans "Transfer type" %}";
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/js/transfer.js"></script>
|
<script src="{% static "note/js/transfer.js" %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -144,7 +144,7 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
|
|||||||
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
The Magic View that make people pay their beer and burgers.
|
The Magic View that make people pay their beer and burgers.
|
||||||
(Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`)
|
(Most of the magic happens in the dark world of Javascript see `static/note/js/consos.js`)
|
||||||
"""
|
"""
|
||||||
model = Transaction
|
model = Transaction
|
||||||
template_name = "note/conso_form.html"
|
template_name = "note/conso_form.html"
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.sessions.models import Session
|
from django.contrib.sessions.models import Session
|
||||||
from note_kfet.middlewares import get_current_session
|
from note_kfet.middlewares import get_current_session
|
||||||
|
|
||||||
@ -33,9 +32,9 @@ def memoize(f):
|
|||||||
sess_funs = new_sess_funs
|
sess_funs = new_sess_funs
|
||||||
|
|
||||||
def func(*args, **kwargs):
|
def func(*args, **kwargs):
|
||||||
if settings.DEBUG:
|
# if settings.DEBUG:
|
||||||
# Don't memoize in DEBUG mode
|
# # Don't memoize in DEBUG mode
|
||||||
return f(*args, **kwargs)
|
# return f(*args, **kwargs)
|
||||||
|
|
||||||
nonlocal last_collect
|
nonlocal last_collect
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
"type": "view",
|
"type": "view",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
"permanent": true,
|
"permanent": false,
|
||||||
"description": "Voir les aliases des notes des clubs et des adhérents du club Kfet"
|
"description": "Voir les aliases des notes des clubs et des adhérents du club Kfet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1103,7 +1103,7 @@
|
|||||||
"treasury",
|
"treasury",
|
||||||
"sogecredit"
|
"sogecredit"
|
||||||
],
|
],
|
||||||
"query": "{\"credit_transaction\": null}",
|
"query": "{}",
|
||||||
"type": "add",
|
"type": "add",
|
||||||
"mask": 1,
|
"mask": 1,
|
||||||
"field": "",
|
"field": "",
|
||||||
@ -2647,6 +2647,166 @@
|
|||||||
"description": "Changer l'image de la note de son club"
|
"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.permission",
|
||||||
|
"pk": 179,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"note",
|
||||||
|
"alias"
|
||||||
|
],
|
||||||
|
"query": "{\"note__noteuser__user\": [\"user\"]}",
|
||||||
|
"type": "view",
|
||||||
|
"mask": 1,
|
||||||
|
"field": "",
|
||||||
|
"permanent": true,
|
||||||
|
"description": "Voir ses propres alias, pour toujours"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "permission.role",
|
"model": "permission.role",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
@ -2717,7 +2877,8 @@
|
|||||||
157,
|
157,
|
||||||
158,
|
158,
|
||||||
159,
|
159,
|
||||||
160
|
160,
|
||||||
|
179
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2821,6 +2982,7 @@
|
|||||||
31,
|
31,
|
||||||
32,
|
32,
|
||||||
33,
|
33,
|
||||||
|
51,
|
||||||
53,
|
53,
|
||||||
54,
|
54,
|
||||||
55,
|
55,
|
||||||
@ -2844,13 +3006,23 @@
|
|||||||
137,
|
137,
|
||||||
138,
|
138,
|
||||||
139,
|
139,
|
||||||
|
140,
|
||||||
143,
|
143,
|
||||||
146,
|
146,
|
||||||
147,
|
147,
|
||||||
150,
|
150,
|
||||||
151,
|
151,
|
||||||
163,
|
163,
|
||||||
164
|
164,
|
||||||
|
170,
|
||||||
|
171,
|
||||||
|
172,
|
||||||
|
173,
|
||||||
|
174,
|
||||||
|
175,
|
||||||
|
176,
|
||||||
|
177,
|
||||||
|
178
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3024,7 +3196,16 @@
|
|||||||
166,
|
166,
|
||||||
167,
|
167,
|
||||||
168,
|
168,
|
||||||
169
|
169,
|
||||||
|
170,
|
||||||
|
171,
|
||||||
|
172,
|
||||||
|
173,
|
||||||
|
174,
|
||||||
|
175,
|
||||||
|
176,
|
||||||
|
177,
|
||||||
|
178
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3050,10 +3231,15 @@
|
|||||||
29,
|
29,
|
||||||
30,
|
30,
|
||||||
31,
|
31,
|
||||||
|
70,
|
||||||
143,
|
143,
|
||||||
166,
|
166,
|
||||||
167,
|
167,
|
||||||
168
|
168,
|
||||||
|
170,
|
||||||
|
171,
|
||||||
|
176,
|
||||||
|
177
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -3219,10 +3405,13 @@
|
|||||||
138,
|
138,
|
||||||
139,
|
139,
|
||||||
140,
|
140,
|
||||||
|
143,
|
||||||
145,
|
145,
|
||||||
146,
|
146,
|
||||||
147,
|
147,
|
||||||
150
|
150,
|
||||||
|
176,
|
||||||
|
177
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -43,6 +43,7 @@ class InstancedPermission:
|
|||||||
obj = copy(obj)
|
obj = copy(obj)
|
||||||
obj.pk = 0
|
obj.pk = 0
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
sid = transaction.savepoint()
|
||||||
for o in self.model.model_class().objects.filter(pk=0).all():
|
for o in self.model.model_class().objects.filter(pk=0).all():
|
||||||
o._force_delete = True
|
o._force_delete = True
|
||||||
Model.delete(o)
|
Model.delete(o)
|
||||||
@ -62,8 +63,10 @@ class InstancedPermission:
|
|||||||
Model.save(obj, force_insert=True)
|
Model.save(obj, force_insert=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._no_signal = True
|
||||||
obj._force_delete = True
|
obj._force_delete = True
|
||||||
Model.delete(obj)
|
Model.delete(obj)
|
||||||
|
transaction.savepoint_rollback(sid)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@ -199,6 +202,7 @@ class Permission(models.Model):
|
|||||||
if self.field and self.type not in {'view', 'change'}:
|
if self.field and self.type not in {'view', 'change'}:
|
||||||
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
self.full_clean()
|
self.full_clean()
|
||||||
super().save()
|
super().save()
|
||||||
|
@ -14,6 +14,7 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
|
|||||||
This is a simple patch of this class that controls view access.
|
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 = {
|
perms_map = {
|
||||||
'GET': ['%(app_label)s.view_%(model_name)s'],
|
'GET': ['%(app_label)s.view_%(model_name)s'],
|
||||||
'OPTIONS': [],
|
'OPTIONS': [],
|
||||||
|
@ -6,6 +6,7 @@ from datetime import date
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import HiddenInput
|
from django.forms import HiddenInput
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
@ -56,6 +57,7 @@ class ProtectQuerysetMixin:
|
|||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
Submit the form, if the page is a FormView.
|
Submit the form, if the page is a FormView.
|
||||||
|
@ -44,6 +44,15 @@ class SignUpForm(UserCreationForm):
|
|||||||
fields = ('first_name', 'last_name', 'username', 'email', )
|
fields = ('first_name', 'last_name', 'username', 'email', )
|
||||||
|
|
||||||
|
|
||||||
|
class DeclareSogeAccountOpenedForm(forms.Form):
|
||||||
|
soge_account = forms.BooleanField(
|
||||||
|
label=_("I declare that I opened a bank account in the Société générale with the BDE partnership."),
|
||||||
|
help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your "
|
||||||
|
"account, you will have to pay the BDE membership."),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class WEISignupForm(forms.Form):
|
class WEISignupForm(forms.Form):
|
||||||
wei_registration = forms.BooleanField(
|
wei_registration = forms.BooleanField(
|
||||||
label=_("Register to the WEI"),
|
label=_("Register to the WEI"),
|
||||||
@ -60,7 +69,7 @@ class ValidationForm(forms.Form):
|
|||||||
soge = forms.BooleanField(
|
soge = forms.BooleanField(
|
||||||
label=_("Inscription paid by Société Générale"),
|
label=_("Inscription paid by Société Générale"),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Check this case is the Société Générale paid the inscription."),
|
help_text=_("Check this case if the Société Générale paid the inscription."),
|
||||||
)
|
)
|
||||||
|
|
||||||
credit_type = forms.ModelChoiceField(
|
credit_type = forms.ModelChoiceField(
|
||||||
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-3 mb-4">
|
<div class="col-xl-5 mb-4">
|
||||||
<div class="card bg-light shadow">
|
<div class="card bg-light shadow">
|
||||||
<div class="card-header text-center" >
|
<div class="card-header text-center" >
|
||||||
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
|
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
|
||||||
@ -50,12 +50,19 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-9">
|
<div class="col-md-7">
|
||||||
<div class="card bg-light shadow">
|
<div class="card bg-light shadow">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="card-header text-center" >
|
<div class="card-header text-center" >
|
||||||
<h4> {% trans "Validate account" %}</h4>
|
<h4> {% trans "Validate account" %}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if declare_soge_account %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% trans "The user declared that he/she opened a bank account in the Société générale." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="card-body" id="profile_infos">
|
<div class="card-body" id="profile_infos">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
@ -104,7 +111,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
|
|
||||||
soge_field.change(fillFields);
|
soge_field.change(fillFields);
|
||||||
|
|
||||||
{% if object.profile.soge %}
|
{% if declare_soge_account %}
|
||||||
soge_field.attr('checked', true);
|
soge_field.attr('checked', true);
|
||||||
fillFields();
|
fillFields();
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -5,6 +5,7 @@ from django.conf import settings
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.shortcuts import resolve_url, redirect
|
from django.shortcuts import resolve_url, redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
@ -23,7 +24,7 @@ from permission.models import Role
|
|||||||
from permission.views import ProtectQuerysetMixin
|
from permission.views import ProtectQuerysetMixin
|
||||||
from treasury.models import SogeCredit
|
from treasury.models import SogeCredit
|
||||||
|
|
||||||
from .forms import SignUpForm, ValidationForm
|
from .forms import SignUpForm, ValidationForm, DeclareSogeAccountOpenedForm
|
||||||
from .tables import FutureUserTable
|
from .tables import FutureUserTable
|
||||||
from .tokens import email_validation_token
|
from .tokens import email_validation_token
|
||||||
|
|
||||||
@ -41,12 +42,14 @@ 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(self.request.POST if self.request.POST else None)
|
context["profile_form"] = self.second_form(self.request.POST if self.request.POST else None)
|
||||||
|
context["soge_form"] = DeclareSogeAccountOpenedForm(self.request.POST if self.request.POST else None)
|
||||||
del context["profile_form"].fields["section"]
|
del context["profile_form"].fields["section"]
|
||||||
del context["profile_form"].fields["report_frequency"]
|
del context["profile_form"].fields["report_frequency"]
|
||||||
del context["profile_form"].fields["last_report"]
|
del context["profile_form"].fields["last_report"]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
If the form is valid, then the user is created with is_active set to False
|
If the form is valid, then the user is created with is_active set to False
|
||||||
@ -70,6 +73,13 @@ class UserCreateView(CreateView):
|
|||||||
|
|
||||||
user.profile.send_email_validation_link()
|
user.profile.send_email_validation_link()
|
||||||
|
|
||||||
|
soge_form = DeclareSogeAccountOpenedForm(self.request.POST)
|
||||||
|
if "soge_account" in soge_form.data and soge_form.data["soge_account"]:
|
||||||
|
# If the user declares that a bank account got opened, prepare the soge credit to warn treasurers
|
||||||
|
soge_credit = SogeCredit(user=user)
|
||||||
|
soge_credit._force_save = True
|
||||||
|
soge_credit.save()
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -180,7 +190,7 @@ class FutureUserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableVi
|
|||||||
| Q(username__iregex="^" + pattern)
|
| Q(username__iregex="^" + pattern)
|
||||||
)
|
)
|
||||||
|
|
||||||
return qs[:20]
|
return qs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
@ -225,6 +235,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
|||||||
fee += 8000
|
fee += 8000
|
||||||
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
|
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
|
||||||
|
|
||||||
|
ctx["declare_soge_account"] = True
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
@ -234,6 +246,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
|||||||
form.fields["first_name"].initial = user.first_name
|
form.fields["first_name"].initial = user.first_name
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
user = self.get_object()
|
user = self.get_object()
|
||||||
|
|
||||||
@ -304,6 +317,13 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
|||||||
user.profile.save()
|
user.profile.save()
|
||||||
user.refresh_from_db()
|
user.refresh_from_db()
|
||||||
|
|
||||||
|
if not soge and SogeCredit.objects.filter(user=user).exists():
|
||||||
|
# If the user declared that a bank account was opened but in the validation form the SoGé case was
|
||||||
|
# unchecked, delete the associated credit
|
||||||
|
soge_credit = SogeCredit.objects.get(user=user)
|
||||||
|
soge_credit._force_delete = True
|
||||||
|
soge_credit.delete()
|
||||||
|
|
||||||
if credit_type is not None and credit_amount > 0:
|
if credit_type is not None and credit_amount > 0:
|
||||||
# Credit the note
|
# Credit the note
|
||||||
SpecialTransaction.objects.create(
|
SpecialTransaction.objects.create(
|
||||||
@ -370,6 +390,8 @@ class FutureUserInvalidateView(ProtectQuerysetMixin, LoginRequiredMixin, View):
|
|||||||
user = User.objects.filter(profile__registration_valid=False)\
|
user = User.objects.filter(profile__registration_valid=False)\
|
||||||
.filter(PermissionBackend.filter_queryset(request.user, User, "change", "is_valid"))\
|
.filter(PermissionBackend.filter_queryset(request.user, User, "change", "is_valid"))\
|
||||||
.get(pk=self.kwargs["pk"])
|
.get(pk=self.kwargs["pk"])
|
||||||
|
# Delete associated soge credits before
|
||||||
|
SogeCredit.objects.filter(user=user).delete()
|
||||||
|
|
||||||
user.delete()
|
user.delete()
|
||||||
|
|
||||||
|
Submodule apps/scripts updated: e5b76b7c35...7e27c3b71b
@ -4,6 +4,7 @@
|
|||||||
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.db import transaction
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.inputs import AmountInput
|
from note_kfet.inputs import AmountInput
|
||||||
|
|
||||||
@ -149,6 +150,7 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
|
|||||||
self.instance.transaction.bank = cleaned_data["bank"]
|
self.instance.transaction.bank = cleaned_data["bank"]
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
"""
|
"""
|
||||||
Save the transaction and the remittance.
|
Save the transaction and the remittance.
|
||||||
|
@ -5,12 +5,12 @@ from datetime import date
|
|||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
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.db.models import Q
|
from django.db.models import Q
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
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 _
|
||||||
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction
|
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction, NoteUser
|
||||||
|
|
||||||
|
|
||||||
class Invoice(models.Model):
|
class Invoice(models.Model):
|
||||||
@ -76,6 +76,7 @@ class Invoice(models.Model):
|
|||||||
verbose_name=_("tex source"),
|
verbose_name=_("tex source"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
When an invoice is generated, we store the tex source.
|
When an invoice is generated, we store the tex source.
|
||||||
@ -228,6 +229,7 @@ class Remittance(models.Model):
|
|||||||
"""
|
"""
|
||||||
return sum(transaction.total for transaction in self.transactions.all())
|
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):
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
# Check if all transactions have the right type.
|
# Check if all transactions have the right type.
|
||||||
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||||
@ -291,11 +293,12 @@ class SogeCredit(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self):
|
||||||
return self.credit_transaction.valid
|
return self.credit_transaction and self.credit_transaction.valid
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def amount(self):
|
def amount(self):
|
||||||
return sum(transaction.total for transaction in self.transactions.all()) + 8000
|
return self.credit_transaction.total if self.valid \
|
||||||
|
else sum(transaction.total for transaction in self.transactions.all()) + 8000
|
||||||
|
|
||||||
def invalidate(self):
|
def invalidate(self):
|
||||||
"""
|
"""
|
||||||
@ -305,10 +308,10 @@ class SogeCredit(models.Model):
|
|||||||
if self.valid:
|
if self.valid:
|
||||||
self.credit_transaction.valid = False
|
self.credit_transaction.valid = False
|
||||||
self.credit_transaction.save()
|
self.credit_transaction.save()
|
||||||
for transaction in self.transactions.all():
|
for tr in self.transactions.all():
|
||||||
transaction.valid = False
|
tr.valid = False
|
||||||
transaction._force_save = True
|
tr._force_save = True
|
||||||
transaction.save()
|
tr.save()
|
||||||
|
|
||||||
def validate(self, force=False):
|
def validate(self, force=False):
|
||||||
if self.valid and not force:
|
if self.valid and not force:
|
||||||
@ -320,18 +323,25 @@ class SogeCredit(models.Model):
|
|||||||
# Refresh credit amount
|
# Refresh credit amount
|
||||||
self.save()
|
self.save()
|
||||||
self.credit_transaction.valid = True
|
self.credit_transaction.valid = True
|
||||||
|
self.credit_transaction._force_save = True
|
||||||
self.credit_transaction.save()
|
self.credit_transaction.save()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
for transaction in self.transactions.all():
|
for tr in self.transactions.all():
|
||||||
transaction.valid = True
|
tr.valid = True
|
||||||
transaction._force_save = True
|
tr._force_save = True
|
||||||
transaction.created_at = timezone.now()
|
tr.created_at = timezone.now()
|
||||||
transaction.save()
|
tr.save()
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
# This is a pre-registered user that declared that a SoGé account was opened.
|
||||||
|
# No note exists yet.
|
||||||
|
if not NoteUser.objects.filter(user=self.user).exists():
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
if not self.credit_transaction:
|
if not self.credit_transaction:
|
||||||
self.credit_transaction = SpecialTransaction.objects.create(
|
credit_transaction = SpecialTransaction(
|
||||||
source=NoteSpecial.objects.get(special_type="Virement bancaire"),
|
source=NoteSpecial.objects.get(special_type="Virement bancaire"),
|
||||||
destination=self.user.note,
|
destination=self.user.note,
|
||||||
quantity=1,
|
quantity=1,
|
||||||
@ -342,6 +352,10 @@ class SogeCredit(models.Model):
|
|||||||
bank="Société générale",
|
bank="Société générale",
|
||||||
valid=False,
|
valid=False,
|
||||||
)
|
)
|
||||||
|
credit_transaction._force_save = True
|
||||||
|
credit_transaction.save()
|
||||||
|
credit_transaction.refresh_from_db()
|
||||||
|
self.credit_transaction = credit_transaction
|
||||||
elif not self.valid:
|
elif not self.valid:
|
||||||
self.credit_transaction.amount = self.amount
|
self.credit_transaction.amount = self.amount
|
||||||
self.credit_transaction._force_save = True
|
self.credit_transaction._force_save = True
|
||||||
@ -361,11 +375,11 @@ class SogeCredit(models.Model):
|
|||||||
"Please ask her/him to credit the note before invalidating this credit."))
|
"Please ask her/him to credit the note before invalidating this credit."))
|
||||||
|
|
||||||
self.invalidate()
|
self.invalidate()
|
||||||
for transaction in self.transactions.all():
|
for tr in self.transactions.all():
|
||||||
transaction._force_save = True
|
tr._force_save = True
|
||||||
transaction.valid = True
|
tr.valid = True
|
||||||
transaction.created_at = timezone.now()
|
tr.created_at = timezone.now()
|
||||||
transaction.save()
|
tr.save()
|
||||||
self.credit_transaction.valid = False
|
self.credit_transaction.valid = False
|
||||||
self.credit_transaction.reason += " (invalide)"
|
self.credit_transaction.reason += " (invalide)"
|
||||||
self.credit_transaction.save()
|
self.credit_transaction.save()
|
||||||
|
@ -60,7 +60,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
let pattern = searchbar_obj.val();
|
let pattern = searchbar_obj.val();
|
||||||
|
|
||||||
$("#credits_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + (
|
$("#credits_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + (
|
||||||
invalid_only_obj.is(':checked') ? "&valid=false" : "") + " #credits_table");
|
invalid_only_obj.is(':checked') ? "" : "&valid=1") + " #credits_table");
|
||||||
|
|
||||||
$(".table-row").click(function () {
|
$(".table-row").click(function () {
|
||||||
window.document.location = $(this).data("href");
|
window.document.location = $(this).data("href");
|
||||||
|
@ -9,6 +9,7 @@ from tempfile import mkdtemp
|
|||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import ValidationError, PermissionDenied
|
from django.core.exceptions import ValidationError, PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms import Form
|
from django.forms import Form
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
@ -65,6 +66,7 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
del form.fields["locked"]
|
del form.fields["locked"]
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
@ -144,6 +146,7 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
|||||||
del form.fields["id"]
|
del form.fields["id"]
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
@ -428,7 +431,7 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi
|
|||||||
if "valid" not in self.request.GET or not self.request.GET["valid"]:
|
if "valid" not in self.request.GET or not self.request.GET["valid"]:
|
||||||
qs = qs.filter(credit_transaction__valid=False)
|
qs = qs.filter(credit_transaction__valid=False)
|
||||||
|
|
||||||
return qs[:20]
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView):
|
class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView):
|
||||||
@ -439,6 +442,7 @@ class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormVie
|
|||||||
form_class = Form
|
form_class = Form
|
||||||
extra_context = {"title": _("Manage credits from the Société générale")}
|
extra_context = {"title": _("Manage credits from the Société générale")}
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if "validate" in form.data:
|
if "validate" in form.data:
|
||||||
self.get_object().validate(True)
|
self.get_object().validate(True)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from random import choice
|
from random import choice
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db import transaction
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
|
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
|
||||||
@ -88,6 +89,7 @@ class WEISurvey2020(WEISurvey):
|
|||||||
"""
|
"""
|
||||||
form.set_registration(self.registration)
|
form.set_registration(self.registration)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
word = form.cleaned_data["word"]
|
word = form.cleaned_data["word"]
|
||||||
self.information.step += 1
|
self.information.step += 1
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
# 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)
|
|
@ -238,7 +238,7 @@ class WEIRegistration(models.Model):
|
|||||||
information_json = models.TextField(
|
information_json = models.TextField(
|
||||||
default="{}",
|
default="{}",
|
||||||
verbose_name=_("registration information"),
|
verbose_name=_("registration information"),
|
||||||
help_text=_("Information about the registration (buses for old members, survey fot the new members), "
|
help_text=_("Information about the registration (buses for old members, survey for the new members), "
|
||||||
"encoded in JSON"),
|
"encoded in JSON"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ from tempfile import mkdtemp
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import Q, Count
|
from django.db.models import Q, Count
|
||||||
from django.db.models.functions.text import Lower
|
from django.db.models.functions.text import Lower
|
||||||
from django.forms import HiddenInput
|
from django.forms import HiddenInput
|
||||||
@ -84,6 +85,7 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
date_end=date.today(),
|
date_end=date.today(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.requires_membership = True
|
form.instance.requires_membership = True
|
||||||
form.instance.parent_club = Club.objects.get(name="Kfet")
|
form.instance.parent_club = Club.objects.get(name="Kfet")
|
||||||
@ -517,6 +519,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
del form.fields["information_json"]
|
del form.fields["information_json"]
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
|
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
|
||||||
form.instance.first_year = True
|
form.instance.first_year = True
|
||||||
@ -597,6 +600,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
|
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
|
||||||
form.instance.first_year = False
|
form.instance.first_year = False
|
||||||
@ -688,6 +692,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
|
|||||||
del form.fields["information_json"]
|
del form.fields["information_json"]
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
# If the membership is already validated, then we update the bus and the team (and the roles)
|
# If the membership is already validated, then we update the bus and the team (and the roles)
|
||||||
if form.instance.is_validated:
|
if form.instance.is_validated:
|
||||||
@ -866,6 +871,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
|||||||
).all()
|
).all()
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
Create membership, check that all is good, make transactions
|
Create membership, check that all is good, make transactions
|
||||||
@ -1016,6 +1022,7 @@ class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
|
|||||||
context["club"] = self.object.wei
|
context["club"] = self.object.wei
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""
|
"""
|
||||||
Update the survey with the data of the form.
|
Update the survey with the data of the form.
|
||||||
|
File diff suppressed because it is too large
Load Diff
3204
locale/es/LC_MESSAGES/django.po
Normal file
3204
locale/es/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -20,3 +20,5 @@
|
|||||||
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports
|
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports
|
||||||
# Mettre à jour les boutons mis en avant
|
# Mettre à jour les boutons mis en avant
|
||||||
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons
|
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons
|
||||||
|
# Vider les tokens Oauth2
|
||||||
|
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens
|
||||||
|
@ -26,6 +26,14 @@ admin_site = StrongAdminSite()
|
|||||||
admin_site.register(Site, SiteAdmin)
|
admin_site.register(Site, SiteAdmin)
|
||||||
|
|
||||||
# Add external apps model
|
# Add external apps model
|
||||||
|
if "oauth2_provider" in settings.INSTALLED_APPS:
|
||||||
|
from oauth2_provider.admin import Application, ApplicationAdmin, Grant, \
|
||||||
|
GrantAdmin, AccessToken, AccessTokenAdmin, RefreshToken, RefreshTokenAdmin
|
||||||
|
admin_site.register(Application, ApplicationAdmin)
|
||||||
|
admin_site.register(Grant, GrantAdmin)
|
||||||
|
admin_site.register(AccessToken, AccessTokenAdmin)
|
||||||
|
admin_site.register(RefreshToken, RefreshTokenAdmin)
|
||||||
|
|
||||||
if "django_htcpcp_tea" in settings.INSTALLED_APPS:
|
if "django_htcpcp_tea" in settings.INSTALLED_APPS:
|
||||||
from django_htcpcp_tea.admin import *
|
from django_htcpcp_tea.admin import *
|
||||||
from django_htcpcp_tea.models import *
|
from django_htcpcp_tea.models import *
|
||||||
@ -44,9 +52,3 @@ if "rest_framework" in settings.INSTALLED_APPS:
|
|||||||
from rest_framework.authtoken.admin import *
|
from rest_framework.authtoken.admin import *
|
||||||
from rest_framework.authtoken.models import *
|
from rest_framework.authtoken.models import *
|
||||||
admin_site.register(Token, TokenAdmin)
|
admin_site.register(Token, TokenAdmin)
|
||||||
|
|
||||||
if "cas_server" in settings.INSTALLED_APPS:
|
|
||||||
from cas_server.admin import *
|
|
||||||
from cas_server.models import *
|
|
||||||
admin_site.register(ServicePattern, ServicePatternAdmin)
|
|
||||||
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"model": "cas_server.servicepattern",
|
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
|
||||||
"pos": 1,
|
|
||||||
"pattern": ".*",
|
|
||||||
"name": "REPLACEME"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -3,7 +3,7 @@
|
|||||||
"model": "sites.site",
|
"model": "sites.site",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"domain": "localhost",
|
"domain": "note.crans.org",
|
||||||
"name": "La Note Kfet \ud83c\udf7b"
|
"name": "La Note Kfet \ud83c\udf7b"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,20 @@ class SessionMiddleware(object):
|
|||||||
|
|
||||||
def __call__(self, request):
|
def __call__(self, request):
|
||||||
user = request.user
|
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:
|
if 'HTTP_X_REAL_IP' in request.META:
|
||||||
ip = request.META.get('HTTP_X_REAL_IP')
|
ip = request.META.get('HTTP_X_REAL_IP')
|
||||||
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
elif 'HTTP_X_FORWARDED_FOR' in request.META:
|
||||||
|
@ -49,13 +49,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "cas_server" in INSTALLED_APPS:
|
|
||||||
# CAS Settings
|
|
||||||
CAS_AUTO_CREATE_USER = False
|
|
||||||
CAS_LOGO_URL = "/static/img/Saperlistpopette.png"
|
|
||||||
CAS_FAVICON_URL = "/static/favicon/favicon-32x32.png"
|
|
||||||
CAS_SHOW_POWERED = False
|
|
||||||
|
|
||||||
if "logs" in INSTALLED_APPS:
|
if "logs" in INSTALLED_APPS:
|
||||||
MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',)
|
MIDDLEWARE += ('note_kfet.middlewares.SessionMiddleware',)
|
||||||
|
|
||||||
|
@ -35,8 +35,10 @@ INSTALLED_APPS = [
|
|||||||
'mailer',
|
'mailer',
|
||||||
'phonenumber_field',
|
'phonenumber_field',
|
||||||
'polymorphic',
|
'polymorphic',
|
||||||
|
'oauth2_provider',
|
||||||
|
|
||||||
# Django contrib
|
# Django contrib
|
||||||
|
# Django Admin will autodiscover our apps for our custom admin site.
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.admindocs',
|
'django.contrib.admindocs',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
@ -154,6 +156,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
LANGUAGES = [
|
LANGUAGES = [
|
||||||
('de', _('German')),
|
('de', _('German')),
|
||||||
('en', _('English')),
|
('en', _('English')),
|
||||||
|
('es', _('Spanish')),
|
||||||
('fr', _('French')),
|
('fr', _('French')),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -213,6 +216,16 @@ EMAIL_HOST_PASSWORD = os.getenv('EMAIL_PASSWORD', None)
|
|||||||
SERVER_EMAIL = os.getenv("NOTE_MAIL", "notekfet@example.com")
|
SERVER_EMAIL = os.getenv("NOTE_MAIL", "notekfet@example.com")
|
||||||
DEFAULT_FROM_EMAIL = "NoteKfet2020 <" + SERVER_EMAIL + ">"
|
DEFAULT_FROM_EMAIL = "NoteKfet2020 <" + SERVER_EMAIL + ">"
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
# https://docs.djangoproject.com/en/2.2/topics/cache/#setting-up-the-cache
|
||||||
|
cache_address = os.getenv("CACHE_ADDRESS", "127.0.0.1:11211")
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||||
|
'LOCATION': cache_address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Django REST Framework
|
# Django REST Framework
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
@ -232,7 +245,7 @@ REST_FRAMEWORK = {
|
|||||||
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
||||||
|
|
||||||
# After login redirect user to transfer page
|
# After login redirect user to transfer page
|
||||||
LOGIN_REDIRECT_URL = '/note/transfer/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
|
||||||
# An user session will expired after 3 hours
|
# An user session will expired after 3 hours
|
||||||
SESSION_COOKIE_AGE = 60 * 60 * 3
|
SESSION_COOKIE_AGE = 60 * 60 * 3
|
||||||
|
@ -24,6 +24,14 @@ if os.getenv("DJANGO_DEV_STORE_METHOD", "sqlite") != "postgresql":
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Dummy cache for development
|
||||||
|
# https://docs.djangoproject.com/en/2.2/topics/cache/#setting-up-the-cache
|
||||||
|
CACHES = {
|
||||||
|
'default': {
|
||||||
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Break it, fix it!
|
# Break it, fix it!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
# CAS
|
# CAS
|
||||||
OPTIONAL_APPS = [
|
OPTIONAL_APPS = [
|
||||||
# 'cas_server',
|
|
||||||
# 'debug_toolbar'
|
# 'debug_toolbar'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -22,6 +22,11 @@
|
|||||||
border-bottom-color: rgba(0, 0, 0, .250);
|
border-bottom-color: rgba(0, 0, 0, .250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fixed width picture column */
|
||||||
|
.picture-col {
|
||||||
|
max-width: 202px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Limit fluid container to a max size */
|
/* Limit fluid container to a max size */
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
max-width: 1600px;
|
max-width: 1600px;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{% load static i18n pretty_money static getenv perms %}
|
{% load static i18n pretty_money static getenv perms memberinfo %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
SPDX-License-Identifier: GPL-3.0-or-later
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
@ -64,7 +64,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-coffee"></i> {% trans 'Consumptions' %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if "note.transaction"|not_empty_model_list %}
|
{% if user.is_authenticated and user|is_member:"Kfet" %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{% url 'note:transfer' as url %}
|
{% url 'note:transfer' as url %}
|
||||||
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %} </a>
|
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %} </a>
|
||||||
@ -150,12 +150,36 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="{% block containertype %}container{% endblock %} my-3">
|
<div class="{% block containertype %}container{% endblock %} my-3">
|
||||||
{% if request.user.is_authenticated and not request.user.profile.email_confirmed %}
|
<div id="messages">
|
||||||
<div class="alert alert-warning">
|
{% if user.is_authenticated %}
|
||||||
{% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %}
|
{% if not user|is_member:"BDE" %}
|
||||||
</div>
|
<div class="alert alert-danger">
|
||||||
{% endif %}
|
{% trans "You are not a BDE member anymore. Please renew your membership if you want to use the note." %}
|
||||||
<div id="messages"></div>
|
</div>
|
||||||
|
{% elif not user|is_member:"Kfet" %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "You are not a Kfet member, so you can't use your note account." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not user.profile.email_confirmed %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if user.sogecredit and not user.sogecredit.valid %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You declared that you opened a bank account in the Société générale. The bank did not validate the creation of the account to the BDE,
|
||||||
|
so the registration bonus of 80 € is not credited and the membership is not paid yet.
|
||||||
|
This verification procedure may last a few days.
|
||||||
|
Please make sure that you go to the end of the account creation.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{# TODO Add banners #}
|
||||||
|
</div>
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<p>Default content...</p>
|
<p>Default content...</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -177,12 +201,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
onchange="this.form.submit()">
|
onchange="this.form.submit()">
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
{% get_available_languages as LANGUAGES %}
|
{% get_available_languages as LANGUAGES %}
|
||||||
{% get_language_info_list for LANGUAGES as languages %}
|
{% for lang_code, lang_name in LANGUAGES %}
|
||||||
{% for language in languages %}
|
<option value="{{ lang_code }}"
|
||||||
<option value="{{ language.code }}"
|
{% if lang_code == LANGUAGE_CODE %}
|
||||||
{% if language.code == LANGUAGE_CODE %}
|
|
||||||
selected{% endif %}>
|
selected{% endif %}>
|
||||||
{{ language.name_local }} ({{ language.code }})
|
{{ lang_name }} ({{ lang_code }})
|
||||||
</option>
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
{% load i18n %}{% load static %}{% get_current_language as LANGUAGE_CODE %}<!DOCTYPE html>
|
|
||||||
<html{% if LANGUAGE_CODE %} lang="{{LANGUAGE_CODE}}"{% endif %}>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge" /><![endif]-->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>{% block title %}{% trans "Central Authentication Service" %}{% endblock %}</title>
|
|
||||||
<link href="{{settings.CAS_COMPONENT_URLS.bootstrap3_css}}" rel="stylesheet">
|
|
||||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src="{{settings.CAS_COMPONENT_URLS.html5shiv}}"></script>
|
|
||||||
<script src="{{settings.CAS_COMPONENT_URLS.respond}}"></script>
|
|
||||||
<![endif]-->
|
|
||||||
{% if settings.CAS_FAVICON_URL %}<link rel="shortcut icon" href="{{settings.CAS_FAVICON_URL}}" />{% endif %}
|
|
||||||
<link href="{% static "cas_server/styles.css" %}" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="wrap">
|
|
||||||
<div class="container">
|
|
||||||
{% if auto_submit %}<noscript>{% endif %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
|
||||||
<h1 id="app-name">
|
|
||||||
{% if settings.CAS_LOGO_URL %}<img src="{{settings.CAS_LOGO_URL}}" alt="cas-logo" />{% endif %}
|
|
||||||
Authentification Note Kfet 2020</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if auto_submit %}</noscript>{% endif %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-12"></div>
|
|
||||||
<div class="col-lg-6 col-md-6 col-sm-8 col-xs-12">
|
|
||||||
{% if auto_submit %}<noscript>{% endif %}
|
|
||||||
{% for msg in CAS_INFO_RENDER %}
|
|
||||||
<div class="alert alert-{{msg.type}}{% if msg.discardable %} alert-dismissable{% endif %}">
|
|
||||||
{% if msg.discardable %}<button type="button" class="close" data-dismiss="alert" aria-hidden="true" id="info-{{msg.name}}">×</button>{% endif %}
|
|
||||||
<p>{{msg.message}}</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
|
|
||||||
<div class="alert alert-info alert-dismissable">
|
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true" id="alert-version">×</button>
|
|
||||||
<p>{% blocktrans %}A new version of the application is available. This instance runs {{VERSION}} and the last version is {{LAST_VERSION}}. Please consider upgrading.{% endblocktrans %}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% block ante_messages %}{% endblock %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div {% spaceless %}
|
|
||||||
{% if message.level == message_levels.DEBUG %}
|
|
||||||
class="alert alert-warning"
|
|
||||||
{% elif message.level == message_levels.INFO %}
|
|
||||||
class="alert alert-info"
|
|
||||||
{% elif message.level == message_levels.SUCCESS %}
|
|
||||||
class="alert alert-success"
|
|
||||||
{% elif message.level == message_levels.WARNING %}
|
|
||||||
class="alert alert-warning"
|
|
||||||
{% else %}
|
|
||||||
class="alert alert-danger"
|
|
||||||
{% endif %}
|
|
||||||
{% endspaceless %}>
|
|
||||||
<p>{{message}}</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% if auto_submit %}</noscript>{% endif %}
|
|
||||||
{% block content %}{% endblock %}
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-3 col-md-3 col-sm-2 col-xs-0"></div>
|
|
||||||
</div>
|
|
||||||
</div> <!-- /container -->
|
|
||||||
</div>
|
|
||||||
<div style="clear: both;"></div>
|
|
||||||
{% if settings.CAS_SHOW_POWERED %}
|
|
||||||
<div id="footer">
|
|
||||||
<p><a class="text-muted" href="https://pypi.org/project/django-cas-server/">django-cas-server powered</a></p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<script src="{{settings.CAS_COMPONENT_URLS.jquery}}"></script>
|
|
||||||
<script src="{{settings.CAS_COMPONENT_URLS.bootstrap3_js}}"></script>
|
|
||||||
<script src="{% static "cas_server/functions.js" %}"></script>
|
|
||||||
<script type="text/javascript">
|
|
||||||
{% if settings.CAS_NEW_VERSION_HTML_WARNING and upgrade_available %}
|
|
||||||
discard_and_remember("#alert-version", "cas-alert-version", "{{LAST_VERSION}}");
|
|
||||||
{% endif %}
|
|
||||||
{% for msg in CAS_INFO_RENDER %}
|
|
||||||
{% if msg.discardable %}
|
|
||||||
discard_and_remember("#info-{{msg.name}}", "cas-info-{{msg.name}}", "{{msg.hash}}");
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% block javascript_inline %}{% endblock %}
|
|
||||||
</script>
|
|
||||||
{% block javascript %}{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<!--
|
|
||||||
Powered by django-cas-server version {{VERSION}}
|
|
||||||
|
|
||||||
Pypi: https://pypi.org/project/django-cas-server/
|
|
||||||
github: https://github.com/nitmir/django-cas-server
|
|
||||||
-->
|
|
@ -23,6 +23,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
{{ profile_form|crispy }}
|
{{ profile_form|crispy }}
|
||||||
|
{{ soge_form|crispy }}
|
||||||
<button class="btn btn-success" type="submit">
|
<button class="btn btn-success" type="submit">
|
||||||
{% trans "Sign up" %}
|
{% trans "Sign up" %}
|
||||||
</button>
|
</button>
|
||||||
|
@ -5,15 +5,14 @@ from django.conf import settings
|
|||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.views.defaults import bad_request, permission_denied, page_not_found, server_error
|
from django.views.defaults import bad_request, permission_denied, page_not_found, server_error
|
||||||
from django.views.generic import RedirectView
|
|
||||||
|
|
||||||
from member.views import CustomLoginView
|
from member.views import CustomLoginView
|
||||||
|
|
||||||
from .admin import admin_site
|
from .admin import admin_site
|
||||||
|
from .views import IndexView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Dev so redirect to something random
|
# Dev so redirect to something random
|
||||||
path('', RedirectView.as_view(pattern_name='note:transfer'), name='index'),
|
path('', IndexView.as_view(), name='index'),
|
||||||
|
|
||||||
# Include project routers
|
# Include project routers
|
||||||
path('note/', include('note.urls')),
|
path('note/', include('note.urls')),
|
||||||
@ -36,15 +35,15 @@ urlpatterns = [
|
|||||||
path('coffee/', include('django_htcpcp_tea.urls')),
|
path('coffee/', include('django_htcpcp_tea.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
# During development, serve media files
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
if settings.DEBUG:
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
if "oauth2_provider" in settings.INSTALLED_APPS:
|
||||||
if "cas_server" in settings.INSTALLED_APPS:
|
# OAuth2 provider
|
||||||
urlpatterns += [
|
urlpatterns.append(
|
||||||
# Include CAS Server routers
|
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
|
||||||
path('cas/', include('cas_server.urls', namespace="cas_server")),
|
)
|
||||||
]
|
|
||||||
|
|
||||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
|
30
note_kfet/views.py
Normal file
30
note_kfet/views.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.views.generic import RedirectView
|
||||||
|
from note.models import Alias
|
||||||
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(LoginRequiredMixin, RedirectView):
|
||||||
|
def get_redirect_url(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Calculate the index page according to the roles.
|
||||||
|
A normal user will have access to the transfer page.
|
||||||
|
A non-Kfet member will have access to its user detail page.
|
||||||
|
The user "note" will display the consumption interface.
|
||||||
|
"""
|
||||||
|
user = self.request.user
|
||||||
|
|
||||||
|
# The account note will have the consumption page as default page
|
||||||
|
if not PermissionBackend.check_perm(user, "auth.view_user", user):
|
||||||
|
return reverse("note:consos")
|
||||||
|
|
||||||
|
# People that can see the alias BDE are Kfet members
|
||||||
|
if PermissionBackend.check_perm(user, "alias.view_alias", Alias.objects.get(name="BDE")):
|
||||||
|
return reverse("note:transfer")
|
||||||
|
|
||||||
|
# Non-Kfet members will don't see the transfer page, but their profile page
|
||||||
|
return reverse("member:user_detail", args=(user.pk,))
|
@ -1,17 +1,18 @@
|
|||||||
beautifulsoup4~=4.7.1
|
beautifulsoup4~=4.7.1
|
||||||
Django~=2.2.15
|
Django~=2.2.15
|
||||||
django-bootstrap-datepicker-plus~=3.0.5
|
django-bootstrap-datepicker-plus~=3.0.5
|
||||||
django-cas-server>=1.2.0
|
|
||||||
django-colorfield~=0.3.2
|
django-colorfield~=0.3.2
|
||||||
django-crispy-forms~=1.7.2
|
django-crispy-forms~=1.7.2
|
||||||
django-extensions~=2.1.4
|
django-extensions~=2.1.4
|
||||||
django-filter~=2.1.0
|
django-filter~=2.1.0
|
||||||
django-htcpcp-tea~=0.3.1
|
django-htcpcp-tea~=0.3.1
|
||||||
django-mailer~=2.0.1
|
django-mailer~=2.0.1
|
||||||
|
django-oauth-toolkit~=1.1.2
|
||||||
django-phonenumber-field~=5.0.0
|
django-phonenumber-field~=5.0.0
|
||||||
django-polymorphic~=2.0.3
|
django-polymorphic~=2.0.3
|
||||||
djangorestframework~=3.9.0
|
djangorestframework~=3.9.0
|
||||||
django-rest-polymorphic~=0.1.9
|
django-rest-polymorphic~=0.1.9
|
||||||
django-tables2~=2.3.1
|
django-tables2~=2.3.1
|
||||||
|
python-memcached~=1.59
|
||||||
phonenumbers~=8.9.10
|
phonenumbers~=8.9.10
|
||||||
Pillow>=5.4.1
|
Pillow>=5.4.1
|
||||||
|
Reference in New Issue
Block a user