Compare commits

...
Sign in to create a new pull request.

33 commits
dev-jb ... main

Author SHA1 Message Date
jbdoderlein
79d51eecfc Add qr to conso + fix issues 2024-08-24 12:27:35 +00:00
jbdoderlein
c73123f450 bigger qr code 2024-08-24 12:27:04 +00:00
jbdoderlein
519547f185 Minimal qr generator implementation 2024-08-21 10:59:08 +00:00
jbdoderlein
a8a2dd3f60 Minimal qr reader implementation 2024-08-21 10:47:49 +00:00
1868dedad6 Add permission to see aliases to everyone 2024-08-14 19:44:21 +02:00
7bf2964261 Include Sinfonie 2024-08-13 16:10:51 +02:00
eb5b6cb65d Typo fix 2024-08-13 16:10:40 +02:00
jbdoderlein
5cd52ce768 Remove weird password from 5.1 2024-08-13 10:20:18 +00:00
jbdoderlein
8e20bdc3e3 Reset migrations 2024-08-13 10:03:40 +00:00
jbdoderlein
f00d7873c9 Small changes 2024-08-13 10:02:50 +00:00
jbdoderlein
f93479e8e8 remove django_htcpcp_te 2024-08-11 10:38:46 +00:00
jbdoderlein
9f1564edf3 Add logout + Viewset name problem from migration 2024-08-10 17:30:16 +00:00
jbdoderlein
68d7a1577d Remove weird package 2024-08-10 15:11:39 +00:00
jbdoderlein
1d24e99857 Change def settings 2024-07-02 09:21:00 +00:00
558350d05b No crash but not datapicker (To fix) 2023-11-09 12:30:21 +01:00
bf0afc4fc5 Remove useless permission mask 2023-11-08 16:24:29 +01:00
6a82a2187e Yes no 2023-11-08 14:11:30 +01:00
55417313c0 Suppression Kfet et traduction (+fix notequal) 2023-11-08 10:58:23 +01:00
b280647c35 Fix Image resize + Partial API fix 2023-11-07 16:22:31 +01:00
8c57eb8096 Update to Django 4.2 (need to fix API) 2023-11-07 15:01:10 +01:00
b03bc3cca4 update package requirements to 4.2 2023-11-07 14:03:03 +01:00
e27f1e9f80 Ajout instructions installation Arch 2023-11-07 13:18:14 +01:00
Jean-Baptiste Doderlein
f91f729390
Merge pull request #2 from jules-timmerman/main
Start fixing README
2023-05-29 14:10:06 +02:00
snoopinou30
5841446795 Start fixing README 2023-05-29 10:49:24 +02:00
Jean-Baptiste Doderlein
121c49a5b6 Add real BD fee 2022-08-30 07:54:39 +00:00
Jean-Baptiste Doderlein
8a03defc37 Some fixtures tweaks 2022-08-20 18:31:23 +00:00
Jean-Baptiste Doderlein
a17e47acb9 Refactor permission (need to re adjust), 2 test not passed(registration, permissions) 2022-08-20 18:10:12 +00:00
Jean-Baptiste Doderlein
838bd2bb23 Remove bank, Kfet and add BDA and BDS (need to fix activity, permission and registration) 2022-08-17 21:29:39 +00:00
Jean-Baptiste Doderlein
3a3e3be64c Remove saclay asset, add new fixtures for activity and notes, clean some migrations 2022-08-06 13:47:38 +00:00
Marsupilami1
5b15c659fe Merge branch 'main' of github.com:jbdoderlein/notes-ker-lann 2022-07-31 14:21:50 +02:00
Marsupilami1
2343eabb59 remove wei 2022-07-31 14:16:24 +02:00
Jean-Baptiste Doderlein
e5f58f30ca Simple gitpod env update 2022-07-31 12:09:21 +00:00
Jean-Baptiste Doderlein
0ae4e513b3
Merge pull request #1 from jbdoderlein/dev-jb
Delete soge in treasury
2022-07-31 14:00:23 +02:00
158 changed files with 7809 additions and 11721 deletions

2
.gitignore vendored
View file

