Compare commits

..

No commits in common. "main" and "dev-jb" have entirely different histories.
main ... dev-jb

158 changed files with 11763 additions and 7851 deletions

2
.gitignore vendored
View file

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

View file

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

View file

@ -35,27 +35,11 @@ Bien que cela permette de créer une instance sur toutes les distributions,
texlive-bin gettext ttf-font-awesome git
$ 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 :
```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.**
@ -145,13 +129,13 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
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
$ 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 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.**
@ -168,19 +152,19 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
```bash
$ 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):
```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 :
```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`.
@ -235,7 +219,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
DJANGO_DB_PASSWORD=CHANGE_ME
DJANGO_DB_PORT=
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
CONTACT_EMAIL=tresorerie.bde@localhost
# 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`,
```
git clone https://github.com/jbdoderlein/notes-ker-lann.git/ --recursive && cd notes-ker-lann
docker build . -t notes-ker-lann
git clone https://gitlab.crans.org/bde/nk20/ --recursive && cd nk20
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`),
```
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,
```
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
@ -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,
```YAML
notes-ker-lann:
build: /chemin/vers/le/code/notes-ker-lann
nk20:
build: /chemin/vers/le/code/nk20
volumes:
- /chemin/vers/le/code/notes-ker-lann:/var/www/notes-ker-lann/
env_file: /chemin/vers/le/code/notes-ker-lann/.env
- /chemin/vers/le/code/nk20:/var/www/note_kfet/
env_file: /chemin/vers/le/code/nk20/.env
restart: always
labels:
- "traefik.http.routers.notes-ker-lann.rule=Host(`ndd.example.com`)"
- "traefik.http.services.notes-ker-lann.loadbalancer.server.port=8080"
- "traefik.http.routers.nk20.rule=Host(`ndd.example.com`)"
- "traefik.http.services.nk20.loadbalancer.server.port=8080"
```
## Documentation

View file

@ -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
}
}
]

View file

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

View file

@ -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
import django.utils.timezone
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')),
('name', models.CharField(max_length=255, verbose_name='name')),
('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_end', models.DateTimeField(verbose_name='end date')),
('valid', models.BooleanField(default=False, verbose_name='valid')),

View file

