Compare commits
No commits in common. "main" and "dev-jb" have entirely different histories.
2
.gitignore
vendored
|
|
@ -54,3 +54,5 @@ ansible/host_vars/*.yaml
|
||||||
!ansible/host_vars/bde*
|
!ansible/host_vars/bde*
|
||||||
ansible/hosts
|
ansible/hosts
|
||||||
|
|
||||||
|
apps/member/migrations
|
||||||
|
apps/wei/migrations
|
||||||
|
|
|
||||||
44
.gitpod.yml
|
|
@ -3,39 +3,33 @@
|
||||||
# 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
|
||||||
command: |
|
init: sudo apt install --no-install-recommends -y \
|
||||||
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 : gp sync-await apt
|
init: pip3 install -r requirements.txt
|
||||||
command: |
|
|
||||||
pip3 install -r requirements.txt
|
|
||||||
gp sync-done pip
|
|
||||||
- name : Setup env
|
- name : Setup env
|
||||||
command: cp .env_example .env
|
init: cp .env_example .env
|
||||||
- name: Django Init
|
- name: Django collectstatic
|
||||||
init : gp sync-await pip
|
init: python3 manage.py collectstatic --noinput
|
||||||
command: |
|
- name: Django compilemessages
|
||||||
python3 manage.py collectstatic --noinput
|
init: python3 manage.py compilemessages
|
||||||
python3 manage.py compilemessages
|
- name: Django makemigrations
|
||||||
python3 manage.py makemigrations
|
init: python3 manage.py makemigrations
|
||||||
python3 manage.py migrate
|
- name: Django migrate
|
||||||
python3 manage.py loaddata initial
|
init: python3 manage.py migrate
|
||||||
python3 manage.py shell -c "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'adminpass')"
|
- name: Django loaddata
|
||||||
gp sync-done django
|
init: python3 manage.py loaddata initial
|
||||||
|
- 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:
|
||||||
- name: Web Dev Server
|
- port: 8000
|
||||||
port: 8000
|
onOpen: open-preview
|
||||||
visibility: public
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
54
README.md
|
|
@ -35,27 +35,11 @@ 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@github.com:jbdoderlein/notes-ker-lann.git --recursive && cd notes-ker-lann
|
$ git clone git@gitlab.crans.org:bde/nk20.git --recursive && cd nk20
|
||||||
```
|
```
|
||||||
|
|
||||||
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.**
|
||||||
|
|
@ -145,13 +129,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/notes-ker-lann`,
|
2. **Clonage du dépot** dans `/var/www/note_kfet`,
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ sudo mkdir -p /var/www/notes-ker-lann && cd /var/www/notes-ker-lann
|
$ sudo mkdir -p /var/www/note_kfet && cd /var/www/note_kfet
|
||||||
$ 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://github.com/jbdoderlein/notes-ker-lann.git --recursive
|
$ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.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.**
|
||||||
|
|
@ -168,19 +152,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/notes-ker-lann/nginx_note.conf /etc/nginx/sites-enabled/
|
$ sudo ln -sf /var/www/note_kfet/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/notes-ker-lann/uwsgi_note.ini /etc/uwsgi/sites/
|
$ sudo ln -sf /var/www/note_kfet/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/notes-ker-lann/uwsgi_note.ini /etc/uwsgi/apps-enabled/
|
$ sudo ln -sf /var/www/note_kfet/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`.
|
||||||
|
|
@ -235,7 +219,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="notes-ker-lann.settings
|
DJANGO_SETTINGS_MODULE="note_kfet.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
|
||||||
|
|
@ -263,21 +247,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://github.com/jbdoderlein/notes-ker-lann.git/ --recursive && cd notes-ker-lann
|
git clone https://gitlab.crans.org/bde/nk20/ --recursive && cd nk20
|
||||||
docker build . -t notes-ker-lann
|
docker build . -t nk20
|
||||||
```
|
```
|
||||||
|
|
||||||
Ensuite pour lancer la note de Ker Lann en tant que vous (option `-u`),
|
Ensuite pour lancer la note Kfet 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/notes-ker-lann/" -p 80:8080 nk20
|
docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/note_kfet/" -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/notes-ker-lann/" -p 80:8080 nk20 python3 ./manage.py createsuperuser
|
docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/note_kfet/" -p 80:8080 nk20 python3 ./manage.py createsuperuser
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Avec Docker Compose
|
#### Avec Docker Compose
|
||||||
|
|
@ -287,15 +271,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
|
||||||
notes-ker-lann:
|
nk20:
|
||||||
build: /chemin/vers/le/code/notes-ker-lann
|
build: /chemin/vers/le/code/nk20
|
||||||
volumes:
|
volumes:
|
||||||
- /chemin/vers/le/code/notes-ker-lann:/var/www/notes-ker-lann/
|
- /chemin/vers/le/code/nk20:/var/www/note_kfet/
|
||||||
env_file: /chemin/vers/le/code/notes-ker-lann/.env
|
env_file: /chemin/vers/le/code/nk20/.env
|
||||||
restart: always
|
restart: always
|
||||||
labels:
|
labels:
|
||||||
- "traefik.http.routers.notes-ker-lann.rule=Host(`ndd.example.com`)"
|
- "traefik.http.routers.nk20.rule=Host(`ndd.example.com`)"
|
||||||
- "traefik.http.services.notes-ker-lann.loadbalancer.server.port=8080"
|
- "traefik.http.services.nk20.loadbalancer.server.port=8080"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
|
||||||
|
|
@ -1 +1,32 @@
|
||||||
[{"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -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 BDE club is attended
|
# By default, the Kfet club is attended
|
||||||
self.fields["attendees_club"].initial = Club.objects.get(name="BDE")
|
self.fields["attendees_club"].initial = Club.objects.get(name="Kfet")
|
||||||
self.fields["attendees_club"].widget.attrs["placeholder"] = "BDE"
|
self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet"
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
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. BDE.', max_length=255, verbose_name='location')),
|
('location', models.CharField(blank=True, default='', help_text='Place where the activity is organized, eg. Kfet.', 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')),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
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,16 +11,58 @@ 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.', 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. Mostly the Kfet club.', 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',
|
||||||
|
|
@ -30,53 +72,7 @@ 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',
|
||||||
|
|
@ -86,4 +82,8 @@ 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')},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
@ -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. BDE."),
|
help_text=_("Place where the activity is organized, eg. Kfet."),
|
||||||
)
|
)
|
||||||
|
|
||||||
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."),
|
help_text=_("Club that is authorized to join the activity. Mostly the Kfet club."),
|
||||||
)
|
)
|
||||||
|
|
||||||
date_start = models.DateTimeField(
|
date_start = models.DateTimeField(
|
||||||
|
|
|
||||||
|
|
@ -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 member." %}",
|
"{% trans "Entry done, but caution: the user is not a Kfet 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 member." %}",
|
"{% trans "Entry done, but caution: the user is not a Kfet member." %}",
|
||||||
"warning", 10000);
|
"warning", 10000);
|
||||||
else
|
else
|
||||||
addMsg("{% trans "Entry done!" %}", "success", 4000);
|
addMsg("{% trans "Entry done!" %}", "success", 4000);
|
||||||
|
|
@ -150,7 +150,8 @@ 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();
|
||||||
|
|
|
||||||
|
|
@ -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:_("yes,no,maybe") }}</dd>
|
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</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:_("yes,no,maybe") }}</dd>
|
<dd class="col-xl-6">{{ activity.valid|yesno }}</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:_("yes,no,maybe") }}</dd>
|
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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="Soir\u00e9e"),
|
activity_type=ActivityType.objects.get(name="Pot"),
|
||||||
creater=self.user,
|
creater=self.user,
|
||||||
organizer=Club.objects.get(name="BDE"),
|
organizer=Club.objects.get(name="Kfet"),
|
||||||
attendees_club=Club.objects.get(name="BDE"),
|
attendees_club=Club.objects.get(name="Kfet"),
|
||||||
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\u00e9e").id,
|
activity_type=ActivityType.objects.get(name="Soirée de club").id,
|
||||||
creater=self.user.id,
|
creater=self.user.id,
|
||||||
organizer=Club.objects.get(name="BDE").id,
|
organizer=Club.objects.get(name="Kfet").id,
|
||||||
attendees_club=Club.objects.get(name="BDE").id,
|
attendees_club=Club.objects.get(name="Kfet").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="Soir\u00e9e").id,
|
activity_type=ActivityType.objects.get(name="Autre").id,
|
||||||
creater=self.user.id,
|
creater=self.user.id,
|
||||||
organizer=Club.objects.get(name="BDE").id,
|
organizer=Club.objects.get(name="Kfet").id,
|
||||||
attendees_club=Club.objects.get(name="BDE").id,
|
attendees_club=Club.objects.get(name="Kfet").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="Activit\u00e9 gratuite ouverte"),
|
activity_type=ActivityType.objects.get(name="Pot"),
|
||||||
creater=self.user,
|
creater=self.user,
|
||||||
organizer=Club.objects.get(name="BDE"),
|
organizer=Club.objects.get(name="Kfet"),
|
||||||
attendees_club=Club.objects.get(name="BDE"),
|
attendees_club=Club.objects.get(name="Kfet"),
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -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 "BDA"}
|
LOCATION:{self.multilines(activity.location, 75, 9) if activity.location else "Kfet"}
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
# 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 include
|
from django.conf.urls import url, 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
|
||||||
|
|
@ -39,6 +38,10 @@ 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.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
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
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
]
|
]
|
||||||
|
|
||||||
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(blank=True, default='', verbose_name='previous data')),
|
('previous', models.TextField(null=True, verbose_name='previous data')),
|
||||||
('data', models.TextField(blank=True, default='', verbose_name='new data')),
|
('data', models.TextField(null=True, 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={
|
||||||
|
|
|
||||||
17
apps/logs/migrations/0002_replace_null_by_blank.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
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;"
|
||||||
|
),
|
||||||
|
]
|
||||||
23
apps/logs/migrations/0003_remove_null_tag_on_charfields.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -19,12 +19,20 @@ 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
|
||||||
|
|
@ -60,7 +68,7 @@ class ProfileForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Profile
|
model = Profile
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
exclude = ('user', 'email_confirmed', 'registration_valid', 'section' )
|
exclude = ('user', 'email_confirmed', 'registration_valid', )
|
||||||
|
|
||||||
|
|
||||||
class ImageForm(forms.Form):
|
class ImageForm(forms.Form):
|
||||||
|
|
@ -106,7 +114,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.Resampling.LANCZOS,
|
Image.ANTIALIAS,
|
||||||
)
|
)
|
||||||
frames.append(frame)
|
frames.append(frame)
|
||||||
|
|
||||||
|
|
@ -178,6 +186,11 @@ 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')
|
||||||
|
|
@ -214,7 +227,7 @@ class MembershipRolesForm(forms.ModelForm):
|
||||||
)
|
)
|
||||||
|
|
||||||
roles = forms.ModelMultipleChoiceField(
|
roles = forms.ModelMultipleChoiceField(
|
||||||
queryset=Role.objects.all(),
|
queryset=Role.objects.filter(weirole=None).all(),
|
||||||
label=_("Roles"),
|
label=_("Roles"),
|
||||||
widget=CheckboxSelectMultiple(),
|
widget=CheckboxSelectMultiple(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
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):
|
||||||
|
|
@ -12,29 +13,10 @@ 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 l’Environnement'), ('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=[
|
||||||
|
|
@ -47,13 +29,36 @@ 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=[
|
||||||
|
|
@ -61,7 +66,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',
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
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(related_name='memberships', to='permission.role', verbose_name='roles'),
|
field=models.ManyToManyField(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='profile',
|
model_name='club',
|
||||||
name='user',
|
name='parent_club',
|
||||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
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='membership',
|
|
||||||
index=models.Index(fields=['user'], name='member_memb_user_id_945dbc_idx'),
|
|
||||||
),
|
),
|
||||||
migrations.AddIndex(
|
migrations.AddIndex(
|
||||||
model_name='profile',
|
model_name='profile',
|
||||||
index=models.Index(fields=['user'], name='member_prof_user_id_30c316_idx'),
|
index=models.Index(fields=['user'], name='member_prof_user_id_30c316_idx'),
|
||||||
),
|
),
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name='membership',
|
||||||
|
index=models.Index(fields=['user'], name='member_memb_user_id_945dbc_idx'),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
71
apps/member/migrations/0003_create_bde_and_kfet.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
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),
|
||||||
|
]
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
"""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),
|
|
||||||
]
|
|
||||||
20
apps/member/migrations/0004_replace_null_by_blank.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
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;",
|
||||||
|
),
|
||||||
|
]
|
||||||
28
apps/member/migrations/0005_remove_null_tag_on_charfields.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
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),
|
||||||
|
]
|
||||||
23
apps/member/migrations/0007_auto_20210313_1235.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/member/migrations/0008_auto_20211005_1544.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -43,7 +43,7 @@ class Profile(models.Model):
|
||||||
|
|
||||||
section = models.CharField(
|
section = models.CharField(
|
||||||
verbose_name=_('section'),
|
verbose_name=_('section'),
|
||||||
help_text=_('Auto generated'),
|
help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
blank=True,
|
blank=True,
|
||||||
default="",
|
default="",
|
||||||
|
|
@ -83,6 +83,26 @@ 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)"),
|
||||||
|
|
@ -133,7 +153,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 Ker Lann] " + str(_("Activate your Note Ker Lann account"))
|
subject = "[Note Kfet] " + str(_("Activate your Note Kfet 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',
|
||||||
|
|
@ -358,10 +378,14 @@ 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")).all())
|
Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).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
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
{% if additional_fee_renewal %}
|
{% if additional_fee_renewal %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
{% if renewal %}
|
{% if renewal %}
|
||||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
{% if club.name == "Kfet" %} {# Auto-renewal #}
|
||||||
The user is not a member of the club·s {{ clubs }}. Please create the required memberships,
|
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||||
otherwise it will fail.
|
The user is not a member of the club·s {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||||
{% endblocktrans %}
|
will be charged to renew automatically the membership in this/these club·s.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||||
|
The user is not a member of the club·s {{ clubs }}. Please create the required memberships,
|
||||||
|
otherwise it will fail.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
{% if club.name == "Kfet" %}
|
||||||
This club has parents {{ clubs }}. Please make sure that the user is a member of this or these club·s,
|
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||||
otherwise the creation of this membership will fail.
|
This club has parents {{ clubs }}. An additional fee of {{ pretty_fee }}
|
||||||
{% endblocktrans %}
|
will be charged to adhere automatically to this/these club·s.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% blocktrans trimmed with clubs=clubs_renewal|join:", " pretty_fee=additional_fee_renewal|pretty_money %}
|
||||||
|
This club has parents {{ clubs }}. Please make sure that the user is a member of this or these club·s,
|
||||||
|
otherwise the creation of this membership will fail.
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -55,6 +69,10 @@ 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 %}
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
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 %}
|
||||||
|
|
@ -25,14 +24,11 @@ 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">
|
||||||
|
|
@ -55,7 +51,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 %}
|
{% elif club and not club.weiclub %}
|
||||||
{% 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>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% load i18n pretty_money perms memberinfo %}
|
{% load i18n pretty_money perms %}
|
||||||
|
|
||||||
<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 and user|is_member:club %}
|
{% if "note.view_note"|has_perm:club.note %}
|
||||||
<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 %}
|
||||||
|
|
|
||||||
|
|
@ -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:_("yes,no,maybe") }}</dd>
|
<dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</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 %}
|
||||||
|
|
|
||||||
|
|
@ -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 Ker Lann</a> via votre propre compte
|
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</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 <TOKEN></code>
|
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token <TOKEN></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 Ker Lann implémente également le protocole <a href="https://oauth.net/2/">OAuth2</a>, afin de
|
La Note Kfet 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 Ker Lann.
|
s'agit d'avoir un site marchand sur lequel faire des transactions via la Note Kfet.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -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 ker lann transfers to transfer money between
|
designed to simplify using note kfet 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 %}
|
||||||
|
|
|
||||||
|
|
@ -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="Pr\u00e9sident\u00b7e"))
|
self.membership.roles.add(Role.objects.get(name="Bureau de club"))
|
||||||
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.all()]))
|
Role.objects.filter(weirole__isnull=True).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,17 +179,20 @@ 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:
|
||||||
club = Club.objects.create(
|
if bde_parent:
|
||||||
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
|
club = Club.objects.get(name="Kfet")
|
||||||
parent_club=None,
|
else:
|
||||||
email="newclub@example.com",
|
club = Club.objects.create(
|
||||||
require_memberships=True,
|
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
|
||||||
membership_fee_paid=1000,
|
parent_club=None,
|
||||||
membership_fee_unpaid=500,
|
email="newclub@example.com",
|
||||||
membership_start=date.today(),
|
require_memberships=True,
|
||||||
membership_end=date.today() + timedelta(days=366),
|
membership_fee_paid=1000,
|
||||||
membership_duration=366,
|
membership_fee_unpaid=500,
|
||||||
)
|
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)
|
||||||
|
|
@ -201,7 +204,8 @@ 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)
|
||||||
|
|
||||||
|
|
@ -219,6 +223,11 @@ 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)
|
||||||
|
|
@ -234,6 +243,7 @@ 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)
|
||||||
|
|
||||||
|
|
@ -251,11 +261,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="Trésorier·ère")).all()],
|
Q(name="Membre de club") | Q(name="Trésorier·ère de club") | Q(name="Bureau de club")).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(), 1)
|
self.assertEqual(self.membership.roles.count(), 3)
|
||||||
|
|
||||||
def test_render_user_list(self):
|
def test_render_user_list(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -389,7 +399,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="Pr\u00e9sident\u00b7e"))
|
self.membership.roles.add(Role.objects.get(name="Bureau de club"))
|
||||||
self.membership.save()
|
self.membership.save()
|
||||||
|
|
||||||
def test_club_api(self):
|
def test_club_api(self):
|
||||||
|
|
|
||||||
|
|
@ -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,\
|
||||||
MembershipRolesForm, AuthenticationForm
|
CustomAuthenticationForm, MembershipRolesForm
|
||||||
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,19 +37,16 @@ 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 = AuthenticationForm
|
form_class = CustomAuthenticationForm
|
||||||
|
|
||||||
@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'] = 42#form.cleaned_data['permission_mask'].rank
|
self.request.session['permission_mask'] = 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):
|
||||||
"""
|
"""
|
||||||
|
|
@ -272,7 +269,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/",
|
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
|
||||||
"name_field": "name",
|
"name_field": "name",
|
||||||
"placeholder": ""
|
"placeholder": ""
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +445,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="Pr\u00e9sident\u00b7e",
|
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
|
||||||
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-")
|
||||||
|
|
@ -544,6 +541,11 @@ 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):
|
||||||
|
|
@ -595,7 +597,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"])
|
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
||||||
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
|
||||||
|
|
@ -659,7 +661,12 @@ 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:
|
if user.note.balance + credit_amount < fee and not Membership.objects.filter(
|
||||||
|
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',
|
||||||
|
|
@ -676,7 +683,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.parent_club and not Membership.objects.filter(
|
if club.name != "Kfet" and 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,
|
||||||
|
|
@ -724,6 +731,7 @@ 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:
|
||||||
|
|
@ -747,7 +755,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 == "BDA" or club.name == "BDS":
|
if club.name == "BDE" or club.name == "Kfet":
|
||||||
# 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
|
||||||
|
|
@ -764,6 +772,7 @@ 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
|
||||||
|
|
@ -775,8 +784,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
|
|
||||||
ret = super().form_valid(form)
|
ret = super().form_valid(form)
|
||||||
|
|
||||||
member_role = Role.objects.filter(Q(name="Adhérent")).all()
|
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \
|
||||||
|
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \
|
||||||
|
if club.name == "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())
|
||||||
|
|
@ -809,7 +819,8 @@ 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(for_club__isnull=True) | Q(for_club=club))).all()
|
form.fields['roles'].queryset = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub'))
|
||||||
|
& (Q(for_club__isnull=True) | Q(for_club=club))).all()
|
||||||
|
|
||||||
return form
|
return form
|
||||||
|
|
||||||
|
|
@ -855,7 +866,8 @@ 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(for_club__isnull=True) | Q(for_club=club))).all()
|
applicable_roles = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub'))
|
||||||
|
& (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'
|
||||||
|
|
|
||||||
|
|
@ -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, # BDA
|
club=2, # Kfet
|
||||||
).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())
|
||||||
|
|
|
||||||
|
|
@ -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,"consumer")
|
router.register(path + '/consumer', ConsumerViewSet)
|
||||||
|
|
||||||
router.register(path + '/transaction/category', TemplateCategoryViewSet)
|
router.register(path + '/transaction/category', TemplateCategoryViewSet)
|
||||||
router.register(path + '/transaction/transaction', TransactionViewSet)
|
router.register(path + '/transaction/transaction', TransactionViewSet)
|
||||||
|
|
|
||||||
|
|
@ -160,13 +160,11 @@ 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)
|
||||||
|
|
@ -176,14 +174,13 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
|
||||||
suffix = '__iregex' if valid_regex else '__istartswith'
|
suffix = '__iregex' if valid_regex else '__istartswith'
|
||||||
alias_prefix = '^' if valid_regex else ''
|
alias_prefix = '^' if valid_regex else ''
|
||||||
queryset = queryset.prefetch_related('note')
|
queryset = queryset.prefetch_related('note')
|
||||||
|
|
||||||
if alias:
|
if alias:
|
||||||
# We match first an alias if it is matched without normalization,
|
# We match first an alias if it is matched without normalization,
|
||||||
# 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})
|
||||||
|
|
@ -194,12 +191,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):
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
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
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
@ -11,31 +11,12 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
('member', '0001_initial'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('member', '0001_initial'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
]
|
]
|
||||||
|
|
||||||
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=[
|
||||||
|
|
@ -45,8 +26,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(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)),
|
('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)),
|
||||||
('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')),
|
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_note.note_set+', to='contenttypes.ContentType')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'note',
|
'verbose_name': 'note',
|
||||||
|
|
@ -65,23 +46,41 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SpecialTransaction',
|
name='Transaction',
|
||||||
fields=[
|
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')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('last_name', models.CharField(max_length=255, verbose_name='name')),
|
('source_alias', models.CharField(default='', max_length=255, verbose_name='used alias')),
|
||||||
('first_name', models.CharField(max_length=255, verbose_name='first_name')),
|
('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=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': 'Special transaction',
|
'verbose_name': 'transaction',
|
||||||
'verbose_name_plural': 'Special transactions',
|
'verbose_name_plural': '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',
|
||||||
|
|
@ -92,7 +91,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={
|
||||||
|
|
@ -101,15 +100,41 @@ class Migration(migrations.Migration):
|
||||||
},
|
},
|
||||||
bases=('note.note',),
|
bases=('note.note',),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.CreateModel(
|
||||||
model_name='transaction',
|
name='NoteUser',
|
||||||
name='destination',
|
fields=[
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': "one's note",
|
||||||
|
'verbose_name_plural': 'users note',
|
||||||
|
},
|
||||||
|
bases=('note.note',),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.CreateModel(
|
||||||
model_name='transaction',
|
name='RecurrentTransaction',
|
||||||
name='source',
|
fields=[
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_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')),
|
||||||
|
],
|
||||||
|
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',
|
||||||
|
|
@ -117,7 +142,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, related_name='alias', to='note.note')),
|
('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'alias',
|
'verbose_name': 'alias',
|
||||||
|
|
@ -133,62 +158,14 @@ 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'),
|
||||||
|
|
@ -201,6 +178,26 @@ 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'),
|
||||||
|
|
@ -209,8 +206,4 @@ 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')},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
17
apps/note/migrations/0003_replace_null_by_blank.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
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;"
|
||||||
|
),
|
||||||
|
]
|
||||||
23
apps/note/migrations/0004_remove_null_tag_on_charfields.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
apps/note/migrations/0005_auto_20210313_1235.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
27
apps/note/migrations/0006_trust.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# 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')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -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 Ker Lann] Passage en négatif (club {})".format(self.club.name), plain_text,
|
send_mail("[Note Kfet] 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 between two users
|
A one-sided trust relationship bertween 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
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,11 @@ 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):
|
||||||
|
|
@ -341,16 +346,20 @@ 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:
|
if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"):
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,7 @@ 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 %}
|
||||||
{% if value != False %}
|
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
||||||
{{ 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>
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ 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>
|
||||||
|
|
||||||
|
|
@ -160,79 +159,6 @@ 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 %}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Ce mail t'a été envoyé parce que le solde de ta Note Ker Lann {{ note }} est négatif !
|
Ce mail t'a été envoyé parce que le solde de ta Note Kfet {{ note }} est négatif !
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Tu peux venir recharger ta note rapidement, ou envoyer un mail à
|
Tu peux venir recharger ta note rapidement à la Kfet, 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 Ker Lann on the" %} {% now "j F Y à H:i:s" %}
|
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>[Note Ker Lann] Liste des négatifs</title>
|
<title>[Note Kfet] 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 Ker Lann on the" %} {% now "j F Y à H:i:s" %}
|
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>[Note Ker Lann] Rapport de la Note Ker Lann</title>
|
<title>[Note Kfet] Rapport de la Note Kfet</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 Ker Lann on the" %} {% now "j F Y à H:i:s" %}
|
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -47,7 +47,6 @@ 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>
|
||||||
|
|
||||||
|
|
@ -140,6 +139,12 @@ 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">
|
||||||
|
|
@ -163,75 +168,6 @@ 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 }};
|
||||||
|
|
|
||||||
|
|
@ -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="BDA"), user=self.user)
|
Membership.objects.create(club=Club.objects.get(name="Kfet"), 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="BDA"), user=self.user)
|
Membership.objects.create(club=Club.objects.get(name="Kfet"), 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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1695,12 +1695,12 @@
|
||||||
"auth",
|
"auth",
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
|
"query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"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"
|
"description": "Voir n'importe quel utilisateur qui est adhérent BDE"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1931,8 +1931,8 @@
|
||||||
"model": "permission.role",
|
"model": "permission.role",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"for_club": null,
|
"for_club": 1,
|
||||||
"name": "Adh\u00e9rent",
|
"name": "Adh\u00e9rent BDE",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
|
|
@ -1957,7 +1957,6 @@
|
||||||
161,
|
161,
|
||||||
162,
|
162,
|
||||||
165,
|
165,
|
||||||
178,
|
|
||||||
186,
|
186,
|
||||||
187,
|
187,
|
||||||
188,
|
188,
|
||||||
|
|
@ -1973,15 +1972,26 @@
|
||||||
"model": "permission.role",
|
"model": "permission.role",
|
||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"for_club": null,
|
"for_club": 2,
|
||||||
"name": "Pr\u00e9sident\u00b7e",
|
"name": "Adh\u00e9rent Kfet",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
50,
|
22,
|
||||||
59,
|
34,
|
||||||
60,
|
36,
|
||||||
61,
|
39,
|
||||||
62,
|
40,
|
||||||
169
|
152,
|
||||||
|
153,
|
||||||
|
154,
|
||||||
|
155,
|
||||||
|
156,
|
||||||
|
157,
|
||||||
|
158,
|
||||||
|
159,
|
||||||
|
160,
|
||||||
|
179,
|
||||||
|
189,
|
||||||
|
190
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1990,34 +2000,9 @@
|
||||||
"pk": 3,
|
"pk": 3,
|
||||||
"fields": {
|
"fields": {
|
||||||
"for_club": null,
|
"for_club": null,
|
||||||
"name": "Tr\u00e9sorier\u00b7\u00e8re",
|
"name": "Membre de club",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
19,
|
22
|
||||||
20,
|
|
||||||
21,
|
|
||||||
27,
|
|
||||||
59,
|
|
||||||
60,
|
|
||||||
61,
|
|
||||||
62,
|
|
||||||
63,
|
|
||||||
64,
|
|
||||||
65,
|
|
||||||
66,
|
|
||||||
67,
|
|
||||||
68,
|
|
||||||
69,
|
|
||||||
151,
|
|
||||||
166,
|
|
||||||
167,
|
|
||||||
168,
|
|
||||||
172,
|
|
||||||
173,
|
|
||||||
174,
|
|
||||||
175,
|
|
||||||
182,
|
|
||||||
184,
|
|
||||||
185
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2026,22 +2011,12 @@
|
||||||
"pk": 4,
|
"pk": 4,
|
||||||
"fields": {
|
"fields": {
|
||||||
"for_club": null,
|
"for_club": null,
|
||||||
"name": "Secr\u00e9taire",
|
"name": "Bureau de club",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
21,
|
47,
|
||||||
54,
|
49,
|
||||||
55,
|
50,
|
||||||
56,
|
169
|
||||||
57,
|
|
||||||
58,
|
|
||||||
59,
|
|
||||||
60,
|
|
||||||
61,
|
|
||||||
145,
|
|
||||||
146,
|
|
||||||
147,
|
|
||||||
176,
|
|
||||||
177
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2050,6 +2025,114 @@
|
||||||
"pk": 5,
|
"pk": 5,
|
||||||
"fields": {
|
"fields": {
|
||||||
"for_club": null,
|
"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,
|
||||||
|
20,
|
||||||
|
21,
|
||||||
|
27,
|
||||||
|
60,
|
||||||
|
61,
|
||||||
|
62,
|
||||||
|
150,
|
||||||
|
166,
|
||||||
|
167,
|
||||||
|
168,
|
||||||
|
182,
|
||||||
|
184,
|
||||||
|
185
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.role",
|
||||||
|
"pk": 7,
|
||||||
|
"fields": {
|
||||||
|
"for_club": 1,
|
||||||
|
"name": "Pr\u00e9sident\u00b7e BDE",
|
||||||
|
"permissions": [
|
||||||
|
24,
|
||||||
|
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,
|
||||||
|
55,
|
||||||
|
56,
|
||||||
|
57,
|
||||||
|
58,
|
||||||
|
63,
|
||||||
|
64,
|
||||||
|
65,
|
||||||
|
66,
|
||||||
|
67,
|
||||||
|
68,
|
||||||
|
69,
|
||||||
|
146,
|
||||||
|
147,
|
||||||
|
150,
|
||||||
|
151,
|
||||||
|
163,
|
||||||
|
164,
|
||||||
|
170,
|
||||||
|
171,
|
||||||
|
172,
|
||||||
|
173,
|
||||||
|
174,
|
||||||
|
175,
|
||||||
|
176,
|
||||||
|
177,
|
||||||
|
178,
|
||||||
|
188,
|
||||||
|
183,
|
||||||
|
186,
|
||||||
|
187
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.role",
|
||||||
|
"pk": 9,
|
||||||
|
"fields": {
|
||||||
|
"for_club": 1,
|
||||||
"name": "Respo info",
|
"name": "Respo info",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
1,
|
1,
|
||||||
|
|
@ -2173,5 +2256,130 @@
|
||||||
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": {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
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):
|
||||||
|
|
@ -9,11 +9,26 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
|
||||||
('member', '0001_initial'),
|
('member', '0001_initial'),
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
]
|
]
|
||||||
|
|
||||||
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=[
|
||||||
|
|
@ -26,35 +41,31 @@ 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')},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,11 @@ 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"))
|
roles = record.roles.filter((~(Q(name="Adhérent BDE")
|
||||||
)).all()
|
| Q(name="Adhérent Kfet")
|
||||||
|
| 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}))
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,39 @@ 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">
|
||||||
|
|
@ -31,6 +63,7 @@ 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" %} :
|
||||||
|
|
@ -65,25 +98,6 @@ 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 %}
|
||||||
|
|
|
||||||
|
|
@ -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"))
|
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
|
||||||
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"))
|
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
|
||||||
membership.save()
|
membership.save()
|
||||||
|
|
||||||
resp = self.client.get(reverse('permission:scopes'))
|
resp = self.client.get(reverse('permission:scopes'))
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ 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):
|
||||||
|
|
@ -40,7 +41,7 @@ class TestPermissionDenied(TestCase):
|
||||||
name="",
|
name="",
|
||||||
description="",
|
description="",
|
||||||
creater=self.user,
|
creater=self.user,
|
||||||
activity_type_id=4,
|
activity_type_id=1,
|
||||||
organizer_id=1,
|
organizer_id=1,
|
||||||
attendees_club_id=1,
|
attendees_club_id=1,
|
||||||
date_start=timezone.now(),
|
date_start=timezone.now(),
|
||||||
|
|
@ -54,7 +55,7 @@ class TestPermissionDenied(TestCase):
|
||||||
name="",
|
name="",
|
||||||
description="",
|
description="",
|
||||||
creater=self.user,
|
creater=self.user,
|
||||||
activity_type_id=4,
|
activity_type_id=1,
|
||||||
organizer_id=1,
|
organizer_id=1,
|
||||||
attendees_club_id=1,
|
attendees_club_id=1,
|
||||||
date_start=timezone.now(),
|
date_start=timezone.now(),
|
||||||
|
|
@ -78,6 +79,56 @@ 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"))
|
||||||
|
|
|
||||||
|
|
@ -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, Role
|
from member.models import Club, Membership
|
||||||
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,22 +23,44 @@ 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)
|
||||||
membership =Membership.objects.create(
|
wei = WEIClub.objects.create(
|
||||||
user=user,
|
name="wei",
|
||||||
club=Club.objects.get(name="BDE")
|
date_start=date.today(),
|
||||||
|
date_end=date.today(),
|
||||||
|
)
|
||||||
|
NoteClub.objects.create(club=wei)
|
||||||
|
weiregistration = WEIRegistration.objects.create(
|
||||||
|
user=user,
|
||||||
|
wei=wei,
|
||||||
|
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.
|
We use a random user with a random WEIClub (to use permissions for the WEI) in a random team in a random bus.
|
||||||
"""
|
"""
|
||||||
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,
|
||||||
|
|
|
||||||
|
|
@ -131,8 +131,11 @@ 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"))
|
).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE")
|
||||||
)))\
|
| 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-")
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ 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
|
||||||
"""
|
"""
|
||||||
|
|
@ -46,6 +45,15 @@ 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.
|
||||||
|
|
@ -75,27 +83,20 @@ 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=False,
|
initial=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
join_bda = forms.BooleanField(
|
# The user can join the Kfet club at the inscription
|
||||||
label=_("Join BDA Club"),
|
join_kfet = forms.BooleanField(
|
||||||
|
label=_("Join Kfet Club"),
|
||||||
required=False,
|
required=False,
|
||||||
initial=False,
|
initial=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
join_bds = forms.BooleanField(
|
|
||||||
label=_("Join BDS Club"),
|
|
||||||
required=False,
|
|
||||||
initial=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
join_sinfonie = forms.BooleanField(
|
|
||||||
label=_("Join Sinfonie Club"),
|
|
||||||
required=False,
|
|
||||||
initial=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 to complete your registration." %}
|
{% trans "You must pay now your membership in the Kfet to complete your registration." %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
||||||
|
|
@ -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 pay your membership. A administrator will then activate your account" %}
|
{% trans "You must also go to the Kfet to pay your membership." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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:_("yes,no,maybe") }}</dd>
|
<dd class="col-xl-6">{{ object.profile.paid|yesno }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
|
|
@ -82,6 +82,10 @@ 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);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% trans "Activation of your Note Ker Lann account" %}</title>
|
<title>Passage en négatif (compte n°{{ note.user.pk }})</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -13,12 +13,12 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% trans "You recently registered on the Note Ker Lann. Please click on the link below to confirm your registration." %}
|
{% trans "You recently registered on the Note Kfet. Please click on the link below to confirm your registration." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="http://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}">
|
<a href="https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}">
|
||||||
http://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
|
https://{{ 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." %}
|
{% 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." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -36,6 +36,6 @@
|
||||||
|
|
||||||
--
|
--
|
||||||
<p>
|
<p>
|
||||||
{% trans "The Note Ker Lann team." %}<br>
|
{% trans "The Note Kfet team." %}<br>
|
||||||
{% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
|
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
{% trans "Hi" %} {{ user.username }},
|
{% trans "Hi" %} {{ user.username }},
|
||||||
|
|
||||||
{% trans "You recently registered on the Note Ker Lann. Please click on the link below to confirm your registration." %}
|
{% trans "You recently registered on the Note Kfet. 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." %}
|
{% 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 "Thanks" %},
|
{% trans "Thanks" %},
|
||||||
|
|
||||||
{% trans "The Note Ker Lann team." %}
|
{% trans "The Note Kfet team." %}
|
||||||
{% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
|
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,10 @@ 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="fr",
|
ml_events_registration="en",
|
||||||
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")
|
||||||
|
|
@ -188,15 +187,41 @@ 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_bda=False,
|
join_kfet=True,
|
||||||
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)
|
||||||
|
|
@ -207,9 +232,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_bda=False,
|
join_kfet=True,
|
||||||
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)
|
||||||
|
|
@ -223,9 +248,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_bda=False,
|
join_kfet=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,26 +273,25 @@ 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_bda=False,
|
join_kfet=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="BDA", user=self.user).exists())
|
self.assertFalse(Membership.objects.filter(club__name="Kfet", 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(), 2)
|
||||||
|
|
||||||
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,BDA and BDS.
|
The user joins the BDE and the Kfet.
|
||||||
"""
|
"""
|
||||||
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)
|
||||||
|
|
@ -283,19 +307,18 @@ 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_bda=True,
|
join_kfet=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="BDA", user=self.user).exists())
|
self.assertTrue(Membership.objects.filter(club__name="Kfet", 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(), 4)
|
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)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ class UserCreateView(CreateView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["profile_form"] = self.second_form(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"]
|
||||||
|
|
||||||
|
|
@ -224,10 +225,9 @@ 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")
|
||||||
bda = Club.objects.get(name="BDA")
|
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
|
||||||
bds = Club.objects.get(name="BDS")
|
kfet = Club.objects.get(name="Kfet")
|
||||||
for auto_club in [bde, bda, bds]:
|
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
|
||||||
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,20 +256,30 @@ 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_bda = form.cleaned_data["join_bda"]
|
join_kfet = form.cleaned_data["join_kfet"]
|
||||||
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")
|
||||||
bda = Club.objects.get(name="BDA")
|
bde_fee = bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
|
||||||
bds = Club.objects.get(name="BDS")
|
# This is mandatory.
|
||||||
for auto_club, auto_join in zip([bde, bda, bds], [join_bde, join_bda, join_bds]):
|
fee += bde_fee if join_bde else 0
|
||||||
bd_fee = auto_club.membership_fee_paid if user.profile.paid else auto_club.membership_fee_unpaid
|
kfet = Club.objects.get(name="Kfet")
|
||||||
fee += bd_fee if auto_join else 0
|
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
|
||||||
|
# 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
|
||||||
|
|
@ -304,23 +314,36 @@ 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 auto_join:
|
if join_bde:
|
||||||
# Create membership for the user to the BDEAS starting today
|
# Create membership for the user to the BDE starting today
|
||||||
membership = Membership(
|
membership = Membership(
|
||||||
club=auto_club,
|
club=bde,
|
||||||
user=user,
|
user=user,
|
||||||
fee=bd_fee,
|
fee=bde_fee,
|
||||||
)
|
)
|
||||||
membership.save()
|
membership.save()
|
||||||
membership.refresh_from_db()
|
membership.refresh_from_db()
|
||||||
membership.roles.add(Role.objects.get(name="Adhérent"))
|
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
|
||||||
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
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class InvoiceForm(forms.ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Invoice
|
model = Invoice
|
||||||
exclude = ('date', 'tex', )
|
exclude = ('bde', 'date', 'tex', )
|
||||||
|
|
||||||
|
|
||||||
class ProductForm(forms.ModelForm):
|
class ProductForm(forms.ModelForm):
|
||||||
|
|
@ -132,6 +132,8 @@ 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):
|
||||||
|
|
@ -146,6 +148,7 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
# Generated by Django 5.1 on 2024-08-13 09:26
|
# Generated by Django 2.2.16 on 2020-09-04 21:41
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import django.core.validators
|
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.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
@ -12,6 +12,7 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
('note', '0001_initial'),
|
('note', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -20,7 +21,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=[('BDE', 'BDE'), ('BDA', 'BDA'), ('BDS', 'BDS')], default='BDE', max_length=32, verbose_name='BD?')),
|
('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')),
|
||||||
('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')),
|
||||||
|
|
@ -35,31 +36,6 @@ 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=[
|
||||||
|
|
@ -67,7 +43,6 @@ 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',
|
||||||
|
|
@ -78,12 +53,55 @@ 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, related_name='transaction_proxies', to='treasury.remittance', verbose_name='Remittance')),
|
('remittance', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, 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',
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
25
apps/treasury/migrations/0003_auto_20210321_1034.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
apps/treasury/migrations/0004_auto_20211005_1544.py
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
16
apps/treasury/migrations/0005_delete_sogecredit.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -28,13 +28,16 @@ class Invoice(models.Model):
|
||||||
|
|
||||||
bde = models.CharField(
|
bde = models.CharField(
|
||||||
max_length=32,
|
max_length=32,
|
||||||
default='BDE',
|
default='Saperlistpopette',
|
||||||
choices=(
|
choices=(
|
||||||
('BDE', 'BDE'),
|
('Saperlistpopette', 'Saper[list]popette'),
|
||||||
('BDA', 'BDA'),
|
('Finalist', 'Fina[list]'),
|
||||||
('BDS', 'BDS'),
|
('Listorique', '[List]orique'),
|
||||||
|
('Satellist', 'Satel[list]'),
|
||||||
|
('Monopolist', 'Monopo[list]'),
|
||||||
|
('Kataclist', 'Katac[list]'),
|
||||||
),
|
),
|
||||||
verbose_name=_("BD?"),
|
verbose_name=_("BDE"),
|
||||||
)
|
)
|
||||||
|
|
||||||
object = models.CharField(
|
object = models.CharField(
|
||||||
|
|
@ -91,10 +94,10 @@ class Invoice(models.Model):
|
||||||
|
|
||||||
products = self.products.all()
|
products = self.products.all()
|
||||||
|
|
||||||
self.place = "Bruz"
|
self.place = "Gif-sur-Yvette"
|
||||||
self.my_name = f"{self.bde} ENS Rennes"
|
self.my_name = "BDE ENS Cachan"
|
||||||
self.my_address_street = "14 avenue Robert Schumann"
|
self.my_address_street = "4 avenue des Sciences"
|
||||||
self.my_city = "35170 Bruz"
|
self.my_city = "91190 Gif-sur-Yvette"
|
||||||
self.bank_code = 30003
|
self.bank_code = 30003
|
||||||
self.desk_code = 3894
|
self.desk_code = 3894
|
||||||
self.account_number = 37280662
|
self.account_number = 37280662
|
||||||
|
|
|
||||||
BIN
apps/treasury/static/img/Finalist.png
Normal file
|
After Width: | Height: | Size: 752 KiB |
BIN
apps/treasury/static/img/Finalist_bg.jpg
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
apps/treasury/static/img/Kataclist.png
Normal file
|
After Width: | Height: | Size: 664 KiB |
BIN
apps/treasury/static/img/Kataclist_bg.jpg
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
apps/treasury/static/img/Listorique.png
Normal file
|
After Width: | Height: | Size: 414 KiB |
BIN
apps/treasury/static/img/Listorique_bg.jpg
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
apps/treasury/static/img/Monopolist.png
Normal file
|
After Width: | Height: | Size: 375 KiB |
BIN
apps/treasury/static/img/Monopolist_bg.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
apps/treasury/static/img/Saperlistpopette.png
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
apps/treasury/static/img/Saperlistpopette_bg.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 202 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
|
@ -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', 'amount', 'reason',)
|
fields = ('created_at', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',)
|
||||||
order_by = ('-created_at',)
|
order_by = ('-created_at',)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,18 @@
|
||||||
{% 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
|
||||||
|
|
@ -93,8 +105,8 @@
|
||||||
|
|
||||||
\renewcommand{\headrulewidth}{0pt}
|
\renewcommand{\headrulewidth}{0pt}
|
||||||
\cfoot{
|
\cfoot{
|
||||||
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 00 00 00 00\newline
|
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 89 88 56 50\newline
|
||||||
Site web : TODO ~--~ E-mail : TODO \newline Numéro SIRET : 000 000 000 00000
|
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,7 @@ 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(
|
||||||
|
|
@ -196,6 +197,7 @@ 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(
|
||||||
|
|
@ -273,6 +275,7 @@ 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()
|
||||||
|
|
@ -317,7 +320,8 @@ 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(
|
||||||
|
|
@ -328,7 +332,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="BDA")
|
self.kfet = Club.objects.get(name="Kfet")
|
||||||
self.bde = self.kfet.parent_club
|
self.bde = self.kfet.parent_club
|
||||||
|
|
||||||
self.kfet_membership = Membership(
|
self.kfet_membership = Membership(
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
class InvoiceDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
|
class InvoiceDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView):
|
||||||
"""
|
"""
|
||||||
Delete a non-validated registration
|
Delete a non-validated WEI registration
|
||||||
"""
|
"""
|
||||||
model = Invoice
|
model = Invoice
|
||||||
extra_context = {"title": _("Delete invoice")}
|
extra_context = {"title": _("Delete invoice")}
|
||||||
|
|
@ -368,6 +368,7 @@ 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)
|
||||||
|
|
|
||||||
4
apps/wei/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
default_app_config = 'wei.apps.WeiConfig'
|
||||||
13
apps/wei/admin.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# 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)
|
||||||
0
apps/wei/api/__init__.py
Normal file
72
apps/wei/api/serializers.py
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# 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__'
|
||||||
17
apps/wei/api/urls.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 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)
|
||||||
105
apps/wei/api/views.py
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
# 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', ]
|
||||||
10
apps/wei/apps.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# 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')
|
||||||