@ -54,5 +54,3 @@ ansible/host_vars/*.yaml
!ansible/host_vars/bde* !ansible/host_vars/bde*
ansible/hosts ansible/hosts
apps/member/migrations
apps/wei/migrations

View file

@ -3,33 +3,39 @@
# and commit this file to your remote git repository to share the goodness with others. # and commit this file to your remote git repository to share the goodness with others.
tasks: tasks:
- name: Apt update
init: sudo apt update
- name: Apt install - name: Apt install
init: sudo apt install --no-install-recommends -y \ command: |
sudo apt update
sudo apt install --no-install-recommends -y \
ipython3 python3-setuptools python3-venv python3-dev \ ipython3 python3-setuptools python3-venv python3-dev \
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
gp sync-done apt
- name : Install requirements - name : Install requirements
init: pip3 install -r requirements.txt init : gp sync-await apt
command: |
pip3 install -r requirements.txt
gp sync-done pip
- name : Setup env - name : Setup env
init: cp .env_example .env command: cp .env_example .env
- name: Django collectstatic - name: Django Init
init: python3 manage.py collectstatic --noinput init : gp sync-await pip
- name: Django compilemessages command: |
init: python3 manage.py compilemessages python3 manage.py collectstatic --noinput
- name: Django makemigrations python3 manage.py compilemessages
init: python3 manage.py makemigrations python3 manage.py makemigrations
- name: Django migrate python3 manage.py migrate
init: python3 manage.py migrate python3 manage.py loaddata initial
- name: Django loaddata python3 manage.py shell -c "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'adminpass')"
init: python3 manage.py loaddata initial gp sync-done django
- name: Django create dev superuser
init: python3 manage.py shell -c "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'adminpass')"
- name: Django start server - name: Django start server
init : gp sync-await django
command: python3 manage.py runserver 0.0.0.0:8000 command: python3 manage.py runserver 0.0.0.0:8000
ports: ports:
- port: 8000 - name: Web Dev Server
onOpen: open-preview port: 8000
visibility: public

View file

@ -35,11 +35,27 @@ Bien que cela permette de créer une instance sur toutes les distributions,
texlive-bin gettext ttf-font-awesome git texlive-bin gettext ttf-font-awesome git
$ paru -S bootstrap jquery # AUR only, need manuel install bootstrap4 and popper.js $ paru -S bootstrap jquery # AUR only, need manuel install bootstrap4 and popper.js
``` ```
Pour Arch il faut installer manuellement libjs-bootstrap4, popper.js et jquery par l'AUR :
```bash
yay -S jquery
```
```bash
mkdir temp
cd temp
curl http://fr.archive.ubuntu.com/ubuntu/pool/universe/p/popper.js/libjs-popper.js_1.16.1+ds-6_all.deb --output libjs-popper.js_1.16.1+ds-6_all.deb
curl http://fr.archive.ubuntu.com/ubuntu/pool/universe/t/twitter-bootstrap4/libjs-bootstrap4_4.6.1+dfsg1-4_all.deb -o libjs-bootstrap4_4.6.1+dfsg1-4_all.deb # Dl paquet Debian
ar vx libjs-bootstrap4_4.6.1+dfsg1-4_all.deb # Unpack deb
ar -xvf data.tar.zst # Extract files
ar vx libjs-popper.js_1.16.1+ds-6_all.deb # Unpack deb
ar -xvf data.tar.zst # Extract files
sudo cp -Rp usr/* /usr # Copy file to correct locations
```
Proposer un packaging correct pour Arch de ce paquet serait une bonne idée.
2. **Clonage du dépot** là où vous voulez : 2. **Clonage du dépot** là où vous voulez :
```bash ```bash
$ git clone git@gitlab.crans.org:bde/nk20.git --recursive && cd nk20 $ git clone git@github.com:jbdoderlein/notes-ker-lann.git --recursive && cd notes-ker-lann
``` ```
3. **Création d'un environment de travail Python décorrélé du système.** 3. **Création d'un environment de travail Python décorrélé du système.**
@ -129,13 +145,13 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
nginx python3-venv git acl nginx python3-venv git acl
``` ```
2. **Clonage du dépot** dans `/var/www/note_kfet`, 2. **Clonage du dépot** dans `/var/www/notes-ker-lann`,
```bash ```bash
$ sudo mkdir -p /var/www/note_kfet && cd /var/www/note_kfet $ sudo mkdir -p /var/www/notes-ker-lann && cd /var/www/notes-ker-lann
$ sudo chown www-data:www-data . $ sudo chown www-data:www-data .
$ sudo chmod g+rwx . $ sudo chmod g+rwx .
$ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git --recursive $ sudo -u www-data git clone https://github.com/jbdoderlein/notes-ker-lann.git --recursive
``` ```
3. **Création d'un environment de travail Python décorrélé du système.** 3. **Création d'un environment de travail Python décorrélé du système.**
@ -152,19 +168,19 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
```bash ```bash
$ cp nginx_note.conf_example nginx_note.conf $ cp nginx_note.conf_example nginx_note.conf
$ sudo ln -sf /var/www/note_kfet/nginx_note.conf /etc/nginx/sites-enabled/ $ sudo ln -sf /var/www/notes-ker-lann/nginx_note.conf /etc/nginx/sites-enabled/
``` ```
Si l'on a un emperor (plusieurs instance uwsgi): Si l'on a un emperor (plusieurs instance uwsgi):
```bash ```bash
$ sudo ln -sf /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/sites/ $ sudo ln -sf /var/www/notes-ker-lann/uwsgi_note.ini /etc/uwsgi/sites/
``` ```
Sinon si on est dans le cas habituel : Sinon si on est dans le cas habituel :
```bash ```bash
$ sudo ln -sf /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/apps-enabled/ $ sudo ln -sf /var/www/notes-ker-lann/uwsgi_note.ini /etc/uwsgi/apps-enabled/
``` ```
Le touch-reload est activé par défault, pour redémarrer la note il suffit donc de faire `touch uwsgi_note.ini`. Le touch-reload est activé par défault, pour redémarrer la note il suffit donc de faire `touch uwsgi_note.ini`.
@ -219,7 +235,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
DJANGO_DB_PASSWORD=CHANGE_ME DJANGO_DB_PASSWORD=CHANGE_ME
DJANGO_DB_PORT= DJANGO_DB_PORT=
DJANGO_SECRET_KEY=CHANGE_ME DJANGO_SECRET_KEY=CHANGE_ME
DJANGO_SETTINGS_MODULE="note_kfet.settings DJANGO_SETTINGS_MODULE="notes-ker-lann.settings
NOTE_URL=localhost # URL où accéder à la note NOTE_URL=localhost # URL où accéder à la note
CONTACT_EMAIL=tresorerie.bde@localhost CONTACT_EMAIL=tresorerie.bde@localhost
# Le reste n'est utile qu'en production, pour configurer l'envoi des mails # Le reste n'est utile qu'en production, pour configurer l'envoi des mails
@ -247,21 +263,21 @@ Il est possible de travailler sur une instance Docker.
Pour construire l'image Docker `nk20`, Pour construire l'image Docker `nk20`,
``` ```
git clone https://gitlab.crans.org/bde/nk20/ --recursive && cd nk20 git clone https://github.com/jbdoderlein/notes-ker-lann.git/ --recursive && cd notes-ker-lann
docker build . -t nk20 docker build . -t notes-ker-lann
``` ```
Ensuite pour lancer la note Kfet en tant que vous (option `-u`), Ensuite pour lancer la note de Ker Lann en tant que vous (option `-u`),
l'exposer sur son port 80 (option `-p`) et monter le code en écriture (option `-v`), l'exposer sur son port 80 (option `-p`) et monter le code en écriture (option `-v`),
``` ```
docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/note_kfet/" -p 80:8080 nk20 docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/notes-ker-lann/" -p 80:8080 nk20
``` ```
Si vous souhaitez lancer une commande spéciale, vous pouvez l'ajouter à la fin, par exemple, Si vous souhaitez lancer une commande spéciale, vous pouvez l'ajouter à la fin, par exemple,
``` ```
docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/note_kfet/" -p 80:8080 nk20 python3 ./manage.py createsuperuser docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/notes-ker-lann/" -p 80:8080 nk20 python3 ./manage.py createsuperuser
``` ```
#### Avec Docker Compose #### Avec Docker Compose
@ -271,15 +287,15 @@ On vous conseilles de faire un fichier d'environnement `.env` en prenant exemple
Pour par exemple utiliser le Docker de la note Kfet avec Traefik pour réaliser le HTTPS, Pour par exemple utiliser le Docker de la note Kfet avec Traefik pour réaliser le HTTPS,
```YAML ```YAML
nk20: notes-ker-lann:
build: /chemin/vers/le/code/nk20 build: /chemin/vers/le/code/notes-ker-lann
volumes: volumes:
- /chemin/vers/le/code/nk20:/var/www/note_kfet/ - /chemin/vers/le/code/notes-ker-lann:/var/www/notes-ker-lann/
env_file: /chemin/vers/le/code/nk20/.env env_file: /chemin/vers/le/code/notes-ker-lann/.env
restart: always restart: always
labels: labels:
- "traefik.http.routers.nk20.rule=Host(`ndd.example.com`)" - "traefik.http.routers.notes-ker-lann.rule=Host(`ndd.example.com`)"
- "traefik.http.services.nk20.loadbalancer.server.port=8080" - "traefik.http.services.notes-ker-lann.loadbalancer.server.port=8080"
``` ```
## Documentation ## Documentation

View file

@ -1,32 +1 @@
[ [{"model": "activity.activitytype", "pk": 4, "fields": {"name": "Activit\u00e9 gratuite ouverte", "manage_entries": false, "can_invite": true, "guest_entry_fee": 0}}, {"model": "activity.activitytype", "pk": 5, "fields": {"name": "Soir\u00e9e", "manage_entries": true, "can_invite": false, "guest_entry_fee": 500}}]
{
"model": "activity.activitytype",
"pk": 1,
"fields": {
"name": "Pot",
"manage_entries": true,
"can_invite": true,
"guest_entry_fee": 500
}
},
{
"model": "activity.activitytype",
"pk": 2,
"fields": {
"name": "Soir\u00e9e de club",
"manage_entries": false,
"can_invite": false,
"guest_entry_fee": 0
}
},
{
"model": "activity.activitytype",
"pk": 3,
"fields": {
"name": "Autre",
"manage_entries": false,
"can_invite": false,
"guest_entry_fee": 0
}
}
]

View file

@ -20,9 +20,9 @@ from .models import Activity, Guest
class ActivityForm(forms.ModelForm): class ActivityForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# By default, the Kfet club is attended # By default, the BDE club is attended
self.fields["attendees_club"].initial = Club.objects.get(name="Kfet") self.fields["attendees_club"].initial = Club.objects.get(name="BDE")
self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet" self.fields["attendees_club"].widget.attrs["placeholder"] = "BDE"
clubs = list(Club.objects.filter(PermissionBackend clubs = list(Club.objects.filter(PermissionBackend
.filter_queryset(get_current_request(), Club, "view")).all()) .filter_queryset(get_current_request(), Club, "view")).all())
shuffle(clubs) shuffle(clubs)

View file

@ -1,7 +1,7 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')), ('name', models.CharField(max_length=255, verbose_name='name')),
('description', models.TextField(verbose_name='description')), ('description', models.TextField(verbose_name='description')),
('location', models.CharField(blank=True, default='', help_text='Place where the activity is organized, eg. Kfet.', max_length=255, verbose_name='location')), ('location', models.CharField(blank=True, default='', help_text='Place where the activity is organized, eg. BDE.', max_length=255, verbose_name='location')),
('date_start', models.DateTimeField(verbose_name='start date')), ('date_start', models.DateTimeField(verbose_name='start date')),
('date_end', models.DateTimeField(verbose_name='end date')), ('date_end', models.DateTimeField(verbose_name='end date')),
('valid', models.BooleanField(default=False, verbose_name='valid')), ('valid', models.BooleanField(default=False, verbose_name='valid')),

View file

@ -1,8 +1,8 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -11,58 +11,16 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('activity', '0001_initial'), ('activity', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('member', '0001_initial'), ('member', '0001_initial'),
('note', '0001_initial'), ('note', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel(
name='GuestTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
('entry', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='activity.Entry')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('note.transaction',),
),
migrations.AddField(
model_name='guest',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.Activity'),
),
migrations.AddField(
model_name='guest',
name='inviter',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='guests', to='note.NoteUser', verbose_name='inviter'),
),
migrations.AddField(
model_name='entry',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='activity.Activity', verbose_name='activity'),
),
migrations.AddField(
model_name='entry',
name='guest',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='activity.Guest'),
),
migrations.AddField(
model_name='entry',
name='note',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.NoteUser', verbose_name='note'),
),
migrations.AddField(
model_name='activity',
name='activity_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.ActivityType', verbose_name='type'),
),
migrations.AddField( migrations.AddField(
model_name='activity', model_name='activity',
name='attendees_club', name='attendees_club',
field=models.ForeignKey(help_text='Club that is authorized to join the activity. Mostly the Kfet club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='attendees club'), field=models.ForeignKey(help_text='Club that is authorized to join the activity.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.club', verbose_name='attendees club'),
), ),
migrations.AddField( migrations.AddField(
model_name='activity', model_name='activity',
@ -72,7 +30,53 @@ class Migration(migrations.Migration):
migrations.AddField( migrations.AddField(
model_name='activity', model_name='activity',
name='organizer', name='organizer',
field=models.ForeignKey(help_text='Club that organizes the activity. The entry fees will go to this club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='organizer'), field=models.ForeignKey(help_text='Club that organizes the activity. The entry fees will go to this club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.club', verbose_name='organizer'),
),
migrations.AddField(
model_name='activity',
name='activity_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.activitytype', verbose_name='type'),
),
migrations.AddField(
model_name='entry',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='activity.activity', verbose_name='activity'),
),
migrations.AddField(
model_name='entry',
name='note',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.noteuser', verbose_name='note'),
),
migrations.AddField(
model_name='guest',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.activity'),
),
migrations.AddField(
model_name='guest',
name='inviter',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='guests', to='note.noteuser', verbose_name='inviter'),
),
migrations.AddField(
model_name='entry',
name='guest',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='activity.guest'),
),
migrations.CreateModel(
name='GuestTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.transaction')),
('entry', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='activity.entry')),
],
options={
'abstract': False,
'base_manager_name': 'objects',
},
bases=('note.transaction',),
),
migrations.AlterUniqueTogether(
name='activity',
unique_together={('name', 'date_start', 'date_end')},
), ),
migrations.AlterUniqueTogether( migrations.AlterUniqueTogether(
name='guest', name='guest',
@ -82,8 +86,4 @@ class Migration(migrations.Migration):
name='entry', name='entry',
unique_together={('activity', 'note', 'guest')}, unique_together={('activity', 'note', 'guest')},
), ),
migrations.AlterUniqueTogether(
name='activity',
unique_together={('name', 'date_start', 'date_end')},
),
] ]

View file

@ -73,7 +73,7 @@ class Activity(models.Model):
max_length=255, max_length=255,
blank=True, blank=True,
default="", default="",
help_text=_("Place where the activity is organized, eg. Kfet."), help_text=_("Place where the activity is organized, eg. BDE."),
) )
activity_type = models.ForeignKey( activity_type = models.ForeignKey(
@ -102,7 +102,7 @@ class Activity(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+', related_name='+',
verbose_name=_('attendees club'), verbose_name=_('attendees club'),
help_text=_("Club that is authorized to join the activity. Mostly the Kfet club."), help_text=_("Club that is authorized to join the activity."),
) )
date_start = models.DateTimeField( date_start = models.DateTimeField(

View file

@ -91,7 +91,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
}).done(function () { }).done(function () {
if (target.hasClass("table-info")) if (target.hasClass("table-info"))
addMsg( addMsg(
"{% trans "Entry done, but caution: the user is not a Kfet member." %}", "{% trans "Entry done, but caution: the user is not a member." %}",
"warning", 10000); "warning", 10000);
else else
addMsg("Entry made!", "success", 4000); addMsg("Entry made!", "success", 4000);
@ -126,7 +126,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
}).done(function () { }).done(function () {
if (target.hasClass("table-info")) if (target.hasClass("table-info"))
addMsg( addMsg(
"{% trans "Entry done, but caution: the user is not a Kfet member." %}", "{% trans "Entry done, but caution: the user is not a member." %}",
"warning", 10000); "warning", 10000);
else else
addMsg("{% trans "Entry done!" %}", "success", 4000); addMsg("{% trans "Entry done!" %}", "success", 4000);
@ -150,8 +150,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
"source": credit_id, "source": credit_id,
"destination": target.attr('data-inviter'), "destination": target.attr('data-inviter'),
"last_name": last_name, "last_name": last_name,
"first_name": first_name, "first_name": first_name
"bank": ""
}).done(function () { }).done(function () {
makeTransaction(); makeTransaction();
reset(); reset();

View file

@ -40,7 +40,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd> <dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd>
<dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</dd> <dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno:_("yes,no,maybe") }}</dd>
{% if activity.activity_type.can_invite %} {% if activity.activity_type.can_invite %}
<dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
@ -48,10 +48,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
<dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.valid|yesno }}</dd> <dd class="col-xl-6">{{ activity.valid|yesno:_("yes,no,maybe") }}</dd>
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.open|yesno }}</dd> <dd class="col-xl-6">{{ activity.open|yesno:_("yes,no,maybe") }}</dd>
</dl> </dl>
</div> </div>

View file

@ -36,10 +36,10 @@ class TestActivities(TestCase):
name="Activity", name="Activity",
description="This is a test activity\non two very very long lines\nbecause this is very important.", description="This is a test activity\non two very very long lines\nbecause this is very important.",
location="Earth", location="Earth",
activity_type=ActivityType.objects.get(name="Pot"), activity_type=ActivityType.objects.get(name="Soir\u00e9e"),
creater=self.user, creater=self.user,
organizer=Club.objects.get(name="Kfet"), organizer=Club.objects.get(name="BDE"),
attendees_club=Club.objects.get(name="Kfet"), attendees_club=Club.objects.get(name="BDE"),
date_start=timezone.now(), date_start=timezone.now(),
date_end=timezone.now() + timedelta(days=2), date_end=timezone.now() + timedelta(days=2),
valid=True, valid=True,
@ -70,10 +70,10 @@ class TestActivities(TestCase):
name="Activity created", name="Activity created",
description="This activity was successfully created.", description="This activity was successfully created.",
location="Earth", location="Earth",
activity_type=ActivityType.objects.get(name="Soirée de club").id, activity_type=ActivityType.objects.get(name="Soir\u00e9e").id,
creater=self.user.id, creater=self.user.id,
organizer=Club.objects.get(name="Kfet").id, organizer=Club.objects.get(name="BDE").id,
attendees_club=Club.objects.get(name="Kfet").id, attendees_club=Club.objects.get(name="BDE").id,
date_start="{:%Y-%m-%d %H:%M}".format(timezone.now()), date_start="{:%Y-%m-%d %H:%M}".format(timezone.now()),
date_end="{:%Y-%m-%d %H:%M}".format(timezone.now() + timedelta(days=2)), date_end="{:%Y-%m-%d %H:%M}".format(timezone.now() + timedelta(days=2)),
valid=True, valid=True,
@ -100,10 +100,10 @@ class TestActivities(TestCase):
name=str(self.activity) + " updated", name=str(self.activity) + " updated",
description="This activity was successfully updated.", description="This activity was successfully updated.",
location="Earth", location="Earth",
activity_type=ActivityType.objects.get(name="Autre").id, activity_type=ActivityType.objects.get(name="Soir\u00e9e").id,
creater=self.user.id, creater=self.user.id,
organizer=Club.objects.get(name="Kfet").id, organizer=Club.objects.get(name="BDE").id,
attendees_club=Club.objects.get(name="Kfet").id, attendees_club=Club.objects.get(name="BDE").id,
date_start="{:%Y-%m-%d %H:%M}".format(timezone.now()), date_start="{:%Y-%m-%d %H:%M}".format(timezone.now()),
date_end="{:%Y-%m-%d %H:%M}".format(timezone.now() + timedelta(days=2)), date_end="{:%Y-%m-%d %H:%M}".format(timezone.now() + timedelta(days=2)),
valid=True, valid=True,
@ -186,10 +186,10 @@ class TestActivityAPI(TestAPI):
name="Activity", name="Activity",
description="This is a test activity\non two very very long lines\nbecause this is very important.", description="This is a test activity\non two very very long lines\nbecause this is very important.",
location="Earth", location="Earth",
activity_type=ActivityType.objects.get(name="Pot"), activity_type=ActivityType.objects.get(name="Activit\u00e9 gratuite ouverte"),
creater=self.user, creater=self.user,
organizer=Club.objects.get(name="Kfet"), organizer=Club.objects.get(name="BDE"),
attendees_club=Club.objects.get(name="Kfet"), attendees_club=Club.objects.get(name="BDE"),
date_start=timezone.now(), date_start=timezone.now(),
date_end=timezone.now() + timedelta(days=2), date_end=timezone.now() + timedelta(days=2),
valid=True, valid=True,

View file

@ -340,7 +340,7 @@ UID:{md5((activity.name + "$" + str(activity.id) + str(activity.date_start)).enc
SUMMARY;CHARSET=UTF-8:{self.multilines(activity.name, 75, 22)} SUMMARY;CHARSET=UTF-8:{self.multilines(activity.name, 75, 22)}
DTSTART;TZID=Europe/Berlin:{"{:%Y%m%dT%H%M%S}".format(activity.date_start)} DTSTART;TZID=Europe/Berlin:{"{:%Y%m%dT%H%M%S}".format(activity.date_start)}
DTEND;TZID=Europe/Berlin:{"{:%Y%m%dT%H%M%S}".format(activity.date_end)} DTEND;TZID=Europe/Berlin:{"{:%Y%m%dT%H%M%S}".format(activity.date_end)}
LOCATION:{self.multilines(activity.location, 75, 9) if activity.location else "Kfet"} LOCATION:{self.multilines(activity.location, 75, 9) if activity.location else "BDA"}
DESCRIPTION;CHARSET=UTF-8:""" + self.multilines(activity.description.replace("\n", "\\n"), 75, 26) + """ DESCRIPTION;CHARSET=UTF-8:""" + self.multilines(activity.description.replace("\n", "\\n"), 75, 26) + """
-- {activity.organizer.name} -- {activity.organizer.name}
END:VEVENT END:VEVENT

View file

@ -2,7 +2,8 @@
# 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.conf.urls import url, include from django.conf.urls import include
from django.urls import re_path as url
from rest_framework import routers from rest_framework import routers
from .views import UserInformationView from .views import UserInformationView
@ -38,10 +39,6 @@ if "logs" in settings.INSTALLED_APPS:
from logs.api.urls import register_logs_urls from logs.api.urls import register_logs_urls
register_logs_urls(router, 'logs') register_logs_urls(router, 'logs')
if "wei" in settings.INSTALLED_APPS:
from wei.api.urls import register_wei_urls
register_wei_urls(router, 'wei')
app_name = 'api' app_name = 'api'
# Wire up our API using automatic URL routing. # Wire up our API using automatic URL routing.

View file

@ -1,9 +1,9 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
@ -22,11 +22,11 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='IP Address')), ('ip', models.GenericIPAddressField(blank=True, null=True, verbose_name='IP Address')),
('instance_pk', models.CharField(max_length=255, verbose_name='identifier')), ('instance_pk', models.CharField(max_length=255, verbose_name='identifier')),
('previous', models.TextField(null=True, verbose_name='previous data')), ('previous', models.TextField(blank=True, default='', verbose_name='previous data')),
('data', models.TextField(null=True, verbose_name='new data')), ('data', models.TextField(blank=True, default='', verbose_name='new data')),
('action', models.CharField(choices=[('create', 'create'), ('edit', 'edit'), ('delete', 'delete')], default='edit', max_length=16, verbose_name='action')), ('action', models.CharField(choices=[('create', 'create'), ('edit', 'edit'), ('delete', 'delete')], default='edit', max_length=16, verbose_name='action')),
('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='timestamp')), ('timestamp', models.DateTimeField(default=django.utils.timezone.now, verbose_name='timestamp')),
('model', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType', verbose_name='model')), ('model', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype', verbose_name='model')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')), ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')),
], ],
options={ options={

View file

@ -1,17 +0,0 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('logs', '0001_initial'),
]
operations = [
migrations.RunSQL(
"UPDATE logs_changelog SET previous = '' WHERE previous IS NULL;"
),
migrations.RunSQL(
"UPDATE logs_changelog SET data = '' WHERE data IS NULL;"
),
]

View file

@ -1,23 +0,0 @@
# Generated by Django 2.2.16 on 2020-09-06 19:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('logs', '0002_replace_null_by_blank'),
]
operations = [
migrations.AlterField(
model_name='changelog',
name='data',
field=models.TextField(blank=True, default='', verbose_name='new data'),
),
migrations.AlterField(
model_name='changelog',
name='previous',
field=models.TextField(blank=True, default='', verbose_name='previous data'),
),
]

View file

@ -19,20 +19,12 @@ from permission.models import PermissionMask, Role
from .models import Profile, Club, Membership from .models import Profile, Club, Membership
class CustomAuthenticationForm(AuthenticationForm):
permission_mask = forms.ModelChoiceField(
label=_("Permission mask"),
queryset=PermissionMask.objects.order_by("rank"),
empty_label=None,
)
class UserForm(forms.ModelForm): class UserForm(forms.ModelForm):
def _get_validation_exclusions(self): def get_validation_exclusions(self):
# Django usernames can only contain letters, numbers, @, ., +, - and _. # Django usernames can only contain letters, numbers, @, ., +, - and _.
# We want to allow users to have uncommon and unpractical usernames: # We want to allow users to have uncommon and unpractical usernames:
# That is their problem, and we have normalized aliases for us. # That is their problem, and we have normalized aliases for us.
return super()._get_validation_exclusions() + ["username"] return super().get_validation_exclusions() + ["username"]
class Meta: class Meta:
model = User model = User
@ -68,7 +60,7 @@ class ProfileForm(forms.ModelForm):
class Meta: class Meta:
model = Profile model = Profile
fields = '__all__' fields = '__all__'
exclude = ('user', 'email_confirmed', 'registration_valid', ) exclude = ('user', 'email_confirmed', 'registration_valid', 'section' )
class ImageForm(forms.Form): class ImageForm(forms.Form):
@ -114,7 +106,7 @@ class ImageForm(forms.Form):
frame = frame.crop((x, y, x + w, y + h)) frame = frame.crop((x, y, x + w, y + h))
frame = frame.resize( frame = frame.resize(
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH), (settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.ANTIALIAS, Image.Resampling.LANCZOS,
) )
frames.append(frame) frames.append(frame)
@ -186,11 +178,6 @@ class MembershipForm(forms.ModelForm):
required=False, required=False,
) )
bank = forms.CharField(
label=_("Bank"),
required=False,
)
class Meta: class Meta:
model = Membership model = Membership
fields = ('user', 'date_start') fields = ('user', 'date_start')
@ -227,7 +214,7 @@ class MembershipRolesForm(forms.ModelForm):
) )
roles = forms.ModelMultipleChoiceField( roles = forms.ModelMultipleChoiceField(
queryset=Role.objects.filter(weirole=None).all(), queryset=Role.objects.all(),
label=_("Roles"), label=_("Roles"),
widget=CheckboxSelectMultiple(), widget=CheckboxSelectMultiple(),
) )

View file

@ -1,11 +1,10 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
import datetime import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import phonenumber_field.modelfields import phonenumber_field.modelfields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -13,10 +12,29 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=50, null=True, region=None, verbose_name='phone number')),
('section', models.CharField(blank=True, default='', help_text='Auto generated', max_length=255, verbose_name='section')),
('department', models.CharField(choices=[('INFO', 'Informatique'), ('MATH', 'Mathématiques'), ('DEM', 'Droit - économie - management'), ('MECATRO', 'Mécatronique'), ('2SEP', 'Sciences du sport et éducation physique'), ('SE', 'Sciences pour lEnvironnement'), ('EXT', 'Externe')], max_length=8, verbose_name='department')),
('promotion', models.PositiveSmallIntegerField(default=2024, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion')),
('address', models.CharField(blank=True, default='', max_length=255, verbose_name='address')),
('paid', models.BooleanField(default=False, help_text='Tells if the user receive a salary.', verbose_name='paid')),
('report_frequency', models.PositiveSmallIntegerField(default=0, verbose_name='report frequency (in days)')),
('last_report', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last report date')),
('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
('registration_valid', models.BooleanField(default=False, verbose_name='registration valid')),
],
options={
'verbose_name': 'user profile',
'verbose_name_plural': 'user profile',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Club', name='Club',
fields=[ fields=[
@ -29,36 +47,13 @@ class Migration(migrations.Migration):
('membership_duration', models.PositiveIntegerField(blank=True, help_text='The longest time (in days) a membership can last (NULL = infinite).', null=True, verbose_name='membership duration')), ('membership_duration', models.PositiveIntegerField(blank=True, help_text='The longest time (in days) a membership can last (NULL = infinite).', null=True, verbose_name='membership duration')),
('membership_start', models.DateField(blank=True, help_text='Date from which the members can renew their membership.', null=True, verbose_name='membership start')), ('membership_start', models.DateField(blank=True, help_text='Date from which the members can renew their membership.', null=True, verbose_name='membership start')),
('membership_end', models.DateField(blank=True, help_text='Maximal date of a membership, after which members must renew it.', null=True, verbose_name='membership end')), ('membership_end', models.DateField(blank=True, help_text='Maximal date of a membership, after which members must renew it.', null=True, verbose_name='membership end')),
('parent_club', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.club', verbose_name='parent club')),
], ],
options={ options={
'verbose_name': 'club', 'verbose_name': 'club',
'verbose_name_plural': 'clubs', 'verbose_name_plural': 'clubs',
}, },
), ),
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=50, null=True, region=None, verbose_name='phone number')),
('section', models.CharField(blank=True, help_text='e.g. "1A0", "9A♥", "SAPHIRE"', max_length=255, null=True, verbose_name='section')),
('department', models.CharField(choices=[('A0', 'Informatics (A0)'), ('A1', 'Mathematics (A1)'), ('A2', 'Physics (A2)'), ("A'2", "Applied physics (A'2)"), ('A2', "Chemistry (A''2)"), ('A3', 'Biology (A3)'), ('B1234', 'SAPHIRE (B1234)'), ('B1', 'Mechanics (B1)'), ('B2', 'Civil engineering (B2)'), ('B3', 'Mechanical engineering (B3)'), ('B4', 'EEA (B4)'), ('C', 'Design (C)'), ('D2', 'Economy-management (D2)'), ('D3', 'Social sciences (D3)'), ('E', 'English (E)'), ('EXT', 'External (EXT)')], max_length=8, verbose_name='department')),
('promotion', models.PositiveSmallIntegerField(default=2020, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion')),
('address', models.CharField(blank=True, max_length=255, null=True, verbose_name='address')),
('paid', models.BooleanField(default=False, help_text='Tells if the user receive a salary.', verbose_name='paid')),
('ml_events_registration', models.CharField(blank=True, choices=[(None, 'No'), ('fr', 'Yes (receive them in french)'), ('en', 'Yes (receive them in english)')], default=None, max_length=2, null=True, verbose_name='Register on the mailing list to stay informed of the events of the campus (1 mail/week)')),
('ml_sport_registration', models.BooleanField(default=False, verbose_name='Register on the mailing list to stay informed of the sport events of the campus (1 mail/week)')),
('ml_art_registration', models.BooleanField(default=False, verbose_name='Register on the mailing list to stay informed of the art events of the campus (1 mail/week)')),
('report_frequency', models.PositiveSmallIntegerField(default=0, verbose_name='report frequency (in days)')),
('last_report', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last report date')),
('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
('registration_valid', models.BooleanField(default=False, verbose_name='registration valid')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'user profile',
'verbose_name_plural': 'user profile',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Membership', name='Membership',
fields=[ fields=[
@ -66,7 +61,7 @@ class Migration(migrations.Migration):
('date_start', models.DateField(default=datetime.date.today, verbose_name='membership starts on')), ('date_start', models.DateField(default=datetime.date.today, verbose_name='membership starts on')),
('date_end', models.DateField(null=True, verbose_name='membership ends on')), ('date_end', models.DateField(null=True, verbose_name='membership ends on')),
('fee', models.PositiveIntegerField(verbose_name='fee')), ('fee', models.PositiveIntegerField(verbose_name='fee')),
('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='club')), ('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.club', verbose_name='club')),
], ],
options={ options={
'verbose_name': 'membership', 'verbose_name': 'membership',

View file

@ -1,8 +1,8 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -10,16 +10,16 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('member', '0001_initial'),
('permission', '0001_initial'), ('permission', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('member', '0001_initial'),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='membership', model_name='membership',
name='roles', name='roles',
field=models.ManyToManyField(to='permission.Role', verbose_name='roles'), field=models.ManyToManyField(related_name='memberships', to='permission.role', verbose_name='roles'),
), ),
migrations.AddField( migrations.AddField(
model_name='membership', model_name='membership',
@ -27,16 +27,16 @@ class Migration(migrations.Migration):
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to=settings.AUTH_USER_MODEL, verbose_name='user'), field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to=settings.AUTH_USER_MODEL, verbose_name='user'),
), ),
migrations.AddField( migrations.AddField(
model_name='club',
name='parent_club',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='parent club'),
),
migrations.AddIndex(
model_name='profile', model_name='profile',
index=models.Index(fields=['user'], name='member_prof_user_id_30c316_idx'), name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
), ),
migrations.AddIndex( migrations.AddIndex(
model_name='membership', model_name='membership',
index=models.Index(fields=['user'], name='member_memb_user_id_945dbc_idx'), index=models.Index(fields=['user'], name='member_memb_user_id_945dbc_idx'),
), ),
migrations.AddIndex(
model_name='profile',
index=models.Index(fields=['user'], name='member_prof_user_id_30c316_idx'),
),
] ]

View file

@ -1,71 +0,0 @@
from django.db import migrations
def create_bde_and_kfet(apps, schema_editor):
"""
The clubs BDE and Kfet are pre-injected.
"""
Club = apps.get_model("member", "club")
NoteClub = apps.get_model("note", "noteclub")
Alias = apps.get_model("note", "alias")
ContentType = apps.get_model('contenttypes', 'ContentType')
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
Club.objects.get_or_create(
id=1,
name="BDE",
email="tresorerie.bde@example.com",
require_memberships=True,
membership_fee_paid=500,
membership_fee_unpaid=500,
membership_duration=396,
membership_start="2021-08-01",
membership_end="2022-09-30",
)
Club.objects.get_or_create(
id=2,
name="Kfet",
parent_club_id=1,
email="tresorerie.bde@example.com",
require_memberships=True,
membership_fee_paid=3500,
membership_fee_unpaid=3500,
membership_duration=396,
membership_start="2021-08-01",
membership_end="2022-09-30",
)
NoteClub.objects.get_or_create(
id=5,
club_id=1,
polymorphic_ctype_id=polymorphic_ctype_id,
)
NoteClub.objects.get_or_create(
id=6,
club_id=2,
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):
dependencies = [
('member', '0002_auto_20200904_2341'),
('note', '0002_create_special_notes'),
]
operations = [
migrations.RunPython(create_bde_and_kfet),
]

View file

@ -0,0 +1,120 @@
"""Migration member default BDE BDA"""
from django.db import migrations
def create_initial_club(apps, schema_editor):
"""
The clubs BDE BDA BDS are pre-injected.
"""
Club = apps.get_model("member", "club")
NoteClub = apps.get_model("note", "noteclub")
Alias = apps.get_model("note", "alias")
ContentType = apps.get_model('contenttypes', 'ContentType')
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
Club.objects.get_or_create(
id=1,
name="BDE",
email="tresorerie.bde@example.com",
require_memberships=True,
membership_fee_paid=3500,
membership_fee_unpaid=2800,
membership_duration=396,
membership_start="2022-08-01",
membership_end="2023-09-30",
)
Club.objects.get_or_create(
id=2,
name="BDA",
email="tresorerie.bda@example.com",
require_memberships=True,
membership_fee_paid=2500,
membership_fee_unpaid=1700,
membership_duration=396,
membership_start="2022-08-01",
membership_end="2023-09-30",
)
Club.objects.get_or_create(
id=3,
name="BDS",
email="tresorerie.bds@example.com",
require_memberships=True,
membership_fee_paid=3000,
membership_fee_unpaid=2300,
membership_duration=396,
membership_start="2022-08-01",
membership_end="2023-09-30",
)
Club.objects.get_or_create(
id=4,
name="Sinfonie",
email="tresorerie@sinfonie.com",
require_memberships=False,
membership_fee_paid=0,
membership_fee_unpaid=0,
membership_duration=396,
membership_start="2022-08-01",
membership_end="2023-09-30",
)
NoteClub.objects.get_or_create(
id=5,
club_id=1,
polymorphic_ctype_id=polymorphic_ctype_id,
)
NoteClub.objects.get_or_create(
id=6,
club_id=2,
polymorphic_ctype_id=polymorphic_ctype_id,
)
NoteClub.objects.get_or_create(
id=7,
club_id=3,
polymorphic_ctype_id=polymorphic_ctype_id,
)
NoteClub.objects.get_or_create(
id=8,
club_id=4,
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="BDA",
normalized_name="bda",
)
Alias.objects.get_or_create(
id=7,
note_id=7,
name="BDS",
normalized_name="bds",
)
Alias.objects.get_or_create(
id=8,
note_id=8,
name="Sinfonie",
normalized_name="sinfonie",
)
class Migration(migrations.Migration):
dependencies = [
('member', '0002_initial'),
('note', '0002_special_note'),
]
operations = [
migrations.RunPython(create_initial_club),
]

View file

@ -1,20 +0,0 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('member', '0003_create_bde_and_kfet'),
]
operations = [
migrations.RunSQL(
"UPDATE member_profile SET address = '' WHERE address IS NULL;",
),
migrations.RunSQL(
"UPDATE member_profile SET ml_events_registration = '' WHERE ml_events_registration IS NULL;",
),
migrations.RunSQL(
"UPDATE member_profile SET section = '' WHERE section IS NULL;",
),
]

View file

@ -1,28 +0,0 @@
# Generated by Django 2.2.16 on 2020-09-06 19:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0004_replace_null_by_blank'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='address',
field=models.CharField(blank=True, default='', max_length=255, verbose_name='address'),
),
migrations.AlterField(
model_name='profile',
name='ml_events_registration',
field=models.CharField(blank=True, choices=[('', 'No'), ('fr', 'Yes (receive them in french)'), ('en', 'Yes (receive them in english)')], default='', max_length=2, verbose_name='Register on the mailing list to stay informed of the events of the campus (1 mail/week)'),
),
migrations.AlterField(
model_name='profile',
name='section',
field=models.CharField(blank=True, default='', help_text='e.g. "1A0", "9A♥", "SAPHIRE"', max_length=255, verbose_name='section'),
),
]

View file

@ -1,50 +0,0 @@
import sys
from django.db import migrations
def give_note_account_permissions(apps, schema_editor):
"""
Automatically manage the membership of the Note account.
"""
User = apps.get_model("auth", "user")
Membership = apps.get_model("member", "membership")
Role = apps.get_model("permission", "role")
note = User.objects.filter(username="note")
if not note.exists():
# We are in a test environment, don't log error message
if len(sys.argv) > 1 and sys.argv[1] == 'test':
return
print("Warning: Note account was not found. The note account was not imported.")
print("Make sure you have imported the NK15 database. The new import script handles correctly the permissions.")
print("This migration will be ignored, you can re-run it if you forgot the note account or ignore it if you "
"don't want this account.")
return
note = note.get()
# Set for the two clubs a large expiration date and the correct role.
for m in Membership.objects.filter(user_id=note.id).all():
m.date_end = "3142-12-12"
m.roles.set(Role.objects.filter(name="PC Kfet").all())
m.save()
# By default, the note account is only authorized to be logged from localhost.
note.password = "ipbased$127.0.0.1"
note.is_active = True
note.save()
# Ensure that the note of the account is disabled
note.note.inactivity_reason = 'forced'
note.note.is_active = False
note.save()
class Migration(migrations.Migration):
dependencies = [
('member', '0005_remove_null_tag_on_charfields'),
('permission', '0001_initial'),
]
operations = [
migrations.RunPython(give_note_account_permissions),
]

View file

@ -1,23 +0,0 @@
# Generated by Django 2.2.19 on 2021-03-13 11:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0006_create_note_account_bde_membership'),
]
operations = [
migrations.AlterField(
model_name='membership',
name='roles',
field=models.ManyToManyField(related_name='memberships', to='permission.Role', verbose_name='roles'),
),
migrations.AlterField(
model_name='profile',
name='promotion',
field=models.PositiveSmallIntegerField(default=2021, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.2.24 on 2021-10-05 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('member', '0007_auto_20210313_1235'),
]
operations = [
migrations.AlterField(
model_name='profile',
name='department',
field=models.CharField(choices=[('A0', 'Informatics (A0)'), ('A1', 'Mathematics (A1)'), ('A2', 'Physics (A2)'), ("A'2", "Applied physics (A'2)"), ("A''2", "Chemistry (A''2)"), ('A3', 'Biology (A3)'), ('B1234', 'SAPHIRE (B1234)'), ('B1', 'Mechanics (B1)'), ('B2', 'Civil engineering (B2)'), ('B3', 'Mechanical engineering (B3)'), ('B4', 'EEA (B4)'), ('C', 'Design (C)'), ('D2', 'Economy-management (D2)'), ('D3', 'Social sciences (D3)'), ('E', 'English (E)'), ('EXT', 'External (EXT)')], max_length=8, verbose_name='department'),
),
]

View file

@ -43,7 +43,7 @@ class Profile(models.Model):
section = models.CharField( section = models.CharField(
verbose_name=_('section'), verbose_name=_('section'),
help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'), help_text=_('Auto generated'),
max_length=255, max_length=255,
blank=True, blank=True,
default="", default="",
@ -83,26 +83,6 @@ class Profile(models.Model):
default=False, default=False,
) )
ml_events_registration = models.CharField(
blank=True,
default='',
max_length=2,
choices=[
('', _("No")),
('fr', _("Yes")),
],
verbose_name=_("Register on the mailing list to stay informed of the events of the campus (1 mail/week)"),
)
ml_sport_registration = models.BooleanField(
default=False,
verbose_name=_("Register on the mailing list to stay informed of the sport events of the campus (1 mail/week)"),
)
ml_art_registration = models.BooleanField(
default=False,
verbose_name=_("Register on the mailing list to stay informed of the art events of the campus (1 mail/week)"),
)
report_frequency = models.PositiveSmallIntegerField( report_frequency = models.PositiveSmallIntegerField(
verbose_name=_("report frequency (in days)"), verbose_name=_("report frequency (in days)"),
@ -153,7 +133,7 @@ class Profile(models.Model):
return str(self.user) return str(self.user)
def send_email_validation_link(self): def send_email_validation_link(self):
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account")) subject = "[Note Ker Lann] " + str(_("Activate your Note Ker Lann account"))
token = email_validation_token.make_token(self.user) token = email_validation_token.make_token(self.user)
uid = urlsafe_base64_encode(force_bytes(self.user_id)) uid = urlsafe_base64_encode(force_bytes(self.user_id))
message = loader.render_to_string('registration/mails/email_validation_email.txt', message = loader.render_to_string('registration/mails/email_validation_email.txt',
@ -378,14 +358,10 @@ class Membership(models.Model):
parent_membership.save() parent_membership.save()
parent_membership.refresh_from_db() parent_membership.refresh_from_db()
if self.club.parent_club.name == "BDE":
parent_membership.roles.set( parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all()) Role.objects.filter(Q(name="Adhérent")).all())
elif self.club.parent_club.name == "Kfet":
parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all())
else:
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save() parent_membership.save()
@transaction.atomic @transaction.atomic

View file

@ -13,29 +13,15 @@ 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 %}
{% if club.name == "Kfet" %} {# Auto-renewal #} {% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
{% 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,
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }} otherwise it will fail.
will be charged to renew automatically the membership in this/these club·s. {% endblocktrans %}
{% 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 %}
{% if club.name == "Kfet" %} {% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
{% 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,
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }} otherwise the creation of this membership will fail.
will be charged to adhere automatically to this/these club·s. {% endblocktrans %}
{% 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 %}
@ -69,10 +55,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
let credit_amount = $("#id_credit_amount"); let credit_amount = $("#id_credit_amount");
credit_amount.attr('disabled', true); credit_amount.attr('disabled', true);
credit_amount.val('{{ total_fee }}'); credit_amount.val('{{ total_fee }}');
let bank = $("#id_bank");
bank.attr('disabled', true);
bank.val('Société générale');
} }
</script> </script>
{% endblock %} {% endblock %}

View file

@ -3,6 +3,7 @@
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %} {% endcomment %}
{% load i18n perms %} {% load i18n perms %}
{% load qr_code %}
{# Use a fluid-width container #} {# Use a fluid-width container #}
{% block containertype %}container-fluid{% endblock %} {% block containertype %}container-fluid{% endblock %}
@ -24,11 +25,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a href="{% url 'member:user_update_pic' user_object.pk %}"> <a href="{% url 'member:user_update_pic' user_object.pk %}">
<img src="{{ user_object.note.display_image.url }}" class="img-thumbnail mt-2"> <img src="{{ user_object.note.display_image.url }}" class="img-thumbnail mt-2">
</a> </a>
{% qr_from_text user_object.username size="M" %}
{% elif club %} {% elif club %}
<a href="{% url 'member:club_update_pic' club.pk %}"> <a href="{% url 'member:club_update_pic' club.pk %}">
<img src="{{ club.note.display_image.url }}" class="img-thumbnail mt-2"> <img src="{{ club.note.display_image.url }}" class="img-thumbnail mt-2">
</a> </a>
{% qr_from_text club.name size="M" %}
{% endif %} {% endif %}
</div> </div>
{% if note.inactivity_reason %} {% if note.inactivity_reason %}
<div class="alert alert-danger polymorphic-add-choice"> <div class="alert alert-danger polymorphic-add-choice">
@ -51,7 +55,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if request.path_info != user_profile_url %} {% if request.path_info != user_profile_url %}
<a class="btn btn-sm btn-primary" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a> <a class="btn btn-sm btn-primary" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
{% endif %} {% endif %}
{% elif club and not club.weiclub %} {% elif club %}
{% if can_add_members %} {% if can_add_members %}
<a class="btn btn-sm btn-success" href="{% url 'member:club_add_member' club_pk=club.pk %}" <a class="btn btn-sm btn-success" href="{% url 'member:club_add_member' club_pk=club.pk %}"
data-turbolinks="false"> {% trans "Add member" %}</a> data-turbolinks="false"> {% trans "Add member" %}</a>

View file

@ -1,4 +1,4 @@
{% load i18n pretty_money perms %} {% load i18n pretty_money perms memberinfo %}
<dl class="row"> <dl class="row">
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
@ -39,7 +39,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if "note.view_note"|has_perm:club.note %} {% if "note.view_note"|has_perm:club.note and user|is_member:club %}
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd> <dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd>
{% endif %} {% endif %}

View file

@ -48,7 +48,7 @@
<dd class="col-xl-6">{{ user_object.profile.address }}</dd> <dd class="col-xl-6">{{ user_object.profile.address }}</dd>
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd> <dd class="col-xl-6">{{ user_object.profile.paid|yesno:_("yes,no,maybe") }}</dd>
{% endif %} {% endif %}
{% if user_object.note and "note.view_note"|has_perm:user_object.note %} {% if user_object.note and "note.view_note"|has_perm:user_object.note %}

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="alert alert-info"> <div class="alert alert-info">
<h4>À quoi sert un jeton d'authentification ?</h4> <h4>À quoi sert un jeton d'authentification ?</h4>
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</a> via votre propre compte Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Ker Lann</a> via votre propre compte
depuis un client externe.<br /> depuis un client externe.<br />
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token &lt;TOKEN&gt;</code> Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token &lt;TOKEN&gt;</code>
pour pouvoir vous identifier.<br /><br /> pour pouvoir vous identifier.<br /><br />
@ -55,10 +55,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-header"> <div class="card-header">
<div class="alert alert-info"> <div class="alert alert-info">
<p> <p>
La Note Kfet implémente également le protocole <a href="https://oauth.net/2/">OAuth2</a>, afin de La Note Ker Lann implémente également le protocole <a href="https://oauth.net/2/">OAuth2</a>, afin de
permettre à des applications tierces d'interagir avec la Note en récoltant des informations permettre à des applications tierces d'interagir avec la Note en récoltant des informations
(de connexion par exemple) voir en permettant des modifications à distance, par exemple lorsqu'il (de connexion par exemple) voir en permettant des modifications à distance, par exemple lorsqu'il
s'agit d'avoir un site marchand sur lequel faire des transactions via la Note Kfet. s'agit d'avoir un site marchand sur lequel faire des transactions via la Note Ker Lann.
</p> </p>
<p> <p>

View file

@ -28,7 +28,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% blocktrans trimmed %} {% blocktrans trimmed %}
Adding someone as a friend enables them to initiate transactions coming Adding someone as a friend enables them to initiate transactions coming
from your account (while keeping your balance positive). This is from your account (while keeping your balance positive). This is
designed to simplify using note kfet transfers to transfer money between designed to simplify using note ker lann transfers to transfer money between
users. The intent is that one person can make all transfers for a group of users. The intent is that one person can make all transfers for a group of
friends without needing additional rights among them. friends without needing additional rights among them.
{% endblocktrans %} {% endblocktrans %}

View file

@ -49,7 +49,7 @@ class TestMemberships(TestCase):
self.club = Club.objects.create(name="totoclub", parent_club=Club.objects.get(name="BDE")) self.club = Club.objects.create(name="totoclub", parent_club=Club.objects.get(name="BDE"))
self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE")) self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
self.membership = Membership.objects.create(user=self.user, club=self.club) self.membership = Membership.objects.create(user=self.user, club=self.club)
self.membership.roles.add(Role.objects.get(name="Bureau de club")) self.membership.roles.add(Role.objects.get(name="Pr\u00e9sident\u00b7e"))
self.membership.save() self.membership.save()
def test_admin_pages(self): def test_admin_pages(self):
@ -161,7 +161,7 @@ class TestMemberships(TestCase):
response = self.client.get(reverse("member:club_members", args=(self.club.pk,)) + "?search=toto&roles=" response = self.client.get(reverse("member:club_members", args=(self.club.pk,)) + "?search=toto&roles="
+ ",".join([str(role.pk) for role in + ",".join([str(role.pk) for role in
Role.objects.filter(weirole__isnull=True).all()])) Role.objects.all()]))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_render_club_add_member(self): def test_render_club_add_member(self):
@ -179,20 +179,17 @@ class TestMemberships(TestCase):
# We create a club without any parent and one club with parent BDE (that is the club Kfet) # We create a club without any parent and one club with parent BDE (that is the club Kfet)
for bde_parent in False, True: for bde_parent in False, True:
if bde_parent: club = Club.objects.create(
club = Club.objects.get(name="Kfet") name="Second club " + ("with BDE" if bde_parent else "without BDE"),
else: parent_club=None,
club = Club.objects.create( email="newclub@example.com",
name="Second club " + ("with BDE" if bde_parent else "without BDE"), require_memberships=True,
parent_club=None, membership_fee_paid=1000,
email="newclub@example.com", membership_fee_unpaid=500,
require_memberships=True, membership_start=date.today(),
membership_fee_paid=1000, membership_end=date.today() + timedelta(days=366),
membership_fee_unpaid=500, membership_duration=366,
membership_start=date.today(), )
membership_end=date.today() + timedelta(days=366),
membership_duration=366,
)
response = self.client.get(reverse("member:club_add_member", args=(club.pk,))) response = self.client.get(reverse("member:club_add_member", args=(club.pk,)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -204,8 +201,7 @@ class TestMemberships(TestCase):
credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
credit_amount=4200, credit_amount=4200,
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto"
bank="Le matelas",
)) ))
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
@ -223,11 +219,6 @@ class TestMemberships(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
bde_membership = self.bde_membership bde_membership = self.bde_membership
if bde_parent:
bde_membership = Membership.objects.get(club__name="BDE", user=user)
bde_membership.date_start = date(year=2000, month=1, day=1)
bde_membership.date_end = date(year=2000, month=12, day=31)
bde_membership.save()
response = self.client.get(reverse("member:club_renew_membership", args=(bde_membership.pk,))) response = self.client.get(reverse("member:club_renew_membership", args=(bde_membership.pk,)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -243,7 +234,6 @@ class TestMemberships(TestCase):
credit_amount=14242, credit_amount=14242,
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="Bank",
)) ))
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
@ -261,11 +251,11 @@ class TestMemberships(TestCase):
response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict( response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict(
roles=[role.id for role in Role.objects.filter( roles=[role.id for role in Role.objects.filter(
Q(name="Membre de club") | Q(name="Trésorier·ère de club") | Q(name="Bureau de club")).all()], Q(name="Trésorier·ère")).all()],
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.membership.refresh_from_db() self.membership.refresh_from_db()
self.assertEqual(self.membership.roles.count(), 3) self.assertEqual(self.membership.roles.count(), 1)
def test_render_user_list(self): def test_render_user_list(self):
""" """
@ -399,7 +389,7 @@ class TestMemberAPI(TestAPI):
) )
self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE")) self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE"))
self.membership = Membership.objects.create(user=self.user, club=self.club) self.membership = Membership.objects.create(user=self.user, club=self.club)
self.membership.roles.add(Role.objects.get(name="Bureau de club")) self.membership.roles.add(Role.objects.get(name="Pr\u00e9sident\u00b7e"))
self.membership.save() self.membership.save()
def test_club_api(self): def test_club_api(self):

View file

@ -28,7 +28,7 @@ from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\ from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
CustomAuthenticationForm, MembershipRolesForm MembershipRolesForm, AuthenticationForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -37,16 +37,19 @@ class CustomLoginView(LoginView):
""" """
Login view, where the user can select its permission mask. Login view, where the user can select its permission mask.
""" """
form_class = CustomAuthenticationForm form_class = AuthenticationForm
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
logout(self.request) logout(self.request)
self.request.user = form.get_user() self.request.user = form.get_user()
_set_current_request(self.request) _set_current_request(self.request)
self.request.session['permission_mask'] = form.cleaned_data['permission_mask'].rank self.request.session['permission_mask'] = 42#form.cleaned_data['permission_mask'].rank
return super().form_valid(form) return super().form_valid(form)
def logout_view(request):
logout(request)
return redirect('index')
class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
@ -269,7 +272,7 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
"class": "autocomplete form-control", "class": "autocomplete form-control",
"id": "trusted", "id": "trusted",
"resetable": True, "resetable": True,
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", "api_url": "/api/note/alias/",
"name_field": "name", "name_field": "name",
"placeholder": "" "placeholder": ""
} }
@ -445,7 +448,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
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="Pr\u00e9sident\u00b7e",
date_start__lte=date.today(), date_end__gte=date.today())\ 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-")
@ -541,11 +544,6 @@ class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
def get_queryset(self, **kwargs): def get_queryset(self, **kwargs):
qs = super().get_queryset(**kwargs) qs = super().get_queryset(**kwargs)
# Don't update a WEI club through this view
if "wei" in settings.INSTALLED_APPS:
qs = qs.filter(weiclub=None)
return qs return qs
def get_success_url(self): def get_success_url(self):
@ -597,7 +595,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
if "club_pk" in self.kwargs: # We create a new membership. if "club_pk" in self.kwargs: # We create a new membership.
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view"))\ club = Club.objects.filter(PermissionBackend.filter_queryset(self.request, Club, "view"))\
.get(pk=self.kwargs["club_pk"], weiclub=None) .get(pk=self.kwargs["club_pk"])
form.fields['credit_amount'].initial = club.membership_fee_paid form.fields['credit_amount'].initial = club.membership_fee_paid
# Ensure that the user is member of the parent club and all its the family tree. # Ensure that the user is member of the parent club and all its the family tree.
c = club c = club
@ -661,12 +659,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
if not credit_type: if not credit_type:
credit_amount = 0 credit_amount = 0
if user.note.balance + credit_amount < fee and not Membership.objects.filter( if user.note.balance + credit_amount < fee:
club__name="Kfet",
user=user,
date_start__lte=date.today(),
date_end__gte=date.today(),
).exists():
# Users without a valid Kfet membership can't have a negative balance. # Users without a valid Kfet membership can't have a negative balance.
# TODO Send a notification to the user (with a mail?) to tell her/him to credit her/his note # TODO Send a notification to the user (with a mail?) to tell her/him to credit her/his note
form.add_error('user', form.add_error('user',
@ -683,7 +676,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
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. # 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( if club.parent_club and not Membership.objects.filter(
user=form.instance.user, user=form.instance.user,
club=club.parent_club, club=club.parent_club,
date_start__gte=club.parent_club.membership_start, date_start__gte=club.parent_club.membership_start,
@ -731,7 +724,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
credit_amount = form.cleaned_data["credit_amount"] credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"] last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"] first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
if credit_type is None: if credit_type is None:
@ -755,7 +747,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
# Now, all is fine, the membership can be created. # Now, all is fine, the membership can be created.
if club.name == "BDE" or club.name == "Kfet": if club.name == "BDE" or club.name == "BDA" or club.name == "BDS":
# When we renew the BDE membership, we update the profile section # When we renew the BDE membership, we update the profile section
# that should happens at least once a year. # that should happens at least once a year.
user.profile.section = user.profile.section_generated user.profile.section = user.profile.section_generated
@ -772,7 +764,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
reason="Crédit " + credit_type.special_type + " (Adhésion " + club.name + ")", reason="Crédit " + credit_type.special_type + " (Adhésion " + club.name + ")",
last_name=last_name, last_name=last_name,
first_name=first_name, first_name=first_name,
bank=bank,
valid=True, valid=True,
) )
transaction._force_save = True transaction._force_save = True
@ -784,9 +775,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
ret = super().form_valid(form) ret = super().form_valid(form)
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")).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()
# Set the same roles as before # Set the same roles as before
if old_membership: if old_membership:
member_role = member_role.union(old_membership.roles.all()) member_role = member_role.union(old_membership.roles.all())
@ -819,8 +809,7 @@ class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
form = super().get_form(form_class) form = super().get_form(form_class)
club = self.object.club club = self.object.club
form.fields['roles'].queryset = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub')) form.fields['roles'].queryset = Role.objects.filter((Q(for_club__isnull=True) | Q(for_club=club))).all()
& (Q(for_club__isnull=True) | Q(for_club=club))).all()
return form return form
@ -866,8 +855,7 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV
).get(pk=self.kwargs["pk"]) ).get(pk=self.kwargs["pk"])
context["club"] = club context["club"] = club
applicable_roles = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub')) applicable_roles = Role.objects.filter((Q(for_club__isnull=True) | Q(for_club=club))).all()
& (Q(for_club__isnull=True) | Q(for_club=club))).all()
context["applicable_roles"] = applicable_roles context["applicable_roles"] = applicable_roles
context["only_active"] = "only_active" not in self.request.GET or self.request.GET["only_active"] != '0' context["only_active"] = "only_active" not in self.request.GET or self.request.GET["only_active"] != '0'

View file

@ -160,7 +160,7 @@ class ConsumerSerializer(serializers.ModelSerializer):
memberships = Membership.objects.filter( memberships = Membership.objects.filter(
PermissionBackend.filter_queryset(get_current_request(), Membership, "view")).filter( PermissionBackend.filter_queryset(get_current_request(), Membership, "view")).filter(
user=obj.note.user, user=obj.note.user,
club=2, # Kfet club=2, # BDA
).order_by("-date_start") ).order_by("-date_start")
if memberships.exists(): if memberships.exists():
return MembershipSerializer().to_representation(memberships.first()) return MembershipSerializer().to_representation(memberships.first())

View file

@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet) router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet) router.register(path + '/consumer', ConsumerViewSet,"consumer")
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View file

@ -160,11 +160,13 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
""" """
queryset = super().get_queryset().distinct() 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", None) alias = self.request.query_params.get("alias", None)
# Check if this is a valid regex. If not, we won't check regex # Check if this is a valid regex. If not, we won't check regex
try: try:
re.compile(alias) re.compile(alias)
@ -180,7 +182,8 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
# then if the normalized pattern matches a normalized alias. # then if the normalized pattern matches a normalized alias.
queryset = queryset.filter( queryset = queryset.filter(
**{f'name{suffix}': alias_prefix + alias} **{f'name{suffix}': alias_prefix + alias}
).union( )
""".union(
queryset.filter( queryset.filter(
Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias}) & ~Q(**{f'name{suffix}': alias_prefix + alias})
@ -191,12 +194,12 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) & ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias}) & ~Q(**{f'name{suffix}': alias_prefix + alias})
), ),
all=True) 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")
return queryset.distinct() return queryset#.distinct()
class TemplateCategoryViewSet(ReadProtectedModelViewSet): class TemplateCategoryViewSet(ReadProtectedModelViewSet):

View file

@ -0,0 +1,57 @@
[
{
"model": "note.templatecategory",
"pk": 1,
"fields": {
"name": "Soft"
}
},
{
"model": "note.templatecategory",
"pk": 2,
"fields": {
"name": "Alcool"
}
},
{
"model": "note.templatecategory",
"pk": 3,
"fields": {
"name": "Nourriture"
}
},
{
"model": "note.templatecategory",
"pk": 4,
"fields": {
"name": "Clubs"
}
},
{
"model": "note.templatecategory",
"pk": 5,
"fields": {
"name": "Goodies"
}
},
{
"model": "note.templatecategory",
"pk": 6,
"fields": {
"name": "Autre"
}
},
{
"model": "note.transactiontemplate",
"pk": 1,
"fields": {
"name": "Test Button",
"destination": 5,
"amount": 1,
"category": 6,
"display": false,
"highlighted": false,
"description": "Test button"
}
}
]

View file

@ -1,9 +1,9 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -11,12 +11,31 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('member', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
('member', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Transaction',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('source_alias', models.CharField(default='', max_length=255, verbose_name='used alias')),
('destination_alias', models.CharField(default='', max_length=255, verbose_name='used alias')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='quantity')),
('amount', models.PositiveIntegerField(verbose_name='amount')),
('reason', models.CharField(max_length=255, verbose_name='reason')),
('valid', models.BooleanField(default=True, verbose_name='valid')),
('invalidity_reason', models.CharField(blank=True, default='', max_length=255, verbose_name='invalidity reason')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
],
options={
'verbose_name': 'transaction',
'verbose_name_plural': 'transactions',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Note', name='Note',
fields=[ fields=[
@ -26,8 +45,8 @@ class Migration(migrations.Migration):
('display_image', models.ImageField(default='pic/default.png', max_length=255, upload_to='pic/', verbose_name='display image')), ('display_image', models.ImageField(default='pic/default.png', max_length=255, upload_to='pic/', verbose_name='display image')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')), ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this note should be treated as active. Unselect this instead of deleting notes.', verbose_name='active')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this note should be treated as active. Unselect this instead of deleting notes.', verbose_name='active')),
('inactivity_reason', models.CharField(choices=[('manual', 'The user blocked his/her note manually, eg. when he/she left the school for holidays. It can be reactivated at any time.'), ('forced', "The note is blocked by the the BDE and can't be manually reactivated.")], default=None, max_length=255, null=True)), ('inactivity_reason', models.CharField(blank=True, choices=[('manual', 'The user blocked his/her note manually, eg. when he/she left the school for holidays. It can be reactivated at any time.'), ('forced', "The note is blocked by the the BDE and can't be manually reactivated.")], default='', max_length=255)),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_note.note_set+', to='contenttypes.ContentType')), ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
], ],
options={ options={
'verbose_name': 'note', 'verbose_name': 'note',
@ -46,41 +65,23 @@ class Migration(migrations.Migration):
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Transaction', name='SpecialTransaction',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.transaction')),
('source_alias', models.CharField(default='', max_length=255, verbose_name='used alias')), ('last_name', models.CharField(max_length=255, verbose_name='name')),
('destination_alias', models.CharField(default='', max_length=255, verbose_name='used alias')), ('first_name', models.CharField(max_length=255, verbose_name='first_name')),
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='quantity')),
('amount', models.PositiveIntegerField(verbose_name='amount')),
('reason', models.CharField(max_length=255, verbose_name='reason')),
('valid', models.BooleanField(default=True, verbose_name='valid')),
('invalidity_reason', models.CharField(blank=True, default=None, max_length=255, null=True, verbose_name='invalidity reason')),
('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='destination')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_note.transaction_set+', to='contenttypes.ContentType')),
('source', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.Note', verbose_name='source')),
], ],
options={ options={
'verbose_name': 'transaction', 'verbose_name': 'Special transaction',
'verbose_name_plural': 'transactions', 'verbose_name_plural': 'Special transactions',
},
),
migrations.CreateModel(
name='MembershipTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
],
options={
'verbose_name': 'membership transaction',
'verbose_name_plural': 'membership transactions',
}, },
bases=('note.transaction',), bases=('note.transaction',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='NoteClub', name='NoteClub',
fields=[ fields=[
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')), ('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.note')),
('club', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to='member.club', verbose_name='club')),
], ],
options={ options={
'verbose_name': 'club note', 'verbose_name': 'club note',
@ -91,7 +92,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='NoteSpecial', name='NoteSpecial',
fields=[ fields=[
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')), ('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.note')),
('special_type', models.CharField(max_length=255, unique=True, verbose_name='type')), ('special_type', models.CharField(max_length=255, unique=True, verbose_name='type')),
], ],
options={ options={
@ -100,41 +101,15 @@ class Migration(migrations.Migration):
}, },
bases=('note.note',), bases=('note.note',),
), ),
migrations.CreateModel( migrations.AddField(
name='NoteUser', model_name='transaction',
fields=[ name='destination',
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Note')), field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_name='destination'),
],
options={
'verbose_name': "one's note",
'verbose_name_plural': 'users note',
},
bases=('note.note',),
), ),
migrations.CreateModel( migrations.AddField(
name='RecurrentTransaction', model_name='transaction',
fields=[ name='source',
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')), field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_name='source'),
],
options={
'verbose_name': 'recurrent transaction',
'verbose_name_plural': 'recurrent transactions',
},
bases=('note.transaction',),
),
migrations.CreateModel(
name='SpecialTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
('last_name', models.CharField(max_length=255, verbose_name='name')),
('first_name', models.CharField(max_length=255, verbose_name='first_name')),
('bank', models.CharField(blank=True, max_length=255, verbose_name='bank')),
],
options={
'verbose_name': 'Special transaction',
'verbose_name_plural': 'Special transactions',
},
bases=('note.transaction',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Alias', name='Alias',
@ -142,7 +117,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')), ('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
('normalized_name', models.CharField(editable=False, max_length=255, unique=True)), ('normalized_name', models.CharField(editable=False, max_length=255, unique=True)),
('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note')), ('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.note')),
], ],
options={ options={
'verbose_name': 'alias', 'verbose_name': 'alias',
@ -158,14 +133,62 @@ class Migration(migrations.Migration):
('display', models.BooleanField(default=True, verbose_name='display')), ('display', models.BooleanField(default=True, verbose_name='display')),
('highlighted', models.BooleanField(default=False, verbose_name='highlighted')), ('highlighted', models.BooleanField(default=False, verbose_name='highlighted')),
('description', models.CharField(blank=True, max_length=255, verbose_name='description')), ('description', models.CharField(blank=True, max_length=255, verbose_name='description')),
('category', models.ForeignKey(max_length=31, on_delete=django.db.models.deletion.PROTECT, related_name='templates', to='note.TemplateCategory', verbose_name='type')), ('category', models.ForeignKey(max_length=31, on_delete=django.db.models.deletion.PROTECT, related_name='templates', to='note.templatecategory', verbose_name='type')),
('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.NoteClub', verbose_name='destination')), ('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.noteclub', verbose_name='destination')),
], ],
options={ options={
'verbose_name': 'transaction template', 'verbose_name': 'transaction template',
'verbose_name_plural': 'transaction templates', 'verbose_name_plural': 'transaction templates',
}, },
), ),
migrations.CreateModel(
name='Trust',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('trusted', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusted', to='note.note', verbose_name='trusted')),
('trusting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusting', to='note.note', verbose_name='trusting')),
],
options={
'verbose_name': 'frienship',
'verbose_name_plural': 'friendships',
},
),
migrations.CreateModel(
name='MembershipTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.transaction')),
('membership', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='transaction', to='member.membership')),
],
options={
'verbose_name': 'membership transaction',
'verbose_name_plural': 'membership transactions',
},
bases=('note.transaction',),
),
migrations.CreateModel(
name='RecurrentTransaction',
fields=[
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.transaction')),
('template', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.transactiontemplate')),
],
options={
'verbose_name': 'recurrent transaction',
'verbose_name_plural': 'recurrent transactions',
},
bases=('note.transaction',),
),
migrations.CreateModel(
name='NoteUser',
fields=[
('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.note')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': "one's note",
'verbose_name_plural': 'users note',
},
bases=('note.note',),
),
migrations.AddIndex( migrations.AddIndex(
model_name='transaction', model_name='transaction',
index=models.Index(fields=['created_at'], name='note_transa_created_bea8b1_idx'), index=models.Index(fields=['created_at'], name='note_transa_created_bea8b1_idx'),
@ -178,26 +201,6 @@ class Migration(migrations.Migration):
model_name='transaction', model_name='transaction',
index=models.Index(fields=['destination'], name='note_transa_destina_6e1bb4_idx'), index=models.Index(fields=['destination'], name='note_transa_destina_6e1bb4_idx'),
), ),
migrations.AddField(
model_name='recurrenttransaction',
name='template',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.TransactionTemplate'),
),
migrations.AddField(
model_name='noteuser',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to=settings.AUTH_USER_MODEL, verbose_name='user'),
),
migrations.AddField(
model_name='noteclub',
name='club',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to='member.Club', verbose_name='club'),
),
migrations.AddField(
model_name='membershiptransaction',
name='membership',
field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='transaction', to='member.Membership'),
),
migrations.AddIndex( migrations.AddIndex(
model_name='alias', model_name='alias',
index=models.Index(fields=['name'], name='note_alias_name_a89405_idx'), index=models.Index(fields=['name'], name='note_alias_name_a89405_idx'),
@ -206,4 +209,8 @@ class Migration(migrations.Migration):
model_name='alias', model_name='alias',
index=models.Index(fields=['normalized_name'], name='note_alias_normali_bd52b4_idx'), index=models.Index(fields=['normalized_name'], name='note_alias_normali_bd52b4_idx'),
), ),
migrations.AlterUniqueTogether(
name='trust',
unique_together={('trusting', 'trusted')},
),
] ]

View file

@ -1,17 +0,0 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('note', '0002_create_special_notes'),
]
operations = [
migrations.RunSQL(
"UPDATE note_note SET inactivity_reason = '' WHERE inactivity_reason IS NULL;"
),
migrations.RunSQL(
"UPDATE note_transaction SET invalidity_reason = '' WHERE invalidity_reason IS NULL;"
),
]

View file

@ -1,23 +0,0 @@
# Generated by Django 2.2.16 on 2020-09-06 19:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('note', '0003_replace_null_by_blank'),
]
operations = [
migrations.AlterField(
model_name='note',
name='inactivity_reason',
field=models.CharField(blank=True, choices=[('manual', 'The user blocked his/her note manually, eg. when he/she left the school for holidays. It can be reactivated at any time.'), ('forced', "The note is blocked by the the BDE and can't be manually reactivated.")], default='', max_length=255),
),
migrations.AlterField(
model_name='transaction',
name='invalidity_reason',
field=models.CharField(blank=True, default='', max_length=255, verbose_name='invalidity reason'),
),
]

View file

@ -1,19 +0,0 @@
# Generated by Django 2.2.19 on 2021-03-13 11:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('note', '0004_remove_null_tag_on_charfields'),
]
operations = [
migrations.AlterField(
model_name='alias',
name='note',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.Note'),
),
]

View file

@ -1,27 +0,0 @@
# Generated by Django 2.2.24 on 2021-09-05 19:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('note', '0005_auto_20210313_1235'),
]
operations = [
migrations.CreateModel(
name='Trust',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('trusted', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusted', to='note.Note', verbose_name='trusted')),
('trusting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusting', to='note.Note', verbose_name='trusting')),
],
options={
'verbose_name': 'frienship',
'verbose_name_plural': 'friendships',
'unique_together': {('trusting', 'trusted')},
},
),
]

View file

@ -189,7 +189,7 @@ class NoteClub(Note):
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))
send_mail("[Note Kfet] Passage en négatif (club {})".format(self.club.name), plain_text, send_mail("[Note Ker Lann] Passage en négatif (club {})".format(self.club.name), plain_text,
settings.DEFAULT_FROM_EMAIL, [self.club.email], html_message=html) settings.DEFAULT_FROM_EMAIL, [self.club.email], html_message=html)
@ -219,7 +219,7 @@ class NoteSpecial(Note):
class Trust(models.Model): class Trust(models.Model):
""" """
A one-sided trust relationship bertween two users A one-sided trust relationship between two users
If another user considers you as your friend, you can transfer money from If another user considers you as your friend, you can transfer money from
them them

View file

@ -306,11 +306,6 @@ class SpecialTransaction(Transaction):
verbose_name=_("first_name"), verbose_name=_("first_name"),
) )
bank = models.CharField(
max_length=255,
verbose_name=_("bank"),
blank=True,
)
@property @property
def type(self): def type(self):
@ -346,20 +341,16 @@ class SpecialTransaction(Transaction):
credit_type = form.cleaned_data["credit_type"] credit_type = form.cleaned_data["credit_type"]
last_name = form.cleaned_data["last_name"] last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"] first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
error = False error = False
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"): if not last_name or not first_name:
if not last_name: if not last_name:
form.add_error('last_name', _("This field is required.")) form.add_error('last_name', _("This field is required."))
error = True error = True
if not first_name: if not first_name:
form.add_error('first_name', _("This field is required.")) form.add_error('first_name', _("This field is required."))
error = True error = True
if not bank and credit_type.special_type == "Chèque":
form.add_error('bank', _("This field is required."))
error = True
return not error return not error

View file

@ -9,7 +9,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
name="{{ widget.name }}" name="{{ widget.name }}"
{# Other attributes are loaded #} {# Other attributes are loaded #}
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} {% if value != False %}
{{ name }}
{% if value != True %}
="{{ value|stringformat:'s' }}"
{% endif %}
{% endif %}
{% endfor %}> {% endfor %}>
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text"></span> <span class="input-group-text"></span>

View file

@ -21,6 +21,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-body text-center text-break p-2"> <div class="card-body text-center text-break p-2">
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span> <span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
</div> </div>
<div id="qrreader"></div>
</div> </div>
</div> </div>
@ -159,6 +160,79 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}
<script>
function onScanSuccess(decodedText, decodedResult) {
// handle the scanned code as you like, for example:
// Add user if exist on sources_notes_display
$.getJSON('/api/note/consumer/?format=json&alias=' + decodedText + '&search=user|club',
function (consumers) {
if (consumers.count > 0) {
// Add the user to the list of sources
let consumer = consumers.results[0];
// check if already in the list
let found = false;
notes_display.forEach(function (disp) {
if (disp.id === consumer.id) {
disp.quantity += 1;
found = true;
}
});
if (!found) {
let disp = {
name: consumer.name,
id: consumer.id,
note: consumer.note,
quantity: 1
}
notes_display.push(disp);
}
// Display
const note_list = $('#' + 'note_list');
let html = ''
notes_display.forEach(function (disp) {
html += li('note' + '_' + disp.id,
disp.name +
'<span class="badge badge-dark badge-pill">' +
disp.quantity + '</span>',
displayStyle(disp.note))
})
// Emitters are displayed
note_list.html(html);
// Add the hover and click events
notes_display.forEach(function (disp) {
const line_obj = $('#' + 'note' + '_' + disp.id)
// Hover an emitter display also the profile picture
line_obj.hover(function () {
displayNote(disp.note, disp.name, 'user_note', 'profile_pic');
})
// When an emitter is clicked, it is removed
line_obj.click(removeNote(disp, 'note', notes_display, 'note_list', 'user_note', 'profile_pic'))
})
// Stop the scanner
html5QrcodeScanner.clear();
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
}
}
);
console.log(`Code matched = ${decodedText}`, decodedResult);
}
function onScanFailure(error) {}
// check if html5QrcodeScanner is already defined
if (typeof html5QrcodeScanner === 'undefined') {
var html5QrcodeScanner = null;
}
html5QrcodeScanner = new Html5QrcodeScanner(
"qrreader",
{ fps: 10, qrbox: {width: 200, height: 200} },
/* verbose= */ false);
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
</script>
<script type="text/javascript" src="{% static "note/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 %}