@ -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.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -11,16 +11,58 @@ class Migration(migrations.Migration):
dependencies = [
('activity', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('member', '0001_initial'),
('note', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
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(
model_name='activity',
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(
model_name='activity',
@ -30,53 +72,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='activity',
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')},
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.AlterUniqueTogether(
name='guest',
@ -86,4 +82,8 @@ class Migration(migrations.Migration):
name='entry',
unique_together={('activity', 'note', 'guest')},
),
migrations.AlterUniqueTogether(
name='activity',
unique_together={('name', 'date_start', 'date_end')},
),
]

View file

@ -73,7 +73,7 @@ class Activity(models.Model):
max_length=255,
blank=True,
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(
@ -102,7 +102,7 @@ class Activity(models.Model):
on_delete=models.PROTECT,
related_name='+',
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(

View file

@ -91,7 +91,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
}).done(function () {
if (target.hasClass("table-info"))
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);
else
addMsg("Entry made!", "success", 4000);
@ -126,7 +126,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
}).done(function () {
if (target.hasClass("table-info"))
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);
else
addMsg("{% trans "Entry done!" %}", "success", 4000);
@ -150,7 +150,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
"source": credit_id,
"destination": target.attr('data-inviter'),
"last_name": last_name,
"first_name": first_name
"first_name": first_name,
"bank": ""
}).done(function () {
makeTransaction();
reset();

View file

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

View file

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

View file

@ -340,7 +340,7 @@ UID:{md5((activity.name + "$" + str(activity.id) + str(activity.date_start)).enc
SUMMARY;CHARSET=UTF-8:{self.multilines(activity.name, 75, 22)}
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)}
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) + """
-- {activity.organizer.name}
END:VEVENT

View file

@ -2,8 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.conf.urls import include
from django.urls import re_path as url
from django.conf.urls import url, include
from rest_framework import routers
from .views import UserInformationView
@ -39,6 +38,10 @@ if "logs" in settings.INSTALLED_APPS:
from logs.api.urls import register_logs_urls
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'
# Wire up our API using automatic URL routing.

View file

@ -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.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
@ -22,11 +22,11 @@ class Migration(migrations.Migration):
('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')),
('instance_pk', models.CharField(max_length=255, verbose_name='identifier')),
('previous', models.TextField(blank=True, default='', verbose_name='previous data')),
('data', models.TextField(blank=True, default='', verbose_name='new data')),
('previous', models.TextField(null=True, verbose_name='previous 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')),
('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')),
],
options={

View 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;"
),
]

View 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'),
),
]

View file

@ -19,12 +19,20 @@ from permission.models import PermissionMask, Role
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):
def get_validation_exclusions(self):
def _get_validation_exclusions(self):
# Django usernames can only contain letters, numbers, @, ., +, - and _.
# We want to allow users to have uncommon and unpractical usernames:
# 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:
model = User
@ -60,7 +68,7 @@ class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = '__all__'
exclude = ('user', 'email_confirmed', 'registration_valid', 'section' )
exclude = ('user', 'email_confirmed', 'registration_valid', )
class ImageForm(forms.Form):
@ -106,7 +114,7 @@ class ImageForm(forms.Form):
frame = frame.crop((x, y, x + w, y + h))
frame = frame.resize(
(settings.PIC_WIDTH, settings.PIC_RATIO * settings.PIC_WIDTH),
Image.Resampling.LANCZOS,
Image.ANTIALIAS,
)
frames.append(frame)
@ -178,6 +186,11 @@ class MembershipForm(forms.ModelForm):
required=False,
)
bank = forms.CharField(
label=_("Bank"),
required=False,
)
class Meta:
model = Membership
fields = ('user', 'date_start')
@ -214,7 +227,7 @@ class MembershipRolesForm(forms.ModelForm):
)
roles = forms.ModelMultipleChoiceField(
queryset=Role.objects.all(),
queryset=Role.objects.filter(weirole=None).all(),
label=_("Roles"),
widget=CheckboxSelectMultiple(),
)

View file

@ -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
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import phonenumber_field.modelfields
from django.db import migrations, models
class Migration(migrations.Migration):
@ -12,29 +13,10 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone_number', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=50, null=True, region=None, verbose_name='phone number')),
('section', models.CharField(blank=True, default='', help_text='Auto generated', max_length=255, verbose_name='section')),
('department', models.CharField(choices=[('INFO', 'Informatique'), ('MATH', 'Mathématiques'), ('DEM', 'Droit - économie - management'), ('MECATRO', 'Mécatronique'), ('2SEP', 'Sciences du sport et éducation physique'), ('SE', 'Sciences pour lEnvironnement'), ('EXT', 'Externe')], max_length=8, verbose_name='department')),
('promotion', models.PositiveSmallIntegerField(default=2024, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion')),
('address', models.CharField(blank=True, default='', max_length=255, verbose_name='address')),
('paid', models.BooleanField(default=False, help_text='Tells if the user receive a salary.', verbose_name='paid')),
('report_frequency', models.PositiveSmallIntegerField(default=0, verbose_name='report frequency (in days)')),
('last_report', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last report date')),
('email_confirmed', models.BooleanField(default=False, verbose_name='email confirmed')),
('registration_valid', models.BooleanField(default=False, verbose_name='registration valid')),
],
options={
'verbose_name': 'user profile',
'verbose_name_plural': 'user profile',
},
),
migrations.CreateModel(
name='Club',
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_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')),
('parent_club', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.club', verbose_name='parent club')),
],
options={
'verbose_name': 'club',
'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(
name='Membership',
fields=[
@ -61,7 +66,7 @@ class Migration(migrations.Migration):
('date_start', models.DateField(default=datetime.date.today, verbose_name='membership starts on')),
('date_end', models.DateField(null=True, verbose_name='membership ends on')),
('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={
'verbose_name': 'membership',

View file

@ -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.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -10,16 +10,16 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('member', '0001_initial'),
('permission', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('member', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='membership',
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(
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'),
),
migrations.AddField(
model_name='profile',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddIndex(
model_name='membership',
index=models.Index(fields=['user'], name='member_memb_user_id_945dbc_idx'),
model_name='club',
name='parent_club',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='parent club'),
),
migrations.AddIndex(
model_name='profile',
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'),
),
]

View 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),
]

View file

@ -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),
]

View 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;",
),
]

View 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'),
),
]

View file

@ -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),
]

View 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'),
),
]

View 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'),
),
]

View file

@ -43,7 +43,7 @@ class Profile(models.Model):
section = models.CharField(
verbose_name=_('section'),
help_text=_('Auto generated'),
help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'),
max_length=255,
blank=True,
default="",
@ -83,6 +83,26 @@ class Profile(models.Model):
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(
verbose_name=_("report frequency (in days)"),
@ -133,7 +153,7 @@ class Profile(models.Model):
return str(self.user)
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)
uid = urlsafe_base64_encode(force_bytes(self.user_id))
message = loader.render_to_string('registration/mails/email_validation_email.txt',
@ -358,10 +378,14 @@ class Membership(models.Model):
parent_membership.save()
parent_membership.refresh_from_db()
parent_membership.roles.set(
Role.objects.filter(Q(name="Adhérent")).all())
if self.club.parent_club.name == "BDE":
parent_membership.roles.set(
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()
@transaction.atomic

View file

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

View file

@ -3,7 +3,6 @@
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n perms %}
{% load qr_code %}
{# Use a fluid-width container #}
{% 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 %}">
<img src="{{ user_object.note.display_image.url }}" class="img-thumbnail mt-2">
</a>
{% qr_from_text user_object.username size="M" %}
{% elif club %}
<a href="{% url 'member:club_update_pic' club.pk %}">
<img src="{{ club.note.display_image.url }}" class="img-thumbnail mt-2">
</a>
{% qr_from_text club.name size="M" %}
{% endif %}
</div>
{% if note.inactivity_reason %}
<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 %}
<a class="btn btn-sm btn-primary" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
{% endif %}
{% elif club %}
{% elif club and not club.weiclub %}
{% if can_add_members %}
<a class="btn btn-sm btn-success" href="{% url 'member:club_add_member' club_pk=club.pk %}"
data-turbolinks="false"> {% trans "Add member" %}</a>

View file

@ -1,4 +1,4 @@
{% load i18n pretty_money perms memberinfo %}
{% load i18n pretty_money perms %}
<dl class="row">
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
@ -39,7 +39,7 @@
{% 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>
<dd class="col-xl-6">{{ club.note.balance | pretty_money }}</dd>
{% endif %}

View file

@ -48,7 +48,7 @@
<dd class="col-xl-6">{{ user_object.profile.address }}</dd>
<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 %}
{% if user_object.note and "note.view_note"|has_perm:user_object.note %}

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="alert alert-info">
<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 />
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token &lt;TOKEN&gt;</code>
pour pouvoir vous identifier.<br /><br />
@ -55,10 +55,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-header">
<div class="alert alert-info">
<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
(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>

View file

@ -28,7 +28,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% blocktrans trimmed %}
Adding someone as a friend enables them to initiate transactions coming
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
friends without needing additional rights among them.
{% endblocktrans %}

View file

@ -49,7 +49,7 @@ class TestMemberships(TestCase):
self.club = Club.objects.create(name="totoclub", parent_club=Club.objects.get(name="BDE"))
self.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.roles.add(Role.objects.get(name="Pr\u00e9sident\u00b7e"))
self.membership.roles.add(Role.objects.get(name="Bureau de club"))
self.membership.save()
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="
+ ",".join([str(role.pk) for role in
Role.objects.all()]))
Role.objects.filter(weirole__isnull=True).all()]))
self.assertEqual(response.status_code, 200)
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)
for bde_parent in False, True:
club = Club.objects.create(
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
parent_club=None,
email="newclub@example.com",
require_memberships=True,
membership_fee_paid=1000,
membership_fee_unpaid=500,
membership_start=date.today(),
membership_end=date.today() + timedelta(days=366),
membership_duration=366,
)
if bde_parent:
club = Club.objects.get(name="Kfet")
else:
club = Club.objects.create(
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
parent_club=None,
email="newclub@example.com",
require_memberships=True,
membership_fee_paid=1000,
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,)))
self.assertEqual(response.status_code, 200)
@ -201,7 +204,8 @@ class TestMemberships(TestCase):
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
credit_amount=4200,
last_name="TOTO",
first_name="Toto"
first_name="Toto",
bank="Le matelas",
))
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
@ -219,6 +223,11 @@ class TestMemberships(TestCase):
self.assertEqual(response.status_code, 200)
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,)))
self.assertEqual(response.status_code, 200)
@ -234,6 +243,7 @@ class TestMemberships(TestCase):
credit_amount=14242,
last_name="TOTO",
first_name="Toto",
bank="Bank",
))
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(
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.membership.refresh_from_db()
self.assertEqual(self.membership.roles.count(), 1)
self.assertEqual(self.membership.roles.count(), 3)
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.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()
def test_club_api(self):

View file

@ -28,7 +28,7 @@ from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
MembershipRolesForm, AuthenticationForm
CustomAuthenticationForm, MembershipRolesForm
from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -37,19 +37,16 @@ class CustomLoginView(LoginView):
"""
Login view, where the user can select its permission mask.
"""
form_class = AuthenticationForm
form_class = CustomAuthenticationForm
@transaction.atomic
def form_valid(self, form):
logout(self.request)
self.request.user = form.get_user()
_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)
def logout_view(request):
logout(request)
return redirect('index')
class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
@ -272,7 +269,7 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
"class": "autocomplete form-control",
"id": "trusted",
"resetable": True,
"api_url": "/api/note/alias/",
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
"name_field": "name",
"placeholder": ""
}
@ -448,7 +445,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
club.update_membership_dates()
# 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())\
.order_by('user__last_name').all()
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
@ -544,6 +541,11 @@ class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
def get_queryset(self, **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
def get_success_url(self):
@ -595,7 +597,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
if "club_pk" in self.kwargs: # We create a new membership.
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
# Ensure that the user is member of the parent club and all its the family tree.
c = club
@ -659,7 +661,12 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
if not credit_type:
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.
# TODO Send a notification to the user (with a mail?) to tell her/him to credit her/his note
form.add_error('user',
@ -676,7 +683,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
error = True
# Must join the parent club before joining this club, except for the Kfet club where it can be at the same time.
if club.parent_club and not Membership.objects.filter(
if club.name != "Kfet" and club.parent_club and not Membership.objects.filter(
user=form.instance.user,
club=club.parent_club,
date_start__gte=club.parent_club.membership_start,
@ -724,6 +731,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
if credit_type is None:
@ -747,7 +755,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
# 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
# that should happens at least once a year.
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 + ")",
last_name=last_name,
first_name=first_name,
bank=bank,
valid=True,
)
transaction._force_save = True
@ -775,8 +784,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
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
if old_membership:
member_role = member_role.union(old_membership.roles.all())
@ -809,7 +819,8 @@ class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
form = super().get_form(form_class)
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
@ -855,7 +866,8 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV
).get(pk=self.kwargs["pk"])
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["only_active"] = "only_active" not in self.request.GET or self.request.GET["only_active"] != '0'

View file

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

View file

@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet)
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/transaction', TransactionViewSet)

View file

@ -160,13 +160,11 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
"""
queryset = super().get_queryset().distinct()
# Sqlite doesn't support ORDER BY in subqueries
queryset = queryset.order_by("name") \
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
alias = self.request.query_params.get("alias", None)
# Check if this is a valid regex. If not, we won't check regex
try:
re.compile(alias)
@ -176,14 +174,13 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
suffix = '__iregex' if valid_regex else '__istartswith'
alias_prefix = '^' if valid_regex else ''
queryset = queryset.prefetch_related('note')
if alias:
# We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias.
queryset = queryset.filter(
**{f'name{suffix}': alias_prefix + alias}
)
""".union(
).union(
queryset.filter(
Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(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'name{suffix}': alias_prefix + alias})
),
all=True)"""
all=True)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name")
return queryset#.distinct()
return queryset.distinct()
class TemplateCategoryViewSet(ReadProtectedModelViewSet):

View file

@ -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"
}
}
]

