diff --git a/Jenkinsfile b/Jenkinsfile index 054dfef..33d3fc1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,5 @@ node { - checkout scm + checkout scm } pipeline { diff --git a/docker/production/nginx/kompass.nginx.conf b/docker/production/nginx/kompass.nginx.conf index 860e127..0e8edbe 100644 --- a/docker/production/nginx/kompass.nginx.conf +++ b/docker/production/nginx/kompass.nginx.conf @@ -27,4 +27,3 @@ server { } } - diff --git a/jdav_web/contrib/management/commands/ensuresuperuser.py b/jdav_web/contrib/management/commands/ensuresuperuser.py index d701d64..857d732 100644 --- a/jdav_web/contrib/management/commands/ensuresuperuser.py +++ b/jdav_web/contrib/management/commands/ensuresuperuser.py @@ -1,28 +1,26 @@ import os + from django.contrib.auth import get_user_model from django.core.management.base import BaseCommand + class Command(BaseCommand): help = "Creates a super-user non-interactively if it doesn't exist." def handle(self, *args, **options): User = get_user_model() - username = os.environ.get('DJANGO_SUPERUSER_USERNAME', '') - password = os.environ.get('DJANGO_SUPERUSER_PASSWORD', '') + username = os.environ.get("DJANGO_SUPERUSER_USERNAME", "") + password = os.environ.get("DJANGO_SUPERUSER_PASSWORD", "") if not username or not password: - self.stdout.write( - self.style.WARNING('Superuser data was not set. Skipping.') - ) + self.stdout.write(self.style.WARNING("Superuser data was not set. Skipping.")) return if not User.objects.filter(username=username).exists(): User.objects.create_superuser(username=username, password=password) - self.stdout.write( - self.style.SUCCESS('Successfully created superuser.') - ) + self.stdout.write(self.style.SUCCESS("Successfully created superuser.")) else: self.stdout.write( - self.style.SUCCESS('Superuser with configured username already exists. Skipping.') + self.style.SUCCESS("Superuser with configured username already exists. Skipping.") ) diff --git a/jdav_web/contrib/templatetags/common.py b/jdav_web/contrib/templatetags/common.py index 4e2b350..7c3dbc2 100644 --- a/jdav_web/contrib/templatetags/common.py +++ b/jdav_web/contrib/templatetags/common.py @@ -3,6 +3,7 @@ from django.conf import settings register = template.Library() + # settings value @register.simple_tag def settings_value(name): diff --git a/jdav_web/finance/migrations/0001_initial.py b/jdav_web/finance/migrations/0001_initial.py index 5f37d7c..6934b07 100644 --- a/jdav_web/finance/migrations/0001_initial.py +++ b/jdav_web/finance/migrations/0001_initial.py @@ -1,136 +1,293 @@ # Generated by Django 4.0.1 on 2023-03-29 22:16 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - initial = True dependencies = [ - ('members', '0002_remove_member_not_waiting_and_more'), + ("members", "0002_remove_member_not_waiting_and_more"), ] operations = [ migrations.CreateModel( - name='Ledger', + name="Ledger", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=30, verbose_name='Name')), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ("name", models.CharField(max_length=30, verbose_name="Name")), ], options={ - 'verbose_name': 'Ledger', - 'verbose_name_plural': 'Ledgers', + "verbose_name": "Ledger", + "verbose_name_plural": "Ledgers", }, ), migrations.CreateModel( - name='Statement', + name="Statement", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('short_description', models.CharField(blank=True, max_length=30, verbose_name='Short description')), - ('explanation', models.TextField(blank=True, verbose_name='Explanation')), - ('night_cost', models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='Price per night')), - ('submitted', models.BooleanField(default=False, verbose_name='Submitted')), - ('submitted_date', models.DateTimeField(default=None, null=True, verbose_name='Submitted on')), - ('confirmed', models.BooleanField(default=False, verbose_name='Confirmed')), - ('confirmed_date', models.DateTimeField(default=None, null=True, verbose_name='Paid on')), - ('confirmed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='confirmed_statements', to='members.member', verbose_name='Authorized by')), - ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_statements', to='members.member', verbose_name='Created by')), - ('excursion', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='members.freizeit', verbose_name='Associated excursion')), - ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='submitted_statements', to='members.member', verbose_name='Submitted by')), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "short_description", + models.CharField(blank=True, max_length=30, verbose_name="Short description"), + ), + ("explanation", models.TextField(blank=True, verbose_name="Explanation")), + ( + "night_cost", + models.DecimalField( + decimal_places=2, default=0, max_digits=5, verbose_name="Price per night" + ), + ), + ("submitted", models.BooleanField(default=False, verbose_name="Submitted")), + ( + "submitted_date", + models.DateTimeField(default=None, null=True, verbose_name="Submitted on"), + ), + ("confirmed", models.BooleanField(default=False, verbose_name="Confirmed")), + ( + "confirmed_date", + models.DateTimeField(default=None, null=True, verbose_name="Paid on"), + ), + ( + "confirmed_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="confirmed_statements", + to="members.member", + verbose_name="Authorized by", + ), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="created_statements", + to="members.member", + verbose_name="Created by", + ), + ), + ( + "excursion", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="members.freizeit", + verbose_name="Associated excursion", + ), + ), + ( + "submitted_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="submitted_statements", + to="members.member", + verbose_name="Submitted by", + ), + ), ], options={ - 'verbose_name': 'Statement', - 'verbose_name_plural': 'Statements', - 'permissions': [('may_edit_submitted_statements', 'Is allowed to edit submitted statements')], + "verbose_name": "Statement", + "verbose_name_plural": "Statements", + "permissions": [ + ("may_edit_submitted_statements", "Is allowed to edit submitted statements") + ], }, ), migrations.CreateModel( - name='Transaction', + name="Transaction", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('reference', models.TextField(verbose_name='Reference')), - ('amount', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='Amount')), - ('confirmed', models.BooleanField(default=False, verbose_name='Paid')), - ('confirmed_date', models.DateTimeField(default=None, null=True, verbose_name='Paid on')), - ('confirmed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='confirmed_transactions', to='members.member', verbose_name='Authorized by')), - ('ledger', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='finance.ledger', verbose_name='Ledger')), - ('member', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='members.member', verbose_name='Recipient')), - ('statement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='finance.statement', verbose_name='Statement')), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ("reference", models.TextField(verbose_name="Reference")), + ( + "amount", + models.DecimalField(decimal_places=2, max_digits=6, verbose_name="Amount"), + ), + ("confirmed", models.BooleanField(default=False, verbose_name="Paid")), + ( + "confirmed_date", + models.DateTimeField(default=None, null=True, verbose_name="Paid on"), + ), + ( + "confirmed_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="confirmed_transactions", + to="members.member", + verbose_name="Authorized by", + ), + ), + ( + "ledger", + models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="finance.ledger", + verbose_name="Ledger", + ), + ), + ( + "member", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="members.member", + verbose_name="Recipient", + ), + ), + ( + "statement", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="finance.statement", + verbose_name="Statement", + ), + ), ], options={ - 'verbose_name': 'Transaction', - 'verbose_name_plural': 'Transactions', + "verbose_name": "Transaction", + "verbose_name_plural": "Transactions", }, ), migrations.CreateModel( - name='Receipt', + name="Receipt", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('short_description', models.CharField(max_length=30, verbose_name='Short description')), - ('amount', models.DecimalField(decimal_places=2, max_digits=6)), - ('comments', models.TextField()), - ('ledger', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='finance.ledger', verbose_name='Ledger')), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "short_description", + models.CharField(max_length=30, verbose_name="Short description"), + ), + ("amount", models.DecimalField(decimal_places=2, max_digits=6)), + ("comments", models.TextField()), + ( + "ledger", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="finance.ledger", + verbose_name="Ledger", + ), + ), ], ), migrations.CreateModel( - name='Bill', + name="Bill", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('short_description', models.CharField(max_length=30, verbose_name='Short description')), - ('explanation', models.TextField(blank=True, verbose_name='Explanation')), - ('amount', models.DecimalField(decimal_places=2, default=0, max_digits=6)), - ('costs_covered', models.BooleanField(default=False, verbose_name='Covered')), - ('refunded', models.BooleanField(default=False, verbose_name='Refunded')), - ('proof', models.ImageField(blank=True, upload_to='bill_images', verbose_name='Proof')), - ('paid_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='members.member', verbose_name='Paid by')), - ('statement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='finance.statement', verbose_name='Statement')), + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "short_description", + models.CharField(max_length=30, verbose_name="Short description"), + ), + ("explanation", models.TextField(blank=True, verbose_name="Explanation")), + ("amount", models.DecimalField(decimal_places=2, default=0, max_digits=6)), + ("costs_covered", models.BooleanField(default=False, verbose_name="Covered")), + ("refunded", models.BooleanField(default=False, verbose_name="Refunded")), + ( + "proof", + models.ImageField(blank=True, upload_to="bill_images", verbose_name="Proof"), + ), + ( + "paid_by", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="members.member", + verbose_name="Paid by", + ), + ), + ( + "statement", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="finance.statement", + verbose_name="Statement", + ), + ), ], options={ - 'verbose_name': 'Bill', - 'verbose_name_plural': 'Bills', + "verbose_name": "Bill", + "verbose_name_plural": "Bills", }, ), migrations.CreateModel( - name='StatementConfirmed', - fields=[ - ], + name="StatementConfirmed", + fields=[], options={ - 'verbose_name': 'Paid statement', - 'verbose_name_plural': 'Paid statements', - 'permissions': (('may_manage_confirmed_statements', 'Can view and manage confirmed statements.'),), - 'proxy': True, - 'indexes': [], - 'constraints': [], + "verbose_name": "Paid statement", + "verbose_name_plural": "Paid statements", + "permissions": ( + ( + "may_manage_confirmed_statements", + "Can view and manage confirmed statements.", + ), + ), + "proxy": True, + "indexes": [], + "constraints": [], }, - bases=('finance.statement',), + bases=("finance.statement",), ), migrations.CreateModel( - name='StatementSubmitted', - fields=[ - ], + name="StatementSubmitted", + fields=[], options={ - 'verbose_name': 'Submitted statement', - 'verbose_name_plural': 'Submitted statements', - 'permissions': (('may_manage_submitted_statements', 'Can view and manage submitted statements.'),), - 'proxy': True, - 'indexes': [], - 'constraints': [], + "verbose_name": "Submitted statement", + "verbose_name_plural": "Submitted statements", + "permissions": ( + ( + "may_manage_submitted_statements", + "Can view and manage submitted statements.", + ), + ), + "proxy": True, + "indexes": [], + "constraints": [], }, - bases=('finance.statement',), + bases=("finance.statement",), ), migrations.CreateModel( - name='StatementUnSubmitted', - fields=[ - ], + name="StatementUnSubmitted", + fields=[], options={ - 'verbose_name': 'Statement in preparation', - 'verbose_name_plural': 'Statements in preparation', - 'proxy': True, - 'indexes': [], - 'constraints': [], + "verbose_name": "Statement in preparation", + "verbose_name_plural": "Statements in preparation", + "proxy": True, + "indexes": [], + "constraints": [], }, - bases=('finance.statement',), + bases=("finance.statement",), ), ] diff --git a/jdav_web/finance/migrations/0002_alter_permissions.py b/jdav_web/finance/migrations/0002_alter_permissions.py index b1a80d6..11b2635 100644 --- a/jdav_web/finance/migrations/0002_alter_permissions.py +++ b/jdav_web/finance/migrations/0002_alter_permissions.py @@ -4,46 +4,50 @@ from django.db import migrations class Migration(migrations.Migration): - - #replaces = [('finance', '0002_billonexcursionproxy_billonstatementproxy_and_more'), ('finance', '0003_alter_statementunsubmitted_options'), ('finance', '0004_alter_billonexcursionproxy_options'), ('finance', '0005_alter_billonstatementproxy_options'), ('finance', '0006_alter_statementsubmitted_options'), ('finance', '0007_alter_billonexcursionproxy_options_and_more')] + # replaces = [('finance', '0002_billonexcursionproxy_billonstatementproxy_and_more'), ('finance', '0003_alter_statementunsubmitted_options'), ('finance', '0004_alter_billonexcursionproxy_options'), ('finance', '0005_alter_billonstatementproxy_options'), ('finance', '0006_alter_statementsubmitted_options'), ('finance', '0007_alter_billonexcursionproxy_options_and_more')] dependencies = [ - ('finance', '0001_initial'), + ("finance", "0001_initial"), ] operations = [ migrations.AlterModelOptions( - name='statementsubmitted', - options={'permissions': [('process_statementsubmitted', 'Can manage submitted statements.')], 'verbose_name': 'Submitted statement', 'verbose_name_plural': 'Submitted statements'}, + name="statementsubmitted", + options={ + "permissions": [("process_statementsubmitted", "Can manage submitted statements.")], + "verbose_name": "Submitted statement", + "verbose_name_plural": "Submitted statements", + }, ), migrations.CreateModel( - name='BillOnExcursionProxy', - fields=[ - ], + name="BillOnExcursionProxy", + fields=[], options={ - 'verbose_name': 'Bill', - 'verbose_name_plural': 'Bills', - 'proxy': True, - 'indexes': [], - 'constraints': [], + "verbose_name": "Bill", + "verbose_name_plural": "Bills", + "proxy": True, + "indexes": [], + "constraints": [], }, - bases=('finance.bill',), + bases=("finance.bill",), ), migrations.CreateModel( - name='BillOnStatementProxy', - fields=[ - ], + name="BillOnStatementProxy", + fields=[], options={ - 'verbose_name': 'Bill', - 'verbose_name_plural': 'Bills', - 'proxy': True, - 'indexes': [], - 'constraints': [], + "verbose_name": "Bill", + "verbose_name_plural": "Bills", + "proxy": True, + "indexes": [], + "constraints": [], }, - bases=('finance.bill',), + bases=("finance.bill",), ), migrations.AlterModelOptions( - name='statementunsubmitted', - options={'verbose_name': 'Statement in preparation', 'verbose_name_plural': 'Statements in preparation'}, + name="statementunsubmitted", + options={ + "verbose_name": "Statement in preparation", + "verbose_name_plural": "Statements in preparation", + }, ), ] diff --git a/jdav_web/finance/migrations/0003_alter_bill_options_and_more.py b/jdav_web/finance/migrations/0003_alter_bill_options_and_more.py index e7f04b4..94d76a9 100644 --- a/jdav_web/finance/migrations/0003_alter_bill_options_and_more.py +++ b/jdav_web/finance/migrations/0003_alter_bill_options_and_more.py @@ -4,38 +4,121 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('finance', '0002_alter_permissions'), + ("finance", "0002_alter_permissions"), ] operations = [ migrations.AlterModelOptions( - name='bill', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'verbose_name': 'Bill', 'verbose_name_plural': 'Bills'}, + name="bill", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "verbose_name": "Bill", + "verbose_name_plural": "Bills", + }, ), migrations.AlterModelOptions( - name='billonexcursionproxy', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'verbose_name': 'Bill', 'verbose_name_plural': 'Bills'}, + name="billonexcursionproxy", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "verbose_name": "Bill", + "verbose_name_plural": "Bills", + }, ), migrations.AlterModelOptions( - name='billonstatementproxy', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'verbose_name': 'Bill', 'verbose_name_plural': 'Bills'}, + name="billonstatementproxy", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "verbose_name": "Bill", + "verbose_name_plural": "Bills", + }, ), migrations.AlterModelOptions( - name='statement', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'permissions': [('may_edit_submitted_statements', 'Is allowed to edit submitted statements')], 'verbose_name': 'Statement', 'verbose_name_plural': 'Statements'}, + name="statement", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "permissions": [ + ("may_edit_submitted_statements", "Is allowed to edit submitted statements") + ], + "verbose_name": "Statement", + "verbose_name_plural": "Statements", + }, ), migrations.AlterModelOptions( - name='statementconfirmed', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'permissions': [('may_manage_confirmed_statements', 'Can view and manage confirmed statements.')], 'verbose_name': 'Paid statement', 'verbose_name_plural': 'Paid statements'}, + name="statementconfirmed", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "permissions": [ + ("may_manage_confirmed_statements", "Can view and manage confirmed statements.") + ], + "verbose_name": "Paid statement", + "verbose_name_plural": "Paid statements", + }, ), migrations.AlterModelOptions( - name='statementsubmitted', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'permissions': [('process_statementsubmitted', 'Can manage submitted statements.')], 'verbose_name': 'Submitted statement', 'verbose_name_plural': 'Submitted statements'}, + name="statementsubmitted", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "permissions": [("process_statementsubmitted", "Can manage submitted statements.")], + "verbose_name": "Submitted statement", + "verbose_name_plural": "Submitted statements", + }, ), migrations.AlterModelOptions( - name='statementunsubmitted', - options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'verbose_name': 'Statement in preparation', 'verbose_name_plural': 'Statements in preparation'}, + name="statementunsubmitted", + options={ + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "verbose_name": "Statement in preparation", + "verbose_name_plural": "Statements in preparation", + }, ), ] diff --git a/jdav_web/finance/migrations/0004_alter_bill_amount.py b/jdav_web/finance/migrations/0004_alter_bill_amount.py index 127b3ee..21826f6 100644 --- a/jdav_web/finance/migrations/0004_alter_bill_amount.py +++ b/jdav_web/finance/migrations/0004_alter_bill_amount.py @@ -1,18 +1,20 @@ # Generated by Django 4.0.1 on 2024-12-02 00:22 -from django.db import migrations, models +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - dependencies = [ - ('finance', '0003_alter_bill_options_and_more'), + ("finance", "0003_alter_bill_options_and_more"), ] operations = [ migrations.AlterField( - model_name='bill', - name='amount', - field=models.DecimalField(decimal_places=2, default=0, max_digits=6, verbose_name='Amount'), + model_name="bill", + name="amount", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=6, verbose_name="Amount" + ), ), ] diff --git a/jdav_web/finance/migrations/0005_alter_bill_proof.py b/jdav_web/finance/migrations/0005_alter_bill_proof.py index e87ad53..e8986c5 100644 --- a/jdav_web/finance/migrations/0005_alter_bill_proof.py +++ b/jdav_web/finance/migrations/0005_alter_bill_proof.py @@ -1,19 +1,20 @@ # Generated by Django 4.0.1 on 2024-12-26 09:45 -from django.db import migrations import utils +from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('finance', '0004_alter_bill_amount'), + ("finance", "0004_alter_bill_amount"), ] operations = [ migrations.AlterField( - model_name='bill', - name='proof', - field=utils.RestrictedFileField(blank=True, upload_to='bill_images', verbose_name='Proof'), + model_name="bill", + name="proof", + field=utils.RestrictedFileField( + blank=True, upload_to="bill_images", verbose_name="Proof" + ), ), ] diff --git a/jdav_web/finance/migrations/0006_statement_add_allowance_to_subsidy_to.py b/jdav_web/finance/migrations/0006_statement_add_allowance_to_subsidy_to.py index df2d124..7eeb7e2 100644 --- a/jdav_web/finance/migrations/0006_statement_add_allowance_to_subsidy_to.py +++ b/jdav_web/finance/migrations/0006_statement_add_allowance_to_subsidy_to.py @@ -1,26 +1,38 @@ # Generated by Django 4.0.1 on 2025-01-18 19:08 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - dependencies = [ - ('members', '0032_member_upload_registration_form_key'), - ('members', '0033_freizeit_approved_extra_youth_leader_count'), - ('finance', '0005_alter_bill_proof'), + ("members", "0032_member_upload_registration_form_key"), + ("members", "0033_freizeit_approved_extra_youth_leader_count"), + ("finance", "0005_alter_bill_proof"), ] operations = [ migrations.AddField( - model_name='statement', - name='allowance_to', - field=models.ManyToManyField(help_text='The youth leaders to which an allowance should be paid. The count must match the number of permitted youth leaders.', related_name='receives_allowance_for_statements', to='members.Member', verbose_name='Pay allowance to'), + model_name="statement", + name="allowance_to", + field=models.ManyToManyField( + help_text="The youth leaders to which an allowance should be paid. The count must match the number of permitted youth leaders.", + related_name="receives_allowance_for_statements", + to="members.Member", + verbose_name="Pay allowance to", + ), ), migrations.AddField( - model_name='statement', - name='subsidy_to', - field=models.ForeignKey(help_text='The person that should receive the subsidy for night and travel costs. Typically the person who paid for them.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='receives_subsidy_for_statements', to='members.member', verbose_name='Pay subsidy to'), + model_name="statement", + name="subsidy_to", + field=models.ForeignKey( + help_text="The person that should receive the subsidy for night and travel costs. Typically the person who paid for them.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="receives_subsidy_for_statements", + to="members.member", + verbose_name="Pay subsidy to", + ), ), ] diff --git a/jdav_web/finance/migrations/0007_alter_statement_allowance_to.py b/jdav_web/finance/migrations/0007_alter_statement_allowance_to.py index 237ced2..35923c1 100644 --- a/jdav_web/finance/migrations/0007_alter_statement_allowance_to.py +++ b/jdav_web/finance/migrations/0007_alter_statement_allowance_to.py @@ -1,19 +1,25 @@ # Generated by Django 4.0.1 on 2025-01-18 22:00 -from django.db import migrations, models +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - dependencies = [ - ('members', '0033_freizeit_approved_extra_youth_leader_count'), - ('finance', '0006_statement_add_allowance_to_subsidy_to'), + ("members", "0033_freizeit_approved_extra_youth_leader_count"), + ("finance", "0006_statement_add_allowance_to_subsidy_to"), ] operations = [ migrations.AlterField( - model_name='statement', - name='allowance_to', - field=models.ManyToManyField(blank=True, help_text='The youth leaders to which an allowance should be paid. The count must match the number of permitted youth leaders.', related_name='receives_allowance_for_statements', to='members.Member', verbose_name='Pay allowance to'), + model_name="statement", + name="allowance_to", + field=models.ManyToManyField( + blank=True, + help_text="The youth leaders to which an allowance should be paid. The count must match the number of permitted youth leaders.", + related_name="receives_allowance_for_statements", + to="members.Member", + verbose_name="Pay allowance to", + ), ), ] diff --git a/jdav_web/finance/migrations/0008_alter_statement_allowance_to_and_more.py b/jdav_web/finance/migrations/0008_alter_statement_allowance_to_and_more.py index faf7504..e0529e1 100644 --- a/jdav_web/finance/migrations/0008_alter_statement_allowance_to_and_more.py +++ b/jdav_web/finance/migrations/0008_alter_statement_allowance_to_and_more.py @@ -1,25 +1,39 @@ # Generated by Django 4.0.1 on 2025-01-23 22:16 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - dependencies = [ - ('members', '0033_freizeit_approved_extra_youth_leader_count'), - ('finance', '0007_alter_statement_allowance_to'), + ("members", "0033_freizeit_approved_extra_youth_leader_count"), + ("finance", "0007_alter_statement_allowance_to"), ] operations = [ migrations.AlterField( - model_name='statement', - name='allowance_to', - field=models.ManyToManyField(blank=True, help_text='The youth leaders to which an allowance should be paid.', related_name='receives_allowance_for_statements', to='members.Member', verbose_name='Pay allowance to'), + model_name="statement", + name="allowance_to", + field=models.ManyToManyField( + blank=True, + help_text="The youth leaders to which an allowance should be paid.", + related_name="receives_allowance_for_statements", + to="members.Member", + verbose_name="Pay allowance to", + ), ), migrations.AlterField( - model_name='statement', - name='subsidy_to', - field=models.ForeignKey(blank=True, help_text='The person that should receive the subsidy for night and travel costs. Typically the person who paid for them.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='receives_subsidy_for_statements', to='members.member', verbose_name='Pay subsidy to'), + model_name="statement", + name="subsidy_to", + field=models.ForeignKey( + blank=True, + help_text="The person that should receive the subsidy for night and travel costs. Typically the person who paid for them.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="receives_subsidy_for_statements", + to="members.member", + verbose_name="Pay subsidy to", + ), ), ] diff --git a/jdav_web/finance/migrations/0009_statement_ljp_to.py b/jdav_web/finance/migrations/0009_statement_ljp_to.py index fc13323..c1279e9 100644 --- a/jdav_web/finance/migrations/0009_statement_ljp_to.py +++ b/jdav_web/finance/migrations/0009_statement_ljp_to.py @@ -1,20 +1,28 @@ # Generated by Django 4.2.20 on 2025-04-03 21:04 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations +from django.db import models class Migration(migrations.Migration): - dependencies = [ - ('members', '0039_membertraining_certificate_attendance'), - ('finance', '0008_alter_statement_allowance_to_and_more'), + ("members", "0039_membertraining_certificate_attendance"), + ("finance", "0008_alter_statement_allowance_to_and_more"), ] operations = [ migrations.AddField( - model_name='statement', - name='ljp_to', - field=models.ForeignKey(blank=True, help_text='The person that should receive the ljp contributions for the participants. Should be only selected if an ljp request was submitted.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='receives_ljp_for_statements', to='members.member', verbose_name='Pay ljp contributions to'), + model_name="statement", + name="ljp_to", + field=models.ForeignKey( + blank=True, + help_text="The person that should receive the ljp contributions for the participants. Should be only selected if an ljp request was submitted.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="receives_ljp_for_statements", + to="members.member", + verbose_name="Pay ljp contributions to", + ), ), ] diff --git a/jdav_web/finance/migrations/0010_statement_status.py b/jdav_web/finance/migrations/0010_statement_status.py index 7c3a4d3..ed5987d 100644 --- a/jdav_web/finance/migrations/0010_statement_status.py +++ b/jdav_web/finance/migrations/0010_statement_status.py @@ -1,6 +1,7 @@ # Generated by Django 4.2.20 on 2025-10-11 15:43 -from django.db import migrations, models +from django.db import migrations +from django.db import models def set_status_from_old_fields(apps, schema_editor): @@ -10,7 +11,7 @@ def set_status_from_old_fields(apps, schema_editor): - If submitted is True but confirmed is False, status = SUBMITTED (1) - Otherwise, status = UNSUBMITTED (0) """ - Statement = apps.get_model('finance', 'Statement') + Statement = apps.get_model("finance", "Statement") UNSUBMITTED, SUBMITTED, CONFIRMED = 0, 1, 2 for statement in Statement.objects.all(): @@ -20,20 +21,23 @@ def set_status_from_old_fields(apps, schema_editor): statement.status = SUBMITTED else: statement.status = UNSUBMITTED - statement.save(update_fields=['status']) + statement.save(update_fields=["status"]) class Migration(migrations.Migration): - dependencies = [ - ('finance', '0009_statement_ljp_to'), + ("finance", "0009_statement_ljp_to"), ] operations = [ migrations.AddField( - model_name='statement', - name='status', - field=models.IntegerField(choices=[(0, 'In preparation'), (1, 'Submitted'), (2, 'Confirmed')], default=0, verbose_name='Status'), + model_name="statement", + name="status", + field=models.IntegerField( + choices=[(0, "In preparation"), (1, "Submitted"), (2, "Confirmed")], + default=0, + verbose_name="Status", + ), ), migrations.RunPython(set_status_from_old_fields, reverse_code=migrations.RunPython.noop), ] diff --git a/jdav_web/finance/migrations/0011_remove_statement_confirmed_and_submitted.py b/jdav_web/finance/migrations/0011_remove_statement_confirmed_and_submitted.py index 53e36a4..fffd505 100644 --- a/jdav_web/finance/migrations/0011_remove_statement_confirmed_and_submitted.py +++ b/jdav_web/finance/migrations/0011_remove_statement_confirmed_and_submitted.py @@ -4,18 +4,17 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('finance', '0010_statement_status'), + ("finance", "0010_statement_status"), ] operations = [ migrations.RemoveField( - model_name='statement', - name='confirmed', + model_name="statement", + name="confirmed", ), migrations.RemoveField( - model_name='statement', - name='submitted', + model_name="statement", + name="submitted", ), ] diff --git a/jdav_web/finance/migrations/0012_statementonexcursionproxy.py b/jdav_web/finance/migrations/0012_statementonexcursionproxy.py index ab95dca..5da8f5c 100644 --- a/jdav_web/finance/migrations/0012_statementonexcursionproxy.py +++ b/jdav_web/finance/migrations/0012_statementonexcursionproxy.py @@ -4,25 +4,30 @@ from django.db import migrations class Migration(migrations.Migration): - dependencies = [ - ('finance', '0011_remove_statement_confirmed_and_submitted'), + ("finance", "0011_remove_statement_confirmed_and_submitted"), ] operations = [ migrations.CreateModel( - name='StatementOnExcursionProxy', - fields=[ - ], + name="StatementOnExcursionProxy", + fields=[], options={ - 'verbose_name': 'Statement', - 'verbose_name_plural': 'Statements', - 'abstract': False, - 'proxy': True, - 'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), - 'indexes': [], - 'constraints': [], + "verbose_name": "Statement", + "verbose_name_plural": "Statements", + "abstract": False, + "proxy": True, + "default_permissions": ( + "add_global", + "change_global", + "view_global", + "delete_global", + "list_global", + "view", + ), + "indexes": [], + "constraints": [], }, - bases=('finance.statement',), + bases=("finance.statement",), ), ] diff --git a/jdav_web/finance/templates/admin/confirmed_statement.html b/jdav_web/finance/templates/admin/confirmed_statement.html index 23e0c2c..7b05716 100644 --- a/jdav_web/finance/templates/admin/confirmed_statement.html +++ b/jdav_web/finance/templates/admin/confirmed_statement.html @@ -81,7 +81,7 @@ links.forEach(link => { imageContainer.innerHTML = ''; // Update the image element - + if(imageText == "") { imageContainer.innerHTML = '{% trans "No QR code can be displayed." %}'; } else { @@ -99,7 +99,7 @@ links.forEach(link => { link.text = '{% trans "Showing" %}'; }); }); - + diff --git a/jdav_web/finance/templates/admin/overview_submitted_statement.html b/jdav_web/finance/templates/admin/overview_submitted_statement.html index c8dc382..7814701 100644 --- a/jdav_web/finance/templates/admin/overview_submitted_statement.html +++ b/jdav_web/finance/templates/admin/overview_submitted_statement.html @@ -124,9 +124,9 @@ {% if statement.ljp_to %}
-{% blocktrans %} The youth leaders have documented interventions worth of {{ total_seminar_days }} seminar -days for {{ participant_count }} eligible participants. Taking into account the maximum contribution quota -of 90% and possible taxes ({{ ljp_tax }}%), this results in a total of {{ paid_ljp_contributions }}€. +{% blocktrans %} The youth leaders have documented interventions worth of {{ total_seminar_days }} seminar +days for {{ participant_count }} eligible participants. Taking into account the maximum contribution quota +of 90% and possible taxes ({{ ljp_tax }}%), this results in a total of {{ paid_ljp_contributions }}€. Once their proposal was approved, the ljp contributions of should be paid to:{% endblocktrans %}
|
diff --git a/jdav_web/finance/templates/finance/statement_summary.tex b/jdav_web/finance/templates/finance/statement_summary.tex
index 99657e4..0ceeb8e 100644
--- a/jdav_web/finance/templates/finance/statement_summary.tex
+++ b/jdav_web/finance/templates/finance/statement_summary.tex
@@ -78,7 +78,7 @@ Zuschüsse und Aufwandsentschädigung werden wie folgt abgerufen:
\noindent\textbf{LJP-Zuschüsse}
\noindent Der LJP-Zuschuss für die Teilnehmenden in Höhe von {{ statement.paid_ljp_contributions|esc_all }} € wird überwiesen an:
-{{ statement.ljp_to.name|esc_all }} Dieser Zuschuss wird aus Landesmitteln gewährt und ist daher
+{{ statement.ljp_to.name|esc_all }} Dieser Zuschuss wird aus Landesmitteln gewährt und ist daher
in der Ausgabenübersicht gesondert aufgeführt.
{% endif %}
diff --git a/jdav_web/finance/tests/__init__.py b/jdav_web/finance/tests/__init__.py
index 023e7d7..8b70455 100644
--- a/jdav_web/finance/tests/__init__.py
+++ b/jdav_web/finance/tests/__init__.py
@@ -1,4 +1,6 @@
+# ruff: noqa F403
+
from .admin import *
+from .migrations import *
from .models import *
from .rules import *
-from .migrations import *
diff --git a/jdav_web/finance/tests/admin.py b/jdav_web/finance/tests/admin.py
index 996074d..72c0d47 100644
--- a/jdav_web/finance/tests/admin.py
+++ b/jdav_web/finance/tests/admin.py
@@ -1,32 +1,33 @@
-import unittest
from http import HTTPStatus
-from django.test import TestCase, override_settings
+
from django.contrib.admin.sites import AdminSite
-from django.test import RequestFactory, Client
-from django.contrib.auth.models import User, Permission
from django.contrib.auth import models as authmodels
-from django.utils import timezone
-from django.contrib.sessions.middleware import SessionMiddleware
+from django.contrib.auth.models import User
+from django.contrib.messages import get_messages
from django.contrib.messages.middleware import MessageMiddleware
from django.contrib.messages.storage.fallback import FallbackStorage
-from django.contrib.messages import get_messages
+from django.contrib.sessions.middleware import SessionMiddleware
+from django.test import Client
+from django.test import RequestFactory
+from django.test import TestCase
+from django.urls import reverse
+from django.utils import timezone
from django.utils.translation import gettext_lazy as _
-from django.urls import reverse, reverse_lazy
-from django.http import HttpResponseRedirect, HttpResponse
-from unittest.mock import Mock, patch
-from django.test.utils import override_settings
-from django.urls import path, include
-from django.contrib import admin as django_admin
-
+from members.models import Freizeit
+from members.models import GEMEINSCHAFTS_TOUR
+from members.models import MALE
+from members.models import Member
+from members.models import MUSKELKRAFT_ANREISE
from members.tests.utils import create_custom_user
-from members.models import Member, MALE, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE
-from ..models import (
- Ledger, Statement, StatementUnSubmitted, StatementConfirmed, Transaction, Bill,
- StatementSubmitted
-)
-from ..admin import (
- LedgerAdmin, StatementAdmin, TransactionAdmin, BillAdmin
-)
+
+from ..admin import StatementAdmin
+from ..admin import TransactionAdmin
+from ..models import Bill
+from ..models import Ledger
+from ..models import Statement
+from ..models import StatementConfirmed
+from ..models import StatementUnSubmitted
+from ..models import Transaction
class AdminTestCase(TestCase):
@@ -35,17 +36,15 @@ class AdminTestCase(TestCase):
self.model = model
if model is not None and admin is not None:
self.admin = admin(model, AdminSite())
- superuser = User.objects.create_superuser(
- username='superuser', password='secret'
- )
- standard = create_custom_user('standard', ['Standard'], 'Paul', 'Wulter')
- trainer = create_custom_user('trainer', ['Standard', 'Trainings'], 'Lise', 'Lotte')
- treasurer = create_custom_user('treasurer', ['Standard', 'Finance'], 'Lara', 'Litte')
- materialwarden = create_custom_user('materialwarden', ['Standard', 'Material'], 'Loro', 'Lutte')
+ User.objects.create_superuser(username="superuser", password="secret")
+ create_custom_user("standard", ["Standard"], "Paul", "Wulter")
+ create_custom_user("trainer", ["Standard", "Trainings"], "Lise", "Lotte")
+ create_custom_user("treasurer", ["Standard", "Finance"], "Lara", "Litte")
+ create_custom_user("materialwarden", ["Standard", "Material"], "Loro", "Lutte")
def _login(self, name):
c = Client()
- res = c.login(username=name, password='secret')
+ res = c.login(username=name, password="secret")
# make sure we logged in
assert res
return c
@@ -57,62 +56,64 @@ class StatementUnSubmittedAdminTestCase(AdminTestCase):
def setUp(self):
super().setUp(model=Statement, admin=StatementAdmin)
- self.superuser = User.objects.get(username='superuser')
+ self.superuser = User.objects.get(username="superuser")
self.member = Member.objects.create(
- prename="Test", lastname="User", birth_date=timezone.now().date(),
- email="test@example.com", gender=MALE, user=self.superuser
+ prename="Test",
+ lastname="User",
+ birth_date=timezone.now().date(),
+ email="test@example.com",
+ gender=MALE,
+ user=self.superuser,
)
self.statement = StatementUnSubmitted.objects.create(
- short_description='Test Statement',
- explanation='Test explanation',
- night_cost=25
+ short_description="Test Statement", explanation="Test explanation", night_cost=25
)
# Create excursion for testing
self.excursion = Freizeit.objects.create(
- name='Test Excursion',
+ name="Test Excursion",
kilometers_traveled=100,
tour_type=GEMEINSCHAFTS_TOUR,
tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=1
+ difficulty=1,
)
# Create confirmed statement with excursion
self.statement_with_excursion = StatementUnSubmitted.objects.create(
- short_description='With Excursion',
- explanation='Test explanation',
+ short_description="With Excursion",
+ explanation="Test explanation",
night_cost=25,
excursion=self.excursion,
)
def test_save_model_with_member(self):
"""Test save_model sets created_by for new objects"""
- request = self.factory.post('/')
+ request = self.factory.post("/")
request.user = self.superuser
# Test with change=False (new object)
- new_statement = Statement(short_description='New Statement')
+ new_statement = Statement(short_description="New Statement")
self.admin.save_model(request, new_statement, None, change=False)
self.assertEqual(new_statement.created_by, self.member)
def test_has_delete_permission(self):
"""Test if unsubmitted statements may be deleted"""
- request = self.factory.post('/')
+ request = self.factory.post("/")
request.user = self.superuser
self.assertTrue(self.admin.has_delete_permission(request, self.statement))
def test_get_fields(self):
"""Test get_fields when excursion is set or not set."""
- request = self.factory.post('/')
+ request = self.factory.post("/")
request.user = self.superuser
- self.assertIn('excursion', self.admin.get_fields(request, self.statement_with_excursion))
- self.assertNotIn('excursion', self.admin.get_fields(request, self.statement))
- self.assertNotIn('excursion', self.admin.get_fields(request))
+ self.assertIn("excursion", self.admin.get_fields(request, self.statement_with_excursion))
+ self.assertNotIn("excursion", self.admin.get_fields(request, self.statement))
+ self.assertNotIn("excursion", self.admin.get_fields(request))
def test_get_inlines(self):
"""Test get_inlines"""
- request = self.factory.post('/')
+ request = self.factory.post("/")
request.user = self.superuser
self.assertEqual(len(self.admin.get_inlines(request, self.statement)), 1)
@@ -121,46 +122,44 @@ class StatementUnSubmittedAdminTestCase(AdminTestCase):
# Mark statement as submitted
self.statement.status = Statement.SUBMITTED
readonly_fields = self.admin.get_readonly_fields(None, self.statement)
- self.assertIn('status', readonly_fields)
- self.assertIn('excursion', readonly_fields)
- self.assertIn('short_description', readonly_fields)
+ self.assertIn("status", readonly_fields)
+ self.assertIn("excursion", readonly_fields)
+ self.assertIn("short_description", readonly_fields)
def test_get_readonly_fields_not_submitted(self):
"""Test readonly fields when statement is not submitted"""
readonly_fields = self.admin.get_readonly_fields(None, self.statement)
- self.assertEqual(readonly_fields, ['status', 'excursion'])
+ self.assertEqual(readonly_fields, ["status", "excursion"])
def test_submit_view_insufficient_permission(self):
- url = reverse('admin:finance_statement_submit',
- args=(self.statement.pk,))
- c = self._login('standard')
+ url = reverse("admin:finance_statement_submit", args=(self.statement.pk,))
+ c = self._login("standard")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Insufficient permissions.'))
+ self.assertContains(response, _("Insufficient permissions."))
def test_submit_view_get(self):
- url = reverse('admin:finance_statement_submit',
- args=(self.statement.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_submit", args=(self.statement.pk,))
+ c = self._login("superuser")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Submit statement'))
+ self.assertContains(response, _("Submit statement"))
def test_submit_view_get_with_excursion(self):
- url = reverse('admin:finance_statement_submit',
- args=(self.statement_with_excursion.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_submit", args=(self.statement_with_excursion.pk,))
+ c = self._login("superuser")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Finance overview'))
+ self.assertContains(response, _("Finance overview"))
def test_submit_view_post(self):
- url = reverse('admin:finance_statement_submit',
- args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'apply': ''})
+ url = reverse("admin:finance_statement_submit", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"apply": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- text = _("Successfully submited %(name)s. The finance department will notify the requestors as soon as possible.") % {'name': str(self.statement)}
+ text = _(
+ "Successfully submited %(name)s. The finance department will notify the requestors as soon as possible."
+ ) % {"name": str(self.statement)}
self.assertContains(response, text)
@@ -170,79 +169,86 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
def setUp(self):
super().setUp(model=Statement, admin=StatementAdmin)
- self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
+ self.user = User.objects.create_user("testuser", "test@example.com", "pass")
self.member = Member.objects.create(
- prename="Test", lastname="User", birth_date=timezone.now().date(),
- email="test@example.com", gender=MALE, user=self.user
+ prename="Test",
+ lastname="User",
+ birth_date=timezone.now().date(),
+ email="test@example.com",
+ gender=MALE,
+ user=self.user,
)
- self.finance_user = User.objects.create_user('finance', 'finance@example.com', 'pass')
- self.finance_user.groups.add(authmodels.Group.objects.get(name='Finance'),
- authmodels.Group.objects.get(name='Standard'))
+ self.finance_user = User.objects.create_user("finance", "finance@example.com", "pass")
+ self.finance_user.groups.add(
+ authmodels.Group.objects.get(name="Finance"),
+ authmodels.Group.objects.get(name="Standard"),
+ )
self.statement = Statement.objects.create(
- short_description='Submitted Statement',
- explanation='Test explanation',
+ short_description="Submitted Statement",
+ explanation="Test explanation",
status=Statement.SUBMITTED,
submitted_by=self.member,
submitted_date=timezone.now(),
- night_cost=25
+ night_cost=25,
)
self.statement_unsubmitted = StatementUnSubmitted.objects.create(
- short_description='Submitted Statement',
- explanation='Test explanation',
- night_cost=25
+ short_description="Submitted Statement", explanation="Test explanation", night_cost=25
)
self.transaction = Transaction.objects.create(
- reference='verylonglong' * 14,
+ reference="verylonglong" * 14,
amount=3,
statement=self.statement,
member=self.member,
)
# Create commonly used test objects
- self.ledger = Ledger.objects.create(name='Test Ledger')
+ self.ledger = Ledger.objects.create(name="Test Ledger")
self.excursion = Freizeit.objects.create(
- name='Test Excursion',
+ name="Test Excursion",
kilometers_traveled=100,
tour_type=GEMEINSCHAFTS_TOUR,
tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=1
+ difficulty=1,
)
self.other_member = Member.objects.create(
- prename="Other", lastname="Member", birth_date=timezone.now().date(),
- email="other@example.com", gender=MALE
+ prename="Other",
+ lastname="Member",
+ birth_date=timezone.now().date(),
+ email="other@example.com",
+ gender=MALE,
)
# Create statements for generate transactions tests
self.statement_no_trans_success = Statement.objects.create(
- short_description='No Transactions Success',
- explanation='Test explanation',
+ short_description="No Transactions Success",
+ explanation="Test explanation",
status=Statement.SUBMITTED,
submitted_by=self.member,
submitted_date=timezone.now(),
- night_cost=25
+ night_cost=25,
)
self.statement_no_trans_error = Statement.objects.create(
- short_description='No Transactions Error',
- explanation='Test explanation',
+ short_description="No Transactions Error",
+ explanation="Test explanation",
status=Statement.SUBMITTED,
submitted_by=self.member,
submitted_date=timezone.now(),
- night_cost=25
+ night_cost=25,
)
# Create bills for generate transactions tests
self.bill_for_success = Bill.objects.create(
statement=self.statement_no_trans_success,
- short_description='Test Bill Success',
+ short_description="Test Bill Success",
amount=50,
paid_by=self.member,
- costs_covered=True
+ costs_covered=True,
)
self.bill_for_error = Bill.objects.create(
statement=self.statement_no_trans_error,
- short_description='Test Bill Error',
+ short_description="Test Bill Error",
amount=50,
paid_by=None, # No payer will cause generate_transactions to fail
costs_covered=True,
@@ -252,57 +258,56 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
"""Helper method to create a bill that matches transaction amount"""
return Bill.objects.create(
statement=statement or self.statement,
- short_description='Test Bill',
+ short_description="Test Bill",
amount=amount or self.transaction.amount,
paid_by=self.member,
- costs_covered=True
+ costs_covered=True,
)
def _create_non_matching_bill(self, statement=None, amount=100):
"""Helper method to create a bill that doesn't match transaction amount"""
return Bill.objects.create(
statement=statement or self.statement,
- short_description='Non-matching Bill',
+ short_description="Non-matching Bill",
amount=amount,
- paid_by=self.member
+ paid_by=self.member,
)
def test_has_change_permission_with_permission(self):
"""Test change permission with proper permission"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.finance_user
self.assertTrue(self.admin.has_change_permission(request))
def test_has_change_permission_without_permission(self):
"""Test change permission without proper permission"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.user
self.assertFalse(self.admin.has_change_permission(request))
def test_has_delete_permission(self):
"""Test that delete permission is disabled"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.finance_user
self.assertFalse(self.admin.has_delete_permission(request))
def test_readonly_fields(self):
- self.assertNotIn('explanation',
- self.admin.get_readonly_fields(None, self.statement_unsubmitted))
+ self.assertNotIn(
+ "explanation", self.admin.get_readonly_fields(None, self.statement_unsubmitted)
+ )
def test_change(self):
- url = reverse('admin:finance_statement_change',
- args=(self.statement.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_change", args=(self.statement.pk,))
+ c = self._login("superuser")
response = c.get(url)
self.assertEqual(response.status_code, HTTPStatus.OK)
def test_overview_view(self):
- url = reverse('admin:finance_statement_overview',
- args=(self.statement.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
response = c.get(url)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('View submitted statement'))
+ self.assertContains(response, _("View submitted statement"))
def test_overview_view_statement_not_found(self):
"""Test overview_view with statement that can't be found in StatementSubmitted queryset"""
@@ -311,8 +316,8 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
self.statement.status = Statement.UNSUBMITTED
self.statement.save()
- url = reverse('admin:finance_statement_overview', args=(self.statement.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
messages = list(get_messages(response.wsgi_request))
@@ -328,11 +333,13 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
# Create a bill that matches the transaction amount to make it valid
self._create_matching_bill()
- url = reverse('admin:finance_statement_overview', args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'transaction_execution_confirm': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"transaction_execution_confirm": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- success_text = _("Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again.") % {'name': str(self.statement)}
+ success_text = _(
+ "Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again."
+ ) % {"name": str(self.statement)}
self.assertContains(response, success_text)
self.statement.refresh_from_db()
self.assertTrue(self.statement.confirmed)
@@ -346,9 +353,9 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
# Create a bill that matches the transaction amount to make it valid
self._create_matching_bill()
- url = reverse('admin:finance_statement_overview', args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'transaction_execution_confirm_and_send': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"transaction_execution_confirm_and_send": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
success_text = _("Successfully sent receipt to the office.")
self.assertContains(response, success_text)
@@ -363,24 +370,24 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
# Create a bill that matches the transaction amount to make total valid
self._create_matching_bill()
- url = reverse('admin:finance_statement_overview',
- args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, data={'confirm': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, data={"confirm": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Statement confirmed'))
+ self.assertContains(response, _("Statement confirmed"))
def test_overview_view_confirm_non_matching_transactions(self):
"""Test overview_view confirm with non-matching transactions"""
# Create a bill that doesn't match the transaction
self._create_non_matching_bill()
- url = reverse('admin:finance_statement_overview',
- args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'confirm': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"confirm": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- error_text = _("Transactions do not match the covered expenses. Please correct the mistakes listed below.")
+ error_text = _(
+ "Transactions do not match the covered expenses. Please correct the mistakes listed below."
+ )
self.assertContains(response, error_text)
def test_overview_view_confirm_missing_ledger(self):
@@ -392,14 +399,15 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
# Create a bill that matches the transaction amount to pass the first check
self._create_matching_bill()
- url = reverse('admin:finance_statement_overview',
- args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'confirm': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"confirm": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
# Check the Django messages for the error
messages = list(get_messages(response.wsgi_request))
- expected_text = str(_("Some transactions have no ledger configured. Please fill in the gaps."))
+ expected_text = str(
+ _("Some transactions have no ledger configured. Please fill in the gaps.")
+ )
self.assertTrue(any(expected_text in str(msg) for msg in messages))
def test_overview_view_confirm_invalid_allowance_to(self):
@@ -420,24 +428,30 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
# Check validity obstruction is allowances
self.assertEqual(self.statement_no_trans_success.validity, Statement.INVALID_ALLOWANCE_TO)
- url = reverse('admin:finance_statement_overview',
- args=(self.statement_no_trans_success.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'confirm': ''})
+ url = reverse(
+ "admin:finance_statement_overview", args=(self.statement_no_trans_success.pk,)
+ )
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"confirm": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
# Check the Django messages for the error
messages = list(get_messages(response.wsgi_request))
- expected_text = str(_("The configured recipients for the allowance don't match the regulations. Please correct this on the excursion."))
+ expected_text = str(
+ _(
+ "The configured recipients for the allowance don't match the regulations. Please correct this on the excursion."
+ )
+ )
self.assertTrue(any(expected_text in str(msg) for msg in messages))
def test_overview_view_reject(self):
"""Test overview_view reject statement"""
- url = reverse('admin:finance_statement_overview', args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'reject': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"reject": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- success_text = _("Successfully rejected %(name)s. The requestor can reapply, when needed.") %\
- {'name': str(self.statement)}
+ success_text = _(
+ "Successfully rejected %(name)s. The requestor can reapply, when needed."
+ ) % {"name": str(self.statement)}
self.assertContains(response, success_text)
# Verify statement was rejected
@@ -449,45 +463,53 @@ class StatementSubmittedAdminTestCase(AdminTestCase):
# Ensure there's already a transaction
self.assertTrue(self.statement.transaction_set.count() > 0)
- url = reverse('admin:finance_statement_overview', args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'generate_transactions': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"generate_transactions": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- error_text = _("%(name)s already has transactions. Please delete them first, if you want to generate new ones") % {'name': str(self.statement)}
+ error_text = _(
+ "%(name)s already has transactions. Please delete them first, if you want to generate new ones"
+ ) % {"name": str(self.statement)}
self.assertContains(response, error_text)
def test_overview_view_generate_transactions_success(self):
"""Test overview_view generate transactions successfully"""
- url = reverse('admin:finance_statement_overview',
- args=(self.statement_no_trans_success.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'generate_transactions': ''})
+ url = reverse(
+ "admin:finance_statement_overview", args=(self.statement_no_trans_success.pk,)
+ )
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"generate_transactions": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- success_text = _("Successfully generated transactions for %(name)s") %\
- {'name': str(self.statement_no_trans_success)}
+ success_text = _("Successfully generated transactions for %(name)s") % {
+ "name": str(self.statement_no_trans_success)
+ }
self.assertContains(response, success_text)
def test_overview_view_generate_transactions_error(self):
"""Test overview_view generate transactions with error"""
- url = reverse('admin:finance_statement_overview',
- args=(self.statement_no_trans_error.pk,))
- c = self._login('superuser')
- response = c.post(url, follow=True, data={'generate_transactions': ''})
+ url = reverse("admin:finance_statement_overview", args=(self.statement_no_trans_error.pk,))
+ c = self._login("superuser")
+ response = c.post(url, follow=True, data={"generate_transactions": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
messages = list(get_messages(response.wsgi_request))
- expected_text = str(_("Error while generating transactions for %(name)s. Do all bills have a payer and, if this statement is attached to an excursion, was a person selected that receives the subsidies?") %\
- {'name': str(self.statement_no_trans_error)})
+ expected_text = str(
+ _(
+ "Error while generating transactions for %(name)s. Do all bills have a payer and, if this statement is attached to an excursion, was a person selected that receives the subsidies?"
+ )
+ % {"name": str(self.statement_no_trans_error)}
+ )
self.assertTrue(any(expected_text in str(msg) for msg in messages))
def test_reduce_transactions_view(self):
- url = reverse('admin:finance_statement_reduce_transactions',
- args=(self.statement.pk,))
- c = self._login('superuser')
- response = c.get(url, data={'redirectTo': reverse('admin:finance_statement_changelist')},
- follow=True)
- self.assertContains(response,
- _("Successfully reduced transactions for %(name)s.") %\
- {'name': str(self.statement)})
+ url = reverse("admin:finance_statement_reduce_transactions", args=(self.statement.pk,))
+ c = self._login("superuser")
+ response = c.get(
+ url, data={"redirectTo": reverse("admin:finance_statement_changelist")}, follow=True
+ )
+ self.assertContains(
+ response,
+ _("Successfully reduced transactions for %(name)s.") % {"name": str(self.statement)},
+ )
class StatementConfirmedAdminTestCase(AdminTestCase):
@@ -496,24 +518,30 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
def setUp(self):
super().setUp(model=Statement, admin=StatementAdmin)
- self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
+ self.user = User.objects.create_user("testuser", "test@example.com", "pass")
self.member = Member.objects.create(
- prename="Test", lastname="User", birth_date=timezone.now().date(),
- email="test@example.com", gender=MALE, user=self.user
+ prename="Test",
+ lastname="User",
+ birth_date=timezone.now().date(),
+ email="test@example.com",
+ gender=MALE,
+ user=self.user,
)
- self.finance_user = User.objects.create_user('finance', 'finance@example.com', 'pass')
- self.finance_user.groups.add(authmodels.Group.objects.get(name='Finance'),
- authmodels.Group.objects.get(name='Standard'))
+ self.finance_user = User.objects.create_user("finance", "finance@example.com", "pass")
+ self.finance_user.groups.add(
+ authmodels.Group.objects.get(name="Finance"),
+ authmodels.Group.objects.get(name="Standard"),
+ )
# Create a base statement first
base_statement = Statement.objects.create(
- short_description='Confirmed Statement',
- explanation='Test explanation',
+ short_description="Confirmed Statement",
+ explanation="Test explanation",
status=Statement.CONFIRMED,
confirmed_by=self.member,
confirmed_date=timezone.now(),
- night_cost=25
+ night_cost=25,
)
# StatementConfirmed is a proxy model, so we can get it from the base statement
@@ -521,32 +549,34 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
# Create an unconfirmed statement for testing
self.unconfirmed_statement = Statement.objects.create(
- short_description='Unconfirmed Statement',
- explanation='Test explanation',
+ short_description="Unconfirmed Statement",
+ explanation="Test explanation",
status=Statement.SUBMITTED,
- night_cost=25
+ night_cost=25,
)
# Create excursion for testing
self.excursion = Freizeit.objects.create(
- name='Test Excursion',
+ name="Test Excursion",
kilometers_traveled=100,
tour_type=GEMEINSCHAFTS_TOUR,
tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=1
+ difficulty=1,
)
# Create confirmed statement with excursion
confirmed_with_excursion_base = Statement.objects.create(
- short_description='Confirmed with Excursion',
- explanation='Test explanation',
+ short_description="Confirmed with Excursion",
+ explanation="Test explanation",
status=Statement.CONFIRMED,
confirmed_by=self.member,
confirmed_date=timezone.now(),
excursion=self.excursion,
- night_cost=25
+ night_cost=25,
+ )
+ self.statement_with_excursion = StatementConfirmed.objects.get(
+ pk=confirmed_with_excursion_base.pk
)
- self.statement_with_excursion = StatementConfirmed.objects.get(pk=confirmed_with_excursion_base.pk)
def _add_session_to_request(self, request):
"""Add session to request"""
@@ -560,20 +590,20 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
def test_has_change_permission(self):
"""Test that change permission is disabled"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.finance_user
self.assertFalse(self.admin.has_change_permission(request, self.statement))
def test_has_delete_permission(self):
"""Test that delete permission is disabled"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.finance_user
self.assertFalse(self.admin.has_delete_permission(request, self.statement))
def test_unconfirm_view_not_confirmed_statement(self):
"""Test unconfirm_view with statement that is not confirmed"""
# Create request for unconfirmed statement
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.finance_user
self._add_session_to_request(request)
@@ -589,7 +619,7 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
def test_unconfirm_view_post_unconfirm_action(self):
"""Test unconfirm_view POST request with 'unconfirm' action"""
# Create POST request with unconfirm action
- request = self.factory.post('/', {'unconfirm': 'true'})
+ request = self.factory.post("/", {"unconfirm": "true"})
request.user = self.finance_user
self._add_session_to_request(request)
@@ -612,7 +642,7 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
def test_unconfirm_view_get_render_template(self):
"""Test unconfirm_view GET request rendering template"""
# Create GET request (no POST data)
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.finance_user
self._add_session_to_request(request)
@@ -626,33 +656,30 @@ class StatementConfirmedAdminTestCase(AdminTestCase):
self.assertEqual(response.status_code, 200)
# Check response content contains expected template elements
- self.assertIn(str(_('Unconfirm statement')).encode('utf-8'), response.content)
+ self.assertIn(str(_("Unconfirm statement")).encode("utf-8"), response.content)
self.assertIn(self.statement.short_description.encode(), response.content)
def test_statement_summary_view_insufficient_permission(self):
- url = reverse('admin:finance_statement_summary',
- args=(self.statement_with_excursion.pk,))
- c = self._login('standard')
+ url = reverse("admin:finance_statement_summary", args=(self.statement_with_excursion.pk,))
+ c = self._login("standard")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Insufficient permissions.'))
+ self.assertContains(response, _("Insufficient permissions."))
def test_statement_summary_view_unconfirmed(self):
- url = reverse('admin:finance_statement_summary',
- args=(self.unconfirmed_statement.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_summary", args=(self.unconfirmed_statement.pk,))
+ c = self._login("superuser")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Statement not found.'))
+ self.assertContains(response, _("Statement not found."))
def test_statement_summary_view_confirmed_with_excursion(self):
"""Test statement_summary_view when statement is confirmed with excursion"""
- url = reverse('admin:finance_statement_summary',
- args=(self.statement_with_excursion.pk,))
- c = self._login('superuser')
+ url = reverse("admin:finance_statement_summary", args=(self.statement_with_excursion.pk,))
+ c = self._login("superuser")
response = c.get(url, follow=True)
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertEqual(response.headers['Content-Type'], 'application/pdf')
+ self.assertEqual(response.headers["Content-Type"], "application/pdf")
class TransactionAdminTestCase(TestCase):
@@ -663,41 +690,44 @@ class TransactionAdminTestCase(TestCase):
self.factory = RequestFactory()
self.admin = TransactionAdmin(Transaction, self.site)
- self.user = User.objects.create_user('testuser', 'test@example.com', 'pass')
+ self.user = User.objects.create_user("testuser", "test@example.com", "pass")
self.member = Member.objects.create(
- prename="Test", lastname="User", birth_date=timezone.now().date(),
- email="test@example.com", gender=MALE, user=self.user
+ prename="Test",
+ lastname="User",
+ birth_date=timezone.now().date(),
+ email="test@example.com",
+ gender=MALE,
+ user=self.user,
)
- self.ledger = Ledger.objects.create(name='Test Ledger')
+ self.ledger = Ledger.objects.create(name="Test Ledger")
self.statement = Statement.objects.create(
- short_description='Test Statement',
- explanation='Test explanation'
+ short_description="Test Statement", explanation="Test explanation"
)
self.transaction = Transaction.objects.create(
member=self.member,
ledger=self.ledger,
amount=100,
- reference='Test transaction',
- statement=self.statement
+ reference="Test transaction",
+ statement=self.statement,
)
def test_has_add_permission(self):
"""Test that add permission is disabled"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.user
self.assertFalse(self.admin.has_add_permission(request))
def test_has_change_permission(self):
"""Test that change permission is disabled"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.user
self.assertFalse(self.admin.has_change_permission(request))
def test_has_delete_permission(self):
"""Test that delete permission is disabled"""
- request = self.factory.get('/')
+ request = self.factory.get("/")
request.user = self.user
self.assertFalse(self.admin.has_delete_permission(request))
diff --git a/jdav_web/finance/tests/migrations.py b/jdav_web/finance/tests/migrations.py
index 5c8894a..9608061 100644
--- a/jdav_web/finance/tests/migrations.py
+++ b/jdav_web/finance/tests/migrations.py
@@ -1,5 +1,4 @@
import django.test
-from django.apps import apps
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
@@ -7,9 +6,9 @@ from django.db.migrations.executor import MigrationExecutor
class StatusMigrationTestCase(django.test.TransactionTestCase):
"""Test the migration from submitted/confirmed fields to status field."""
- app = 'finance'
- migrate_from = [('finance', '0009_statement_ljp_to')]
- migrate_to = [('finance', '0010_statement_status')]
+ app = "finance"
+ migrate_from = [("finance", "0009_statement_ljp_to")]
+ migrate_to = [("finance", "0010_statement_status")]
def setUp(self):
# Get the state before migration
@@ -18,26 +17,20 @@ class StatusMigrationTestCase(django.test.TransactionTestCase):
# Get the old models (before migration)
old_apps = executor.loader.project_state(self.migrate_from).apps
- self.Statement = old_apps.get_model(self.app, 'Statement')
+ self.Statement = old_apps.get_model(self.app, "Statement")
# Create statements with different combinations of submitted/confirmed
# created_by is nullable, so we don't need to create a Member
self.unsubmitted = self.Statement.objects.create(
- short_description='Unsubmitted Statement',
- submitted=False,
- confirmed=False
+ short_description="Unsubmitted Statement", submitted=False, confirmed=False
)
self.submitted = self.Statement.objects.create(
- short_description='Submitted Statement',
- submitted=True,
- confirmed=False
+ short_description="Submitted Statement", submitted=True, confirmed=False
)
self.confirmed = self.Statement.objects.create(
- short_description='Confirmed Statement',
- submitted=True,
- confirmed=True
+ short_description="Confirmed Statement", submitted=True, confirmed=True
)
def test_status_field_migration(self):
@@ -49,7 +42,7 @@ class StatusMigrationTestCase(django.test.TransactionTestCase):
# Get the new models (after migration)
new_apps = executor.loader.project_state(self.migrate_to).apps
- Statement = new_apps.get_model(self.app, 'Statement')
+ Statement = new_apps.get_model(self.app, "Statement")
# Constants from the Statement model
UNSUBMITTED = 0
@@ -58,13 +51,22 @@ class StatusMigrationTestCase(django.test.TransactionTestCase):
# Verify the migration worked correctly
unsubmitted = Statement.objects.get(pk=self.unsubmitted.pk)
- self.assertEqual(unsubmitted.status, UNSUBMITTED,
- 'Statement with submitted=False, confirmed=False should have status=UNSUBMITTED')
+ self.assertEqual(
+ unsubmitted.status,
+ UNSUBMITTED,
+ "Statement with submitted=False, confirmed=False should have status=UNSUBMITTED",
+ )
submitted = Statement.objects.get(pk=self.submitted.pk)
- self.assertEqual(submitted.status, SUBMITTED,
- 'Statement with submitted=True, confirmed=False should have status=SUBMITTED')
+ self.assertEqual(
+ submitted.status,
+ SUBMITTED,
+ "Statement with submitted=True, confirmed=False should have status=SUBMITTED",
+ )
confirmed = Statement.objects.get(pk=self.confirmed.pk)
- self.assertEqual(confirmed.status, CONFIRMED,
- 'Statement with submitted=True, confirmed=True should have status=CONFIRMED')
+ self.assertEqual(
+ confirmed.status,
+ CONFIRMED,
+ "Statement with submitted=True, confirmed=True should have status=CONFIRMED",
+ )
diff --git a/jdav_web/finance/tests/models.py b/jdav_web/finance/tests/models.py
index 2b640f7..e991594 100644
--- a/jdav_web/finance/tests/models.py
+++ b/jdav_web/finance/tests/models.py
@@ -1,16 +1,34 @@
-from unittest import skip
+from decimal import Decimal
+
+from dateutil.relativedelta import relativedelta
+from django.conf import settings
from django.test import TestCase
from django.utils import timezone
-from django.conf import settings
from django.utils.translation import gettext_lazy as _
-from decimal import Decimal
-from finance.models import Statement, StatementUnSubmitted, StatementSubmitted, Bill, Ledger, Transaction,\
- StatementUnSubmittedManager, StatementSubmittedManager, StatementConfirmedManager,\
- StatementConfirmed, TransactionIssue, StatementManager
-from members.models import Member, Group, Freizeit, LJPProposal, Intervention, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE, NewMemberOnList,\
- FAHRGEMEINSCHAFT_ANREISE, MALE, FEMALE, DIVERSE
-from dateutil.relativedelta import relativedelta
-from utils import get_member
+from finance.models import Bill
+from finance.models import Ledger
+from finance.models import Statement
+from finance.models import StatementConfirmed
+from finance.models import StatementConfirmedManager
+from finance.models import StatementManager
+from finance.models import StatementSubmitted
+from finance.models import StatementSubmittedManager
+from finance.models import StatementUnSubmitted
+from finance.models import StatementUnSubmittedManager
+from finance.models import Transaction
+from finance.models import TransactionIssue
+from members.models import DIVERSE
+from members.models import FAHRGEMEINSCHAFT_ANREISE
+from members.models import Freizeit
+from members.models import GEMEINSCHAFTS_TOUR
+from members.models import Group
+from members.models import Intervention
+from members.models import LJPProposal
+from members.models import MALE
+from members.models import Member
+from members.models import MUSKELKRAFT_ANREISE
+from members.models import NewMemberOnList
+
# Create your tests here.
class StatementTestCase(TestCase):
@@ -22,82 +40,149 @@ class StatementTestCase(TestCase):
def setUp(self):
self.jl = Group.objects.create(name="Jugendleiter")
- self.fritz = Member.objects.create(prename="Fritz", lastname="Wulter", birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=MALE)
+ self.fritz = Member.objects.create(
+ prename="Fritz",
+ lastname="Wulter",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ )
self.fritz.group.add(self.jl)
self.fritz.save()
- self.personal_account = Ledger.objects.create(name='personal account')
+ self.personal_account = Ledger.objects.create(name="personal account")
- self.st = Statement.objects.create(short_description='A statement', explanation='Important!', night_cost=0)
- Bill.objects.create(statement=self.st, short_description='food', explanation='i was hungry',
- amount=67.3, costs_covered=False, paid_by=self.fritz)
- Transaction.objects.create(reference='gift', amount=12.3,
- ledger=self.personal_account, member=self.fritz,
- statement=self.st)
+ self.st = Statement.objects.create(
+ short_description="A statement", explanation="Important!", night_cost=0
+ )
+ Bill.objects.create(
+ statement=self.st,
+ short_description="food",
+ explanation="i was hungry",
+ amount=67.3,
+ costs_covered=False,
+ paid_by=self.fritz,
+ )
+ Transaction.objects.create(
+ reference="gift",
+ amount=12.3,
+ ledger=self.personal_account,
+ member=self.fritz,
+ statement=self.st,
+ )
- self.st2 = Statement.objects.create(short_description='Actual expenses', night_cost=0)
- Bill.objects.create(statement=self.st2, short_description='food', explanation='i was hungry',
- amount=67.3, costs_covered=True, paid_by=self.fritz)
+ self.st2 = Statement.objects.create(short_description="Actual expenses", night_cost=0)
+ Bill.objects.create(
+ statement=self.st2,
+ short_description="food",
+ explanation="i was hungry",
+ amount=67.3,
+ costs_covered=True,
+ paid_by=self.fritz,
+ )
- ex = Freizeit.objects.create(name='Wild trip', kilometers_traveled=self.kilometers_traveled,
- tour_type=GEMEINSCHAFTS_TOUR,
- tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=1)
- self.st3 = Statement.objects.create(night_cost=self.night_cost, excursion=ex, subsidy_to=self.fritz)
+ ex = Freizeit.objects.create(
+ name="Wild trip",
+ kilometers_traveled=self.kilometers_traveled,
+ tour_type=GEMEINSCHAFTS_TOUR,
+ tour_approach=MUSKELKRAFT_ANREISE,
+ difficulty=1,
+ )
+ self.st3 = Statement.objects.create(
+ night_cost=self.night_cost, excursion=ex, subsidy_to=self.fritz
+ )
for i in range(self.participant_count):
- m = Member.objects.create(prename='Fritz {}'.format(i), lastname='Walter', birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=MALE)
+ m = Member.objects.create(
+ prename="Fritz {}".format(i),
+ lastname="Walter",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ )
mol = NewMemberOnList.objects.create(member=m, memberlist=ex)
ex.membersonlist.add(mol)
for i in range(self.staff_count):
- m = Member.objects.create(prename='Fritz {}'.format(i), lastname='Walter', birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=MALE)
- Bill.objects.create(statement=self.st3, short_description='food', explanation='i was hungry',
- amount=42.69, costs_covered=True, paid_by=m)
+ m = Member.objects.create(
+ prename="Fritz {}".format(i),
+ lastname="Walter",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ )
+ Bill.objects.create(
+ statement=self.st3,
+ short_description="food",
+ explanation="i was hungry",
+ amount=42.69,
+ costs_covered=True,
+ paid_by=m,
+ )
m.group.add(self.jl)
ex.jugendleiter.add(m)
if i < self.allowance_to_count:
self.st3.allowance_to.add(m)
# Create a small excursion with < 5 theoretic LJP participants for LJP contribution test
- small_ex = Freizeit.objects.create(name='Small trip', kilometers_traveled=100,
- tour_type=GEMEINSCHAFTS_TOUR,
- tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=1)
+ small_ex = Freizeit.objects.create(
+ name="Small trip",
+ kilometers_traveled=100,
+ tour_type=GEMEINSCHAFTS_TOUR,
+ tour_approach=MUSKELKRAFT_ANREISE,
+ difficulty=1,
+ )
# Add only 3 participants (< 5 for theoretic_ljp_participant_count)
for i in range(3):
# Create young participants (< 6 years old) so they don't count toward LJP
birth_date = timezone.now().date() - relativedelta(years=4)
- m = Member.objects.create(prename='Small {}'.format(i), lastname='Participant',
- birth_date=birth_date,
- email=settings.TEST_MAIL, gender=MALE)
+ m = Member.objects.create(
+ prename="Small {}".format(i),
+ lastname="Participant",
+ birth_date=birth_date,
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ )
NewMemberOnList.objects.create(member=m, memberlist=small_ex)
# Create LJP proposal for the small excursion
- ljp_proposal = LJPProposal.objects.create(title='Small LJP', category=LJPProposal.LJP_STAFF_TRAINING)
+ ljp_proposal = LJPProposal.objects.create(
+ title="Small LJP", category=LJPProposal.LJP_STAFF_TRAINING
+ )
small_ex.ljpproposal = ljp_proposal
small_ex.save()
self.st_small = Statement.objects.create(night_cost=10, excursion=small_ex)
- ex = Freizeit.objects.create(name='Wild trip 2', kilometers_traveled=self.kilometers_traveled,
- tour_type=GEMEINSCHAFTS_TOUR,
- tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=2)
- self.st4 = Statement.objects.create(night_cost=self.night_cost, excursion=ex, subsidy_to=self.fritz)
+ ex = Freizeit.objects.create(
+ name="Wild trip 2",
+ kilometers_traveled=self.kilometers_traveled,
+ tour_type=GEMEINSCHAFTS_TOUR,
+ tour_approach=MUSKELKRAFT_ANREISE,
+ difficulty=2,
+ )
+ self.st4 = Statement.objects.create(
+ night_cost=self.night_cost, excursion=ex, subsidy_to=self.fritz
+ )
for i in range(2):
- m = Member.objects.create(prename='Peter {}'.format(i), lastname='Walter',
- birth_date=timezone.now().date() - relativedelta(years=30),
- email=settings.TEST_MAIL, gender=DIVERSE)
+ m = Member.objects.create(
+ prename="Peter {}".format(i),
+ lastname="Walter",
+ birth_date=timezone.now().date() - relativedelta(years=30),
+ email=settings.TEST_MAIL,
+ gender=DIVERSE,
+ )
mol = NewMemberOnList.objects.create(member=m, memberlist=ex)
ex.membersonlist.add(mol)
base = timezone.now()
- ex = Freizeit.objects.create(name='Wild trip with old people', kilometers_traveled=self.kilometers_traveled,
- tour_type=GEMEINSCHAFTS_TOUR,
- tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=2, date=timezone.datetime(2024, 1, 2, 8, 0, 0, tzinfo=base.tzinfo), end=timezone.datetime(2024, 1, 5, 17, 0, 0, tzinfo=base.tzinfo) )
+ ex = Freizeit.objects.create(
+ name="Wild trip with old people",
+ kilometers_traveled=self.kilometers_traveled,
+ tour_type=GEMEINSCHAFTS_TOUR,
+ tour_approach=MUSKELKRAFT_ANREISE,
+ difficulty=2,
+ date=timezone.datetime(2024, 1, 2, 8, 0, 0, tzinfo=base.tzinfo),
+ end=timezone.datetime(2024, 1, 5, 17, 0, 0, tzinfo=base.tzinfo),
+ )
settings.EXCURSION_ORG_FEE = 20
settings.LJP_TAX = 0.2
@@ -106,207 +191,335 @@ class StatementTestCase(TestCase):
self.st5 = Statement.objects.create(night_cost=self.night_cost, excursion=ex)
for i in range(9):
- m = Member.objects.create(prename='Peter {}'.format(i), lastname='Walter', birth_date=timezone.now().date() - relativedelta(years=i+21),
- email=settings.TEST_MAIL, gender=DIVERSE)
+ m = Member.objects.create(
+ prename="Peter {}".format(i),
+ lastname="Walter",
+ birth_date=timezone.now().date() - relativedelta(years=i + 21),
+ email=settings.TEST_MAIL,
+ gender=DIVERSE,
+ )
mol = NewMemberOnList.objects.create(member=m, memberlist=ex)
ex.membersonlist.add(mol)
ljpproposal = LJPProposal.objects.create(
- title='Test proposal',
+ title="Test proposal",
category=LJPProposal.LJP_STAFF_TRAINING,
goal=LJPProposal.LJP_ENVIRONMENT,
- goal_strategy='my strategy',
+ goal_strategy="my strategy",
not_bw_reason=LJPProposal.NOT_BW_ROOMS,
- excursion=self.st5.excursion)
+ excursion=self.st5.excursion,
+ )
for i in range(3):
- int = Intervention.objects.create(
- date_start=timezone.datetime(2024, 1, 2+i, 12, 0, 0, tzinfo=base.tzinfo),
- duration = 2+i,
- activity = 'hi',
- ljp_proposal=ljpproposal
- )
+ Intervention.objects.create(
+ date_start=timezone.datetime(2024, 1, 2 + i, 12, 0, 0, tzinfo=base.tzinfo),
+ duration=2 + i,
+ activity="hi",
+ ljp_proposal=ljpproposal,
+ )
self.b1 = Bill.objects.create(
statement=self.st5,
- short_description='covered bill',
- explanation='hi',
- amount='300',
+ short_description="covered bill",
+ explanation="hi",
+ amount="300",
paid_by=self.fritz,
costs_covered=True,
- refunded=False
+ refunded=False,
)
self.b2 = Bill.objects.create(
statement=self.st5,
- short_description='non-covered bill',
- explanation='hi',
- amount='900',
+ short_description="non-covered bill",
+ explanation="hi",
+ amount="900",
paid_by=self.fritz,
costs_covered=False,
- refunded=False
+ refunded=False,
)
self.st6 = Statement.objects.create(night_cost=self.night_cost)
- Bill.objects.create(statement=self.st6, amount='42', costs_covered=True)
+ Bill.objects.create(statement=self.st6, amount="42", costs_covered=True)
def test_org_fee(self):
# org fee should be collected if participants are older than 26
- self.assertEqual(self.st5.excursion.old_participant_count, 3, 'Calculation of number of old people in excursion is incorrect.')
+ self.assertEqual(
+ self.st5.excursion.old_participant_count,
+ 3,
+ "Calculation of number of old people in excursion is incorrect.",
+ )
- total_org = 4 * 3 * 20 # 4 days, 3 old people, 20€ per day
+ total_org = 4 * 3 * 20 # 4 days, 3 old people, 20€ per day
- self.assertEqual(self.st5.total_org_fee_theoretical, total_org, 'Theoretical org_fee should equal to amount per day per person * n_persons * n_days if there are old people.')
- self.assertEqual(self.st5.total_org_fee, 0, 'Paid org fee should be 0 if no allowance and subsidies are paid if there are old people.')
+ self.assertEqual(
+ self.st5.total_org_fee_theoretical,
+ total_org,
+ "Theoretical org_fee should equal to amount per day per person * n_persons * n_days if there are old people.",
+ )
+ self.assertEqual(
+ self.st5.total_org_fee,
+ 0,
+ "Paid org fee should be 0 if no allowance and subsidies are paid if there are old people.",
+ )
self.assertIsNone(self.st5.org_fee_payant)
# now collect subsidies
self.st5.subsidy_to = self.fritz
- self.assertEqual(self.st5.total_org_fee, total_org, 'Paid org fee should equal to amount per day per person * n_persons * n_days if subsidies are paid.')
+ self.assertEqual(
+ self.st5.total_org_fee,
+ total_org,
+ "Paid org fee should equal to amount per day per person * n_persons * n_days if subsidies are paid.",
+ )
# now collect allowances
self.st5.allowance_to.add(self.fritz)
self.st5.subsidy_to = None
- self.assertEqual(self.st5.total_org_fee, total_org, 'Paid org fee should equal to amount per day per person * n_persons * n_days if allowances are paid.')
+ self.assertEqual(
+ self.st5.total_org_fee,
+ total_org,
+ "Paid org fee should equal to amount per day per person * n_persons * n_days if allowances are paid.",
+ )
# now collect both
self.st5.subsidy_to = self.fritz
- self.assertEqual(self.st5.total_org_fee, total_org, 'Paid org fee should equal to amount per day per person * n_persons * n_days if subsidies and allowances are paid.')
+ self.assertEqual(
+ self.st5.total_org_fee,
+ total_org,
+ "Paid org fee should equal to amount per day per person * n_persons * n_days if subsidies and allowances are paid.",
+ )
- self.assertEqual(self.st5.org_fee_payant, self.fritz, 'Org fee payant should be the receiver allowances and subsidies.')
+ self.assertEqual(
+ self.st5.org_fee_payant,
+ self.fritz,
+ "Org fee payant should be the receiver allowances and subsidies.",
+ )
# return to previous state
self.st5.subsidy_to = None
self.st5.allowance_to.remove(self.fritz)
-
def test_ljp_payment(self):
-
expected_intervention_hours = 2 + 3 + 4
- expected_seminar_days = 0 + 0.5 + 0.5 # >=2.5h = 0.5days, >=5h = 1.0day
- expected_ljp = (1-settings.LJP_TAX) * expected_seminar_days * settings.LJP_CONTRIBUTION_PER_DAY * 9
+ expected_seminar_days = 0 + 0.5 + 0.5 # >=2.5h = 0.5days, >=5h = 1.0day
+ expected_ljp = (
+ (1 - settings.LJP_TAX) * expected_seminar_days * settings.LJP_CONTRIBUTION_PER_DAY * 9
+ )
# (1 - 20% tax) * 1 seminar day * 20€ * 9 participants
- self.assertEqual(self.st5.excursion.total_intervention_hours, expected_intervention_hours, 'Calculation of total intervention hours is incorrect.')
- self.assertEqual(self.st5.excursion.total_seminar_days, expected_seminar_days, 'Calculation of total seminar days is incorrect.')
+ self.assertEqual(
+ self.st5.excursion.total_intervention_hours,
+ expected_intervention_hours,
+ "Calculation of total intervention hours is incorrect.",
+ )
+ self.assertEqual(
+ self.st5.excursion.total_seminar_days,
+ expected_seminar_days,
+ "Calculation of total seminar days is incorrect.",
+ )
- self.assertEqual(self.st5.paid_ljp_contributions, 0, 'No LJP contributions should be paid if no receiver is set.')
+ self.assertEqual(
+ self.st5.paid_ljp_contributions,
+ 0,
+ "No LJP contributions should be paid if no receiver is set.",
+ )
# now we want to pay out the LJP contributions
self.st5.ljp_to = self.fritz
- self.assertEqual(self.st5.paid_ljp_contributions, expected_ljp, 'LJP contributions should be paid if a receiver is set.')
+ self.assertEqual(
+ self.st5.paid_ljp_contributions,
+ expected_ljp,
+ "LJP contributions should be paid if a receiver is set.",
+ )
# now the total costs paid by trip organisers is lower than expected ljp contributions, should be reduced automatically
- self.b2.amount=100
+ self.b2.amount = 100
self.b2.save()
- self.assertEqual(self.st5.total_bills_not_covered, 100, 'Changes in bills should be reflected in the total costs paid by trip organisers')
- self.assertGreaterEqual(self.st5.total_bills_not_covered, self.st5.paid_ljp_contributions, 'LJP contributions should be less than or equal to the costs paid by trip organisers')
+ self.assertEqual(
+ self.st5.total_bills_not_covered,
+ 100,
+ "Changes in bills should be reflected in the total costs paid by trip organisers",
+ )
+ self.assertGreaterEqual(
+ self.st5.total_bills_not_covered,
+ self.st5.paid_ljp_contributions,
+ "LJP contributions should be less than or equal to the costs paid by trip organisers",
+ )
self.st5.ljp_to = None
def test_staff_count(self):
- self.assertEqual(self.st4.admissible_staff_count, 0,
- 'Admissible staff count is not 0, although not enough participants.')
+ self.assertEqual(
+ self.st4.admissible_staff_count,
+ 0,
+ "Admissible staff count is not 0, although not enough participants.",
+ )
for i in range(2):
- m = Member.objects.create(prename='Peter {}'.format(i), lastname='Walter', birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=DIVERSE)
+ m = Member.objects.create(
+ prename="Peter {}".format(i),
+ lastname="Walter",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=DIVERSE,
+ )
mol = NewMemberOnList.objects.create(member=m, memberlist=self.st4.excursion)
self.st4.excursion.membersonlist.add(mol)
- self.assertEqual(self.st4.admissible_staff_count, 2,
- 'Admissible staff count is not 2, although there are 4 participants.')
+ self.assertEqual(
+ self.st4.admissible_staff_count,
+ 2,
+ "Admissible staff count is not 2, although there are 4 participants.",
+ )
def test_reduce_transactions(self):
self.st3.generate_transactions()
- self.assertTrue(self.st3.allowance_to_valid, 'Configured `allowance_to` field is invalid.')
+ self.assertTrue(self.st3.allowance_to_valid, "Configured `allowance_to` field is invalid.")
# every youth leader on `st3` paid one bill, the first three receive the allowance
# and one receives the subsidies
- self.assertEqual(self.st3.transaction_set.count(), self.st3.real_staff_count + self.staff_count + 1,
- 'Transaction count is not twice the staff count.')
+ self.assertEqual(
+ self.st3.transaction_set.count(),
+ self.st3.real_staff_count + self.staff_count + 1,
+ "Transaction count is not twice the staff count.",
+ )
self.st3.reduce_transactions()
- self.assertEqual(self.st3.transaction_set.count(), self.st3.real_staff_count + self.staff_count + 1,
- 'Transaction count after reduction is not the same as before, although no ledgers are configured.')
+ self.assertEqual(
+ self.st3.transaction_set.count(),
+ self.st3.real_staff_count + self.staff_count + 1,
+ "Transaction count after reduction is not the same as before, although no ledgers are configured.",
+ )
for trans in self.st3.transaction_set.all():
trans.ledger = self.personal_account
trans.save()
self.st3.reduce_transactions()
# the three yls that receive an allowance should only receive one transaction after reducing,
# the additional one is the one for the subsidies
- self.assertEqual(self.st3.transaction_set.count(), self.staff_count + 1,
- 'Transaction count after setting ledgers and reduction is incorrect.')
+ self.assertEqual(
+ self.st3.transaction_set.count(),
+ self.staff_count + 1,
+ "Transaction count after setting ledgers and reduction is incorrect.",
+ )
self.st3.reduce_transactions()
- self.assertEqual(self.st3.transaction_set.count(), self.staff_count + 1,
- 'Transaction count did change after reducing a second time.')
+ self.assertEqual(
+ self.st3.transaction_set.count(),
+ self.staff_count + 1,
+ "Transaction count did change after reducing a second time.",
+ )
def test_confirm_statement(self):
- self.assertFalse(self.st3.confirm(confirmer=self.fritz), 'Statement was confirmed, although it is not submitted.')
+ self.assertFalse(
+ self.st3.confirm(confirmer=self.fritz),
+ "Statement was confirmed, although it is not submitted.",
+ )
self.st3.submit(submitter=self.fritz)
- self.assertTrue(self.st3.submitted, 'Statement is not submitted, although it was.')
- self.assertEqual(self.st3.submitted_by, self.fritz,
- 'Statement was not submitted by fritz.')
+ self.assertTrue(self.st3.submitted, "Statement is not submitted, although it was.")
+ self.assertEqual(self.st3.submitted_by, self.fritz, "Statement was not submitted by fritz.")
- self.assertFalse(self.st3.confirm(), 'Statement was confirmed, but is not valid yet.')
+ self.assertFalse(self.st3.confirm(), "Statement was confirmed, but is not valid yet.")
self.st3.generate_transactions()
for trans in self.st3.transaction_set.all():
trans.ledger = self.personal_account
trans.save()
- self.assertEqual(self.st3.validity, Statement.VALID,
- 'Statement is not valid, although it was setup to be so.')
- self.assertTrue(self.st3.confirm(confirmer=self.fritz),
- 'Statement was not confirmed, although it submitted and valid.')
- self.assertEqual(self.st3.confirmed_by, self.fritz, 'Statement not confirmed by fritz.')
+ self.assertEqual(
+ self.st3.validity,
+ Statement.VALID,
+ "Statement is not valid, although it was setup to be so.",
+ )
+ self.assertTrue(
+ self.st3.confirm(confirmer=self.fritz),
+ "Statement was not confirmed, although it submitted and valid.",
+ )
+ self.assertEqual(self.st3.confirmed_by, self.fritz, "Statement not confirmed by fritz.")
for trans in self.st3.transaction_set.all():
- self.assertTrue(trans.confirmed, 'Transaction on confirmed statement is not confirmed.')
- self.assertEqual(trans.confirmed_by, self.fritz, 'Transaction on confirmed statement is not confirmed by fritz.')
+ self.assertTrue(trans.confirmed, "Transaction on confirmed statement is not confirmed.")
+ self.assertEqual(
+ trans.confirmed_by,
+ self.fritz,
+ "Transaction on confirmed statement is not confirmed by fritz.",
+ )
def test_excursion_statement(self):
- self.assertEqual(self.st3.excursion.staff_count, self.staff_count,
- 'Calculated staff count is not constructed staff count.')
- self.assertEqual(self.st3.excursion.participant_count, self.participant_count,
- 'Calculated participant count is not constructed participant count.')
- self.assertLess(self.st3.admissible_staff_count, self.staff_count,
- 'All staff members are refinanced, although {} is too much for {} participants.'.format(self.staff_count, self.participant_count))
- self.assertFalse(self.st3.transactions_match_expenses,
- 'Transactions match expenses, but currently no one is paid.')
- self.assertGreater(self.st3.total_staff, 0,
- 'There are no costs for the staff, although there are enough participants.')
- self.assertEqual(self.st3.total_nights, 0,
- 'There are costs for the night, although there was no night.')
- self.assertEqual(self.st3.real_night_cost, settings.MAX_NIGHT_COST,
- 'Real night cost is not the max, although the given one is way too high.')
+ self.assertEqual(
+ self.st3.excursion.staff_count,
+ self.staff_count,
+ "Calculated staff count is not constructed staff count.",
+ )
+ self.assertEqual(
+ self.st3.excursion.participant_count,
+ self.participant_count,
+ "Calculated participant count is not constructed participant count.",
+ )
+ self.assertLess(
+ self.st3.admissible_staff_count,
+ self.staff_count,
+ "All staff members are refinanced, although {} is too much for {} participants.".format(
+ self.staff_count, self.participant_count
+ ),
+ )
+ self.assertFalse(
+ self.st3.transactions_match_expenses,
+ "Transactions match expenses, but currently no one is paid.",
+ )
+ self.assertGreater(
+ self.st3.total_staff,
+ 0,
+ "There are no costs for the staff, although there are enough participants.",
+ )
+ self.assertEqual(
+ self.st3.total_nights, 0, "There are costs for the night, although there was no night."
+ )
+ self.assertEqual(
+ self.st3.real_night_cost,
+ settings.MAX_NIGHT_COST,
+ "Real night cost is not the max, although the given one is way too high.",
+ )
# changing means of transport changes euro_per_km
epkm = self.st3.euro_per_km
self.st3.excursion.tour_approach = FAHRGEMEINSCHAFT_ANREISE
- self.assertNotEqual(epkm, self.st3.euro_per_km, 'Changing means of transport did not change euro per km.')
+ self.assertNotEqual(
+ epkm, self.st3.euro_per_km, "Changing means of transport did not change euro per km."
+ )
self.st3.generate_transactions()
- self.assertTrue(self.st3.transactions_match_expenses,
- "Transactions don't match expenses after generating them.")
- self.assertGreater(self.st3.total, 0, 'Total is 0.')
+ self.assertTrue(
+ self.st3.transactions_match_expenses,
+ "Transactions don't match expenses after generating them.",
+ )
+ self.assertGreater(self.st3.total, 0, "Total is 0.")
def test_generate_transactions(self):
# self.st2 has an unpaid bill
- self.assertFalse(self.st2.transactions_match_expenses,
- 'Transactions match expenses, but one bill is not paid.')
+ self.assertFalse(
+ self.st2.transactions_match_expenses,
+ "Transactions match expenses, but one bill is not paid.",
+ )
self.st2.generate_transactions()
# now transactions should match expenses
- self.assertTrue(self.st2.transactions_match_expenses,
- "Transactions don't match expenses after generating them.")
+ self.assertTrue(
+ self.st2.transactions_match_expenses,
+ "Transactions don't match expenses after generating them.",
+ )
# self.st2 is still not valid
- self.assertEqual(self.st2.validity, Statement.MISSING_LEDGER,
- 'Statement is valid, although transaction has no ledger setup.')
+ self.assertEqual(
+ self.st2.validity,
+ Statement.MISSING_LEDGER,
+ "Statement is valid, although transaction has no ledger setup.",
+ )
for trans in self.st2.transaction_set.all():
trans.ledger = self.personal_account
trans.save()
- self.assertEqual(self.st2.validity, Statement.VALID,
- 'Statement is still invalid, after setting up ledger.')
+ self.assertEqual(
+ self.st2.validity,
+ Statement.VALID,
+ "Statement is still invalid, after setting up ledger.",
+ )
# create a new transaction issue by manually changing amount
t1 = self.st2.transaction_set.all()[0]
t1.amount = 123
t1.save()
- self.assertFalse(self.st2.transactions_match_expenses,
- 'Transactions match expenses, but one transaction was tweaked.')
+ self.assertFalse(
+ self.st2.transactions_match_expenses,
+ "Transactions match expenses, but one transaction was tweaked.",
+ )
def test_generate_transactions_not_covered(self):
bill = self.st2.bill_set.all()[0]
@@ -334,19 +547,28 @@ class StatementTestCase(TestCase):
def test_detect_unallowed_gift(self):
# there is a bill
- self.assertGreater(self.st.total_bills_theoretic, 0, 'Theoretic bill total is 0 (should be > 0).')
+ self.assertGreater(
+ self.st.total_bills_theoretic, 0, "Theoretic bill total is 0 (should be > 0)."
+ )
# but it is not covered
- self.assertEqual(self.st.total_bills, 0, 'Real bill total is not 0.')
- self.assertEqual(self.st.total, 0, 'Total is not 0.')
- self.assertGreater(self.st.total_theoretic, 0, 'Total in theorey is 0.')
+ self.assertEqual(self.st.total_bills, 0, "Real bill total is not 0.")
+ self.assertEqual(self.st.total, 0, "Total is not 0.")
+ self.assertGreater(self.st.total_theoretic, 0, "Total in theorey is 0.")
self.st.generate_transactions()
- self.assertEqual(self.st.transaction_set.count(), 1, 'Generating transactions did produce new transactions.')
+ self.assertEqual(
+ self.st.transaction_set.count(),
+ 1,
+ "Generating transactions did produce new transactions.",
+ )
# but there is a transaction anyway
- self.assertFalse(self.st.transactions_match_expenses,
- 'Transactions match expenses, although an unreasonable gift is paid.')
+ self.assertFalse(
+ self.st.transactions_match_expenses,
+ "Transactions match expenses, although an unreasonable gift is paid.",
+ )
# so statement must be invalid
- self.assertFalse(self.st.is_valid(),
- 'Transaction is valid, although an unreasonable gift is paid.')
+ self.assertFalse(
+ self.st.is_valid(), "Transaction is valid, although an unreasonable gift is paid."
+ )
def test_allowance_to_valid(self):
self.assertEqual(self.st3.excursion.participant_count, self.participant_count)
@@ -391,20 +613,18 @@ class StatementTestCase(TestCase):
def test_template_context(self):
# with excursion
- self.assertTrue('euro_per_km' in self.st3.template_context())
+ self.assertTrue("euro_per_km" in self.st3.template_context())
# without excursion
- self.assertFalse('euro_per_km' in self.st2.template_context())
+ self.assertFalse("euro_per_km" in self.st2.template_context())
def test_grouped_bills(self):
bills = self.st2.grouped_bills()
- self.assertTrue('amount' in bills[0])
+ self.assertTrue("amount" in bills[0])
def test_euro_per_km_no_excursion(self):
"""Test euro_per_km when no excursion is associated"""
statement = Statement.objects.create(
- short_description="Test Statement",
- explanation="Test explanation",
- night_cost=25
+ short_description="Test Statement", explanation="Test explanation", night_cost=25
)
self.assertEqual(statement.euro_per_km, 0)
@@ -414,7 +634,7 @@ class StatementTestCase(TestCase):
short_description="Test Statement",
explanation="Test explanation",
night_cost=25,
- created_by=self.fritz
+ created_by=self.fritz,
)
self.assertFalse(statement.submitted)
@@ -431,12 +651,12 @@ class StatementTestCase(TestCase):
"""Test statement template context when excursion is present"""
# Use existing excursion from setUp
context = self.st3.template_context()
- self.assertIn('euro_per_km', context)
- self.assertIsInstance(context['euro_per_km'], (int, float, Decimal))
+ self.assertIn("euro_per_km", context)
+ self.assertIsInstance(context["euro_per_km"], (int, float, Decimal))
def test_title_with_excursion(self):
title = self.st3.title
- self.assertIn('Wild trip', title)
+ self.assertIn("Wild trip", title)
def test_transaction_issues_with_org_fee(self):
issues = self.st4.transaction_issues
@@ -455,14 +675,17 @@ class StatementTestCase(TestCase):
self.st4.save()
# Verify org fee is calculated
- self.assertGreater(self.st4.total_org_fee, 0, "Org fee should be > 0 with subsidies and old participants")
+ self.assertGreater(
+ self.st4.total_org_fee, 0, "Org fee should be > 0 with subsidies and old participants"
+ )
initial_count = Transaction.objects.count()
self.st4.generate_transactions()
final_count = Transaction.objects.count()
self.assertGreater(final_count, initial_count)
- org_fee_transaction = Transaction.objects.filter(statement=self.st4,
- reference__icontains=_('reduced by org fee')).first()
+ org_fee_transaction = Transaction.objects.filter(
+ statement=self.st4, reference__icontains=_("reduced by org fee")
+ ).first()
self.assertIsNotNone(org_fee_transaction)
def test_generate_transactions_ljp(self):
@@ -472,7 +695,9 @@ class StatementTestCase(TestCase):
self.st3.generate_transactions()
final_count = Transaction.objects.count()
self.assertGreater(final_count, initial_count)
- ljp_transaction = Transaction.objects.filter(statement=self.st3, member=self.fritz, reference__icontains='LJP').first()
+ ljp_transaction = Transaction.objects.filter(
+ statement=self.st3, member=self.fritz, reference__icontains="LJP"
+ ).first()
self.assertIsNotNone(ljp_transaction)
def test_subsidies_paid_property(self):
@@ -485,8 +710,11 @@ class StatementTestCase(TestCase):
self.st_small.save()
# Verify that the small excursion has < 5 theoretic LJP participants
- self.assertLess(self.st_small.excursion.theoretic_ljp_participant_count, 5,
- "Should have < 5 theoretic LJP participants")
+ self.assertLess(
+ self.st_small.excursion.theoretic_ljp_participant_count,
+ 5,
+ "Should have < 5 theoretic LJP participants",
+ )
ljp_contrib = self.st_small.paid_ljp_contributions
self.assertEqual(ljp_contrib, 0)
@@ -499,25 +727,29 @@ class StatementTestCase(TestCase):
class LedgerTestCase(TestCase):
def setUp(self):
- self.personal_account = Ledger.objects.create(name='personal account')
+ self.personal_account = Ledger.objects.create(name="personal account")
def test_str(self):
- self.assertTrue(str(self.personal_account), 'personal account')
+ self.assertTrue(str(self.personal_account), "personal account")
class ManagerTestCase(TestCase):
def setUp(self):
- self.st = Statement.objects.create(short_description='A statement',
- explanation='Important!',
- night_cost=0)
- self.st_submitted = Statement.objects.create(short_description='A statement',
- explanation='Important!',
- night_cost=0,
- status=Statement.SUBMITTED)
- self.st_confirmed = Statement.objects.create(short_description='A statement',
- explanation='Important!',
- night_cost=0,
- status=Statement.CONFIRMED)
+ self.st = Statement.objects.create(
+ short_description="A statement", explanation="Important!", night_cost=0
+ )
+ self.st_submitted = Statement.objects.create(
+ short_description="A statement",
+ explanation="Important!",
+ night_cost=0,
+ status=Statement.SUBMITTED,
+ )
+ self.st_confirmed = Statement.objects.create(
+ short_description="A statement",
+ explanation="Important!",
+ night_cost=0,
+ status=Statement.CONFIRMED,
+ )
def test_get_queryset(self):
# TODO: remove this manager, since it is not used
@@ -527,30 +759,43 @@ class ManagerTestCase(TestCase):
mgr_unsubmitted = StatementUnSubmittedManager()
mgr_unsubmitted.model = StatementUnSubmitted
- self.assertQuerysetEqual(mgr_unsubmitted.get_queryset(), Statement.objects.filter(pk=self.st.pk))
+ self.assertQuerysetEqual(
+ mgr_unsubmitted.get_queryset(), Statement.objects.filter(pk=self.st.pk)
+ )
mgr_submitted = StatementSubmittedManager()
mgr_submitted.model = StatementSubmitted
- self.assertQuerysetEqual(mgr_submitted.get_queryset(), Statement.objects.filter(pk=self.st_submitted.pk))
+ self.assertQuerysetEqual(
+ mgr_submitted.get_queryset(), Statement.objects.filter(pk=self.st_submitted.pk)
+ )
mgr_confirmed = StatementConfirmedManager()
mgr_confirmed.model = StatementConfirmed
- self.assertQuerysetEqual(mgr_confirmed.get_queryset(), Statement.objects.filter(pk=self.st_confirmed.pk))
+ self.assertQuerysetEqual(
+ mgr_confirmed.get_queryset(), Statement.objects.filter(pk=self.st_confirmed.pk)
+ )
class TransactionTestCase(TestCase):
def setUp(self):
- self.st = Statement.objects.create(short_description='A statement',
- explanation='Important!',
- night_cost=0)
- self.personal_account = Ledger.objects.create(name='personal account')
- self.fritz = Member.objects.create(prename="Fritz", lastname="Wulter", birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=MALE)
- self.trans = Transaction.objects.create(reference='foobar',
- amount=42,
- member=self.fritz,
- ledger=self.personal_account,
- statement=self.st)
+ self.st = Statement.objects.create(
+ short_description="A statement", explanation="Important!", night_cost=0
+ )
+ self.personal_account = Ledger.objects.create(name="personal account")
+ self.fritz = Member.objects.create(
+ prename="Fritz",
+ lastname="Wulter",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ )
+ self.trans = Transaction.objects.create(
+ reference="foobar",
+ amount=42,
+ member=self.fritz,
+ ledger=self.personal_account,
+ statement=self.st,
+ )
def test_str(self):
self.assertTrue(str(self.trans.pk) in str(self.trans))
@@ -558,9 +803,9 @@ class TransactionTestCase(TestCase):
def test_escape_reference(self):
"""Test transaction reference escaping with various special characters"""
test_cases = [
- ('harmless', 'harmless'),
- ('äöüÄÖÜß', 'aeoeueAeOeUess'),
- ('ha@r!?mless+09', 'har?mless+09'),
+ ("harmless", "harmless"),
+ ("äöüÄÖÜß", "aeoeueAeOeUess"),
+ ("ha@r!?mless+09", "har?mless+09"),
("simple", "simple"),
("test@email.com", "testemail.com"),
("ref!with#special$chars%", "refwithspecialchars"),
@@ -574,26 +819,26 @@ class TransactionTestCase(TestCase):
def test_code(self):
self.trans.amount = 0
# amount is zero, so empty
- self.assertEqual(self.trans.code(), '')
+ self.assertEqual(self.trans.code(), "")
self.trans.amount = 42
# iban is invalid, so empty
- self.assertEqual(self.trans.code(), '')
+ self.assertEqual(self.trans.code(), "")
# a valid (random) iban
- self.fritz.iban = 'DE89370400440532013000'
- self.assertNotEqual(self.trans.code(), '')
+ self.fritz.iban = "DE89370400440532013000"
+ self.assertNotEqual(self.trans.code(), "")
def test_code_with_zero_amount(self):
"""Test transaction code generation with zero amount"""
transaction = Transaction.objects.create(
reference="test-ref",
- amount=Decimal('0.00'),
+ amount=Decimal("0.00"),
member=self.fritz,
ledger=self.personal_account,
- statement=self.st
+ statement=self.st,
)
# Zero amount should return empty code
- self.assertEqual(transaction.code(), '')
+ self.assertEqual(transaction.code(), "")
def test_code_with_invalid_iban(self):
"""Test transaction code generation with invalid IBAN"""
@@ -602,36 +847,33 @@ class TransactionTestCase(TestCase):
transaction = Transaction.objects.create(
reference="test-ref",
- amount=Decimal('100.00'),
+ amount=Decimal("100.00"),
member=self.fritz,
ledger=self.personal_account,
- statement=self.st
+ statement=self.st,
)
# Invalid IBAN should return empty code
- self.assertEqual(transaction.code(), '')
+ self.assertEqual(transaction.code(), "")
class BillTestCase(TestCase):
def setUp(self):
- self.st = Statement.objects.create(short_description='A statement',
- explanation='Important!',
- night_cost=0)
- self.bill = Bill.objects.create(statement=self.st,
- short_description='foobar')
+ self.st = Statement.objects.create(
+ short_description="A statement", explanation="Important!", night_cost=0
+ )
+ self.bill = Bill.objects.create(statement=self.st, short_description="foobar")
def test_str(self):
- self.assertTrue('€' in str(self.bill))
+ self.assertTrue("€" in str(self.bill))
def test_pretty_amount(self):
- self.assertTrue('€' in self.bill.pretty_amount())
+ self.assertTrue("€" in self.bill.pretty_amount())
def test_pretty_amount_formatting(self):
"""Test bill pretty_amount formatting with specific values"""
bill = Bill.objects.create(
- statement=self.st,
- short_description="Test Bill",
- amount=Decimal('42.50')
+ statement=self.st, short_description="Test Bill", amount=Decimal("42.50")
)
pretty = bill.pretty_amount()
@@ -641,19 +883,17 @@ class BillTestCase(TestCase):
def test_zero_amount(self):
"""Test bill with zero amount"""
bill = Bill.objects.create(
- statement=self.st,
- short_description="Zero Bill",
- amount=Decimal('0.00')
+ statement=self.st, short_description="Zero Bill", amount=Decimal("0.00")
)
- self.assertEqual(bill.amount, Decimal('0.00'))
+ self.assertEqual(bill.amount, Decimal("0.00"))
pretty = bill.pretty_amount()
self.assertIn("0.00", pretty)
class TransactionIssueTestCase(TestCase):
def setUp(self):
- self.issue = TransactionIssue('foo', 42, 26)
+ self.issue = TransactionIssue("foo", 42, 26)
def test_difference(self):
self.assertEqual(self.issue.difference, 26 - 42)
diff --git a/jdav_web/finance/tests/rules.py b/jdav_web/finance/tests/rules.py
index 5470f54..a2d593b 100644
--- a/jdav_web/finance/tests/rules.py
+++ b/jdav_web/finance/tests/rules.py
@@ -1,11 +1,21 @@
-from django.test import TestCase
-from django.utils import timezone
+from unittest.mock import Mock
+
from django.conf import settings
from django.contrib.auth.models import User
-from unittest.mock import Mock
-from finance.rules import is_creator, not_submitted, leads_excursion
-from finance.models import Statement, Ledger
-from members.models import Member, Group, Freizeit, GEMEINSCHAFTS_TOUR, MUSKELKRAFT_ANREISE, MALE, FEMALE
+from django.test import TestCase
+from django.utils import timezone
+from finance.models import Ledger
+from finance.models import Statement
+from finance.rules import is_creator
+from finance.rules import leads_excursion
+from finance.rules import not_submitted
+from members.models import FEMALE
+from members.models import Freizeit
+from members.models import GEMEINSCHAFTS_TOUR
+from members.models import Group
+from members.models import MALE
+from members.models import Member
+from members.models import MUSKELKRAFT_ANREISE
class FinanceRulesTestCase(TestCase):
@@ -15,15 +25,23 @@ class FinanceRulesTestCase(TestCase):
self.user1 = User.objects.create_user(username="alice", password="test123")
self.member1 = Member.objects.create(
- prename="Alice", lastname="Smith", birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=FEMALE, user=self.user1
+ prename="Alice",
+ lastname="Smith",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=FEMALE,
+ user=self.user1,
)
self.member1.group.add(self.group)
self.user2 = User.objects.create_user(username="bob", password="test123")
self.member2 = Member.objects.create(
- prename="Bob", lastname="Jones", birth_date=timezone.now().date(),
- email=settings.TEST_MAIL, gender=MALE, user=self.user2
+ prename="Bob",
+ lastname="Jones",
+ birth_date=timezone.now().date(),
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ user=self.user2,
)
self.member2.group.add(self.group)
@@ -32,7 +50,7 @@ class FinanceRulesTestCase(TestCase):
kilometers_traveled=100,
tour_type=GEMEINSCHAFTS_TOUR,
tour_approach=MUSKELKRAFT_ANREISE,
- difficulty=2
+ difficulty=2,
)
self.freizeit.jugendleiter.add(self.member1)
@@ -41,7 +59,7 @@ class FinanceRulesTestCase(TestCase):
explanation="Test explanation",
night_cost=27,
created_by=self.member1,
- excursion=self.freizeit
+ excursion=self.freizeit,
)
self.statement.allowance_to.add(self.member1)
@@ -68,8 +86,8 @@ class FinanceRulesTestCase(TestCase):
# Create a mock Freizeit that truly doesn't have the statement attribute
mock_freizeit = Mock(spec=Freizeit)
# Remove the statement attribute entirely
- if hasattr(mock_freizeit, 'statement'):
- delattr(mock_freizeit, 'statement')
+ if hasattr(mock_freizeit, "statement"):
+ delattr(mock_freizeit, "statement")
self.assertTrue(not_submitted(self.user1, mock_freizeit))
def test_leads_excursion_freizeit_user_is_leader(self):
@@ -96,7 +114,7 @@ class FinanceRulesTestCase(TestCase):
explanation="Test explanation",
night_cost=27,
created_by=self.member1,
- excursion=None
+ excursion=None,
)
result = leads_excursion(self.user1, statement_no_excursion)
self.assertFalse(result)
diff --git a/jdav_web/logindata/migrations/0001_initial.py b/jdav_web/logindata/migrations/0001_initial.py
index 0a26b33..30cfd91 100644
--- a/jdav_web/logindata/migrations/0001_initial.py
+++ b/jdav_web/logindata/migrations/0001_initial.py
@@ -1,55 +1,58 @@
# Generated by Django 4.0.1 on 2024-11-23 21:15
import django.contrib.auth.models
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
initial = True
dependencies = [
- ('auth', '0012_alter_user_first_name_max_length'),
+ ("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.CreateModel(
- name='RegistrationPassword',
+ name="RegistrationPassword",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('password', models.CharField(max_length=100, verbose_name='Password')),
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ ("password", models.CharField(max_length=100, verbose_name="Password")),
],
),
migrations.CreateModel(
- name='AuthGroup',
- fields=[
- ],
+ name="AuthGroup",
+ fields=[],
options={
- 'verbose_name': 'Permission group',
- 'verbose_name_plural': 'Permission groups',
- 'proxy': True,
- 'indexes': [],
- 'constraints': [],
+ "verbose_name": "Permission group",
+ "verbose_name_plural": "Permission groups",
+ "proxy": True,
+ "indexes": [],
+ "constraints": [],
},
- bases=('auth.group',),
+ bases=("auth.group",),
managers=[
- ('objects', django.contrib.auth.models.GroupManager()),
+ ("objects", django.contrib.auth.models.GroupManager()),
],
),
migrations.CreateModel(
- name='LoginDatum',
- fields=[
- ],
+ name="LoginDatum",
+ fields=[],
options={
- 'verbose_name': 'Login Datum',
- 'verbose_name_plural': 'Login Data',
- 'proxy': True,
- 'indexes': [],
- 'constraints': [],
+ "verbose_name": "Login Datum",
+ "verbose_name_plural": "Login Data",
+ "proxy": True,
+ "indexes": [],
+ "constraints": [],
},
- bases=('auth.user',),
+ bases=("auth.user",),
managers=[
- ('objects', django.contrib.auth.models.UserManager()),
+ ("objects", django.contrib.auth.models.UserManager()),
],
),
]
diff --git a/jdav_web/logindata/migrations/0002_alter_registrationpassword_options.py b/jdav_web/logindata/migrations/0002_alter_registrationpassword_options.py
index 8331c8e..84ab482 100644
--- a/jdav_web/logindata/migrations/0002_alter_registrationpassword_options.py
+++ b/jdav_web/logindata/migrations/0002_alter_registrationpassword_options.py
@@ -4,14 +4,16 @@ from django.db import migrations
class Migration(migrations.Migration):
-
dependencies = [
- ('logindata', '0001_initial'),
+ ("logindata", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
- name='registrationpassword',
- options={'verbose_name': 'Active registration password', 'verbose_name_plural': 'Active registration passwords'},
+ name="registrationpassword",
+ options={
+ "verbose_name": "Active registration password",
+ "verbose_name_plural": "Active registration passwords",
+ },
),
]
diff --git a/jdav_web/logindata/tests/__init__.py b/jdav_web/logindata/tests/__init__.py
index e39bc3b..9dacb05 100644
--- a/jdav_web/logindata/tests/__init__.py
+++ b/jdav_web/logindata/tests/__init__.py
@@ -1,2 +1,4 @@
+# ruff: noqa F403
+
+from .oauth import *
from .views import *
-from .oauth import *
\ No newline at end of file
diff --git a/jdav_web/logindata/tests/oauth.py b/jdav_web/logindata/tests/oauth.py
index 9a414a1..867889d 100644
--- a/jdav_web/logindata/tests/oauth.py
+++ b/jdav_web/logindata/tests/oauth.py
@@ -1,9 +1,11 @@
-from django.test import TestCase
-from django.contrib.auth.models import User
-from django.conf import settings
from unittest.mock import Mock
+
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.test import TestCase
from logindata.oauth import CustomOAuth2Validator
-from members.models import Member, MALE
+from members.models import MALE
+from members.models import Member
class CustomOAuth2ValidatorTestCase(TestCase):
@@ -13,8 +15,12 @@ class CustomOAuth2ValidatorTestCase(TestCase):
# Create user with member
self.user_with_member = User.objects.create_user(username="alice", password="test123")
self.member = Member.objects.create(
- prename="Alice", lastname="Smith", birth_date="1990-01-01",
- email=settings.TEST_MAIL, gender=MALE, user=self.user_with_member
+ prename="Alice",
+ lastname="Smith",
+ birth_date="1990-01-01",
+ email=settings.TEST_MAIL,
+ gender=MALE,
+ user=self.user_with_member,
)
# Create user without member
@@ -27,8 +33,8 @@ class CustomOAuth2ValidatorTestCase(TestCase):
result = self.validator.get_additional_claims(request)
- self.assertEqual(result['email'], settings.TEST_MAIL)
- self.assertEqual(result['preferred_username'], 'alice')
+ self.assertEqual(result["email"], settings.TEST_MAIL)
+ self.assertEqual(result["preferred_username"], "alice")
def test_get_additional_claims_without_member(self):
"""Test get_additional_claims when user has no member"""
diff --git a/jdav_web/logindata/tests/views.py b/jdav_web/logindata/tests/views.py
index 00e22d2..e109e07 100644
--- a/jdav_web/logindata/tests/views.py
+++ b/jdav_web/logindata/tests/views.py
@@ -1,12 +1,16 @@
from http import HTTPStatus
-from django.test import TestCase, Client
+
+from django.contrib.auth.models import Group
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext as _
-from django.contrib.auth.models import User, Group
+from members.models import DIVERSE
+from members.models import Member
-from members.models import Member, DIVERSE
-from ..models import RegistrationPassword, initial_user_setup
+from ..models import RegistrationPassword
class RegistrationPasswordTestCase(TestCase):
@@ -22,133 +26,152 @@ class RegisterViewTestCase(TestCase):
# Create a test member with invite key
self.member = Member.objects.create(
- prename='Test',
- lastname='User',
+ prename="Test",
+ lastname="User",
birth_date=timezone.now().date(),
- email='test@example.com',
+ email="test@example.com",
gender=DIVERSE,
- invite_as_user_key='test_key_123'
+ invite_as_user_key="test_key_123",
)
# Create a registration password
- self.registration_password = RegistrationPassword.objects.create(
- password='test_password'
- )
+ self.registration_password = RegistrationPassword.objects.create(password="test_password")
# Get or create Standard group for user setup
- self.standard_group, created = Group.objects.get_or_create(name='Standard')
+ self.standard_group, created = Group.objects.get_or_create(name="Standard")
def test_register_get_without_key_redirects(self):
"""Test GET request without key redirects to startpage."""
- url = reverse('logindata:register')
+ url = reverse("logindata:register")
response = self.client.get(url)
self.assertEqual(response.status_code, HTTPStatus.FOUND)
def test_register_post_without_key_redirects(self):
"""Test POST request without key redirects to startpage."""
- url = reverse('logindata:register')
+ url = reverse("logindata:register")
response = self.client.post(url)
self.assertEqual(response.status_code, HTTPStatus.FOUND)
def test_register_get_with_empty_key_shows_failed(self):
"""Test GET request with empty key shows registration failed page."""
- url = reverse('logindata:register')
- response = self.client.get(url, {'key': ''})
+ url = reverse("logindata:register")
+ response = self.client.get(url, {"key": ""})
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
+ self.assertContains(
+ response, _("Something went wrong. The registration key is invalid or has expired.")
+ )
def test_register_get_with_invalid_key_shows_failed(self):
"""Test GET request with invalid key shows registration failed page."""
- url = reverse('logindata:register')
- response = self.client.get(url, {'key': 'invalid_key'})
+ url = reverse("logindata:register")
+ response = self.client.get(url, {"key": "invalid_key"})
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
+ self.assertContains(
+ response, _("Something went wrong. The registration key is invalid or has expired.")
+ )
def test_register_get_with_valid_key_shows_password_form(self):
"""Test GET request with valid key shows password entry form."""
- url = reverse('logindata:register')
- response = self.client.get(url, {'key': self.member.invite_as_user_key})
+ url = reverse("logindata:register")
+ response = self.client.get(url, {"key": self.member.invite_as_user_key})
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Set login data'))
- self.assertContains(response, _('Welcome, '))
+ self.assertContains(response, _("Set login data"))
+ self.assertContains(response, _("Welcome, "))
self.assertContains(response, self.member.prename)
def test_register_post_without_password_shows_failed(self):
"""Test POST request without password shows registration failed page."""
- url = reverse('logindata:register')
- response = self.client.post(url, {'key': self.member.invite_as_user_key})
+ url = reverse("logindata:register")
+ response = self.client.post(url, {"key": self.member.invite_as_user_key})
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
+ self.assertContains(
+ response, _("Something went wrong. The registration key is invalid or has expired.")
+ )
def test_register_post_with_wrong_password_shows_error(self):
"""Test POST request with wrong password shows error message."""
- url = reverse('logindata:register')
- response = self.client.post(url, {
- 'key': self.member.invite_as_user_key,
- 'password': 'wrong_password'
- })
+ url = reverse("logindata:register")
+ response = self.client.post(
+ url, {"key": self.member.invite_as_user_key, "password": "wrong_password"}
+ )
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('You entered a wrong password.'))
+ self.assertContains(response, _("You entered a wrong password."))
def test_register_post_with_correct_password_shows_form(self):
"""Test POST request with correct password shows user creation form."""
- url = reverse('logindata:register')
- response = self.client.post(url, {
- 'key': self.member.invite_as_user_key,
- 'password': self.registration_password.password
- })
+ url = reverse("logindata:register")
+ response = self.client.post(
+ url,
+ {
+ "key": self.member.invite_as_user_key,
+ "password": self.registration_password.password,
+ },
+ )
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Set login data'))
+ self.assertContains(response, _("Set login data"))
self.assertContains(response, self.member.suggested_username())
def test_register_post_with_save_and_invalid_form_shows_errors(self):
"""Test POST request with save but invalid form shows form errors."""
- url = reverse('logindata:register')
- response = self.client.post(url, {
- 'key': self.member.invite_as_user_key,
- 'password': self.registration_password.password,
- 'save': 'true',
- 'username': '', # Invalid - empty username
- 'password1': 'testpass123',
- 'password2': 'different_pass' # Invalid - passwords don't match
- })
+ url = reverse("logindata:register")
+ response = self.client.post(
+ url,
+ {
+ "key": self.member.invite_as_user_key,
+ "password": self.registration_password.password,
+ "save": "true",
+ "username": "", # Invalid - empty username
+ "password1": "testpass123",
+ "password2": "different_pass", # Invalid - passwords don't match
+ },
+ )
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Set login data'))
+ self.assertContains(response, _("Set login data"))
def test_register_post_with_save_and_valid_form_shows_success(self):
"""Test POST request with save and valid form shows success page."""
- url = reverse('logindata:register')
- response = self.client.post(url, {
- 'key': self.member.invite_as_user_key,
- 'password': self.registration_password.password,
- 'save': 'true',
- 'username': 'testuser',
- 'password1': 'testpass123',
- 'password2': 'testpass123'
- })
+ url = reverse("logindata:register")
+ response = self.client.post(
+ url,
+ {
+ "key": self.member.invite_as_user_key,
+ "password": self.registration_password.password,
+ "save": "true",
+ "username": "testuser",
+ "password1": "testpass123",
+ "password2": "testpass123",
+ },
+ )
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('You successfully set your login data. You can now proceed to'))
+ self.assertContains(
+ response, _("You successfully set your login data. You can now proceed to")
+ )
# Verify user was created and associated with member
- user = User.objects.get(username='testuser')
+ user = User.objects.get(username="testuser")
self.assertEqual(user.is_staff, True)
self.member.refresh_from_db()
self.assertEqual(self.member.user, user)
- self.assertEqual(self.member.invite_as_user_key, '')
+ self.assertEqual(self.member.invite_as_user_key, "")
def test_register_post_with_save_and_no_standard_group_shows_failed(self):
"""Test POST request with save but no Standard group shows failed page."""
# Delete the Standard group
self.standard_group.delete()
- url = reverse('logindata:register')
- response = self.client.post(url, {
- 'key': self.member.invite_as_user_key,
- 'password': self.registration_password.password,
- 'save': 'true',
- 'username': 'testuser',
- 'password1': 'testpass123',
- 'password2': 'testpass123'
- })
+ url = reverse("logindata:register")
+ response = self.client.post(
+ url,
+ {
+ "key": self.member.invite_as_user_key,
+ "password": self.registration_password.password,
+ "save": "true",
+ "username": "testuser",
+ "password1": "testpass123",
+ "password2": "testpass123",
+ },
+ )
self.assertEqual(response.status_code, HTTPStatus.OK)
- self.assertContains(response, _('Something went wrong. The registration key is invalid or has expired.'))
\ No newline at end of file
+ self.assertContains(
+ response, _("Something went wrong. The registration key is invalid or has expired.")
+ )
diff --git a/jdav_web/ludwigsburgalpin/migrations/0001_initial_squashed_0007_alter_termin_group.py b/jdav_web/ludwigsburgalpin/migrations/0001_initial_squashed_0007_alter_termin_group.py
index c34e944..3292c16 100644
--- a/jdav_web/ludwigsburgalpin/migrations/0001_initial_squashed_0007_alter_termin_group.py
+++ b/jdav_web/ludwigsburgalpin/migrations/0001_initial_squashed_0007_alter_termin_group.py
@@ -1,46 +1,194 @@
# Generated by Django 4.0.1 on 2023-03-29 20:40
import django.core.validators
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
- replaces = [('ludwigsburgalpin', '0001_initial'), ('ludwigsburgalpin', '0002_auto_20190926_1432'), ('ludwigsburgalpin', '0003_auto_20190926_1749'), ('ludwigsburgalpin', '0004_alter_termin_id'), ('ludwigsburgalpin', '0005_alter_termin_id'), ('ludwigsburgalpin', '0006_termin_anforderung_dauer_termin_anforderung_hoehe_and_more'), ('ludwigsburgalpin', '0007_alter_termin_group')]
-
- dependencies = [
+ replaces = [
+ ("ludwigsburgalpin", "0001_initial"),
+ ("ludwigsburgalpin", "0002_auto_20190926_1432"),
+ ("ludwigsburgalpin", "0003_auto_20190926_1749"),
+ ("ludwigsburgalpin", "0004_alter_termin_id"),
+ ("ludwigsburgalpin", "0005_alter_termin_id"),
+ ("ludwigsburgalpin", "0006_termin_anforderung_dauer_termin_anforderung_hoehe_and_more"),
+ ("ludwigsburgalpin", "0007_alter_termin_group"),
]
+ dependencies = []
+
operations = [
migrations.CreateModel(
- name='Termin',
+ name="Termin",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('title', models.CharField(max_length=100, verbose_name='Titel')),
- ('start_date', models.DateField(verbose_name='Von')),
- ('end_date', models.DateField(verbose_name='Bis')),
- ('group', models.CharField(choices=[('ASG', 'Alpinsportgruppe'), ('OGB', 'Ortsgruppe Bietigheim'), ('OGV', 'Ortsgruppe Vaihingen'), ('JUG', 'Jugend'), ('FAM', 'Familie'), ('Ü30', 'Ü30'), ('MTB', 'Mountainbike'), ('RA', 'RegioAktiv'), ('SEK', 'Sektion')], max_length=100, verbose_name='Gruppe')),
- ('description', models.TextField(blank=True, verbose_name='Beschreibung')),
- ('email', models.EmailField(max_length=100, verbose_name='Email')),
- ('phone', models.CharField(blank=True, max_length=20, verbose_name='Telefonnumer')),
- ('responsible', models.CharField(max_length=100, verbose_name='Organisator')),
- ('anforderung_dauer', models.IntegerField(blank=True, default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Etappendauer in Stunden')),
- ('anforderung_hoehe', models.IntegerField(blank=True, default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Höhenmeter in Meter')),
- ('anforderung_strecke', models.IntegerField(blank=True, default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='Strecke in Kilometer')),
- ('category', models.CharField(choices=[('WAN', 'Wandern'), ('BW', 'Bergwandern'), ('KST', 'Klettersteig'), ('KL', 'Klettern'), ('SKI', 'Piste, Loipe'), ('SCH', 'Schneeschuhgehen'), ('ST', 'Skitour'), ('STH', 'Skihochtour'), ('HT', 'Hochtour'), ('MTB', 'Montainbike'), ('AUS', 'Ausbildung'), ('SON', 'Sonstiges z.B. Treffen')], default='SON', max_length=100, verbose_name='Kategorie')),
- ('condition', models.CharField(choices=[('gering', 'gering'), ('mittel', 'mittel'), ('groß', 'groß'), ('sehr groß', 'sehr groß')], default='mittel', max_length=100, verbose_name='Kondition')),
- ('equipment', models.TextField(blank=True, verbose_name='Ausrüstung')),
- ('eventart', models.CharField(choices=[('Einzeltermin', 'Einzeltermin'), ('Mehrtagesevent', 'Mehrtagesevent'), ('Regelmäßiges Event/Training', 'Regelmäßiges Event/Training'), ('Tagesevent', 'Tagesevent'), ('Wochenendevent', 'Wochenendevent')], default='Einzeltermin', max_length=100, verbose_name='Eventart')),
- ('klassifizierung', models.CharField(choices=[('Gemeinschaftstour', 'Gemeinschaftstour'), ('Ausbildung', 'Ausbildung')], default='Gemeinschaftstour', max_length=100, verbose_name='Klassifizierung')),
- ('max_participants', models.IntegerField(default=10, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Max. Teilnehmerzahl')),
- ('saison', models.CharField(choices=[('ganzjährig', 'ganzjährig'), ('Indoor', 'Indoor'), ('Sommer', 'Sommer'), ('Winter', 'Winter')], default='ganzjährig', max_length=100, verbose_name='Saison')),
- ('subtitle', models.CharField(blank=True, max_length=100, verbose_name='Untertitel')),
- ('technik', models.CharField(choices=[('leicht', 'leicht'), ('mittel', 'mittel'), ('schwer', 'schwer'), ('sehr schwer', 'sehr schwer')], default='mittel', max_length=100, verbose_name='Technik')),
- ('voraussetzungen', models.TextField(blank=True, verbose_name='Voraussetzungen')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ ("title", models.CharField(max_length=100, verbose_name="Titel")),
+ ("start_date", models.DateField(verbose_name="Von")),
+ ("end_date", models.DateField(verbose_name="Bis")),
+ (
+ "group",
+ models.CharField(
+ choices=[
+ ("ASG", "Alpinsportgruppe"),
+ ("OGB", "Ortsgruppe Bietigheim"),
+ ("OGV", "Ortsgruppe Vaihingen"),
+ ("JUG", "Jugend"),
+ ("FAM", "Familie"),
+ ("Ü30", "Ü30"),
+ ("MTB", "Mountainbike"),
+ ("RA", "RegioAktiv"),
+ ("SEK", "Sektion"),
+ ],
+ max_length=100,
+ verbose_name="Gruppe",
+ ),
+ ),
+ ("description", models.TextField(blank=True, verbose_name="Beschreibung")),
+ ("email", models.EmailField(max_length=100, verbose_name="Email")),
+ ("phone", models.CharField(blank=True, max_length=20, verbose_name="Telefonnumer")),
+ ("responsible", models.CharField(max_length=100, verbose_name="Organisator")),
+ (
+ "anforderung_dauer",
+ models.IntegerField(
+ blank=True,
+ default=0,
+ validators=[django.core.validators.MinValueValidator(0)],
+ verbose_name="Etappendauer in Stunden",
+ ),
+ ),
+ (
+ "anforderung_hoehe",
+ models.IntegerField(
+ blank=True,
+ default=0,
+ validators=[django.core.validators.MinValueValidator(0)],
+ verbose_name="Höhenmeter in Meter",
+ ),
+ ),
+ (
+ "anforderung_strecke",
+ models.IntegerField(
+ blank=True,
+ default=0,
+ validators=[django.core.validators.MinValueValidator(0)],
+ verbose_name="Strecke in Kilometer",
+ ),
+ ),
+ (
+ "category",
+ models.CharField(
+ choices=[
+ ("WAN", "Wandern"),
+ ("BW", "Bergwandern"),
+ ("KST", "Klettersteig"),
+ ("KL", "Klettern"),
+ ("SKI", "Piste, Loipe"),
+ ("SCH", "Schneeschuhgehen"),
+ ("ST", "Skitour"),
+ ("STH", "Skihochtour"),
+ ("HT", "Hochtour"),
+ ("MTB", "Montainbike"),
+ ("AUS", "Ausbildung"),
+ ("SON", "Sonstiges z.B. Treffen"),
+ ],
+ default="SON",
+ max_length=100,
+ verbose_name="Kategorie",
+ ),
+ ),
+ (
+ "condition",
+ models.CharField(
+ choices=[
+ ("gering", "gering"),
+ ("mittel", "mittel"),
+ ("groß", "groß"),
+ ("sehr groß", "sehr groß"),
+ ],
+ default="mittel",
+ max_length=100,
+ verbose_name="Kondition",
+ ),
+ ),
+ ("equipment", models.TextField(blank=True, verbose_name="Ausrüstung")),
+ (
+ "eventart",
+ models.CharField(
+ choices=[
+ ("Einzeltermin", "Einzeltermin"),
+ ("Mehrtagesevent", "Mehrtagesevent"),
+ ("Regelmäßiges Event/Training", "Regelmäßiges Event/Training"),
+ ("Tagesevent", "Tagesevent"),
+ ("Wochenendevent", "Wochenendevent"),
+ ],
+ default="Einzeltermin",
+ max_length=100,
+ verbose_name="Eventart",
+ ),
+ ),
+ (
+ "klassifizierung",
+ models.CharField(
+ choices=[
+ ("Gemeinschaftstour", "Gemeinschaftstour"),
+ ("Ausbildung", "Ausbildung"),
+ ],
+ default="Gemeinschaftstour",
+ max_length=100,
+ verbose_name="Klassifizierung",
+ ),
+ ),
+ (
+ "max_participants",
+ models.IntegerField(
+ default=10,
+ validators=[django.core.validators.MinValueValidator(1)],
+ verbose_name="Max. Teilnehmerzahl",
+ ),
+ ),
+ (
+ "saison",
+ models.CharField(
+ choices=[
+ ("ganzjährig", "ganzjährig"),
+ ("Indoor", "Indoor"),
+ ("Sommer", "Sommer"),
+ ("Winter", "Winter"),
+ ],
+ default="ganzjährig",
+ max_length=100,
+ verbose_name="Saison",
+ ),
+ ),
+ (
+ "subtitle",
+ models.CharField(blank=True, max_length=100, verbose_name="Untertitel"),
+ ),
+ (
+ "technik",
+ models.CharField(
+ choices=[
+ ("leicht", "leicht"),
+ ("mittel", "mittel"),
+ ("schwer", "schwer"),
+ ("sehr schwer", "sehr schwer"),
+ ],
+ default="mittel",
+ max_length=100,
+ verbose_name="Technik",
+ ),
+ ),
+ ("voraussetzungen", models.TextField(blank=True, verbose_name="Voraussetzungen")),
],
options={
- 'verbose_name_plural': 'Termine',
- 'verbose_name': 'Termin',
+ "verbose_name_plural": "Termine",
+ "verbose_name": "Termin",
},
),
]
diff --git a/jdav_web/mailer/admin.py b/jdav_web/mailer/admin.py
index 2fa17cc..615db82 100644
--- a/jdav_web/mailer/admin.py
+++ b/jdav_web/mailer/admin.py
@@ -7,14 +7,10 @@ from django.contrib import admin
from django.contrib import messages
from django.contrib.admin import helpers
from django.shortcuts import render
-from django.utils.translation import (
- gettext_lazy as _,
-)
+from django.utils.translation import gettext_lazy as _
from members.admin import FilteredMemberFieldMixin
from members.models import Member
-from rules.contrib.admin import (
- ObjectPermissionsModelAdmin,
-)
+from rules.contrib.admin import ObjectPermissionsModelAdmin
from .mailutils import NOT_SENT
from .mailutils import PARTLY_SENT
@@ -23,6 +19,7 @@ from .models import EmailAddress
from .models import EmailAddressForm
from .models import Message
from .models import MessageForm
+
# from easy_select2 import apply_select2
diff --git a/jdav_web/mailer/mailutils.py b/jdav_web/mailer/mailutils.py
index c98dca1..6164e2e 100644
--- a/jdav_web/mailer/mailutils.py
+++ b/jdav_web/mailer/mailutils.py
@@ -4,7 +4,6 @@ from django.conf import settings
from django.core import mail
from django.core.mail import EmailMessage
-
logger = logging.getLogger(__name__)
diff --git a/jdav_web/mailer/management/commands/notify_active.py b/jdav_web/mailer/management/commands/notify_active.py
index 4a71779..665e546 100644
--- a/jdav_web/mailer/management/commands/notify_active.py
+++ b/jdav_web/mailer/management/commands/notify_active.py
@@ -1,20 +1,19 @@
+from django.conf import settings
from django.core.management.base import BaseCommand
-from mailer.models import Message
-from members.models import Member, annotate_activity_score
-from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from mailer.mailutils import send
-from django.conf import settings
-
-import re
+from members.models import annotate_activity_score
+from members.models import Member
class Command(BaseCommand):
- help = 'Congratulates the most active members'
+ help = "Congratulates the most active members"
requires_system_checks = False
def handle(self, *args, **options):
- qs = list(reversed(annotate_activity_score(Member.objects.all()).order_by('_activity_score')))[:settings.CONGRATULATE_MEMBERS_MAX]
+ qs = list(
+ reversed(annotate_activity_score(Member.objects.all()).order_by("_activity_score"))
+ )[: settings.CONGRATULATE_MEMBERS_MAX]
for position, member in enumerate(qs):
positiontext = "{}. ".format(position + 1) if position > 0 else ""
score = member._activity_score
@@ -28,11 +27,17 @@ class Command(BaseCommand):
level = 4
else:
level = 5
- content = settings.NOTIFY_MOST_ACTIVE_TEXT.format(name=member.prename,
- congratulate_max=CONGRATULATE_MEMBERS_MAX,
- score=score,
- level=level,
- position=positiontext)
- send(_("Congratulation %(name)s") % { 'name': member.prename },
- content, settings.DEFAULT_SENDING_ADDRESS, [member.email],
- reply_to=[settings.RESPONSIBLE_MAIL])
+ content = settings.NOTIFY_MOST_ACTIVE_TEXT.format(
+ name=member.prename,
+ congratulate_max=settings.CONGRATULATE_MEMBERS_MAX,
+ score=score,
+ level=level,
+ position=positiontext,
+ )
+ send(
+ _("Congratulation %(name)s") % {"name": member.prename},
+ content,
+ settings.DEFAULT_SENDING_ADDRESS,
+ [member.email],
+ reply_to=[settings.RESPONSIBLE_MAIL],
+ )
diff --git a/jdav_web/mailer/management/commands/reply_addrs.py b/jdav_web/mailer/management/commands/reply_addrs.py
index 89f373e..a256c73 100644
--- a/jdav_web/mailer/management/commands/reply_addrs.py
+++ b/jdav_web/mailer/management/commands/reply_addrs.py
@@ -1,30 +1,30 @@
+import re
+
from django.core.management.base import BaseCommand
from mailer.models import Message
from members.models import Member
-from django.db.models import Q
-
-import re
class Command(BaseCommand):
- help = 'Shows reply-to addresses'
+ help = "Shows reply-to addresses"
requires_system_checks = False
def add_arguments(self, parser):
- parser.add_argument('--message_id', default="-1")
- parser.add_argument('--subject', default="")
+ parser.add_argument("--message_id", default="-1")
+ parser.add_argument("--subject", default="")
def handle(self, *args, **options):
replies = []
try:
- message_id = int(options['message_id'])
+ message_id = int(options["message_id"])
message = Message.objects.get(pk=message_id)
if message.reply_to:
replies = list(message.reply_to.all())
replies.extend(message.reply_to_email_address.all())
except (Message.DoesNotExist, ValueError):
- extracted = re.match("^([Ww][Gg]: *|[Ff][Ww]: *|[Rr][Ee]: *|[Aa][Ww]: *)* *(.*)$",
- options['subject']).group(2)
+ extracted = re.match(
+ "^([Ww][Gg]: *|[Ff][Ww]: *|[Rr][Ee]: *|[Aa][Ww]: *)* *(.*)$", options["subject"]
+ ).group(2)
try:
msgs = Message.objects.filter(subject=extracted)
message = msgs.all()[0]
@@ -36,8 +36,7 @@ class Command(BaseCommand):
if not replies:
# send mail to all jugendleiters
- replies = Member.objects.filter(group__name='Jugendleiter',
- gets_newsletter=True)
- forwards = [l.email for l in replies]
+ replies = Member.objects.filter(group__name="Jugendleiter", gets_newsletter=True)
+ forwards = [lst.email for lst in replies]
self.stdout.write(" ".join(forwards))
diff --git a/jdav_web/mailer/migrations/0001_initial_squashed_0006_auto_20210924_1155.py b/jdav_web/mailer/migrations/0001_initial_squashed_0006_auto_20210924_1155.py
index 9673d80..4d52ea7 100644
--- a/jdav_web/mailer/migrations/0001_initial_squashed_0006_auto_20210924_1155.py
+++ b/jdav_web/mailer/migrations/0001_initial_squashed_0006_auto_20210924_1155.py
@@ -1,79 +1,165 @@
# Generated by Django 4.0.1 on 2023-03-29 20:38
import django.core.validators
-from django.db import migrations, models
import django.db.models.deletion
import utils
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
- replaces = [('mailer', '0001_initial'), ('mailer', '0002_auto_20190615_1225'), ('mailer', '0003_emailaddress'), ('mailer', '0004_auto_20200924_1744'), ('mailer', '0005_auto_20200924_2139'), ('mailer', '0006_auto_20210924_1155')]
+ replaces = [
+ ("mailer", "0001_initial"),
+ ("mailer", "0002_auto_20190615_1225"),
+ ("mailer", "0003_emailaddress"),
+ ("mailer", "0004_auto_20200924_1744"),
+ ("mailer", "0005_auto_20200924_2139"),
+ ("mailer", "0006_auto_20210924_1155"),
+ ]
dependencies = [
- ('members', '0006_auto_20190914_2341'),
- ('members', '0008_auto_20210924_1155'),
- ('members', '0001_initial'),
- ('members', '0007_auto_20200924_1512'),
- ('members', '0005_auto_20190615_1224'),
+ ("members", "0006_auto_20190914_2341"),
+ ("members", "0008_auto_20210924_1155"),
+ ("members", "0001_initial"),
+ ("members", "0007_auto_20200924_1512"),
+ ("members", "0005_auto_20190615_1224"),
]
operations = [
migrations.CreateModel(
- name='Message',
+ name="Message",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('subject', models.CharField(max_length=50, verbose_name='subject')),
- ('content', models.TextField(verbose_name='content')),
- ('sent', models.BooleanField(default=False, verbose_name='sent')),
- ('to_groups', models.ManyToManyField(blank=True, to='members.Group', verbose_name='to group')),
- ('to_members', models.ManyToManyField(blank=True, to='members.Member', verbose_name='to member')),
- ('reply_to', models.ManyToManyField(blank=True, related_name='reply_to', to='members.Member', verbose_name='reply to participant')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ ("subject", models.CharField(max_length=50, verbose_name="subject")),
+ ("content", models.TextField(verbose_name="content")),
+ ("sent", models.BooleanField(default=False, verbose_name="sent")),
+ (
+ "to_groups",
+ models.ManyToManyField(blank=True, to="members.Group", verbose_name="to group"),
+ ),
+ (
+ "to_members",
+ models.ManyToManyField(
+ blank=True, to="members.Member", verbose_name="to member"
+ ),
+ ),
+ (
+ "reply_to",
+ models.ManyToManyField(
+ blank=True,
+ related_name="reply_to",
+ to="members.Member",
+ verbose_name="reply to participant",
+ ),
+ ),
],
options={
- 'verbose_name_plural': 'messages',
- 'permissions': (('submit_mails', 'Can submit mails'),),
- 'verbose_name': 'message',
+ "verbose_name_plural": "messages",
+ "permissions": (("submit_mails", "Can submit mails"),),
+ "verbose_name": "message",
},
),
migrations.CreateModel(
- name='Attachment',
+ name="Attachment",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('f', utils.RestrictedFileField(blank=True, upload_to='attachments', verbose_name='file')),
- ('msg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mailer.message')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ (
+ "f",
+ utils.RestrictedFileField(
+ blank=True, upload_to="attachments", verbose_name="file"
+ ),
+ ),
+ (
+ "msg",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="mailer.message"
+ ),
+ ),
],
options={
- 'verbose_name_plural': 'attachments',
- 'verbose_name': 'attachment',
+ "verbose_name_plural": "attachments",
+ "verbose_name": "attachment",
},
),
migrations.CreateModel(
- name='EmailAddress',
+ name="EmailAddress",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=50, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z]*$', 'Only alphanumeric characters are allowed')], verbose_name='name')),
- ('to_members', models.ManyToManyField(blank=True, to='members.Member', verbose_name='Forward to participants')),
- ('to_groups', models.ManyToManyField(blank=True, to='members.Group', verbose_name='Forward to group')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
+ ),
+ ),
+ (
+ "name",
+ models.CharField(
+ max_length=50,
+ validators=[
+ django.core.validators.RegexValidator(
+ "^[0-9a-zA-Z]*$", "Only alphanumeric characters are allowed"
+ )
+ ],
+ verbose_name="name",
+ ),
+ ),
+ (
+ "to_members",
+ models.ManyToManyField(
+ blank=True, to="members.Member", verbose_name="Forward to participants"
+ ),
+ ),
+ (
+ "to_groups",
+ models.ManyToManyField(
+ blank=True, to="members.Group", verbose_name="Forward to group"
+ ),
+ ),
],
options={
- 'verbose_name_plural': 'email addresses',
- 'verbose_name': 'email address',
+ "verbose_name_plural": "email addresses",
+ "verbose_name": "email address",
},
),
migrations.AddField(
- model_name='message',
- name='reply_to_email_address',
- field=models.ManyToManyField(blank=True, related_name='reply_to_email_addr', to='mailer.EmailAddress', verbose_name='reply to custom email address'),
+ model_name="message",
+ name="reply_to_email_address",
+ field=models.ManyToManyField(
+ blank=True,
+ related_name="reply_to_email_addr",
+ to="mailer.EmailAddress",
+ verbose_name="reply to custom email address",
+ ),
),
migrations.AddField(
- model_name='message',
- name='to_freizeit',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='members.freizeit', verbose_name='to freizeit'),
+ model_name="message",
+ name="to_freizeit",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="members.freizeit",
+ verbose_name="to freizeit",
+ ),
),
migrations.AddField(
- model_name='message',
- name='to_notelist',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='members.membernotelist', verbose_name='to notes list'),
+ model_name="message",
+ name="to_notelist",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="members.membernotelist",
+ verbose_name="to notes list",
+ ),
),
]
diff --git a/jdav_web/mailer/migrations/0002_message_created_by.py b/jdav_web/mailer/migrations/0002_message_created_by.py
index 1a2d565..ea73320 100644
--- a/jdav_web/mailer/migrations/0002_message_created_by.py
+++ b/jdav_web/mailer/migrations/0002_message_created_by.py
@@ -1,20 +1,27 @@
# Generated by Django 4.0.1 on 2023-04-02 12:06
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
- ('members', '0006_rename_permissions'),
- ('mailer', '0001_initial_squashed_0006_auto_20210924_1155'),
+ ("members", "0006_rename_permissions"),
+ ("mailer", "0001_initial_squashed_0006_auto_20210924_1155"),
]
operations = [
migrations.AddField(
- model_name='message',
- name='created_by',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_messages', to='members.member', verbose_name='Created by'),
+ model_name="message",
+ name="created_by",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="created_messages",
+ to="members.member",
+ verbose_name="Created by",
+ ),
),
]
diff --git a/jdav_web/mailer/migrations/0003_alter_message_options.py b/jdav_web/mailer/migrations/0003_alter_message_options.py
index 2b142bd..a765d5f 100644
--- a/jdav_web/mailer/migrations/0003_alter_message_options.py
+++ b/jdav_web/mailer/migrations/0003_alter_message_options.py
@@ -4,14 +4,25 @@ from django.db import migrations
class Migration(migrations.Migration):
-
dependencies = [
- ('mailer', '0002_message_created_by'),
+ ("mailer", "0002_message_created_by"),
]
operations = [
migrations.AlterModelOptions(
- name='message',
- options={'default_permissions': ('add_global', 'change_global', 'view_global', 'delete_global', 'list_global', 'view'), 'permissions': (('submit_mails', 'Can submit mails'),), 'verbose_name': 'message', 'verbose_name_plural': 'messages'},
+ name="message",
+ options={
+ "default_permissions": (
+ "add_global",
+ "change_global",
+ "view_global",
+ "delete_global",
+ "list_global",
+ "view",
+ ),
+ "permissions": (("submit_mails", "Can submit mails"),),
+ "verbose_name": "message",
+ "verbose_name_plural": "messages",
+ },
),
]
diff --git a/jdav_web/mailer/migrations/0004_alter_attachment_f.py b/jdav_web/mailer/migrations/0004_alter_attachment_f.py
index d92419c..8607d55 100644
--- a/jdav_web/mailer/migrations/0004_alter_attachment_f.py
+++ b/jdav_web/mailer/migrations/0004_alter_attachment_f.py
@@ -1,19 +1,18 @@
# Generated by Django 4.0.1 on 2024-11-17 23:31
-from django.db import migrations
import utils
+from django.db import migrations
class Migration(migrations.Migration):
-
dependencies = [
- ('mailer', '0003_alter_message_options'),
+ ("mailer", "0003_alter_message_options"),
]
operations = [
migrations.AlterField(
- model_name='attachment',
- name='f',
- field=utils.RestrictedFileField(upload_to='attachments', verbose_name='file'),
+ model_name="attachment",
+ name="f",
+ field=utils.RestrictedFileField(upload_to="attachments", verbose_name="file"),
),
]
diff --git a/jdav_web/mailer/migrations/0005_alter_emailaddress_name.py b/jdav_web/mailer/migrations/0005_alter_emailaddress_name.py
index 4910bc5..7a55fab 100644
--- a/jdav_web/mailer/migrations/0005_alter_emailaddress_name.py
+++ b/jdav_web/mailer/migrations/0005_alter_emailaddress_name.py
@@ -1,19 +1,27 @@
# Generated by Django 4.0.1 on 2024-11-23 14:03
import django.core.validators
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
- ('mailer', '0004_alter_attachment_f'),
+ ("mailer", "0004_alter_attachment_f"),
]
operations = [
migrations.AlterField(
- model_name='emailaddress',
- name='name',
- field=models.CharField(max_length=50, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z._-]*$', 'Only alphanumeric characters, ., - and _ are allowed')], verbose_name='name'),
+ model_name="emailaddress",
+ name="name",
+ field=models.CharField(
+ max_length=50,
+ validators=[
+ django.core.validators.RegexValidator(
+ "^[0-9a-zA-Z._-]*$", "Only alphanumeric characters, ., - and _ are allowed"
+ )
+ ],
+ verbose_name="name",
+ ),
),
]
diff --git a/jdav_web/mailer/migrations/0006_emailaddress_allowed_senders.py b/jdav_web/mailer/migrations/0006_emailaddress_allowed_senders.py
index dd6de2f..1f86d24 100644
--- a/jdav_web/mailer/migrations/0006_emailaddress_allowed_senders.py
+++ b/jdav_web/mailer/migrations/0006_emailaddress_allowed_senders.py
@@ -1,19 +1,25 @@
# Generated by Django 4.0.1 on 2024-12-01 15:54
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
- ('members', '0029_alter_member_gender_alter_memberwaitinglist_gender'),
- ('mailer', '0005_alter_emailaddress_name'),
+ ("members", "0029_alter_member_gender_alter_memberwaitinglist_gender"),
+ ("mailer", "0005_alter_emailaddress_name"),
]
operations = [
migrations.AddField(
- model_name='emailaddress',
- name='allowed_senders',
- field=models.ManyToManyField(blank=True, help_text='Only forward e-mails of members of selected groups. Leave empty to allow all senders.', related_name='allowed_sender_on_emailaddresses', to='members.Group', verbose_name='Allowed sender'),
+ model_name="emailaddress",
+ name="allowed_senders",
+ field=models.ManyToManyField(
+ blank=True,
+ help_text="Only forward e-mails of members of selected groups. Leave empty to allow all senders.",
+ related_name="allowed_sender_on_emailaddresses",
+ to="members.Group",
+ verbose_name="Allowed sender",
+ ),
),
]
diff --git a/jdav_web/mailer/migrations/0007_emailaddress_internal_only.py b/jdav_web/mailer/migrations/0007_emailaddress_internal_only.py
index 6bc75ce..c975df7 100644
--- a/jdav_web/mailer/migrations/0007_emailaddress_internal_only.py
+++ b/jdav_web/mailer/migrations/0007_emailaddress_internal_only.py
@@ -1,18 +1,22 @@
# Generated by Django 4.0.1 on 2024-12-01 17:45
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
- ('mailer', '0006_emailaddress_allowed_senders'),
+ ("mailer", "0006_emailaddress_allowed_senders"),
]
operations = [
migrations.AddField(
- model_name='emailaddress',
- name='internal_only',
- field=models.BooleanField(default=False, help_text='Only allow forwarding to this e-mail address from the internal domain.', verbose_name='Restrict to internal email addresses'),
+ model_name="emailaddress",
+ name="internal_only",
+ field=models.BooleanField(
+ default=False,
+ help_text="Only allow forwarding to this e-mail address from the internal domain.",
+ verbose_name="Restrict to internal email addresses",
+ ),
),
]
diff --git a/jdav_web/mailer/migrations/0008_alter_emailaddress_name.py b/jdav_web/mailer/migrations/0008_alter_emailaddress_name.py
index 952bacd..4494e8d 100644
--- a/jdav_web/mailer/migrations/0008_alter_emailaddress_name.py
+++ b/jdav_web/mailer/migrations/0008_alter_emailaddress_name.py
@@ -1,19 +1,28 @@
# Generated by Django 4.0.1 on 2024-12-03 23:19
import django.core.validators
-from django.db import migrations, models
+from django.db import migrations
+from django.db import models
class Migration(migrations.Migration):
-
dependencies = [
- ('mailer', '0007_emailaddress_internal_only'),
+ ("mailer", "0007_emailaddress_internal_only"),
]
operations = [
migrations.AlterField(
- model_name='emailaddress',
- name='name',
- field=models.CharField(max_length=50, unique=True, validators=[django.core.validators.RegexValidator('^[0-9a-zA-Z._-]*$', 'Only alphanumeric characters, ., - and _ are allowed')], verbose_name='name'),
+ model_name="emailaddress",
+ name="name",
+ field=models.CharField(
+ max_length=50,
+ unique=True,
+ validators=[
+ django.core.validators.RegexValidator(
+ "^[0-9a-zA-Z._-]*$", "Only alphanumeric characters, ., - and _ are allowed"
+ )
+ ],
+ verbose_name="name",
+ ),
),
]
diff --git a/jdav_web/mailer/models.py b/jdav_web/mailer/models.py
index da77291..a8ecb80 100644
--- a/jdav_web/mailer/models.py
+++ b/jdav_web/mailer/models.py
@@ -20,7 +20,6 @@ from .mailutils import send
from .mailutils import SENT
from .rules import is_creator
-
logger = logging.getLogger(__name__)
diff --git a/jdav_web/mailer/templates/mailer/subscribe.html b/jdav_web/mailer/templates/mailer/subscribe.html
index 66b4223..281a1e8 100644
--- a/jdav_web/mailer/templates/mailer/subscribe.html
+++ b/jdav_web/mailer/templates/mailer/subscribe.html
@@ -1,4 +1,4 @@
-
+
{% load i18n %}
diff --git a/jdav_web/mailer/templates/mailer/unsubscribe.html b/jdav_web/mailer/templates/mailer/unsubscribe.html
index 53ba56e..e85b77d 100644
--- a/jdav_web/mailer/templates/mailer/unsubscribe.html
+++ b/jdav_web/mailer/templates/mailer/unsubscribe.html
@@ -20,7 +20,7 @@
{% trans "Email address" %}
-
+
diff --git a/jdav_web/mailer/tests/__init__.py b/jdav_web/mailer/tests/__init__.py index 7d5234a..0bad135 100644 --- a/jdav_web/mailer/tests/__init__.py +++ b/jdav_web/mailer/tests/__init__.py @@ -1,5 +1,7 @@ -from .models import * +# ruff: noqa F403 + from .admin import * -from .views import * -from .rules import * from .mailutils import * +from .models import * +from .rules import * +from .views import * diff --git a/jdav_web/mailer/tests/admin.py b/jdav_web/mailer/tests/admin.py index 52b0dab..127fb15 100644 --- a/jdav_web/mailer/tests/admin.py +++ b/jdav_web/mailer/tests/admin.py @@ -1,29 +1,31 @@ import json -import unittest from http import HTTPStatus -from django.test import TestCase, override_settings +from unittest.mock import patch + +from django.conf import settings from django.contrib.admin.sites import AdminSite -from django.test import RequestFactory, Client -from django.contrib.auth.models import User, Permission -from django.utils import timezone -from django.contrib.sessions.middleware import SessionMiddleware +from django.contrib.auth.models import User +from django.contrib.messages import get_messages from django.contrib.messages.middleware import MessageMiddleware from django.contrib.messages.storage.fallback import FallbackStorage -from django.contrib.messages import get_messages +from django.contrib.sessions.middleware import SessionMiddleware +from django.http import HttpResponseRedirect +from django.test import RequestFactory +from django.test import TestCase +from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django.urls import reverse, reverse_lazy -from django.http import HttpResponseRedirect, HttpResponse -from unittest.mock import Mock, patch -from django.test.utils import override_settings -from django.urls import path, include -from django.contrib import admin as django_admin -from django.conf import settings - +from members.models import DIVERSE +from members.models import Group +from members.models import Member from members.tests.utils import create_custom_user -from members.models import Member, MALE, DIVERSE, Group -from ..models import Message, Attachment, EmailAddress -from ..admin import MessageAdmin, submit_message -from ..mailutils import SENT, NOT_SENT, PARTLY_SENT + +from ..admin import MessageAdmin +from ..admin import submit_message +from ..mailutils import NOT_SENT +from ..mailutils import PARTLY_SENT +from ..mailutils import SENT +from ..models import EmailAddress +from ..models import Message class AdminTestCase(TestCase): @@ -32,11 +34,9 @@ class AdminTestCase(TestCase): self.model = model if model is not None and admin is not None: self.admin = admin(model, AdminSite()) - superuser = User.objects.create_superuser( - username='superuser', password='secret' - ) - standard = create_custom_user('standard', ['Standard'], 'Paul', 'Wulter') - trainer = create_custom_user('trainer', ['Standard', 'Trainings'], 'Lise', 'Lotte') + User.objects.create_superuser(username="superuser", password="secret") + create_custom_user("standard", ["Standard"], "Paul", "Wulter") + create_custom_user("trainer", ["Standard", "Trainings"], "Lise", "Lotte") def _add_middleware(self, request): """Add required middleware to request.""" @@ -56,53 +56,56 @@ class MessageAdminTestCase(AdminTestCase): super().setUp(Message, MessageAdmin) # Create test data - self.group = Group.objects.create(name='Test Group') - self.email_address = EmailAddress.objects.create(name='testmail') + self.group = Group.objects.create(name="Test Group") + self.email_address = EmailAddress.objects.create(name="testmail") # Create test member with internal email self.internal_member = Member.objects.create( - prename='Internal', - lastname='User', + prename="Internal", + lastname="User", birth_date=timezone.now().date(), - email=f'internal@{settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]}', - gender=DIVERSE + email=f"internal@{settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER[0]}", + gender=DIVERSE, ) # Create test member with external email self.external_member = Member.objects.create( - prename='External', - lastname='User', + prename="External", + lastname="User", birth_date=timezone.now().date(), - email='external@example.com', - gender=DIVERSE + email="external@example.com", + gender=DIVERSE, ) # Create users for testing - self.user_with_internal_member = User.objects.create_user(username='testuser', password='secret') + self.user_with_internal_member = User.objects.create_user( + username="testuser", password="secret" + ) self.user_with_internal_member.member = self.internal_member self.user_with_internal_member.save() - self.user_with_external_member = User.objects.create_user(username='external_user', password='secret') + self.user_with_external_member = User.objects.create_user( + username="external_user", password="secret" + ) self.user_with_external_member.member = self.external_member self.user_with_external_member.save() - self.user_without_member = User.objects.create_user(username='no_member_user', password='secret') + self.user_without_member = User.objects.create_user( + username="no_member_user", password="secret" + ) # Create test message - self.message = Message.objects.create( - subject='Test Message', - content='Test content' - ) + self.message = Message.objects.create(subject="Test Message", content="Test content") self.message.to_groups.add(self.group) self.message.to_members.add(self.internal_member) def test_save_model_sets_created_by(self): """Test that save_model sets created_by when creating new message.""" - request = self.factory.post('/admin/mailer/message/add/') + request = self.factory.post("/admin/mailer/message/add/") request.user = self.user_with_internal_member # Create new message - new_message = Message(subject='New Message', content='New content') + new_message = Message(subject="New Message", content="New content") # Test save_model for new object (change=False) self.admin.save_model(request, new_message, None, change=False) @@ -111,7 +114,7 @@ class MessageAdminTestCase(AdminTestCase): def test_save_model_does_not_change_created_by_on_update(self): """Test that save_model doesn't change created_by when updating.""" - request = self.factory.post('/admin/mailer/message/1/change/') + request = self.factory.post("/admin/mailer/message/1/change/") request.user = self.user_with_internal_member # Message already has created_by set @@ -122,12 +125,12 @@ class MessageAdminTestCase(AdminTestCase): self.assertEqual(self.message.created_by, self.external_member) - @patch('mailer.models.Message.submit') + @patch("mailer.models.Message.submit") def test_submit_message_success(self, mock_submit): """Test submit_message with successful send.""" mock_submit.return_value = SENT - request = self.factory.post('/admin/mailer/message/') + request = self.factory.post("/admin/mailer/message/") request.user = self.user_with_internal_member self._add_middleware(request) @@ -140,14 +143,14 @@ class MessageAdminTestCase(AdminTestCase): # Check success message messages_list = list(get_messages(request)) self.assertEqual(len(messages_list), 1) - self.assertIn(str(_('Successfully sent message')), str(messages_list[0])) + self.assertIn(str(_("Successfully sent message")), str(messages_list[0])) - @patch('mailer.models.Message.submit') + @patch("mailer.models.Message.submit") def test_submit_message_not_sent(self, mock_submit): """Test submit_message when sending fails.""" mock_submit.return_value = NOT_SENT - request = self.factory.post('/admin/mailer/message/') + request = self.factory.post("/admin/mailer/message/") request.user = self.user_with_internal_member self._add_middleware(request) @@ -157,14 +160,14 @@ class MessageAdminTestCase(AdminTestCase): # Check error message messages_list = list(get_messages(request)) self.assertEqual(len(messages_list), 1) - self.assertIn(str(_('Failed to send message')), str(messages_list[0])) + self.assertIn(str(_("Failed to send message")), str(messages_list[0])) - @patch('mailer.models.Message.submit') + @patch("mailer.models.Message.submit") def test_submit_message_partly_sent(self, mock_submit): """Test submit_message when partially sent.""" mock_submit.return_value = PARTLY_SENT - request = self.factory.post('/admin/mailer/message/') + request = self.factory.post("/admin/mailer/message/") request.user = self.user_with_internal_member self._add_middleware(request) @@ -174,11 +177,11 @@ class MessageAdminTestCase(AdminTestCase): # Check warning message messages_list = list(get_messages(request)) self.assertEqual(len(messages_list), 1) - self.assertIn(str(_('Failed to send some messages')), str(messages_list[0])) + self.assertIn(str(_("Failed to send some messages")), str(messages_list[0])) def test_submit_message_user_has_no_member(self): """Test submit_message when user has no associated member.""" - request = self.factory.post('/admin/mailer/message/') + request = self.factory.post("/admin/mailer/message/") request.user = self.user_without_member self._add_middleware(request) @@ -188,11 +191,18 @@ class MessageAdminTestCase(AdminTestCase): # Check error message messages_list = list(get_messages(request)) self.assertEqual(len(messages_list), 1) - self.assertIn(str(_('Your account is not connected to a member. Please contact your system administrator.')), str(messages_list[0])) + self.assertIn( + str( + _( + "Your account is not connected to a member. Please contact your system administrator." + ) + ), + str(messages_list[0]), + ) def test_submit_message_user_has_external_email(self): """Test submit_message when user has external email.""" - request = self.factory.post('/admin/mailer/message/') + request = self.factory.post("/admin/mailer/message/") request.user = self.user_with_external_member self._add_middleware(request) @@ -202,12 +212,20 @@ class MessageAdminTestCase(AdminTestCase): # Check error message messages_list = list(get_messages(request)) self.assertEqual(len(messages_list), 1) - self.assertIn(str(_('Your email address is not an internal email address. Please use an email address with one of the following domains: %(domains)s.') % {'domains': ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)}), str(messages_list[0])) + self.assertIn( + str( + _( + "Your email address is not an internal email address. Please use an email address with one of the following domains: %(domains)s." + ) + % {"domains": ", ".join(settings.ALLOWED_EMAIL_DOMAINS_FOR_INVITE_AS_USER)} + ), + str(messages_list[0]), + ) - @patch('mailer.admin.submit_message') + @patch("mailer.admin.submit_message") def test_send_message_action_confirmed(self, mock_submit_message): """Test send_message action when confirmed.""" - request = self.factory.post('/admin/mailer/message/', {'confirmed': 'true'}) + request = self.factory.post("/admin/mailer/message/", {"confirmed": "true"}) request.user = self.user_with_internal_member self._add_middleware(request) @@ -224,7 +242,7 @@ class MessageAdminTestCase(AdminTestCase): def test_send_message_action_not_confirmed(self): """Test send_message action when not confirmed (shows confirmation page).""" - request = self.factory.post('/admin/mailer/message/') + request = self.factory.post("/admin/mailer/message/") request.user = self.user_with_internal_member self._add_middleware(request) @@ -237,17 +255,17 @@ class MessageAdminTestCase(AdminTestCase): self.assertIsNotNone(result) self.assertEqual(result.status_code, HTTPStatus.OK) - @patch('mailer.admin.submit_message') + @patch("mailer.admin.submit_message") def test_response_change_with_send(self, mock_submit_message): """Test response_change when _send is in POST.""" - request = self.factory.post('/admin/mailer/message/1/change/', {'_send': 'Send'}) + request = self.factory.post("/admin/mailer/message/1/change/", {"_send": "Send"}) request.user = self.user_with_internal_member self._add_middleware(request) # Test response_change - with patch.object(self.admin.__class__.__bases__[2], 'response_change') as mock_super: - mock_super.return_value = HttpResponseRedirect('/admin/') - result = self.admin.response_change(request, self.message) + with patch.object(self.admin.__class__.__bases__[2], "response_change") as mock_super: + mock_super.return_value = HttpResponseRedirect("/admin/") + self.admin.response_change(request, self.message) # Verify submit_message was called mock_submit_message.assert_called_once_with(self.message, request) @@ -255,17 +273,17 @@ class MessageAdminTestCase(AdminTestCase): # Verify super method was called mock_super.assert_called_once() - @patch('mailer.admin.submit_message') + @patch("mailer.admin.submit_message") def test_response_change_without_send(self, mock_submit_message): """Test response_change when _send is not in POST.""" - request = self.factory.post('/admin/mailer/message/1/change/', {'_save': 'Save'}) + request = self.factory.post("/admin/mailer/message/1/change/", {"_save": "Save"}) request.user = self.user_with_internal_member self._add_middleware(request) # Test response_change - with patch.object(self.admin.__class__.__bases__[2], 'response_change') as mock_super: - mock_super.return_value = HttpResponseRedirect('/admin/') - result = self.admin.response_change(request, self.message) + with patch.object(self.admin.__class__.__bases__[2], "response_change") as mock_super: + mock_super.return_value = HttpResponseRedirect("/admin/") + self.admin.response_change(request, self.message) # Verify submit_message was NOT called mock_submit_message.assert_not_called() @@ -273,17 +291,17 @@ class MessageAdminTestCase(AdminTestCase): # Verify super method was called mock_super.assert_called_once() - @patch('mailer.admin.submit_message') + @patch("mailer.admin.submit_message") def test_response_add_with_send(self, mock_submit_message): """Test response_add when _send is in POST.""" - request = self.factory.post('/admin/mailer/message/add/', {'_send': 'Send'}) + request = self.factory.post("/admin/mailer/message/add/", {"_send": "Send"}) request.user = self.user_with_internal_member self._add_middleware(request) # Test response_add - with patch.object(self.admin.__class__.__bases__[2], 'response_add') as mock_super: - mock_super.return_value = HttpResponseRedirect('/admin/') - result = self.admin.response_add(request, self.message) + with patch.object(self.admin.__class__.__bases__[2], "response_add") as mock_super: + mock_super.return_value = HttpResponseRedirect("/admin/") + self.admin.response_add(request, self.message) # Verify submit_message was called mock_submit_message.assert_called_once_with(self.message, request) @@ -295,7 +313,7 @@ class MessageAdminTestCase(AdminTestCase): """Test get_form when members parameter is provided.""" # Create request with members parameter members_ids = [self.internal_member.pk, self.external_member.pk] - request = self.factory.get(f'/admin/mailer/message/add/?members={json.dumps(members_ids)}') + request = self.factory.get(f"/admin/mailer/message/add/?members={json.dumps(members_ids)}") request.user = self.user_with_internal_member # Test get_form @@ -303,7 +321,9 @@ class MessageAdminTestCase(AdminTestCase): form = form_class() # Verify initial members are set - self.assertEqual(list(form.fields['to_members'].initial), [self.internal_member, self.external_member]) + self.assertEqual( + list(form.fields["to_members"].initial), [self.internal_member, self.external_member] + ) def test_get_form_with_invalid_members_param(self): """Test get_form when members parameter is not a list.""" @@ -320,7 +340,7 @@ class MessageAdminTestCase(AdminTestCase): def test_get_form_without_members_param(self): """Test get_form when no members parameter is provided.""" # Create request without members parameter - request = self.factory.get('/admin/mailer/message/add/') + request = self.factory.get("/admin/mailer/message/add/") request.user = self.user_with_internal_member # Test get_form diff --git a/jdav_web/mailer/tests/mailutils.py b/jdav_web/mailer/tests/mailutils.py index 00eed05..f626400 100644 --- a/jdav_web/mailer/tests/mailutils.py +++ b/jdav_web/mailer/tests/mailutils.py @@ -1,6 +1,10 @@ -from django.test import TestCase, override_settings -from unittest.mock import patch, Mock -from mailer.mailutils import send, SENT, NOT_SENT +from unittest.mock import Mock +from unittest.mock import patch + +from django.test import TestCase +from mailer.mailutils import NOT_SENT +from mailer.mailutils import send +from mailer.mailutils import SENT class MailUtilsTest(TestCase): @@ -11,24 +15,36 @@ class MailUtilsTest(TestCase): self.recipient = "recipient@example.com" def test_send_with_reply_to(self): - with patch('mailer.mailutils.mail.get_connection') as mock_connection: + with patch("mailer.mailutils.mail.get_connection") as mock_connection: mock_conn = Mock() mock_connection.return_value = mock_conn - result = send(self.subject, self.content, self.sender, self.recipient, reply_to=["reply@example.com"]) + result = send( + self.subject, + self.content, + self.sender, + self.recipient, + reply_to=["reply@example.com"], + ) self.assertEqual(result, SENT) def test_send_with_message_id(self): - with patch('mailer.mailutils.mail.get_connection') as mock_connection: + with patch("mailer.mailutils.mail.get_connection") as mock_connection: mock_conn = Mock() mock_connection.return_value = mock_conn - result = send(self.subject, self.content, self.sender, self.recipient, message_id=" |
|---|