View file

@ -14,7 +14,7 @@
</p> </p>
<p> <p>
Ce mail t'a été envoyé parce que le solde de ta Note Kfet {{ note }} est négatif ! Ce mail t'a été envoyé parce que le solde de ta Note Ker Lann {{ note }} est négatif !
</p> </p>
<p> <p>
@ -32,7 +32,7 @@
</p> </p>
<p> <p>
Tu peux venir recharger ta note rapidement à la Kfet, ou envoyer un mail à Tu peux venir recharger ta note rapidement, ou envoyer un mail à
la trésorerie du BdE (<a href="mailto:tresorerie.bde@lists.crans.org">tresorerie.bde@lists.crans.org</a>) la trésorerie du BdE (<a href="mailto:tresorerie.bde@lists.crans.org">tresorerie.bde@lists.crans.org</a>)
pour payer par virement bancaire. pour payer par virement bancaire.
</p> </p>
@ -40,7 +40,7 @@
-- --
<p> <p>
Le BDE<br> Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
</p> </p>
</body> </body>
</html> </html>

View file

@ -5,7 +5,7 @@
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>[Note Kfet] Liste des négatifs</title> <title>[Note Ker Lann] Liste des négatifs</title>
</head> </head>
<body> <body>
<table> <table>
@ -43,7 +43,7 @@
-- --
<p> <p>
Le BDE<br> Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
</p> </p>
</body> </body>
</html> </html>