View file

@ -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.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
@ -11,31 +11,12 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('member', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('member', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'),
]
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(
name='Note',
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')),
('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')),
('inactivity_reason', models.CharField(blank=True, choices=[('manual', 'The user blocked his/her note manually, eg. when he/she left the school for holidays. It can be reactivated at any time.'), ('forced', "The note is blocked by the the BDE and can't be manually reactivated.")], default='', max_length=255)),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
('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_note.note_set+', to='contenttypes.ContentType')),
],
options={
'verbose_name': 'note',
@ -65,23 +46,41 @@ class Migration(migrations.Migration):
},
),
migrations.CreateModel(
name='SpecialTransaction',
name='Transaction',
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')),
('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=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={
'verbose_name': 'Special transaction',
'verbose_name_plural': 'Special transactions',
'verbose_name': 'transaction',
'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',),
),
migrations.CreateModel(
name='NoteClub',
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')),
('club', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to='member.club', verbose_name='club')),
('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': 'club note',
@ -92,7 +91,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='NoteSpecial',
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')),
],
options={
@ -101,15 +100,41 @@ class Migration(migrations.Migration):
},
bases=('note.note',),
),
migrations.AddField(
model_name='transaction',
name='destination',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_name='destination'),
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')),
],
options={
'verbose_name': "one's note",
'verbose_name_plural': 'users note',
},
bases=('note.note',),
),
migrations.AddField(
model_name='transaction',
name='source',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_name='source'),
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')),
],
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(
name='Alias',
@ -117,7 +142,7 @@ class Migration(migrations.Migration):
('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')),
('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={
'verbose_name': 'alias',
@ -133,62 +158,14 @@ class Migration(migrations.Migration):
('display', models.BooleanField(default=True, verbose_name='display')),
('highlighted', models.BooleanField(default=False, verbose_name='highlighted')),
('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')),
('destination', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.noteclub', verbose_name='destination')),
('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')),
],
options={
'verbose_name': 'transaction template',
'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(
model_name='transaction',
index=models.Index(fields=['created_at'], name='note_transa_created_bea8b1_idx'),
@ -201,6 +178,26 @@ class Migration(migrations.Migration):
model_name='transaction',
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(
model_name='alias',
index=models.Index(fields=['name'], name='note_alias_name_a89405_idx'),
@ -209,8 +206,4 @@ class Migration(migrations.Migration):
model_name='alias',
index=models.Index(fields=['normalized_name'], name='note_alias_normali_bd52b4_idx'),
),
migrations.AlterUniqueTogether(
name='trust',
unique_together={('trusting', 'trusted')},
),
]

