diff --git a/.gitignore b/.gitignore index caa056f..b31756b 100644 --- a/.gitignore +++ b/.gitignore @@ -54,5 +54,3 @@ ansible/host_vars/*.yaml !ansible/host_vars/bde* ansible/hosts -apps/member/migrations -apps/wei/migrations diff --git a/.gitpod.yml b/.gitpod.yml index b488382..6de1ba5 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -3,33 +3,39 @@ # 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 - init: sudo apt install --no-install-recommends -y \ + command: | + sudo apt update + sudo apt install --no-install-recommends -y \ ipython3 python3-setuptools python3-venv python3-dev \ texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git + gp sync-done apt - name : Install requirements - init: pip3 install -r requirements.txt + init : gp sync-await apt + command: | + pip3 install -r requirements.txt + gp sync-done pip - name : Setup env - 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')" + 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 + - name: Django start server + init : gp sync-await django command: python3 manage.py runserver 0.0.0.0:8000 ports: - - port: 8000 - onOpen: open-preview + - name: Web Dev Server + port: 8000 + visibility: public + diff --git a/README.md b/README.md index db695cf..06ba125 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,27 @@ 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@gitlab.crans.org:bde/nk20.git --recursive && cd nk20 + $ git clone git@github.com:jbdoderlein/notes-ker-lann.git --recursive && cd notes-ker-lann ``` 3. **Création d'un environment de travail Python décorrélé du système.** @@ -129,13 +145,13 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous. nginx python3-venv git acl ``` -2. **Clonage du dépot** dans `/var/www/note_kfet`, +2. **Clonage du dépot** dans `/var/www/notes-ker-lann`, ```bash - $ sudo mkdir -p /var/www/note_kfet && cd /var/www/note_kfet + $ sudo mkdir -p /var/www/notes-ker-lann && cd /var/www/notes-ker-lann $ sudo chown www-data:www-data . $ sudo chmod g+rwx . - $ sudo -u www-data git clone https://gitlab.crans.org/bde/nk20.git --recursive + $ sudo -u www-data git clone https://github.com/jbdoderlein/notes-ker-lann.git --recursive ``` 3. **Création d'un environment de travail Python décorrélé du système.** @@ -152,19 +168,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/note_kfet/nginx_note.conf /etc/nginx/sites-enabled/ + $ sudo ln -sf /var/www/notes-ker-lann/nginx_note.conf /etc/nginx/sites-enabled/ ``` Si l'on a un emperor (plusieurs instance uwsgi): ```bash - $ sudo ln -sf /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/sites/ + $ sudo ln -sf /var/www/notes-ker-lann/uwsgi_note.ini /etc/uwsgi/sites/ ``` Sinon si on est dans le cas habituel : ```bash - $ sudo ln -sf /var/www/note_kfet/uwsgi_note.ini /etc/uwsgi/apps-enabled/ + $ sudo ln -sf /var/www/notes-ker-lann/uwsgi_note.ini /etc/uwsgi/apps-enabled/ ``` Le touch-reload est activé par défault, pour redémarrer la note il suffit donc de faire `touch uwsgi_note.ini`. @@ -219,7 +235,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="note_kfet.settings + DJANGO_SETTINGS_MODULE="notes-ker-lann.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 @@ -247,21 +263,21 @@ Il est possible de travailler sur une instance Docker. Pour construire l'image Docker `nk20`, ``` -git clone https://gitlab.crans.org/bde/nk20/ --recursive && cd nk20 -docker build . -t nk20 +git clone https://github.com/jbdoderlein/notes-ker-lann.git/ --recursive && cd notes-ker-lann +docker build . -t notes-ker-lann ``` -Ensuite pour lancer la note Kfet en tant que vous (option `-u`), +Ensuite pour lancer la note de Ker Lann en tant que vous (option `-u`), l'exposer sur son port 80 (option `-p`) et monter le code en écriture (option `-v`), ``` -docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/note_kfet/" -p 80:8080 nk20 +docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/notes-ker-lann/" -p 80:8080 nk20 ``` Si vous souhaitez lancer une commande spéciale, vous pouvez l'ajouter à la fin, par exemple, ``` -docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/note_kfet/" -p 80:8080 nk20 python3 ./manage.py createsuperuser +docker run -it --rm -u $(id -u):$(id -g) -v "$(pwd):/var/www/notes-ker-lann/" -p 80:8080 nk20 python3 ./manage.py createsuperuser ``` #### Avec Docker Compose @@ -271,15 +287,15 @@ On vous conseilles de faire un fichier d'environnement `.env` en prenant exemple Pour par exemple utiliser le Docker de la note Kfet avec Traefik pour réaliser le HTTPS, ```YAML -nk20: - build: /chemin/vers/le/code/nk20 +notes-ker-lann: + build: /chemin/vers/le/code/notes-ker-lann volumes: - - /chemin/vers/le/code/nk20:/var/www/note_kfet/ - env_file: /chemin/vers/le/code/nk20/.env + - /chemin/vers/le/code/notes-ker-lann:/var/www/notes-ker-lann/ + env_file: /chemin/vers/le/code/notes-ker-lann/.env restart: always labels: - - "traefik.http.routers.nk20.rule=Host(`ndd.example.com`)" - - "traefik.http.services.nk20.loadbalancer.server.port=8080" + - "traefik.http.routers.notes-ker-lann.rule=Host(`ndd.example.com`)" + - "traefik.http.services.notes-ker-lann.loadbalancer.server.port=8080" ``` ## Documentation diff --git a/apps/activity/fixtures/initial.json b/apps/activity/fixtures/initial.json index 63c5009..2ebec84 100644 --- a/apps/activity/fixtures/initial.json +++ b/apps/activity/fixtures/initial.json @@ -1,32 +1 @@ -[ - { - "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 - } - } -] +[{"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}}] \ No newline at end of file diff --git a/apps/activity/forms.py b/apps/activity/forms.py index 1ca98ce..a37a2bf 100644 --- a/apps/activity/forms.py +++ b/apps/activity/forms.py @@ -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 Kfet club is attended - self.fields["attendees_club"].initial = Club.objects.get(name="Kfet") - self.fields["attendees_club"].widget.attrs["placeholder"] = "Kfet" + # 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" clubs = list(Club.objects.filter(PermissionBackend .filter_queryset(get_current_request(), Club, "view")).all()) shuffle(clubs) diff --git a/apps/activity/migrations/0001_initial.py b/apps/activity/migrations/0001_initial.py index ba2d68d..1ead0b8 100644 --- a/apps/activity/migrations/0001_initial.py +++ b/apps/activity/migrations/0001_initial.py @@ -1,7 +1,7 @@ -# Generated by Django 2.2.16 on 2020-09-04 21:41 +# Generated by Django 5.1 on 2024-08-13 09:26 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models 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. Kfet.', max_length=255, verbose_name='location')), + ('location', models.CharField(blank=True, default='', help_text='Place where the activity is organized, eg. BDE.', max_length=255, verbose_name='location')), ('date_start', models.DateTimeField(verbose_name='start date')), ('date_end', models.DateTimeField(verbose_name='end date')), ('valid', models.BooleanField(default=False, verbose_name='valid')), diff --git a/apps/activity/migrations/0002_auto_20200904_2341.py b/apps/activity/migrations/0002_initial.py similarity index 80% rename from apps/activity/migrations/0002_auto_20200904_2341.py rename to apps/activity/migrations/0002_initial.py index 4afa74d..ca40bef 100644 --- a/apps/activity/migrations/0002_auto_20200904_2341.py +++ b/apps/activity/migrations/0002_initial.py @@ -1,8 +1,8 @@ -# Generated by Django 2.2.16 on 2020-09-04 21:41 +# Generated by Django 5.1 on 2024-08-13 09:26 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): @@ -11,58 +11,16 @@ 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. Mostly the Kfet club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='attendees club'), + field=models.ForeignKey(help_text='Club that is authorized to join the activity.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.club', verbose_name='attendees club'), ), migrations.AddField( model_name='activity', @@ -72,7 +30,53 @@ 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'), + field=models.ForeignKey(help_text='Club that organizes the activity. The entry fees will go to this club.', on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.club', verbose_name='organizer'), + ), + migrations.AddField( + model_name='activity', + name='activity_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.activitytype', verbose_name='type'), + ), + migrations.AddField( + model_name='entry', + name='activity', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='entries', to='activity.activity', verbose_name='activity'), + ), + migrations.AddField( + model_name='entry', + name='note', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.noteuser', verbose_name='note'), + ), + migrations.AddField( + model_name='guest', + name='activity', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='activity.activity'), + ), + migrations.AddField( + model_name='guest', + name='inviter', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='guests', to='note.noteuser', verbose_name='inviter'), + ), + migrations.AddField( + model_name='entry', + name='guest', + field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='activity.guest'), + ), + migrations.CreateModel( + name='GuestTransaction', + fields=[ + ('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.transaction')), + ('entry', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, to='activity.entry')), + ], + options={ + 'abstract': False, + 'base_manager_name': 'objects', + }, + bases=('note.transaction',), + ), + migrations.AlterUniqueTogether( + name='activity', + unique_together={('name', 'date_start', 'date_end')}, ), migrations.AlterUniqueTogether( name='guest', @@ -82,8 +86,4 @@ class Migration(migrations.Migration): name='entry', unique_together={('activity', 'note', 'guest')}, ), - migrations.AlterUniqueTogether( - name='activity', - unique_together={('name', 'date_start', 'date_end')}, - ), ] diff --git a/apps/activity/models.py b/apps/activity/models.py index f8e5fab..b9541cb 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -73,7 +73,7 @@ class Activity(models.Model): max_length=255, blank=True, default="", - help_text=_("Place where the activity is organized, eg. Kfet."), + help_text=_("Place where the activity is organized, eg. BDE."), ) activity_type = models.ForeignKey( @@ -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. Mostly the Kfet club."), + help_text=_("Club that is authorized to join the activity."), ) date_start = models.DateTimeField( diff --git a/apps/activity/templates/activity/activity_entry.html b/apps/activity/templates/activity/activity_entry.html index 0286bdb..22374dd 100644 --- a/apps/activity/templates/activity/activity_entry.html +++ b/apps/activity/templates/activity/activity_entry.html @@ -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 Kfet member." %}", + "{% trans "Entry done, but caution: the user is not a 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 Kfet member." %}", + "{% trans "Entry done, but caution: the user is not a member." %}", "warning", 10000); else addMsg("{% trans "Entry done!" %}", "success", 4000); @@ -150,8 +150,7 @@ SPDX-License-Identifier: GPL-3.0-or-later "source": credit_id, "destination": target.attr('data-inviter'), "last_name": last_name, - "first_name": first_name, - "bank": "" + "first_name": first_name }).done(function () { makeTransaction(); reset(); diff --git a/apps/activity/templates/activity/includes/activity_info.html b/apps/activity/templates/activity/includes/activity_info.html index a16ad33..8619cf0 100644 --- a/apps/activity/templates/activity/includes/activity_info.html +++ b/apps/activity/templates/activity/includes/activity_info.html @@ -40,7 +40,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
Authorization: Token <TOKEN>
pour pouvoir vous identifier.- La Note Kfet implémente également le protocole OAuth2, afin de + La Note Ker Lann implémente également le protocole OAuth2, 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 Kfet. + s'agit d'avoir un site marchand sur lequel faire des transactions via la Note Ker Lann.
diff --git a/apps/member/templates/member/profile_trust.html b/apps/member/templates/member/profile_trust.html index bd8d6b5..696c0d4 100644 --- a/apps/member/templates/member/profile_trust.html +++ b/apps/member/templates/member/profile_trust.html @@ -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 kfet transfers to transfer money between + designed to simplify using note ker lann transfers to transfer money between users. The intent is that one person can make all transfers for a group of friends without needing additional rights among them. {% endblocktrans %} diff --git a/apps/member/tests/test_memberships.py b/apps/member/tests/test_memberships.py index a46a23e..b329138 100644 --- a/apps/member/tests/test_memberships.py +++ b/apps/member/tests/test_memberships.py @@ -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="Bureau de club")) + self.membership.roles.add(Role.objects.get(name="Pr\u00e9sident\u00b7e")) 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.filter(weirole__isnull=True).all()])) + Role.objects.all()])) self.assertEqual(response.status_code, 200) def test_render_club_add_member(self): @@ -179,20 +179,17 @@ class TestMemberships(TestCase): # We create a club without any parent and one club with parent BDE (that is the club Kfet) for bde_parent in False, True: - 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, - ) + 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) @@ -204,8 +201,7 @@ class TestMemberships(TestCase): credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_amount=4200, last_name="TOTO", - first_name="Toto", - bank="Le matelas", + first_name="Toto" )) self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200) @@ -223,11 +219,6 @@ 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) @@ -243,7 +234,6 @@ class TestMemberships(TestCase): credit_amount=14242, last_name="TOTO", first_name="Toto", - bank="Bank", )) self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200) @@ -261,11 +251,11 @@ class TestMemberships(TestCase): response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict( roles=[role.id for role in Role.objects.filter( - Q(name="Membre de club") | Q(name="Trésorier·ère de club") | Q(name="Bureau de club")).all()], + Q(name="Trésorier·ère")).all()], )) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.membership.refresh_from_db() - self.assertEqual(self.membership.roles.count(), 3) + self.assertEqual(self.membership.roles.count(), 1) def test_render_user_list(self): """ @@ -399,7 +389,7 @@ class TestMemberAPI(TestAPI): ) self.bde_membership = Membership.objects.create(user=self.user, club=Club.objects.get(name="BDE")) self.membership = Membership.objects.create(user=self.user, club=self.club) - self.membership.roles.add(Role.objects.get(name="Bureau de club")) + self.membership.roles.add(Role.objects.get(name="Pr\u00e9sident\u00b7e")) self.membership.save() def test_club_api(self): diff --git a/apps/member/views.py b/apps/member/views.py index 5605330..2b7a1a1 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -28,7 +28,7 @@ from permission.models import Role from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\ - CustomAuthenticationForm, MembershipRolesForm + MembershipRolesForm, AuthenticationForm from .models import Club, Membership from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable @@ -37,16 +37,19 @@ class CustomLoginView(LoginView): """ Login view, where the user can select its permission mask. """ - form_class = CustomAuthenticationForm + form_class = AuthenticationForm @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'] = form.cleaned_data['permission_mask'].rank + self.request.session['permission_mask'] = 42#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): """ @@ -269,7 +272,7 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): "class": "autocomplete form-control", "id": "trusted", "resetable": True, - "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", + "api_url": "/api/note/alias/", "name_field": "name", "placeholder": "" } @@ -445,7 +448,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): club.update_membership_dates() # managers list - managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club", + managers = Membership.objects.filter(club=self.object, roles__name="Pr\u00e9sident\u00b7e", date_start__lte=date.today(), date_end__gte=date.today())\ .order_by('user__last_name').all() context["managers"] = ClubManagerTable(data=managers, prefix="managers-") @@ -541,11 +544,6 @@ 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): @@ -597,7 +595,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"], weiclub=None) + .get(pk=self.kwargs["club_pk"]) 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 @@ -661,12 +659,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): if not credit_type: credit_amount = 0 - 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(): + if user.note.balance + credit_amount < fee: # 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', @@ -683,7 +676,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.name != "Kfet" and club.parent_club and not Membership.objects.filter( + if club.parent_club and not Membership.objects.filter( user=form.instance.user, club=club.parent_club, date_start__gte=club.parent_club.membership_start, @@ -731,7 +724,6 @@ 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: @@ -755,7 +747,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): # Now, all is fine, the membership can be created. - if club.name == "BDE" or club.name == "Kfet": + if club.name == "BDE" or club.name == "BDA" or club.name == "BDS": # When we renew the BDE membership, we update the profile section # that should happens at least once a year. user.profile.section = user.profile.section_generated @@ -772,7 +764,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): reason="Crédit " + credit_type.special_type + " (Adhésion " + club.name + ")", last_name=last_name, first_name=first_name, - bank=bank, valid=True, ) transaction._force_save = True @@ -784,9 +775,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): ret = super().form_valid(form) - 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() + member_role = Role.objects.filter(Q(name="Adhérent")).all() + # Set the same roles as before if old_membership: member_role = member_role.union(old_membership.roles.all()) @@ -819,8 +809,7 @@ class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): form = super().get_form(form_class) club = self.object.club - form.fields['roles'].queryset = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub')) - & (Q(for_club__isnull=True) | Q(for_club=club))).all() + form.fields['roles'].queryset = Role.objects.filter((Q(for_club__isnull=True) | Q(for_club=club))).all() return form @@ -866,8 +855,7 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV ).get(pk=self.kwargs["pk"]) context["club"] = club - applicable_roles = Role.objects.filter(Q(weirole__isnull=not hasattr(club, 'weiclub')) - & (Q(for_club__isnull=True) | Q(for_club=club))).all() + applicable_roles = Role.objects.filter((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' diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py index 33bf75b..c6f62e3 100644 --- a/apps/note/api/serializers.py +++ b/apps/note/api/serializers.py @@ -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, # Kfet + club=2, # BDA ).order_by("-date_start") if memberships.exists(): return MembershipSerializer().to_representation(memberships.first()) diff --git a/apps/note/api/urls.py b/apps/note/api/urls.py index d15e824..8e3b184 100644 --- a/apps/note/api/urls.py +++ b/apps/note/api/urls.py @@ -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) + router.register(path + '/consumer', ConsumerViewSet,"consumer") router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/transaction', TransactionViewSet) diff --git a/apps/note/api/views.py b/apps/note/api/views.py index 34ffaf2..6ee2c00 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -160,11 +160,13 @@ 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) @@ -174,13 +176,14 @@ 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}) @@ -191,12 +194,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): diff --git a/apps/note/fixtures/initial.json b/apps/note/fixtures/initial.json new file mode 100644 index 0000000..56be0fa --- /dev/null +++ b/apps/note/fixtures/initial.json @@ -0,0 +1,57 @@ +[ + { + "model": "note.templatecategory", + "pk": 1, + "fields": { + "name": "Soft" + } + }, + { + "model": "note.templatecategory", + "pk": 2, + "fields": { + "name": "Alcool" + } + }, + { + "model": "note.templatecategory", + "pk": 3, + "fields": { + "name": "Nourriture" + } + }, + { + "model": "note.templatecategory", + "pk": 4, + "fields": { + "name": "Clubs" + } + }, + { + "model": "note.templatecategory", + "pk": 5, + "fields": { + "name": "Goodies" + } + }, + { + "model": "note.templatecategory", + "pk": 6, + "fields": { + "name": "Autre" + } + }, + { + "model": "note.transactiontemplate", + "pk": 1, + "fields": { + "name": "Test Button", + "destination": 5, + "amount": 1, + "category": 6, + "display": false, + "highlighted": false, + "description": "Test button" + } + } +] \ No newline at end of file diff --git a/apps/note/migrations/0001_initial.py b/apps/note/migrations/0001_initial.py index a0ebda3..2eca3e2 100644 --- a/apps/note/migrations/0001_initial.py +++ b/apps/note/migrations/0001_initial.py @@ -1,9 +1,9 @@ -# Generated by Django 2.2.16 on 2020-09-04 21:41 +# Generated by Django 5.1 on 2024-08-13 09:26 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): @@ -11,12 +11,31 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('member', '0001_initial'), ('contenttypes', '0002_remove_content_type_name'), + ('member', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] 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=[ @@ -26,8 +45,8 @@ class Migration(migrations.Migration): ('display_image', models.ImageField(default='pic/default.png', max_length=255, upload_to='pic/', verbose_name='display image')), ('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(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')), + ('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')), ], options={ 'verbose_name': 'note', @@ -46,41 +65,23 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='Transaction', + name='SpecialTransaction', 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=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')), + ('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')), ], options={ - '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', + 'verbose_name': 'Special transaction', + 'verbose_name_plural': 'Special 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')), + ('note_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.note')), + ('club', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='note', to='member.club', verbose_name='club')), ], options={ 'verbose_name': 'club note', @@ -91,7 +92,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={ @@ -100,41 +101,15 @@ class Migration(migrations.Migration): }, bases=('note.note',), ), - 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='destination', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='note.note', verbose_name='destination'), ), - 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.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='Alias', @@ -142,7 +117,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, to='note.Note')), + ('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.note')), ], options={ 'verbose_name': 'alias', @@ -158,14 +133,62 @@ 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'), @@ -178,26 +201,6 @@ 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'), @@ -206,4 +209,8 @@ 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')}, + ), ] diff --git a/apps/note/migrations/0002_create_special_notes.py b/apps/note/migrations/0002_special_note.py similarity index 100% rename from apps/note/migrations/0002_create_special_notes.py rename to apps/note/migrations/0002_special_note.py diff --git a/apps/note/migrations/0003_replace_null_by_blank.py b/apps/note/migrations/0003_replace_null_by_blank.py deleted file mode 100644 index 21da860..0000000 --- a/apps/note/migrations/0003_replace_null_by_blank.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('note', '0002_create_special_notes'), - ] - - operations = [ - migrations.RunSQL( - "UPDATE note_note SET inactivity_reason = '' WHERE inactivity_reason IS NULL;" - ), - migrations.RunSQL( - "UPDATE note_transaction SET invalidity_reason = '' WHERE invalidity_reason IS NULL;" - ), - ] diff --git a/apps/note/migrations/0004_remove_null_tag_on_charfields.py b/apps/note/migrations/0004_remove_null_tag_on_charfields.py deleted file mode 100644 index 012fc35..0000000 --- a/apps/note/migrations/0004_remove_null_tag_on_charfields.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2.16 on 2020-09-06 19:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('note', '0003_replace_null_by_blank'), - ] - - operations = [ - migrations.AlterField( - model_name='note', - name='inactivity_reason', - field=models.CharField(blank=True, choices=[('manual', 'The user blocked his/her note manually, eg. when he/she left the school for holidays. It can be reactivated at any time.'), ('forced', "The note is blocked by the the BDE and can't be manually reactivated.")], default='', max_length=255), - ), - migrations.AlterField( - model_name='transaction', - name='invalidity_reason', - field=models.CharField(blank=True, default='', max_length=255, verbose_name='invalidity reason'), - ), - ] diff --git a/apps/note/migrations/0005_auto_20210313_1235.py b/apps/note/migrations/0005_auto_20210313_1235.py deleted file mode 100644 index 09696c3..0000000 --- a/apps/note/migrations/0005_auto_20210313_1235.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.19 on 2021-03-13 11:35 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('note', '0004_remove_null_tag_on_charfields'), - ] - - operations = [ - migrations.AlterField( - model_name='alias', - name='note', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='alias', to='note.Note'), - ), - ] diff --git a/apps/note/migrations/0006_trust.py b/apps/note/migrations/0006_trust.py deleted file mode 100644 index 4ed059f..0000000 --- a/apps/note/migrations/0006_trust.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2.24 on 2021-09-05 19:16 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('note', '0005_auto_20210313_1235'), - ] - - operations = [ - migrations.CreateModel( - name='Trust', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('trusted', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusted', to='note.Note', verbose_name='trusted')), - ('trusting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='trusting', to='note.Note', verbose_name='trusting')), - ], - options={ - 'verbose_name': 'frienship', - 'verbose_name_plural': 'friendships', - 'unique_together': {('trusting', 'trusted')}, - }, - ), - ] diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index 6db9e5f..bdf0354 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -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 Kfet] Passage en négatif (club {})".format(self.club.name), plain_text, + send_mail("[Note Ker Lann] Passage en négatif (club {})".format(self.club.name), plain_text, settings.DEFAULT_FROM_EMAIL, [self.club.email], html_message=html) @@ -219,7 +219,7 @@ class NoteSpecial(Note): class Trust(models.Model): """ - A one-sided trust relationship bertween two users + A one-sided trust relationship between two users If another user considers you as your friend, you can transfer money from them diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 3c6b4c7..09c9874 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -306,11 +306,6 @@ class SpecialTransaction(Transaction): verbose_name=_("first_name"), ) - bank = models.CharField( - max_length=255, - verbose_name=_("bank"), - blank=True, - ) @property def type(self): @@ -346,20 +341,16 @@ 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 or (not bank and credit_type.special_type == "Chèque"): + if not last_name or not first_name: 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 diff --git a/apps/note/templates/note/amount_input.html b/apps/note/templates/note/amount_input.html index d487311..3da9ac4 100644 --- a/apps/note/templates/note/amount_input.html +++ b/apps/note/templates/note/amount_input.html @@ -9,7 +9,12 @@ SPDX-License-Identifier: GPL-3.0-or-later name="{{ widget.name }}" {# Other attributes are loaded #} {% for name, value in widget.attrs.items %} - {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} + {% if value != False %} + {{ name }} + {% if value != True %} + ="{{ value|stringformat:'s' }}" + {% endif %} + {% endif %} {% endfor %}>
Le BDE
- {% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}
+ {% trans "Mail generated by the Note Ker Lann on the" %} {% now "j F Y à H:i:s" %}