View file

@ -8,7 +8,7 @@
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>[Note Kfet] Rapport de la Note Kfet</title> <title>[Note Ker Lann] Rapport de la Note Ker Lann</title>
<link rel="stylesheet" href="https://{{ "NOTE_URL"|getenv }}{% static "bootstrap4/css/bootstrap.min.css" %}"> <link rel="stylesheet" href="https://{{ "NOTE_URL"|getenv }}{% static "bootstrap4/css/bootstrap.min.css" %}">
<script src="https://{{ "NOTE_URL"|getenv }}{% static "bootstrap4/js/bootstrap.min.js" %}"></script> <script src="https://{{ "NOTE_URL"|getenv }}{% static "bootstrap4/js/bootstrap.min.js" %}"></script>
@ -48,7 +48,7 @@
-- --
<p> <p>
Le BDE<br> Le BDE<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
</p> </p>
</body> </body>
</html> </html>

View file

@ -47,6 +47,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<div class="card-body text-center p-2"> <div class="card-body text-center p-2">
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span> <span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
</div> </div>
<div id="qrreader"></div>
</div> </div>
</div> </div>
@ -139,12 +140,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<input type="text" id="first_name" class="form-control" /> <input type="text" id="first_name" class="form-control" />
</div> </div>
</div> </div>
<div class="form-row">
<div class="col-md-12">
<label for="bank">{% trans "Bank" %} :</label>
<input type="text" id="bank" class="form-control" />
</div>
</div>
</div> </div>
<hr> <hr>
<div class="form-row"> <div class="form-row">
@ -168,6 +163,75 @@ SPDX-License-Identifier: GPL-2.0-or-later
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}
<script>
function onScanSuccess(decodedText, decodedResult) {
// handle the scanned code as you like, for example:
// Add user if exist on sources_notes_display
$.getJSON('/api/note/consumer/?format=json&alias=' + decodedText + '&search=user|club',
function (consumers) {
if (consumers.count > 0) {
// Add the user to the list of sources
let consumer = consumers.results[0];
let found = false;
sources_notes_display.forEach(function (disp) {
if (disp.id === consumer.id) {
disp.quantity += 1;
found = true;
}
});
if (!found) {
let disp = {
name: consumer.name,
id: consumer.id,
note: consumer.note,
quantity: 1
}
sources_notes_display.push(disp);
}
// Display
const note_list = $('#' + 'source_note_list');
let html = ''
sources_notes_display.forEach(function (disp) {
html += li('source_note' + '_' + disp.id,
disp.name +
'<span class="badge badge-dark badge-pill">' +
disp.quantity + '</span>',
displayStyle(disp.note))
})
// Emitters are displayed
note_list.html(html);
// Stop the scanner
sources_notes_display.forEach(function (disp) {
const line_obj = $('#' + 'source_note' + '_' + disp.id)
// Hover an emitter display also the profile picture
line_obj.hover(function () {
displayNote(disp.note, disp.name, 'user_note', 'profile_pic');
})
// When an emitter is clicked, it is removed
line_obj.click(removeNote(disp, 'source_note', sources_notes_display, 'source_note_list', 'user_note', 'profile_pic'))
})
// Stop the scanner
html5QrcodeScanner.clear();
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
}
}
);
console.log(`Code matched = ${decodedText}`, decodedResult);
}
function onScanFailure(error) {}
if (typeof html5QrcodeScanner === 'undefined') {
var html5QrcodeScanner = null;
}
html5QrcodeScanner = new Html5QrcodeScanner(
"qrreader",
{ fps: 10, qrbox: {width: 250, height: 250} },
/* verbose= */ false);
html5QrcodeScanner.render(onScanSuccess, onScanFailure);
</script>
<script> <script>
TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }}; TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }};
SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }}; SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }};