View 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;"
),
]

View 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'),
),
]

View 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'),
),
]

View 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')},
},
),
]

View file

@ -189,7 +189,7 @@ class NoteClub(Note):
def send_mail_negative_balance(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))
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)
@ -219,7 +219,7 @@ class NoteSpecial(Note):
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
them

View file

@ -306,6 +306,11 @@ class SpecialTransaction(Transaction):
verbose_name=_("first_name"),
)
bank = models.CharField(
max_length=255,
verbose_name=_("bank"),
blank=True,
)
@property
def type(self):
@ -341,16 +346,20 @@ class SpecialTransaction(Transaction):
credit_type = form.cleaned_data["credit_type"]
last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
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:
form.add_error('last_name', _("This field is required."))
error = True
if not first_name:
form.add_error('first_name', _("This field is required."))
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

View file

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

View file

@ -21,7 +21,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-body text-center text-break p-2">
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
</div>
<div id="qrreader"></div>
</div>
</div>
@ -160,79 +159,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblock %}
{% 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">
{% for button in highlighted %}

View file

@ -14,7 +14,7 @@
</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>
@ -32,7 +32,7 @@
</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>)
pour payer par virement bancaire.
</p>
@ -40,7 +40,7 @@
--
<p>
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>
</body>
</html>

View file