View file

@ -34,7 +34,7 @@ class TestTransactions(TestCase):
membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user) membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
membership.roles.add(Role.objects.get(name="Respo info")) membership.roles.add(Role.objects.get(name="Respo info"))
membership.save() membership.save()
Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user) Membership.objects.create(club=Club.objects.get(name="BDA"), user=self.user)
self.user.note.refresh_from_db() self.user.note.refresh_from_db()
self.second_user = User.objects.create( self.second_user = User.objects.create(
@ -377,7 +377,7 @@ class TestNoteAPI(TestAPI):
membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user) membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
membership.roles.add(Role.objects.get(name="Respo info")) membership.roles.add(Role.objects.get(name="Respo info"))
membership.save() membership.save()
Membership.objects.create(club=Club.objects.get(name="Kfet"), user=self.user) Membership.objects.create(club=Club.objects.get(name="BDA"), user=self.user)
self.user.note.last_negative = timezone.now() self.user.note.last_negative = timezone.now()
self.user.note.save() self.user.note.save()

View file

@ -1695,12 +1695,12 @@
"auth", "auth",
"user" "user"
], ],
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", "query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"type": "view", "type": "view",
"mask": 2, "mask": 2,
"field": "", "field": "",
"permanent": false, "permanent": false,
"description": "Voir n'importe quel utilisateur qui est adhérent BDE" "description": "Voir n'importe quel utilisateur qui est adhérent"
} }
}, },
{ {
@ -1931,8 +1931,8 @@
"model": "permission.role", "model": "permission.role",
"pk": 1, "pk": 1,
"fields": { "fields": {
"for_club": 1, "for_club": null,
"name": "Adh\u00e9rent BDE", "name": "Adh\u00e9rent",
"permissions": [ "permissions": [
1, 1,
2, 2,
@ -1957,6 +1957,7 @@
161, 161,
162, 162,
165, 165,
178,
186, 186,
187, 187,
188, 188,
@ -1972,26 +1973,15 @@
"model": "permission.role", "model": "permission.role",
"pk": 2, "pk": 2,
"fields": { "fields": {
"for_club": 2, "for_club": null,
"name": "Adh\u00e9rent Kfet", "name": "Pr\u00e9sident\u00b7e",
"permissions": [ "permissions": [
22, 50,
34, 59,
36, 60,
39, 61,
40, 62,
152, 169
153,
154,
155,
156,
157,
158,
159,
160,
179,
189,
190
] ]
} }
}, },
@ -2000,57 +1990,31 @@
"pk": 3, "pk": 3,
"fields": { "fields": {
"for_club": null, "for_club": null,
"name": "Membre de club", "name": "Tr\u00e9sorier\u00b7\u00e8re",
"permissions": [ "permissions": [
22
]
}
},
{
"model": "permission.role",
"pk": 4,
"fields": {
"for_club": null,
"name": "Bureau de club",
"permissions": [
47,
49,
50,
169
]
}
},
{
"model": "permission.role",
"pk": 5,
"fields": {
"for_club": null,
"name": "Pr\u00e9sident\u00b7e de club",
"permissions": [
50,
62
]
}
},
{
"model": "permission.role",
"pk": 6,
"fields": {
"for_club": null,
"name": "Tr\u00e9sorier\u00b7\u00e8re de club",
"permissions": [
59,
19, 19,
20, 20,
21, 21,
27, 27,
59,
60, 60,
61, 61,
62, 62,
150, 63,
64,
65,
66,
67,
68,
69,
151,
166, 166,
167, 167,
168, 168,
172,
173,
174,
175,
182, 182,
184, 184,
185 185
@ -2059,80 +2023,33 @@
}, },
{ {
"model": "permission.role", "model": "permission.role",
"pk": 7, "pk": 4,
"fields": { "fields": {
"for_club": 1, "for_club": null,
"name": "Pr\u00e9sident\u00b7e BDE", "name": "Secr\u00e9taire",
"permissions": [ "permissions": [
24, 21,
25,
26,
27,
30,
33
]
}
},
{
"model": "permission.role",
"pk": 8,
"fields": {
"for_club": 1,
"name": "Tr\u00e9sorier\u00b7\u00e8re BDE",
"permissions": [
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
43,
51,
53,
54, 54,
55, 55,
56, 56,
57, 57,
58, 58,
63, 59,
64, 60,
65, 61,
66, 145,
67,
68,
69,
146, 146,
147, 147,
150,
151,
163,
164,
170,
171,
172,
173,
174,
175,
176, 176,
177, 177
178,
188,
183,
186,
187
] ]
} }
}, },
{ {
"model": "permission.role", "model": "permission.role",
"pk": 9, "pk": 5,
"fields": { "fields": {
"for_club": 1, "for_club": null,
"name": "Respo info", "name": "Respo info",
"permissions": [ "permissions": [
1, 1,
@ -2256,130 +2173,5 @@
196 196
] ]
} }
},
{
"model": "permission.role",
"pk": 10,
"fields": {
"for_club": 2,
"name": "GC Kfet",
"permissions": [
32,
56,
58,
55,
57,
52,
23,
24,
25,
26,
27,
28,
29,
30,
31,
166,
167,
168,
170,
171,
176,
177,
178,
179,
180,
181,
182
]
}
},
{
"model": "permission.role",
"pk": 11,
"fields": {
"for_club": 2,
"name": "Res[pot]",
"permissions": [
37,
38,
41,
42,
43,
44,
45,
46,
148,
149,
182
]
}
},
{
"model": "permission.role",
"pk": 17,
"fields": {
"for_club": null,
"name": "1A",
"permissions": []
}
},
{
"model": "permission.role",
"pk": 19,
"fields": {
"for_club": 1,
"name": "Secrétaire BDE",
"permissions": [
54,
55,
56,
57,
58,
145,
146,
147,
150,
176,
177
]
}
},
{
"model": "permission.role",
"pk": 20,
"fields": {
"for_club": 1,
"name": "PC Kfet",
"permissions": [
6,
22,
24,
25,
26,
27,
30,
49,
50,
55,
56,
57,
58,
147,
150,
166,
167,
168,
176,
177,
180,
181
]
}
},
{
"model": "wei.weirole",
"pk": 17,
"fields": {}
} }
] ]

View file

@ -1,7 +1,7 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -9,26 +9,11 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('member', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'), ('contenttypes', '0002_remove_content_type_name'),
('member', '0001_initial'),
] ]
operations = [ operations = [
migrations.CreateModel(
name='Permission',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('query', models.TextField(verbose_name='query')),
('type', models.CharField(choices=[('add', 'add'), ('view', 'view'), ('change', 'change'), ('delete', 'delete')], max_length=15, verbose_name='type')),
('field', models.CharField(blank=True, max_length=255, verbose_name='field')),
('permanent', models.BooleanField(default=False, help_text='Tells if the permission should be granted even if the membership of the user is expired.', verbose_name='permanent')),
('description', models.CharField(blank=True, max_length=255, verbose_name='description')),
],
options={
'verbose_name': 'permission',
'verbose_name_plural': 'permissions',
},
),
migrations.CreateModel( migrations.CreateModel(
name='PermissionMask', name='PermissionMask',
fields=[ fields=[
@ -41,31 +26,35 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'permission masks', 'verbose_name_plural': 'permission masks',
}, },
), ),
migrations.CreateModel(
name='Permission',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('query', models.TextField(verbose_name='query')),
('type', models.CharField(choices=[('add', 'add'), ('view', 'view'), ('change', 'change'), ('delete', 'delete')], max_length=15, verbose_name='type')),
('field', models.CharField(blank=True, max_length=255, verbose_name='field')),
('permanent', models.BooleanField(default=False, help_text='Tells if the permission should be granted even if the membership of the user is expired.', verbose_name='permanent')),
('description', models.CharField(blank=True, max_length=255, verbose_name='description')),
('model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype', verbose_name='model')),
('mask', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='permissions', to='permission.permissionmask', verbose_name='mask')),
],
options={
'verbose_name': 'permission',
'verbose_name_plural': 'permissions',
'unique_together': {('model', 'query', 'type', 'field')},
},
),
migrations.CreateModel( migrations.CreateModel(
name='Role', name='Role',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')), ('name', models.CharField(max_length=255, verbose_name='name')),
('for_club', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='for club')), ('for_club', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.club', verbose_name='for club')),
('permissions', models.ManyToManyField(to='permission.Permission', verbose_name='permissions')), ('permissions', models.ManyToManyField(to='permission.permission', verbose_name='permissions')),
], ],
options={ options={
'verbose_name': 'role permissions', 'verbose_name': 'role permissions',
'verbose_name_plural': 'role permissions', 'verbose_name_plural': 'role permissions',
}, },
), ),
migrations.AddField(
model_name='permission',
name='mask',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='permissions', to='permission.PermissionMask', verbose_name='mask'),
),
migrations.AddField(
model_name='permission',
name='model',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.ContentType', verbose_name='model'),
),
migrations.AlterUniqueTogether(
name='permission',
unique_together={('model', 'query', 'type', 'field')},
),
] ]

View file

@ -36,11 +36,8 @@ class RightsTable(tables.Table):
def render_roles(self, record): def render_roles(self, record):
# If the user has the right to manage the roles, display the link to manage them # If the user has the right to manage the roles, display the link to manage them
roles = record.roles.filter((~(Q(name="Adhérent BDE") roles = record.roles.filter((~(Q(name="Adhérent"))
| Q(name="Adhérent Kfet") )).all()
| Q(name="Membre de club")
| Q(name="Bureau de club"))
& Q(weirole__isnull=True))).all()
s = ", ".join(str(role) for role in roles) s = ", ".join(str(role) for role in roles)
if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record): if PermissionBackend.check_perm(get_current_request(), "member.change_membership_roles", record):
s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk})) s = format_html("<a href='" + str(reverse_lazy("member:club_manage_roles", kwargs={"pk": record.pk}))

View file

@ -6,39 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% block content %} {% block content %}
{% if user.is_authenticated %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "Users that have surnormal rights" %}
</h3>
<div class="card-body">
<div class="alert alert-info">
<i class="fa fa-info-circle"></i> {% trans "Superusers have all rights on everything, to manage the website." %}
</div>
<div class="card">
<div class="card-head">
<h4 class="card-header text-center">
<a href="#" data-toggle="collapse" data-target="#card-superusers">{% trans "Superusers" %}</a>
</h4>
</div>
<div class="card-body collapse show" id="card-superusers">
{% render_table superusers %}
</div>
</div>
<hr>
<div class="card">
<div class="card-head">
<h4 class="card-header text-center">
<a href="#" data-toggle="collapse" data-target="#card-clubs">{% trans "Club managers" %}</a>
</h4>
</div>
<div class="card-body collapse show" id="card-clubs">
{% render_table special_memberships_table %}
</div>
</div>
</div>
</div>
{% endif %}
<div class="card bg-light"> <div class="card bg-light">
<h3 class="card-header text-center"> <h3 class="card-header text-center">
@ -63,7 +31,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
data-target="#collapse{{ role|slugify }}" data-target="#collapse{{ role|slugify }}"
aria-expanded="true" aria-controls="collapse{{ role|slugify }}"> aria-expanded="true" aria-controls="collapse{{ role|slugify }}">
{{ role }} {{ role }}
{% if role.weirole %}(<em>Pour le WEI</em>){% endif %}
{% if role.for_club %}(<em>Pour le club {{ role.for_club }} uniquement</em>){% endif %} {% if role.for_club %}(<em>Pour le club {{ role.for_club }} uniquement</em>){% endif %}
{% if role.clubs %} {% if role.clubs %}
<small><span class="badge badge-success">{% trans "Owned" %} : <small><span class="badge badge-success">{% trans "Owned" %} :
@ -98,6 +65,25 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% if user.is_authenticated %}
<div class="card bg-light mb-3">
<h3 class="card-header text-center">
{% trans "Users that have surnormal rights" %}
</h3>
<div class="card-body">
<div class="card">
<div class="card-head">
<h4 class="card-header text-center">
<a href="#" data-toggle="collapse" data-target="#card-clubs">{% trans "Club managers" %}</a>
</h4>
</div>
<div class="card-body collapse show" id="card-clubs">
{% render_table special_memberships_table %}
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}

View file

@ -58,7 +58,7 @@ class OAuth2TestCase(TestCase):
# Create membership to validate permissions # Create membership to validate permissions
NoteUser.objects.create(user=self.user) NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk) membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent"))
membership.save() membership.save()
# User is now a member and can now see its own user detail # User is now a member and can now see its own user detail
@ -85,7 +85,7 @@ class OAuth2TestCase(TestCase):
bde = Club.objects.get(name="BDE") bde = Club.objects.get(name="BDE")
NoteUser.objects.create(user=self.user) NoteUser.objects.create(user=self.user)
membership = Membership.objects.create(user=self.user, club_id=bde.pk) membership = Membership.objects.create(user=self.user, club_id=bde.pk)
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent"))
membership.save() membership.save()
resp = self.client.get(reverse('permission:scopes')) resp = self.client.get(reverse('permission:scopes'))

View file

@ -11,7 +11,6 @@ from django.utils.crypto import get_random_string
from activity.models import Activity from activity.models import Activity
from member.models import Club, Membership from member.models import Club, Membership
from note.models import NoteUser from note.models import NoteUser
from wei.models import WEIClub, Bus, WEIRegistration
class TestPermissionDenied(TestCase): class TestPermissionDenied(TestCase):
@ -41,7 +40,7 @@ class TestPermissionDenied(TestCase):
name="", name="",
description="", description="",
creater=self.user, creater=self.user,
activity_type_id=1, activity_type_id=4,
organizer_id=1, organizer_id=1,
attendees_club_id=1, attendees_club_id=1,
date_start=timezone.now(), date_start=timezone.now(),
@ -55,7 +54,7 @@ class TestPermissionDenied(TestCase):
name="", name="",
description="", description="",
creater=self.user, creater=self.user,
activity_type_id=1, activity_type_id=4,
organizer_id=1, organizer_id=1,
attendees_club_id=1, attendees_club_id=1,
date_start=timezone.now(), date_start=timezone.now(),
@ -79,56 +78,6 @@ class TestPermissionDenied(TestCase):
response = self.client.get(reverse("member:club_renew_membership", kwargs=dict(pk=membership.pk))) response = self.client.get(reverse("member:club_renew_membership", kwargs=dict(pk=membership.pk)))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
def test_create_weiclub(self):
response = self.client.get(reverse("wei:wei_create"))
self.assertEqual(response.status_code, 403)
def test_create_wei_bus(self):
wei = WEIClub.objects.create(
membership_start=date.today(),
date_start=date.today() + timedelta(days=1),
date_end=date.today() + timedelta(days=1),
)
response = self.client.get(reverse("wei:add_bus", kwargs=dict(pk=wei.pk)))
self.assertEqual(response.status_code, 403)
def test_create_wei_team(self):
wei = WEIClub.objects.create(
membership_start=date.today(),
date_start=date.today() + timedelta(days=1),
date_end=date.today() + timedelta(days=1),
)
bus = Bus.objects.create(wei=wei)
response = self.client.get(reverse("wei:add_team", kwargs=dict(pk=bus.pk)))
self.assertEqual(response.status_code, 403)
def test_create_1a_weiregistration(self):
wei = WEIClub.objects.create(
membership_start=date.today(),
date_start=date.today() + timedelta(days=1),
date_end=date.today() + timedelta(days=1),
)
response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=wei.pk)))
self.assertEqual(response.status_code, 403)
def test_create_old_weiregistration(self):
wei = WEIClub.objects.create(
membership_start=date.today(),
date_start=date.today() + timedelta(days=1),
date_end=date.today() + timedelta(days=1),
)
response = self.client.get(reverse("wei:wei_register_2A", kwargs=dict(wei_pk=wei.pk)))
self.assertEqual(response.status_code, 403)
def test_validate_weiregistration(self):
wei = WEIClub.objects.create(
membership_start=date.today(),
date_start=date.today() + timedelta(days=1),
date_end=date.today() + timedelta(days=1),
)
registration = WEIRegistration.objects.create(wei=wei, user=self.user, birth_date="2000-01-01")
response = self.client.get(reverse("wei:validate_registration", kwargs=dict(pk=registration.pk)))
self.assertEqual(response.status_code, 403)
def test_create_invoice(self): def test_create_invoice(self):
response = self.client.get(reverse("treasury:invoice_create")) response = self.client.get(reverse("treasury:invoice_create"))