@ -5,7 +5,7 @@
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>[Note Ker Lann] Liste des négatifs</title>
<title>[Note Kfet] Liste des négatifs</title>
</head>
<body>
<table>
@ -43,7 +43,7 @@
--
<p>
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>
</body>
</html>

View file

@ -8,7 +8,7 @@
<html lang="fr">
<head>
<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" %}">
<script src="https://{{ "NOTE_URL"|getenv }}{% static "bootstrap4/js/bootstrap.min.js" %}"></script>
@ -48,7 +48,7 @@
--
<p>
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>
</body>
</html>

View file

@ -47,7 +47,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
<div class="card-body text-center p-2">
<span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
</div>
<div id="qrreader"></div>
</div>
</div>
@ -140,6 +139,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
<input type="text" id="first_name" class="form-control" />
</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>
<hr>
<div class="form-row">
@ -163,75 +168,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
{% endblock %}
{% 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>
TRANSFER_POLYMORPHIC_CTYPE = {{ polymorphic_ctype }};
SPECIAL_TRANSFER_POLYMORPHIC_CTYPE = {{ special_polymorphic_ctype }};

View file

@ -34,7 +34,7 @@ class TestTransactions(TestCase):
membership = Membership.objects.create(club=Club.objects.get(name="BDE"), user=self.user)
membership.roles.add(Role.objects.get(name="Respo info"))
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.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.roles.add(Role.objects.get(name="Respo info"))
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.save()

View file

@ -1695,12 +1695,12 @@
"auth",
"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",
"mask": 2,
"field": "",
"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",
"pk": 1,
"fields": {
"for_club": null,
"name": "Adh\u00e9rent",
"for_club": 1,
"name": "Adh\u00e9rent BDE",
"permissions": [
1,
2,
@ -1957,7 +1957,6 @@
161,
162,
165,
178,
186,
187,
188,
@ -1973,15 +1972,26 @@
"model": "permission.role",
"pk": 2,
"fields": {
"for_club": null,
"name": "Pr\u00e9sident\u00b7e",
"for_club": 2,
"name": "Adh\u00e9rent Kfet",
"permissions": [
50,
59,
60,
61,
62,
169
22,
34,
36,
39,
40,
152,
153,
154,
155,
156,
157,
158,
159,
160,
179,
189,
190
]
}
},
@ -1990,34 +2000,9 @@
"pk": 3,
"fields": {
"for_club": null,
"name": "Tr\u00e9sorier\u00b7\u00e8re",
"name": "Membre de club",
"permissions": [
19,
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
22
]
}
},
@ -2026,22 +2011,12 @@
"pk": 4,
"fields": {
"for_club": null,
"name": "Secr\u00e9taire",
"name": "Bureau de club",
"permissions": [
21,
54,
55,
56,
57,
58,
59,
60,
61,
145,
146,
147,
176,
177
47,
49,
50,
169
]
}
},
@ -2050,6 +2025,114 @@
"pk": 5,
"fields": {
"for_club": null,
"name": "Pr\u00e9sident\u00b7e de club",
"permissions": [
50,
62
]
}
},
{
"model": "permission.role",
"pk": 6,
"fields": {
"for_club": null,
"name": "Tr\u00e9sorier\u00b7\u00e8re de club",
"permissions": [
59,
19,
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",
"permissions": [
1,
@ -2173,5 +2256,130 @@
196
]
}
},
{
"model": "permission.role",
"pk": 10,
"fields": {
"for_club": 2,
"name": "GC Kfet",
"permissions": [
32,
56,
58,
55,
57,
52,
23,
24,
25,
26,
27,
28,
29,
30,
31,
166,
167,
168,
170,
171,
176,
177,
178,
179,
180,
181,
182
]
}
},
{
"model": "permission.role",
"pk": 11,
"fields": {
"for_club": 2,
"name": "Res[pot]",
"permissions": [
37,
38,
41,
42,
43,
44,
45,
46,
148,
149,
182
]
}
},
{
"model": "permission.role",
"pk": 17,
"fields": {
"for_club": null,
"name": "1A",
"permissions": []
}
},
{
"model": "permission.role",
"pk": 19,
"fields": {
"for_club": 1,
"name": "Secrétaire BDE",
"permissions": [
54,
55,
56,
57,
58,
145,
146,
147,
150,
176,
177
]
}
},
{
"model": "permission.role",
"pk": 20,
"fields": {
"for_club": 1,
"name": "PC Kfet",
"permissions": [
6,
22,
24,
25,
26,
27,
30,
49,
50,
55,
56,
57,
58,
147,
150,
166,
167,
168,
176,
177,
180,
181
]
}
},
{
"model": "wei.weirole",
"pk": 17,
"fields": {}
}
]