View file

@ -9,9 +9,9 @@ from django.core.exceptions import FieldError
from django.db.models import F, Q from django.db.models import F, Q
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from member.models import Club, Membership from member.models import Club, Membership, Role
from note.models import NoteUser, Note, NoteClub, NoteSpecial from note.models import NoteUser, Note, NoteClub, NoteSpecial
from wei.models import WEIMembership, WEIRegistration, WEIClub, Bus, BusTeam
from ..models import Permission from ..models import Permission
@ -23,44 +23,22 @@ class PermissionQueryTestCase(TestCase):
def setUpTestData(cls): def setUpTestData(cls):
user = User.objects.create(username="user") user = User.objects.create(username="user")
NoteUser.objects.create(user=user) NoteUser.objects.create(user=user)
wei = WEIClub.objects.create( membership =Membership.objects.create(
name="wei",
date_start=date.today(),
date_end=date.today(),
)
NoteClub.objects.create(club=wei)
weiregistration = WEIRegistration.objects.create(
user=user, user=user,
wei=wei, club=Club.objects.get(name="BDE")
birth_date=date.today(),
)
bus = Bus.objects.create(
name="bus",
wei=wei,
)
team = BusTeam.objects.create(
name="team",
bus=bus,
color=0xFFFFFF,
)
WEIMembership.objects.create(
user=user,
club=wei,
registration=weiregistration,
bus=bus,
team=team,
) )
membership.roles.add(Role.objects.get(name="Adhérent"))
membership.save()
def test_permission_queries(self): def test_permission_queries(self):
""" """
Check for all permissions that the query is compilable and that the database can parse the query. Check for all permissions that the query is compilable and that the database can parse the query.
We use a random user with a random WEIClub (to use permissions for the WEI) in a random team in a random bus. We use a random user.
""" """
for perm in Permission.objects.all(): for perm in Permission.objects.all():
try: try:
instanced = perm.about( instanced = perm.about(
user=User.objects.get(), user=User.objects.get(),
club=WEIClub.objects.get(),
membership=Membership.objects.get(), membership=Membership.objects.get(),
User=User, User=User,
Club=Club, Club=Club,

View file

@ -131,11 +131,8 @@ class RightsView(TemplateView):
special_memberships = Membership.objects.filter( special_memberships = Membership.objects.filter(
date_start__lte=date.today(), date_start__lte=date.today(),
date_end__gte=date.today(), date_end__gte=date.today(),
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE") ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent"))
| Q(name="Adhérent Kfet") )))\
| Q(name="Membre de club")
| Q(name="Bureau de club"))
& Q(weirole__isnull=True))))\
.order_by("club__name", "user__last_name")\ .order_by("club__name", "user__last_name")\
.distinct().all() .distinct().all()
context["special_memberships_table"] = RightsTable(special_memberships, prefix="clubs-") context["special_memberships_table"] = RightsTable(special_memberships, prefix="clubs-")

View file

@ -10,6 +10,7 @@ from note_kfet.inputs import AmountInput
class SignUpForm(UserCreationForm): class SignUpForm(UserCreationForm):
usable_password = None
""" """
Pre-register users with all information Pre-register users with all information
""" """
@ -45,15 +46,6 @@ class SignUpForm(UserCreationForm):
class WEISignupForm(forms.Form):
wei_registration = forms.BooleanField(
label=_("Register to the WEI"),
required=False,
help_text=_("Check this case if you want to register to the WEI. If you hesitate, you will be able to register"
" later, after validating your account in the Kfet."),
)
class ValidationForm(forms.Form): class ValidationForm(forms.Form):
""" """
Validate the inscription of the new users and pay memberships. Validate the inscription of the new users and pay memberships.
@ -83,20 +75,27 @@ class ValidationForm(forms.Form):
required=False, required=False,
) )
bank = forms.CharField(
label=_("Bank"),
required=False,
)
join_bde = forms.BooleanField( join_bde = forms.BooleanField(
label=_("Join BDE Club"), label=_("Join BDE Club"),
required=False, required=False,
initial=True, initial=False,
) )
# The user can join the Kfet club at the inscription join_bda = forms.BooleanField(
join_kfet = forms.BooleanField( label=_("Join BDA Club"),
label=_("Join Kfet Club"),
required=False, required=False,
initial=True, initial=False,
) )
join_bds = forms.BooleanField(
label=_("Join BDS Club"),
required=False,
initial=False,
)
join_sinfonie = forms.BooleanField(
label=_("Join Sinfonie Club"),
required=False,
initial=False,
)

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</p> </p>
{% else %} {% else %}
<p> <p>
{% trans "You must pay now your membership in the Kfet to complete your registration." %} {% trans "You must pay now your membership to complete your registration." %}
</p> </p>
{% endif %} {% endif %}
{% else %} {% else %}

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans "An email has been sent. Please click on the link to activate your account." %} {% trans "An email has been sent. Please click on the link to activate your account." %}
</p> </p>
<p> <p>
{% trans "You must also go to the Kfet to pay your membership." %} {% trans "You must also pay your membership. A administrator will then activate your account" %}
</p> </p>
</div> </div>
</div> </div>

View file

@ -41,7 +41,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<dd class="col-xl-6">{{ object.profile.phone_number }}</dd> <dd class="col-xl-6">{{ object.profile.phone_number }}</dd>
<dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
<dd class="col-xl-6">{{ object.profile.paid|yesno }}</dd> <dd class="col-xl-6">{{ object.profile.paid|yesno:_("yes,no,maybe") }}</dd>
</dl> </dl>
</div> </div>
<div class="card-footer text-center"> <div class="card-footer text-center">
@ -82,10 +82,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
credit_amount.attr('disabled', true); credit_amount.attr('disabled', true);
credit_amount.val('{{ total_fee }}'); credit_amount.val('{{ total_fee }}');
let bank = $("#id_bank");
bank.attr('disabled', true);
bank.val('Société générale');
let join_bde = $("#id_join_bde"); let join_bde = $("#id_join_bde");
join_bde.attr('disabled', true); join_bde.attr('disabled', true);

View file

@ -4,7 +4,7 @@
<html lang="fr"> <html lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Passage en négatif (compte n°{{ note.user.pk }})</title> <title>{% trans "Activation of your Note Ker Lann account" %}</title>
</head> </head>
<body> <body>
@ -13,12 +13,12 @@
</p> </p>
<p> <p>
{% trans "You recently registered on the Note Kfet. Please click on the link below to confirm your registration." %} {% trans "You recently registered on the Note Ker Lann. Please click on the link below to confirm your registration." %}
</p> </p>
<p> <p>
<a href="https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}"> <a href="http://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}">
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %} http://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
</a> </a>
</p> </p>
@ -27,7 +27,7 @@
</p> </p>
<p> <p>
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet." %} {% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership." %}
</p> </p>
<p> <p>
@ -36,6 +36,6 @@
-- --
<p> <p>
{% trans "The Note Kfet team." %}<br> {% trans "The Note Ker Lann team." %}<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
</p> </p>

View file

@ -2,15 +2,15 @@
{% trans "Hi" %} {{ user.username }}, {% trans "Hi" %} {{ user.username }},
{% trans "You recently registered on the Note Kfet. Please click on the link below to confirm your registration." %} {% trans "You recently registered on the Note Ker Lann. Please click on the link below to confirm your registration." %}
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %} https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %} {% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet." %} {% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership." %}
{% trans "Thanks" %}, {% trans "Thanks" %},
{% trans "The Note Kfet team." %} {% trans "The Note Ker Lann team." %}
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} {% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}

View file

@ -44,10 +44,11 @@ class TestSignup(TestCase):
promotion=Club.objects.get(name="BDE").membership_start.year, promotion=Club.objects.get(name="BDE").membership_start.year,
address="Earth", address="Earth",
paid=False, paid=False,
ml_events_registration="en", ml_events_registration="fr",
ml_sport_registration=True, ml_sport_registration=True,
ml_art_registration=True, ml_art_registration=True,
)) ))
# Fail I don't know why ?
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200) self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
self.assertTrue(User.objects.filter(username="toto").exists()) self.assertTrue(User.objects.filter(username="toto").exists())
user = User.objects.get(username="toto") user = User.objects.get(username="toto")
@ -187,41 +188,15 @@ class TestValidateRegistration(TestCase):
Send wrong data and check that errors are detected Send wrong data and check that errors are detected
""" """
# BDE Membership is mandatory
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
credit_amount=4200,
last_name="TOTO",
first_name="Toto",
bank="Société générale",
join_bde=False,
join_kfet=False,
))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors)
# Same
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
credit_type="",
credit_amount=0,
last_name="TOTO",
first_name="Toto",
bank="Société générale",
join_bde=False,
join_kfet=True,
))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors)
# The BDE membership is not free # The BDE membership is not free
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
credit_amount=0, credit_amount=0,
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="J'ai pas d'argent",
join_bde=True, join_bde=True,
join_kfet=True, join_bda=False,
join_bds=False
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors) self.assertTrue(response.context["form"].errors)
@ -232,9 +207,9 @@ class TestValidateRegistration(TestCase):
credit_amount=4000, credit_amount=4000,
last_name="", last_name="",
first_name="", first_name="",
bank="",
join_bde=True, join_bde=True,
join_kfet=True, join_bda=False,
join_bds=False
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors) self.assertTrue(response.context["form"].errors)
@ -248,9 +223,9 @@ class TestValidateRegistration(TestCase):
credit_amount=500, credit_amount=500,
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="Société générale",
join_bde=True, join_bde=True,
join_kfet=False, join_bda=False,
join_bds=False
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors) self.assertTrue(response.context["form"].errors)
@ -273,25 +248,26 @@ class TestValidateRegistration(TestCase):
credit_amount=500, credit_amount=500,
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="Société générale",
join_bde=True, join_bde=True,
join_kfet=False, join_bda=False,
join_bds=True
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.user.profile.refresh_from_db() self.user.profile.refresh_from_db()
self.assertTrue(self.user.profile.registration_valid) self.assertTrue(self.user.profile.registration_valid)
self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) self.assertTrue(NoteUser.objects.filter(user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists())
self.assertFalse(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) self.assertFalse(Membership.objects.filter(club__name="BDA", user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="BDS", user=self.user).exists())
self.assertEqual(Transaction.objects.filter( self.assertEqual(Transaction.objects.filter(
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2) Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3)
response = self.client.get(self.user.profile.get_absolute_url()) response = self.client.get(self.user.profile.get_absolute_url())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_validate_kfet_registration(self): def test_validate_kfet_registration(self):
""" """
The user joins the BDE and the Kfet. The user joins the BDE,BDA and BDS.
""" """
response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,))) response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -307,18 +283,19 @@ class TestValidateRegistration(TestCase):
credit_amount=4000, credit_amount=4000,
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="Société générale",
join_bde=True, join_bde=True,
join_kfet=True, join_bda=True,
join_bds=True
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.user.profile.refresh_from_db() self.user.profile.refresh_from_db()
self.assertTrue(self.user.profile.registration_valid) self.assertTrue(self.user.profile.registration_valid)
self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) self.assertTrue(NoteUser.objects.filter(user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) self.assertTrue(Membership.objects.filter(club__name="BDA", user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="BDS", user=self.user).exists())
self.assertEqual(Transaction.objects.filter( self.assertEqual(Transaction.objects.filter(
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) Q(source=self.user.note) | Q(destination=self.user.note)).count(), 4)
response = self.client.get(self.user.profile.get_absolute_url()) response = self.client.get(self.user.profile.get_absolute_url())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View file

@ -41,7 +41,6 @@ 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)
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"]
@ -225,9 +224,10 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
user = self.get_object() user = self.get_object()
fee = 0 fee = 0
bde = Club.objects.get(name="BDE") bde = Club.objects.get(name="BDE")
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid bda = Club.objects.get(name="BDA")
kfet = Club.objects.get(name="Kfet") bds = Club.objects.get(name="BDS")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid for auto_club in [bde, bda, bds]:
fee += auto_club.membership_fee_paid if user.profile.paid else auto_club.membership_fee_unpaid
ctx["total_fee"] = "{:.02f}".format(fee / 100, ) ctx["total_fee"] = "{:.02f}".format(fee / 100, )
return ctx return ctx
@ -256,30 +256,20 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
credit_amount = form.cleaned_data["credit_amount"] credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"] last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"] first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
join_bde = form.cleaned_data["join_bde"] join_bde = form.cleaned_data["join_bde"]
join_kfet = form.cleaned_data["join_kfet"] join_bda = form.cleaned_data["join_bda"]
join_bds = form.cleaned_data["join_bds"]
if not join_bde:
# This software belongs to the BDE.
form.add_error('join_bde', _("You must join the BDE."))
return super().form_invalid(form)
# Calculate required registration fee # Calculate required registration fee
fee = 0 fee = 0
bde = Club.objects.get(name="BDE") bde = Club.objects.get(name="BDE")
bde_fee = bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid bda = Club.objects.get(name="BDA")
# This is mandatory. bds = Club.objects.get(name="BDS")
fee += bde_fee if join_bde else 0 for auto_club, auto_join in zip([bde, bda, bds], [join_bde, join_bda, join_bds]):
kfet = Club.objects.get(name="Kfet") bd_fee = auto_club.membership_fee_paid if user.profile.paid else auto_club.membership_fee_unpaid
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid fee += bd_fee if auto_join else 0
# Add extra fee for the full membership
fee += kfet_fee if join_kfet else 0
# If the bank pays, then we don't credit now. Treasurers will validate the transaction
# and credit the note later.
credit_type = credit_type
# If the user does not select any payment method, then no credit will be performed. # If the user does not select any payment method, then no credit will be performed.
credit_amount = 0 if credit_type is None else credit_amount credit_amount = 0 if credit_type is None else credit_amount
@ -314,35 +304,22 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
reason="Crédit " + credit_type.special_type + " (Inscription)", reason="Crédit " + credit_type.special_type + " (Inscription)",
last_name=last_name, last_name=last_name,
first_name=first_name, first_name=first_name,
bank=bank,
valid=True, valid=True,
) )
for auto_club, auto_join in zip([bde, bda, bds], [join_bde, join_bda, join_bds]):
bd_fee = auto_club.membership_fee_paid if user.profile.paid else auto_club.membership_fee_unpaid
if join_bde: if auto_join:
# Create membership for the user to the BDE starting today # Create membership for the user to the BDEAS starting today
membership = Membership( membership = Membership(
club=bde, club=auto_club,
user=user, user=user,
fee=bde_fee, fee=bd_fee,
) )
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent"))
membership.save() membership.save()
if join_kfet:
# Create membership for the user to the Kfet starting today
membership = Membership(
club=kfet,
user=user,
fee=kfet_fee,
)
membership.save()
membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
membership.save()
return ret return ret

View file