View file

@ -1,7 +1,7 @@
# Generated by Django 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
import django.db.models.deletion
class Migration(migrations.Migration):
@ -9,11 +9,26 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('member', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'),
]
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(
name='PermissionMask',
fields=[
@ -26,35 +41,31 @@ class Migration(migrations.Migration):
'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(
name='Role',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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')),
('permissions', models.ManyToManyField(to='permission.permission', verbose_name='permissions')),
('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')),
],
options={
'verbose_name': 'role permissions',
'verbose_name_plural': 'role permissions',
},
),
migrations.AddField(
model_name='permission',
name='mask',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='permissions', to='permission.PermissionMask', verbose_name='mask'),
),
migrations.AddField(
model_name='permission',
name='model',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.ContentType', verbose_name='model'),
),
migrations.AlterUniqueTogether(
name='permission',
unique_together={('model', 'query', 'type', 'field')},
),
]

View file

@ -36,8 +36,11 @@ class RightsTable(tables.Table):
def render_roles(self, record):
# If the user has the right to manage the roles, display the link to manage them
roles = record.roles.filter((~(Q(name="Adhérent"))
)).all()
roles = record.roles.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))).all()
s = ", ".join(str(role) for role in roles)
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}))

View file

@ -6,7 +6,39 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% load render_table from django_tables2 %}
{% 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">
<h3 class="card-header text-center">
@ -31,6 +63,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
data-target="#collapse{{ role|slugify }}"
aria-expanded="true" aria-controls="collapse{{ role|slugify }}">
{{ 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.clubs %}
<small><span class="badge badge-success">{% trans "Owned" %} :
@ -65,25 +98,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endfor %}
</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 %}
{% block extrajavascript %}

View file

@ -58,7 +58,7 @@ class OAuth2TestCase(TestCase):
# Create membership to validate permissions
NoteUser.objects.create(user=self.user)
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()
# 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")
NoteUser.objects.create(user=self.user)
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()
resp = self.client.get(reverse('permission:scopes'))

View file

@ -11,6 +11,7 @@ from django.utils.crypto import get_random_string
from activity.models import Activity
from member.models import Club, Membership
from note.models import NoteUser
from wei.models import WEIClub, Bus, WEIRegistration
class TestPermissionDenied(TestCase):
@ -40,7 +41,7 @@ class TestPermissionDenied(TestCase):
name="",
description="",
creater=self.user,
activity_type_id=4,
activity_type_id=1,
organizer_id=1,
attendees_club_id=1,
date_start=timezone.now(),
@ -54,7 +55,7 @@ class TestPermissionDenied(TestCase):
name="",
description="",
creater=self.user,
activity_type_id=4,
activity_type_id=1,
organizer_id=1,
attendees_club_id=1,
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)))
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):
response = self.client.get(reverse("treasury:invoice_create"))

View file