@ -29,7 +29,7 @@ class InvoiceForm(forms.ModelForm):
class Meta: class Meta:
model = Invoice model = Invoice
exclude = ('bde', 'date', 'tex', ) exclude = ('date', 'tex', )
class ProductForm(forms.ModelForm): class ProductForm(forms.ModelForm):
@ -132,8 +132,6 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
first_name = forms.Field(label=_("First name")) first_name = forms.Field(label=_("First name"))
bank = forms.Field(label=_("Bank"))
amount = forms.IntegerField(label=_("Amount"), min_value=0, widget=AmountInput(), disabled=True, required=False) amount = forms.IntegerField(label=_("Amount"), min_value=0, widget=AmountInput(), disabled=True, required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -148,7 +146,6 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
cleaned_data = super().clean() cleaned_data = super().clean()
self.instance.transaction.last_name = cleaned_data["last_name"] self.instance.transaction.last_name = cleaned_data["last_name"]
self.instance.transaction.first_name = cleaned_data["first_name"] self.instance.transaction.first_name = cleaned_data["first_name"]
self.instance.transaction.bank = cleaned_data["bank"]
return cleaned_data return cleaned_data
@transaction.atomic @transaction.atomic

View file

@ -1,10 +1,10 @@
# Generated by Django 2.2.16 on 2020-09-04 21:41 # Generated by Django 5.1 on 2024-08-13 09:26
import datetime import datetime
from django.conf import settings import django.core.validators
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -12,7 +12,6 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('note', '0001_initial'), ('note', '0001_initial'),
] ]
@ -21,7 +20,7 @@ class Migration(migrations.Migration):
name='Invoice', name='Invoice',
fields=[ fields=[
('id', models.PositiveIntegerField(primary_key=True, serialize=False, verbose_name='Invoice identifier')), ('id', models.PositiveIntegerField(primary_key=True, serialize=False, verbose_name='Invoice identifier')),
('bde', models.CharField(choices=[('Saperlistpopette.png', 'Saper[list]popette'), ('Finalist.png', 'Fina[list]'), ('Listorique.png', '[List]orique'), ('Satellist.png', 'Satel[list]'), ('Monopolist.png', 'Monopo[list]'), ('Kataclist.png', 'Katac[list]')], default='Saperlistpopette.png', max_length=32, verbose_name='BDE')), ('bde', models.CharField(choices=[('BDE', 'BDE'), ('BDA', 'BDA'), ('BDS', 'BDS')], default='BDE', max_length=32, verbose_name='BD?')),
('object', models.CharField(max_length=255, verbose_name='Object')), ('object', models.CharField(max_length=255, verbose_name='Object')),
('description', models.TextField(verbose_name='Description')), ('description', models.TextField(verbose_name='Description')),
('name', models.CharField(max_length=255, verbose_name='Name')), ('name', models.CharField(max_length=255, verbose_name='Name')),
@ -36,6 +35,31 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'invoices', 'verbose_name_plural': 'invoices',
}, },
), ),
migrations.CreateModel(
name='Product',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('designation', models.CharField(max_length=255, verbose_name='Designation')),
('quantity', models.DecimalField(decimal_places=2, max_digits=7, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity')),
('amount', models.IntegerField(verbose_name='Unit price')),
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='treasury.invoice', verbose_name='invoice')),
],
options={
'verbose_name': 'product',
'verbose_name_plural': 'products',
},
),
migrations.CreateModel(
name='RemittanceType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('note', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.notespecial')),
],
options={
'verbose_name': 'remittance type',
'verbose_name_plural': 'remittance types',
},
),
migrations.CreateModel( migrations.CreateModel(
name='Remittance', name='Remittance',
fields=[ fields=[
@ -43,6 +67,7 @@ class Migration(migrations.Migration):
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date')), ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date')),
('comment', models.CharField(max_length=255, verbose_name='Comment')), ('comment', models.CharField(max_length=255, verbose_name='Comment')),
('closed', models.BooleanField(default=False, verbose_name='Closed')), ('closed', models.BooleanField(default=False, verbose_name='Closed')),
('remittance_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='treasury.remittancetype', verbose_name='Type')),
], ],
options={ options={
'verbose_name': 'remittance', 'verbose_name': 'remittance',
@ -53,55 +78,12 @@ class Migration(migrations.Migration):
name='SpecialTransactionProxy', name='SpecialTransactionProxy',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('remittance', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='treasury.Remittance', verbose_name='Remittance')), ('remittance', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transaction_proxies', to='treasury.remittance', verbose_name='Remittance')),
('transaction', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.SpecialTransaction')), ('transaction', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.specialtransaction')),
], ],
options={ options={
'verbose_name': 'special transaction proxy', 'verbose_name': 'special transaction proxy',
'verbose_name_plural': 'special transaction proxies', 'verbose_name_plural': 'special transaction proxies',
}, },
), ),
migrations.CreateModel(
name='SogeCredit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('credit_transaction', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='note.SpecialTransaction', verbose_name='credit transaction')),
('transactions', models.ManyToManyField(related_name='_sogecredit_transactions_+', to='note.MembershipTransaction', verbose_name='membership transactions')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': 'Credit from the Société générale',
'verbose_name_plural': 'Credits from the Société générale',
},
),
migrations.CreateModel(
name='RemittanceType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('note', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.NoteSpecial')),
],
options={
'verbose_name': 'remittance type',
'verbose_name_plural': 'remittance types',
},
),
migrations.AddField(
model_name='remittance',
name='remittance_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='treasury.RemittanceType', verbose_name='Type'),
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('designation', models.CharField(max_length=255, verbose_name='Designation')),
('quantity', models.PositiveIntegerField(verbose_name='Quantity')),
('amount', models.IntegerField(verbose_name='Unit price')),
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='treasury.Invoice', verbose_name='invoice')),
],
options={
'verbose_name': 'product',
'verbose_name_plural': 'products',
},
),
] ]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.2.16 on 2020-09-06 13:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='invoice',
name='bde',
field=models.CharField(choices=[('Saperlistpopette', 'Saper[list]popette'), ('Finalist', 'Fina[list]'), ('Listorique', '[List]orique'), ('Satellist', 'Satel[list]'), ('Monopolist', 'Monopo[list]'), ('Kataclist', 'Katac[list]')], default='Saperlistpopette', max_length=32, verbose_name='BDE'),
),
]

View file

@ -1,25 +0,0 @@
# Generated by Django 2.2.19 on 2021-03-21 09:34
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('treasury', '0002_invoice_remove_png_extension'),
]
operations = [
migrations.AlterField(
model_name='product',
name='quantity',
field=models.DecimalField(decimal_places=2, max_digits=7, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Quantity'),
),
migrations.AlterField(
model_name='specialtransactionproxy',
name='remittance',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transaction_proxies', to='treasury.Remittance', verbose_name='Remittance'),
),
]

View file

@ -1,18 +0,0 @@
# Generated by Django 2.2.24 on 2021-10-05 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0003_auto_20210321_1034'),
]
operations = [
migrations.AlterField(
model_name='sogecredit',
name='transactions',
field=models.ManyToManyField(blank=True, related_name='_sogecredit_transactions_+', to='note.MembershipTransaction', verbose_name='membership transactions'),
),
]

View file

@ -1,16 +0,0 @@
# Generated by Django 2.2.28 on 2022-07-31 11:45
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('treasury', '0004_auto_20211005_1544'),
]
operations = [
migrations.DeleteModel(
name='SogeCredit',
),
]

View file

@ -28,16 +28,13 @@ class Invoice(models.Model):
bde = models.CharField( bde = models.CharField(
max_length=32, max_length=32,
default='Saperlistpopette', default='BDE',
choices=( choices=(
('Saperlistpopette', 'Saper[list]popette'), ('BDE', 'BDE'),
('Finalist', 'Fina[list]'), ('BDA', 'BDA'),
('Listorique', '[List]orique'), ('BDS', 'BDS'),
('Satellist', 'Satel[list]'),
('Monopolist', 'Monopo[list]'),
('Kataclist', 'Katac[list]'),
), ),
verbose_name=_("BDE"), verbose_name=_("BD?"),
) )
object = models.CharField( object = models.CharField(
@ -94,10 +91,10 @@ class Invoice(models.Model):
products = self.products.all() products = self.products.all()
self.place = "Gif-sur-Yvette" self.place = "Bruz"
self.my_name = "BDE ENS Cachan" self.my_name = f"{self.bde} ENS Rennes"
self.my_address_street = "4 avenue des Sciences" self.my_address_street = "14 avenue Robert Schumann"
self.my_city = "91190 Gif-sur-Yvette" self.my_city = "35170 Bruz"
self.bank_code = 30003 self.bank_code = 30003
self.desk_code = 3894 self.desk_code = 3894
self.account_number = 37280662 self.account_number = 37280662

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View file

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before After
Before After

View file

@ -118,5 +118,5 @@ class SpecialTransactionTable(tables.Table):
} }
model = SpecialTransaction model = SpecialTransaction
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
fields = ('created_at', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',) fields = ('created_at', 'source', 'destination', 'last_name', 'first_name', 'amount', 'reason',)
order_by = ('-created_at',) order_by = ('-created_at',)

View file

@ -47,18 +47,6 @@
{% endfor %} {% endfor %}
} }
% Logo du BDE
\AddToShipoutPicture*{
\put(0,0){
\parbox[b][\paperheight]{\paperwidth}{%
\vfill
\centering
\includegraphics[width=\textwidth]{../../apps/treasury/static/img/{{ obj.bde }}_bg.jpg}
\vfill
}
}
}
%%%%%%%%%%%%%%%%%%%%% A MODIFIER DANS LA FACTURE %%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%% A MODIFIER DANS LA FACTURE %%%%%%%%%%%%%%%%%%%%%
% Infos Association % Infos Association
@ -105,8 +93,8 @@
\renewcommand{\headrulewidth}{0pt} \renewcommand{\headrulewidth}{0pt}
\cfoot{ \cfoot{
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 89 88 56 50\newline \small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 00 00 00 00\newline
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011 Site web : TODO ~--~ E-mail : TODO \newline Numéro SIRET : 000 000 000 00000
} }
} }

View file

@ -187,7 +187,6 @@ class TestRemittances(TestCase):
reason="Credit", reason="Credit",
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="Société générale",
) )
self.second_credit = SpecialTransaction.objects.create( self.second_credit = SpecialTransaction.objects.create(
@ -197,7 +196,6 @@ class TestRemittances(TestCase):
reason="Second credit", reason="Second credit",
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto",
bank="Société générale",
) )
self.remittance = Remittance.objects.create( self.remittance = Remittance.objects.create(
@ -275,7 +273,6 @@ class TestRemittances(TestCase):
remittance=self.remittance.pk, remittance=self.remittance.pk,
last_name="Last Name", last_name="Last Name",
first_name="First Name", first_name="First Name",
bank="Bank",
)) ))
self.assertRedirects(response, reverse("treasury:remittance_list"), 302, 200) self.assertRedirects(response, reverse("treasury:remittance_list"), 302, 200)
self.credit.refresh_from_db() self.credit.refresh_from_db()
@ -320,8 +317,7 @@ class TestTreasuryAPI(TestAPI):
amount=4200, amount=4200,
reason="Credit", reason="Credit",
last_name="TOTO", last_name="TOTO",
first_name="Toto", first_name="Toto"
bank="Société générale",
) )
self.remittance = Remittance.objects.create( self.remittance = Remittance.objects.create(
@ -332,7 +328,7 @@ class TestTreasuryAPI(TestAPI):
self.credit.specialtransactionproxy.remittance = self.remittance self.credit.specialtransactionproxy.remittance = self.remittance
self.credit.specialtransactionproxy.save() self.credit.specialtransactionproxy.save()
self.kfet = Club.objects.get(name="Kfet") self.kfet = Club.objects.get(name="BDA")
self.bde = self.kfet.parent_club self.bde = self.kfet.parent_club
self.kfet_membership = Membership( self.kfet_membership = Membership(

View file

@ -174,7 +174,7 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
class InvoiceDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView): class InvoiceDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
""" """
Delete a non-validated WEI registration Delete a non-validated registration
""" """
model = Invoice model = Invoice
extra_context = {"title": _("Delete invoice")} extra_context = {"title": _("Delete invoice")}
@ -368,7 +368,6 @@ class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin,
form = context["form"] form = context["form"]
form.fields["last_name"].initial = self.object.transaction.last_name form.fields["last_name"].initial = self.object.transaction.last_name
form.fields["first_name"].initial = self.object.transaction.first_name form.fields["first_name"].initial = self.object.transaction.first_name
form.fields["bank"].initial = self.object.transaction.bank
form.fields["amount"].initial = self.object.transaction.amount form.fields["amount"].initial = self.object.transaction.amount
form.fields["remittance"].queryset = form.fields["remittance"] \ form.fields["remittance"].queryset = form.fields["remittance"] \
.queryset.filter(remittance_type__note=self.object.transaction.source) .queryset.filter(remittance_type__note=self.object.transaction.source)

View file

@ -1,4 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'wei.apps.WeiConfig'

View file

@ -1,13 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from note_kfet.admin import admin_site
from .models import WEIClub, WEIRegistration, WEIMembership, WEIRole, Bus, BusTeam
admin_site.register(WEIClub)
admin_site.register(WEIRegistration)
admin_site.register(WEIMembership)
admin_site.register(WEIRole)
admin_site.register(Bus)
admin_site.register(BusTeam)

View file

@ -1,72 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import serializers
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
class WEIClubSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Clubs.
The djangorestframework plugin will analyse the model `WEIClub` and parse all fields in the API.
"""
class Meta:
model = WEIClub
fields = '__all__'
class BusSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Bus.
The djangorestframework plugin will analyse the model `Bus` and parse all fields in the API.
"""
class Meta:
model = Bus
fields = '__all__'
class BusTeamSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Bus teams.
The djangorestframework plugin will analyse the model `BusTeam` and parse all fields in the API.
"""
class Meta:
model = BusTeam
fields = '__all__'
class WEIRoleSerializer(serializers.ModelSerializer):
"""
REST API Serializer for WEI roles.
The djangorestframework plugin will analyse the model `WEIRole` and parse all fields in the API.
"""
class Meta:
model = WEIRole
fields = '__all__'
class WEIRegistrationSerializer(serializers.ModelSerializer):
"""
REST API Serializer for WEI registrations.
The djangorestframework plugin will analyse the model `WEIRegistration` and parse all fields in the API.
"""
class Meta:
model = WEIRegistration
fields = '__all__'
class WEIMembershipSerializer(serializers.ModelSerializer):
"""
REST API Serializer for WEI memberships.
The djangorestframework plugin will analyse the model `WEIMembership` and parse all fields in the API.
"""
class Meta:
model = WEIMembership
fields = '__all__'

View file

@ -1,17 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from .views import WEIClubViewSet, BusViewSet, BusTeamViewSet, WEIRoleViewSet, WEIRegistrationViewSet, \
WEIMembershipViewSet
def register_wei_urls(router, path):
"""
Configure router for Member REST API.
"""
router.register(path + '/club', WEIClubViewSet)
router.register(path + '/bus', BusViewSet)
router.register(path + '/team', BusTeamViewSet)
router.register(path + '/role', WEIRoleViewSet)
router.register(path + '/registration', WEIRegistrationViewSet)
router.register(path + '/membership', WEIMembershipViewSet)

View file

@ -1,105 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter, SearchFilter
from api.viewsets import ReadProtectedModelViewSet
from .serializers import WEIClubSerializer, BusSerializer, BusTeamSerializer, WEIRoleSerializer, \
WEIRegistrationSerializer, WEIMembershipSerializer
from ..models import WEIClub, Bus, BusTeam, WEIRole, WEIRegistration, WEIMembership
class WEIClubViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer,
then render it on /api/wei/club/
"""
queryset = WEIClub.objects.order_by('id')
serializer_class = WEIClubSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'year', 'date_start', 'date_end', 'email', 'note__alias__name',
'note__alias__normalized_name', 'parent_club', 'parent_club__name', 'require_memberships',
'membership_fee_paid', 'membership_fee_unpaid', 'membership_duration', 'membership_start',
'membership_end', ]
search_fields = ['$name', '$email', '$note__alias__name', '$note__alias__normalized_name', ]
class BusViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `Bus` objects, serialize it to JSON with the given serializer,
then render it on /api/wei/bus/
"""
queryset = Bus.objects.order_by('id')
serializer_class = BusSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'wei', 'description', ]
search_fields = ['$name', '$wei__name', '$description', ]
class BusTeamViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
then render it on /api/wei/team/
"""
queryset = BusTeam.objects.order_by('id')
serializer_class = BusTeamSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'bus', 'color', 'description', 'bus__wei', ]
search_fields = ['$name', '$bus__name', '$bus__wei__name', '$description', ]
class WEIRoleViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `WEIRole` objects, serialize it to JSON with the given serializer,
then render it on /api/wei/role/
"""
queryset = WEIRole.objects.order_by('id')
serializer_class = WEIRoleSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['name', 'permissions', 'memberships', ]
search_fields = ['$name', ]
class WEIRegistrationViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all WEIRegistration objects, serialize it to JSON with the given serializer,
then render it on /api/wei/registration/
"""
queryset = WEIRegistration.objects.order_by('id')
serializer_class = WEIRegistrationSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email',
'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name',
'wei__email', 'wei__year', 'soge_credit', 'caution_check', 'birth_date', 'gender',
'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name',
'emergency_contact_phone', ]
search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email',
'$user__note__alias__name', '$user__note__alias__normalized_name', '$wei__name',
'$wei__email', '$health_issues', '$emergency_contact_name', '$emergency_contact_phone', ]
class WEIMembershipViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `BusTeam` objects, serialize it to JSON with the given serializer,
then render it on /api/wei/membership/
"""
queryset = WEIMembership.objects.order_by('id')
serializer_class = WEIMembershipSerializer
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
filterset_fields = ['club__name', 'club__email', 'club__note__alias__name',
'club__note__alias__normalized_name', 'user__username', 'user__last_name',
'user__first_name', 'user__email', 'user__note__alias__name',
'user__note__alias__normalized_name', 'date_start', 'date_end', 'fee', 'roles', 'bus',
'bus__name', 'team', 'team__name', 'registration', ]
ordering_fields = ['id', 'date_start', 'date_end', ]
search_fields = ['$club__name', '$club__email', '$club__note__alias__name',
'$club__note__alias__normalized_name', '$user__username', '$user__last_name',
'$user__first_name', '$user__email', '$user__note__alias__name',
'$user__note__alias__normalized_name', '$roles__name', '$bus__name', '$team__name', ]

View file

@ -1,10 +0,0 @@
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class WeiConfig(AppConfig):
name = 'wei'
verbose_name = _('WEI')

Some files were not shown because too many files have changed in this diff Show more