@ -9,9 +9,9 @@ from django.core.exceptions import FieldError
from django.db.models import F, Q
from django.test import TestCase
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 wei.models import WEIMembership, WEIRegistration, WEIClub, Bus, BusTeam
from ..models import Permission
@ -23,22 +23,44 @@ class PermissionQueryTestCase(TestCase):
def setUpTestData(cls):
user = User.objects.create(username="user")
NoteUser.objects.create(user=user)
membership =Membership.objects.create(
user=user,
club=Club.objects.get(name="BDE")
wei = WEIClub.objects.create(
name="wei",
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):
"""
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():
try:
instanced = perm.about(
user=User.objects.get(),
club=WEIClub.objects.get(),
membership=Membership.objects.get(),
User=User,
Club=Club,

View file

@ -131,8 +131,11 @@ class RightsView(TemplateView):
special_memberships = Membership.objects.filter(
date_start__lte=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")\
.distinct().all()
context["special_memberships_table"] = RightsTable(special_memberships, prefix="clubs-")

View file

@ -10,7 +10,6 @@ from note_kfet.inputs import AmountInput
class SignUpForm(UserCreationForm):
usable_password = None
"""
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):
"""
Validate the inscription of the new users and pay memberships.
@ -75,27 +83,20 @@ class ValidationForm(forms.Form):
required=False,
)
bank = forms.CharField(
label=_("Bank"),
required=False,
)
join_bde = forms.BooleanField(
label=_("Join BDE Club"),
required=False,
initial=False,
initial=True,
)
join_bda = forms.BooleanField(
label=_("Join BDA Club"),
# The user can join the Kfet club at the inscription
join_kfet = forms.BooleanField(
label=_("Join Kfet Club"),
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,
)

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</p>
{% else %}
<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>
{% endif %}
{% else %}

View file

@ -14,7 +14,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans "An email has been sent. Please click on the link to activate your account." %}
</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>
</div>
</div>

View file

@ -41,7 +41,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<dd class="col-xl-6">{{ object.profile.phone_number }}</dd>
<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>
</div>
<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.val('{{ total_fee }}');
let bank = $("#id_bank");
bank.attr('disabled', true);
bank.val('Société générale');
let join_bde = $("#id_join_bde");
join_bde.attr('disabled', true);

View file

@ -4,7 +4,7 @@
<html lang="fr">
<head>
<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>
<body>
@ -13,12 +13,12 @@
</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>
<a href="http://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}">
http://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
<a href="https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}">
https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %}
</a>
</p>
@ -27,7 +27,7 @@
</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>
@ -36,6 +36,6 @@
--
<p>
{% trans "The Note Ker Lann team." %}<br>
{% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
{% trans "The Note Kfet team." %}<br>
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
</p>

View file

@ -2,15 +2,15 @@
{% 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 %}
{% 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 "The Note Ker Lann team." %}
{% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}
{% trans "The Note Kfet team." %}
{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

View file

@ -44,11 +44,10 @@ class TestSignup(TestCase):
promotion=Club.objects.get(name="BDE").membership_start.year,
address="Earth",
paid=False,
ml_events_registration="fr",
ml_events_registration="en",
ml_sport_registration=True,
ml_art_registration=True,
))
# Fail I don't know why ?
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
self.assertTrue(User.objects.filter(username="toto").exists())
user = User.objects.get(username="toto")
@ -188,15 +187,41 @@ class TestValidateRegistration(TestCase):
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
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_amount=0,
last_name="TOTO",
first_name="Toto",
bank="J'ai pas d'argent",
join_bde=True,
join_bda=False,
join_bds=False
join_kfet=True,
))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors)
@ -207,9 +232,9 @@ class TestValidateRegistration(TestCase):
credit_amount=4000,
last_name="",
first_name="",
bank="",
join_bde=True,
join_bda=False,
join_bds=False
join_kfet=True,
))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors)
@ -223,9 +248,9 @@ class TestValidateRegistration(TestCase):
credit_amount=500,
last_name="TOTO",
first_name="Toto",
bank="Société générale",
join_bde=True,
join_bda=False,
join_bds=False
join_kfet=False,
))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context["form"].errors)
@ -248,26 +273,25 @@ class TestValidateRegistration(TestCase):
credit_amount=500,
last_name="TOTO",
first_name="Toto",
bank="Société générale",
join_bde=True,
join_bda=False,
join_bds=True
join_kfet=False,
))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.user.profile.refresh_from_db()
self.assertTrue(self.user.profile.registration_valid)
self.assertTrue(NoteUser.objects.filter(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.assertTrue(Membership.objects.filter(club__name="BDS", user=self.user).exists())
self.assertFalse(Membership.objects.filter(club__name="Kfet", user=self.user).exists())
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())
self.assertEqual(response.status_code, 200)
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,)))
self.assertEqual(response.status_code, 200)
@ -283,19 +307,18 @@ class TestValidateRegistration(TestCase):
credit_amount=4000,
last_name="TOTO",
first_name="Toto",
bank="Société générale",
join_bde=True,
join_bda=True,
join_bds=True
join_kfet=True,
))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.user.profile.refresh_from_db()
self.assertTrue(self.user.profile.registration_valid)
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="BDA", user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="BDS", user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists())
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())
self.assertEqual(response.status_code, 200)

View file

@ -41,6 +41,7 @@ class UserCreateView(CreateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
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["last_report"]
@ -224,10 +225,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
user = self.get_object()
fee = 0
bde = Club.objects.get(name="BDE")
bda = Club.objects.get(name="BDA")
bds = Club.objects.get(name="BDS")
for auto_club in [bde, bda, bds]:
fee += auto_club.membership_fee_paid if user.profile.paid else auto_club.membership_fee_unpaid
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
kfet = Club.objects.get(name="Kfet")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
return ctx
@ -256,20 +256,30 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"]
first_name = form.cleaned_data["first_name"]
bank = form.cleaned_data["bank"]
join_bde = form.cleaned_data["join_bde"]
join_bda = form.cleaned_data["join_bda"]
join_bds = form.cleaned_data["join_bds"]
join_kfet = form.cleaned_data["join_kfet"]
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
fee = 0
bde = Club.objects.get(name="BDE")
bda = Club.objects.get(name="BDA")
bds = Club.objects.get(name="BDS")
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
fee += bd_fee if auto_join else 0
bde_fee = bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
# This is mandatory.
fee += bde_fee if join_bde else 0
kfet = Club.objects.get(name="Kfet")
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.
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)",
last_name=last_name,
first_name=first_name,
bank=bank,
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:
# Create membership for the user to the BDEAS starting today
membership = Membership(
club=auto_club,
user=user,
fee=bd_fee,
)
membership.save()
membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent"))
membership.save()
if join_bde:
# Create membership for the user to the BDE starting today
membership = Membership(
club=bde,
user=user,
fee=bde_fee,
)
membership.save()
membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
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
def get_success_url(self):

View file

@ -29,7 +29,7 @@ class InvoiceForm(forms.ModelForm):
class Meta:
model = Invoice
exclude = ('date', 'tex', )
exclude = ('bde', 'date', 'tex', )
class ProductForm(forms.ModelForm):
@ -132,6 +132,8 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
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)
def __init__(self, *args, **kwargs):
@ -146,6 +148,7 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
cleaned_data = super().clean()
self.instance.transaction.last_name = cleaned_data["last_name"]
self.instance.transaction.first_name = cleaned_data["first_name"]
self.instance.transaction.bank = cleaned_data["bank"]
return cleaned_data
@transaction.atomic

View file

@ -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 django.core.validators
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
@ -12,6 +12,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('note', '0001_initial'),
]
@ -20,7 +21,7 @@ class Migration(migrations.Migration):
name='Invoice',
fields=[
('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')),
('description', models.TextField(verbose_name='Description')),
('name', models.CharField(max_length=255, verbose_name='Name')),
@ -35,31 +36,6 @@ class Migration(migrations.Migration):
'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(
name='Remittance',
fields=[
@ -67,7 +43,6 @@ class Migration(migrations.Migration):
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date')),
('comment', models.CharField(max_length=255, verbose_name='Comment')),
('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={
'verbose_name': 'remittance',
@ -78,12 +53,55 @@ class Migration(migrations.Migration):
name='SpecialTransactionProxy',
fields=[
('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')),
('transaction', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.specialtransaction')),
('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')),
],
options={
'verbose_name': 'special transaction proxy',
'verbose_name_plural': 'special transaction proxies',
},
),
migrations.CreateModel(
name='SogeCredit',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('credit_transaction', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='note.SpecialTransaction', verbose_name='credit transaction')),
('transactions', models.ManyToManyField(related_name='_sogecredit_transactions_+', to='note.MembershipTransaction', verbose_name='membership transactions')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': 'Credit from the Société générale',
'verbose_name_plural': 'Credits from the Société générale',
},
),
migrations.CreateModel(
name='RemittanceType',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('note', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='note.NoteSpecial')),
],
options={
'verbose_name': 'remittance type',
'verbose_name_plural': 'remittance types',
},
),
migrations.AddField(
model_name='remittance',
name='remittance_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='treasury.RemittanceType', verbose_name='Type'),
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('designation', models.CharField(max_length=255, verbose_name='Designation')),
('quantity', models.PositiveIntegerField(verbose_name='Quantity')),
('amount', models.IntegerField(verbose_name='Unit price')),
('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='products', to='treasury.Invoice', verbose_name='invoice')),
],
options={
'verbose_name': 'product',
'verbose_name_plural': 'products',
},
),
]

View file

@ -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'),
),
]

View 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'),
),
]

View 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'),
),
]

View 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',
),
]

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View file

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Before After
Before After

View file

@ -118,5 +118,5 @@ class SpecialTransactionTable(tables.Table):
}
model = SpecialTransaction
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',)

View file

@ -47,6 +47,18 @@
{% 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 %%%%%%%%%%%%%%%%%%%%%
% Infos Association
@ -93,8 +105,8 @@
\renewcommand{\headrulewidth}{0pt}
\cfoot{
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 00 00 00 00\newline
Site web : TODO ~--~ E-mail : TODO \newline Numéro SIRET : 000 000 000 00000
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 89 88 56 50\newline
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
}
}

View file

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

View file

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

4
apps/wei/__init__.py Normal file
View 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
View 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
View file

View 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
View 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
View 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
View 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')

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