From cb58b00b6e761f2a7242ffe2a2a6d8a5edde2520 Mon Sep 17 00:00:00 2001 From: klmp200 Date: Thu, 4 Oct 2018 21:29:19 +0200 Subject: [PATCH] All: Apply Black coding rules --- .gitlab-ci.yml | 4 +- README.md | 1 + accounting/__init__.py | 1 - accounting/migrations/0001_initial.py | 302 +++- .../migrations/0002_auto_20160824_2152.py | 92 +- .../migrations/0003_auto_20160824_2203.py | 48 +- .../migrations/0004_auto_20161005_1505.py | 43 +- .../migrations/0005_auto_20170324_0917.py | 14 +- accounting/models.py | 273 ++-- accounting/tests.py | 320 ++-- accounting/urls.py | 147 +- accounting/views.py | 553 ++++--- api/__init__.py | 1 - api/urls.py | 35 +- api/views/__init__.py | 20 +- api/views/api.py | 7 +- api/views/club.py | 13 +- api/views/counter.py | 6 +- api/views/group.py | 1 - api/views/launderette.py | 50 +- api/views/user.py | 13 +- club/__init__.py | 1 - club/migrations/0001_initial.py | 92 +- club/migrations/0002_auto_20160824_2152.py | 58 +- club/migrations/0003_auto_20160902_2042.py | 12 +- club/migrations/0004_auto_20160915_1057.py | 16 +- club/migrations/0005_auto_20161120_1149.py | 19 +- club/migrations/0006_auto_20161229_0040.py | 14 +- club/migrations/0007_auto_20170324_0917.py | 9 +- club/migrations/0008_auto_20170515_2214.py | 10 +- club/migrations/0009_auto_20170822_2232.py | 95 +- club/migrations/0010_auto_20170912_2028.py | 40 +- club/migrations/0010_club_logo.py | 14 +- club/migrations/0011_auto_20180426_2013.py | 16 +- club/models.py | 237 ++- club/tests.py | 124 +- club/urls.py | 118 +- com/__init__.py | 1 - com/admin.py | 1 - com/migrations/0001_initial.py | 36 +- com/migrations/0002_news_newsdate.py | 101 +- com/migrations/0003_auto_20170115_2300.py | 87 +- com/migrations/0004_auto_20171221_1614.py | 78 +- com/migrations/0005_auto_20180318_2227.py | 12 +- com/models.py | 138 +- com/tests.py | 34 +- com/urls.py | 121 +- com/views.py | 559 ++++--- core/__init__.py | 2 +- core/admin.py | 40 +- core/apps.py | 12 +- core/lookups.py | 30 +- core/management/commands/__init__.py | 1 - core/management/commands/check_fs.py | 10 +- core/management/commands/compilestatic.py | 16 +- core/management/commands/markdown.py | 9 +- core/management/commands/populate.py | 915 ++++++++--- core/management/commands/repair_fs.py | 10 +- core/management/commands/setup.py | 18 +- core/markdown.py | 116 +- core/middleware.py | 10 +- core/migrations/0001_initial.py | 632 ++++++-- core/migrations/0002_auto_20160831_0144.py | 12 +- core/migrations/0003_auto_20160902_1914.py | 24 +- core/migrations/0004_user_godfathers.py | 14 +- core/migrations/0005_auto_20161105_1035.py | 25 +- core/migrations/0006_auto_20161108_1703.py | 12 +- .../0008_sithfile_asked_for_removal.py | 12 +- core/migrations/0009_auto_20161120_1155.py | 39 +- core/migrations/0010_sithfile_is_in_sas.py | 12 +- core/migrations/0011_auto_20161124_0848.py | 48 +- core/migrations/0012_notification.py | 51 +- core/migrations/0013_auto_20161209_2338.py | 23 +- core/migrations/0014_auto_20161210_0009.py | 24 +- core/migrations/0015_sithfile_moderator.py | 17 +- core/migrations/0016_auto_20161212_1922.py | 18 +- core/migrations/0017_auto_20161220_1626.py | 12 +- core/migrations/0018_auto_20161224_0211.py | 25 +- .../0019_preferences_receive_weekmail.py | 14 +- core/migrations/0020_auto_20170324_0917.py | 24 +- core/migrations/0021_auto_20170822_1529.py | 26 +- core/migrations/0022_auto_20170822_2232.py | 26 +- core/migrations/0023_auto_20170902_1226.py | 36 +- core/migrations/0024_auto_20170906_1317.py | 26 +- core/migrations/0025_auto_20170919_1521.py | 21 +- core/migrations/0026_auto_20170926_1512.py | 29 +- core/migrations/0027_gift.py | 34 +- core/migrations/0028_auto_20171216_2044.py | 30 +- core/migrations/0029_auto_20180426_2013.py | 17 +- core/models.py | 640 +++++--- core/operations.py | 2 +- core/scss/finder.py | 7 +- core/scss/processor.py | 5 +- core/templatetags/__init__.py | 1 - core/templatetags/renderer.py | 34 +- core/tests.py | 398 +++-- core/urls.py | 232 ++- core/utils.py | 151 +- core/views/__init__.py | 87 +- core/views/files.py | 233 ++- core/views/forms.py | 291 ++-- core/views/group.py | 7 +- core/views/page.py | 78 +- core/views/site.py | 44 +- core/views/user.py | 549 ++++--- counter/__init__.py | 1 - counter/migrations/0001_initial.py | 456 ++++-- counter/migrations/0002_auto_20160826_1342.py | 101 +- .../migrations/0003_permanency_activity.py | 16 +- counter/migrations/0004_auto_20160826_1907.py | 12 +- counter/migrations/0005_auto_20160826_2330.py | 31 +- counter/migrations/0006_auto_20160831_1304.py | 17 +- counter/migrations/0007_product_archived.py | 12 +- counter/migrations/0008_counter_token.py | 14 +- counter/migrations/0009_eticket.py | 37 +- counter/migrations/0010_auto_20161003_1900.py | 18 +- counter/migrations/0011_auto_20161004_2039.py | 14 +- counter/migrations/0012_auto_20170515_2202.py | 14 +- .../0013_customer_recorded_products.py | 27 +- counter/migrations/0014_auto_20170816_1521.py | 12 +- counter/migrations/0014_auto_20170817_1537.py | 12 +- counter/migrations/0015_merge.py | 7 +- .../migrations/0016_producttype_comment.py | 12 +- counter/models.py | 439 ++++-- counter/tests.py | 120 +- counter/urls.py | 135 +- counter/views.py | 1335 +++++++++++------ eboutic/__init__.py | 1 - eboutic/migrations/0001_initial.py | 139 +- eboutic/models.py | 53 +- eboutic/tests.py | 245 ++- eboutic/tests/test.py | 4 - eboutic/urls.py | 12 +- eboutic/views.py | 200 ++- election/migrations/0001_initial.py | 214 ++- election/migrations/0002_election_archived.py | 12 +- .../migrations/0003_auto_20171202_1819.py | 13 +- election/models.py | 100 +- election/tests.py | 50 +- election/urls.py | 64 +- election/views.py | 333 ++-- forum/__init__.py | 1 - forum/admin.py | 1 - forum/migrations/0001_initial.py | 224 ++- forum/migrations/0002_auto_20170312_1753.py | 28 +- forum/migrations/0003_auto_20170510_1754.py | 14 +- forum/migrations/0004_auto_20170531_1949.py | 70 +- .../0005_forumtopic_subscribed_users.py | 14 +- forum/migrations/0006_auto_20180426_2013.py | 26 +- forum/models.py | 183 ++- forum/urls.py | 73 +- forum/views.py | 149 +- launderette/__init__.py | 1 - launderette/migrations/0001_initial.py | 188 ++- launderette/models.py | 107 +- launderette/urls.py | 61 +- launderette/views.py | 304 ++-- matmat/urls.py | 8 +- matmat/views.py | 89 +- migrate.py | 1265 +++++++++------- rootplace/__init__.py | 1 - rootplace/urls.py | 4 +- rootplace/views.py | 16 +- sas/__init__.py | 1 - sas/migrations/0001_initial.py | 20 +- sas/migrations/0002_auto_20161119_1241.py | 33 +- sas/models.py | 129 +- sas/urls.py | 40 +- sas/views.py | 192 ++- sith/__init__.py | 1 - sith/settings.py | 497 +++--- sith/toolbar_debug.py | 4 +- sith/urls.py | 68 +- sith/wsgi.py | 2 +- stock/__init__.py | 1 - stock/admin.py | 2 +- stock/migrations/0001_initial.py | 178 ++- stock/models.py | 93 +- stock/urls.py | 83 +- stock/views.py | 361 +++-- subscription/__init__.py | 1 - subscription/admin.py | 7 +- subscription/migrations/0001_initial.py | 82 +- .../migrations/0002_auto_20160830_1719.py | 21 +- .../migrations/0003_auto_20160902_1914.py | 28 +- .../migrations/0004_auto_20170821_1849.py | 30 +- .../migrations/0005_auto_20170821_2054.py | 29 +- .../migrations/0006_auto_20170902_1222.py | 30 +- .../migrations/0007_auto_20180706_1135.py | 31 +- .../migrations/0008_auto_20180831_2016.py | 32 +- .../migrations/0009_auto_20180920_1421.py | 33 +- .../migrations/0010_auto_20180920_1441.py | 33 +- subscription/models.py | 178 ++- subscription/tests.py | 151 +- subscription/urls.py | 4 +- subscription/views.py | 122 +- trombi/__init__.py | 1 - trombi/migrations/0001_initial.py | 132 +- .../migrations/0002_trombi_show_profiles.py | 14 +- .../0003_trombicomment_is_moderated.py | 14 +- .../migrations/0004_trombiclubmembership.py | 51 +- trombi/models.py | 112 +- trombi/urls.py | 68 +- trombi/views.py | 313 ++-- 204 files changed, 13173 insertions(+), 6376 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5b5cb5ef..e94b7d00 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,11 +8,12 @@ setup: - apt-get update - apt-get install -y gettext - pip install -r requirements.txt + - pip install coverage + - pip install black test: stage: test script: - - pip install coverage - ./manage.py compilemessages - coverage run ./manage.py test - coverage html @@ -24,5 +25,4 @@ test: black: stage: test script: - - pip install black - black --check . diff --git a/README.md b/README.md index b82e3a49..e4e66955 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) [![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) ## Sith AE diff --git a/accounting/__init__.py b/accounting/__init__.py index 0a9419f8..0ace29c4 100644 --- a/accounting/__init__.py +++ b/accounting/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py index 19b967bc..9bd829a6 100644 --- a/accounting/migrations/0001_initial.py +++ b/accounting/migrations/0001_initial.py @@ -8,103 +8,271 @@ import accounting.models class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='AccountingType', + name="AccountingType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('code', models.CharField(max_length=16, verbose_name='code', validators=[django.core.validators.RegexValidator('^[0-9]*$', 'An accounting type code contains only numbers')])), - ('label', models.CharField(max_length=128, verbose_name='label')), - ('movement_type', models.CharField(choices=[('CREDIT', 'Credit'), ('DEBIT', 'Debit'), ('NEUTRAL', 'Neutral')], max_length=12, verbose_name='movement type')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "code", + models.CharField( + max_length=16, + verbose_name="code", + validators=[ + django.core.validators.RegexValidator( + "^[0-9]*$", + "An accounting type code contains only numbers", + ) + ], + ), + ), + ("label", models.CharField(max_length=128, verbose_name="label")), + ( + "movement_type", + models.CharField( + choices=[ + ("CREDIT", "Credit"), + ("DEBIT", "Debit"), + ("NEUTRAL", "Neutral"), + ], + max_length=12, + verbose_name="movement type", + ), + ), ], options={ - 'verbose_name': 'accounting type', - 'ordering': ['movement_type', 'code'], + "verbose_name": "accounting type", + "ordering": ["movement_type", "code"], }, ), migrations.CreateModel( - name='BankAccount', + name="BankAccount", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('iban', models.CharField(max_length=255, blank=True, verbose_name='iban')), - ('number', models.CharField(max_length=255, blank=True, verbose_name='account number')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "iban", + models.CharField(max_length=255, blank=True, verbose_name="iban"), + ), + ( + "number", + models.CharField( + max_length=255, blank=True, verbose_name="account number" + ), + ), + ], + options={"verbose_name": "Bank account", "ordering": ["club", "name"]}, + ), + migrations.CreateModel( + name="ClubAccount", + fields=[ + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), ], options={ - 'verbose_name': 'Bank account', - 'ordering': ['club', 'name'], + "verbose_name": "Club account", + "ordering": ["bank_account", "name"], }, ), migrations.CreateModel( - name='ClubAccount', + name="Company", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=60, verbose_name="name")), ], - options={ - 'verbose_name': 'Club account', - 'ordering': ['bank_account', 'name'], - }, + options={"verbose_name": "company"}, ), migrations.CreateModel( - name='Company', + name="GeneralJournal", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=60, verbose_name='name')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start_date", models.DateField(verbose_name="start date")), + ( + "end_date", + models.DateField( + null=True, verbose_name="end date", default=None, blank=True + ), + ), + ("name", models.CharField(max_length=40, verbose_name="name")), + ( + "closed", + models.BooleanField(verbose_name="is closed", default=False), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, + default=0, + verbose_name="amount", + max_digits=12, + ), + ), + ( + "effective_amount", + accounting.models.CurrencyField( + decimal_places=2, + default=0, + verbose_name="effective_amount", + max_digits=12, + ), + ), ], - options={ - 'verbose_name': 'company', - }, + options={"verbose_name": "General journal", "ordering": ["-start_date"]}, ), migrations.CreateModel( - name='GeneralJournal', + name="Operation", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateField(verbose_name='start date')), - ('end_date', models.DateField(null=True, verbose_name='end date', default=None, blank=True)), - ('name', models.CharField(max_length=40, verbose_name='name')), - ('closed', models.BooleanField(verbose_name='is closed', default=False)), - ('amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='amount', max_digits=12)), - ('effective_amount', accounting.models.CurrencyField(decimal_places=2, default=0, verbose_name='effective_amount', max_digits=12)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("number", models.IntegerField(verbose_name="number")), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), + ("date", models.DateField(verbose_name="date")), + ("remark", models.CharField(max_length=128, verbose_name="comment")), + ( + "mode", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CASH", "Cash"), + ("TRANSFERT", "Transfert"), + ("CARD", "Credit card"), + ], + max_length=255, + verbose_name="payment method", + ), + ), + ( + "cheque_number", + models.CharField( + max_length=32, + null=True, + verbose_name="cheque number", + default="", + blank=True, + ), + ), + ("done", models.BooleanField(verbose_name="is done", default=False)), + ( + "target_type", + models.CharField( + choices=[ + ("USER", "User"), + ("CLUB", "Club"), + ("ACCOUNT", "Account"), + ("COMPANY", "Company"), + ("OTHER", "Other"), + ], + max_length=10, + verbose_name="target type", + ), + ), + ( + "target_id", + models.IntegerField( + null=True, verbose_name="target id", blank=True + ), + ), + ( + "target_label", + models.CharField( + max_length=32, + blank=True, + verbose_name="target label", + default="", + ), + ), + ( + "accounting_type", + models.ForeignKey( + null=True, + related_name="operations", + verbose_name="accounting type", + to="accounting.AccountingType", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'General journal', - 'ordering': ['-start_date'], - }, + options={"ordering": ["-number"]}, ), migrations.CreateModel( - name='Operation', + name="SimplifiedAccountingType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('number', models.IntegerField(verbose_name='number')), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), - ('date', models.DateField(verbose_name='date')), - ('remark', models.CharField(max_length=128, verbose_name='comment')), - ('mode', models.CharField(choices=[('CHECK', 'Check'), ('CASH', 'Cash'), ('TRANSFERT', 'Transfert'), ('CARD', 'Credit card')], max_length=255, verbose_name='payment method')), - ('cheque_number', models.CharField(max_length=32, null=True, verbose_name='cheque number', default='', blank=True)), - ('done', models.BooleanField(verbose_name='is done', default=False)), - ('target_type', models.CharField(choices=[('USER', 'User'), ('CLUB', 'Club'), ('ACCOUNT', 'Account'), ('COMPANY', 'Company'), ('OTHER', 'Other')], max_length=10, verbose_name='target type')), - ('target_id', models.IntegerField(null=True, verbose_name='target id', blank=True)), - ('target_label', models.CharField(max_length=32, blank=True, verbose_name='target label', default='')), - ('accounting_type', models.ForeignKey(null=True, related_name='operations', verbose_name='accounting type', to='accounting.AccountingType', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("label", models.CharField(max_length=128, verbose_name="label")), + ( + "accounting_type", + models.ForeignKey( + verbose_name="simplified accounting types", + to="accounting.AccountingType", + related_name="simplified_types", + ), + ), ], options={ - 'ordering': ['-number'], - }, - ), - migrations.CreateModel( - name='SimplifiedAccountingType', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('label', models.CharField(max_length=128, verbose_name='label')), - ('accounting_type', models.ForeignKey(verbose_name='simplified accounting types', to='accounting.AccountingType', related_name='simplified_types')), - ], - options={ - 'verbose_name': 'simplified type', - 'ordering': ['accounting_type__movement_type', 'accounting_type__code'], + "verbose_name": "simplified type", + "ordering": ["accounting_type__movement_type", "accounting_type__code"], }, ), ] diff --git a/accounting/migrations/0002_auto_20160824_2152.py b/accounting/migrations/0002_auto_20160824_2152.py index b44310ff..e0322f08 100644 --- a/accounting/migrations/0002_auto_20160824_2152.py +++ b/accounting/migrations/0002_auto_20160824_2152.py @@ -7,54 +7,88 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('club', '0001_initial'), - ('accounting', '0001_initial'), - ('core', '0001_initial'), + ("club", "0001_initial"), + ("accounting", "0001_initial"), + ("core", "0001_initial"), ] operations = [ migrations.AddField( - model_name='operation', - name='invoice', - field=models.ForeignKey(null=True, related_name='operations', verbose_name='invoice', to='core.SithFile', blank=True), + model_name="operation", + name="invoice", + field=models.ForeignKey( + null=True, + related_name="operations", + verbose_name="invoice", + to="core.SithFile", + blank=True, + ), ), migrations.AddField( - model_name='operation', - name='journal', - field=models.ForeignKey(verbose_name='journal', to='accounting.GeneralJournal', related_name='operations'), + model_name="operation", + name="journal", + field=models.ForeignKey( + verbose_name="journal", + to="accounting.GeneralJournal", + related_name="operations", + ), ), migrations.AddField( - model_name='operation', - name='linked_operation', - field=models.OneToOneField(blank=True, to='accounting.Operation', null=True, related_name='operation_linked_to', verbose_name='linked operation', default=None), + model_name="operation", + name="linked_operation", + field=models.OneToOneField( + blank=True, + to="accounting.Operation", + null=True, + related_name="operation_linked_to", + verbose_name="linked operation", + default=None, + ), ), migrations.AddField( - model_name='operation', - name='simpleaccounting_type', - field=models.ForeignKey(null=True, related_name='operations', verbose_name='simple type', to='accounting.SimplifiedAccountingType', blank=True), + model_name="operation", + name="simpleaccounting_type", + field=models.ForeignKey( + null=True, + related_name="operations", + verbose_name="simple type", + to="accounting.SimplifiedAccountingType", + blank=True, + ), ), migrations.AddField( - model_name='generaljournal', - name='club_account', - field=models.ForeignKey(verbose_name='club account', to='accounting.ClubAccount', related_name='journals'), + model_name="generaljournal", + name="club_account", + field=models.ForeignKey( + verbose_name="club account", + to="accounting.ClubAccount", + related_name="journals", + ), ), migrations.AddField( - model_name='clubaccount', - name='bank_account', - field=models.ForeignKey(verbose_name='bank account', to='accounting.BankAccount', related_name='club_accounts'), + model_name="clubaccount", + name="bank_account", + field=models.ForeignKey( + verbose_name="bank account", + to="accounting.BankAccount", + related_name="club_accounts", + ), ), migrations.AddField( - model_name='clubaccount', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='club_account'), + model_name="clubaccount", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="club_account" + ), ), migrations.AddField( - model_name='bankaccount', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='bank_accounts'), + model_name="bankaccount", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="bank_accounts" + ), ), migrations.AlterUniqueTogether( - name='operation', - unique_together=set([('number', 'journal')]), + name="operation", unique_together=set([("number", "journal")]) ), ] diff --git a/accounting/migrations/0003_auto_20160824_2203.py b/accounting/migrations/0003_auto_20160824_2203.py index 57bd9e22..47ae232f 100644 --- a/accounting/migrations/0003_auto_20160824_2203.py +++ b/accounting/migrations/0003_auto_20160824_2203.py @@ -7,44 +7,44 @@ import phonenumber_field.modelfields class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0002_auto_20160824_2152'), - ] + dependencies = [("accounting", "0002_auto_20160824_2152")] operations = [ migrations.AddField( - model_name='company', - name='city', - field=models.CharField(blank=True, verbose_name='city', max_length=60), + model_name="company", + name="city", + field=models.CharField(blank=True, verbose_name="city", max_length=60), ), migrations.AddField( - model_name='company', - name='country', - field=models.CharField(blank=True, verbose_name='country', max_length=32), + model_name="company", + name="country", + field=models.CharField(blank=True, verbose_name="country", max_length=32), ), migrations.AddField( - model_name='company', - name='email', - field=models.EmailField(blank=True, verbose_name='email', max_length=254), + model_name="company", + name="email", + field=models.EmailField(blank=True, verbose_name="email", max_length=254), ), migrations.AddField( - model_name='company', - name='phone', - field=phonenumber_field.modelfields.PhoneNumberField(blank=True, verbose_name='phone', max_length=128), + model_name="company", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, verbose_name="phone", max_length=128 + ), ), migrations.AddField( - model_name='company', - name='postcode', - field=models.CharField(blank=True, verbose_name='postcode', max_length=10), + model_name="company", + name="postcode", + field=models.CharField(blank=True, verbose_name="postcode", max_length=10), ), migrations.AddField( - model_name='company', - name='street', - field=models.CharField(blank=True, verbose_name='street', max_length=60), + model_name="company", + name="street", + field=models.CharField(blank=True, verbose_name="street", max_length=60), ), migrations.AddField( - model_name='company', - name='website', - field=models.CharField(blank=True, verbose_name='website', max_length=64), + model_name="company", + name="website", + field=models.CharField(blank=True, verbose_name="website", max_length=64), ), ] diff --git a/accounting/migrations/0004_auto_20161005_1505.py b/accounting/migrations/0004_auto_20161005_1505.py index b1bfe2d8..c363cddb 100644 --- a/accounting/migrations/0004_auto_20161005_1505.py +++ b/accounting/migrations/0004_auto_20161005_1505.py @@ -7,26 +7,45 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0003_auto_20160824_2203'), - ] + dependencies = [("accounting", "0003_auto_20160824_2203")] operations = [ migrations.CreateModel( - name='Label', + name="Label", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), - ('name', models.CharField(max_length=64, verbose_name='label')), - ('club_account', models.ForeignKey(related_name='labels', verbose_name='club account', to='accounting.ClubAccount')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + auto_created=True, + serialize=False, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="label")), + ( + "club_account", + models.ForeignKey( + related_name="labels", + verbose_name="club account", + to="accounting.ClubAccount", + ), + ), ], ), migrations.AddField( - model_name='operation', - name='label', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, related_name='operations', null=True, blank=True, verbose_name='label', to='accounting.Label'), + model_name="operation", + name="label", + field=models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + related_name="operations", + null=True, + blank=True, + verbose_name="label", + to="accounting.Label", + ), ), migrations.AlterUniqueTogether( - name='label', - unique_together=set([('name', 'club_account')]), + name="label", unique_together=set([("name", "club_account")]) ), ] diff --git a/accounting/migrations/0005_auto_20170324_0917.py b/accounting/migrations/0005_auto_20170324_0917.py index d97d82f0..c8258f8e 100644 --- a/accounting/migrations/0005_auto_20170324_0917.py +++ b/accounting/migrations/0005_auto_20170324_0917.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('accounting', '0004_auto_20161005_1505'), - ] + dependencies = [("accounting", "0004_auto_20161005_1505")] operations = [ migrations.AlterField( - model_name='operation', - name='remark', - field=models.CharField(null=True, max_length=128, blank=True, verbose_name='comment'), - ), + model_name="operation", + name="remark", + field=models.CharField( + null=True, max_length=128, blank=True, verbose_name="comment" + ), + ) ] diff --git a/accounting/models.py b/accounting/models.py index e5d73652..efcbdf2c 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -41,9 +41,10 @@ class CurrencyField(models.DecimalField): """ This is a custom database field used for currency """ + def __init__(self, *args, **kwargs): - kwargs['max_digits'] = 12 - kwargs['decimal_places'] = 2 + kwargs["max_digits"] = 12 + kwargs["decimal_places"] = 2 super(CurrencyField, self).__init__(*args, **kwargs) def to_python(self, value): @@ -52,18 +53,19 @@ class CurrencyField(models.DecimalField): except AttributeError: return None + # Accounting classes class Company(models.Model): - name = models.CharField(_('name'), max_length=60) - street = models.CharField(_('street'), max_length=60, blank=True) - city = models.CharField(_('city'), max_length=60, blank=True) - postcode = models.CharField(_('postcode'), max_length=10, blank=True) - country = models.CharField(_('country'), max_length=32, blank=True) - phone = PhoneNumberField(_('phone'), blank=True) - email = models.EmailField(_('email'), blank=True) - website = models.CharField(_('website'), max_length=64, blank=True) + name = models.CharField(_("name"), max_length=60) + street = models.CharField(_("street"), max_length=60, blank=True) + city = models.CharField(_("city"), max_length=60, blank=True) + postcode = models.CharField(_("postcode"), max_length=10, blank=True) + country = models.CharField(_("country"), max_length=32, blank=True) + phone = PhoneNumberField(_("phone"), blank=True) + email = models.EmailField(_("email"), blank=True) + website = models.CharField(_("website"), max_length=64, blank=True) class Meta: verbose_name = _("company") @@ -81,7 +83,7 @@ class Company(models.Model): Method to see if that object can be edited by the given user """ for club in user.memberships.filter(end_date=None).all(): - if club and club.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if club and club.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -90,12 +92,12 @@ class Company(models.Model): Method to see if that object can be viewed by the given user """ for club in user.memberships.filter(end_date=None).all(): - if club and club.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if club and club.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:co_edit', kwargs={'co_id': self.id}) + return reverse("accounting:co_edit", kwargs={"co_id": self.id}) def get_display_name(self): return self.name @@ -105,14 +107,14 @@ class Company(models.Model): class BankAccount(models.Model): - name = models.CharField(_('name'), max_length=30) - iban = models.CharField(_('iban'), max_length=255, blank=True) - number = models.CharField(_('account number'), max_length=255, blank=True) + name = models.CharField(_("name"), max_length=30) + iban = models.CharField(_("iban"), max_length=255, blank=True) + number = models.CharField(_("account number"), max_length=255, blank=True) club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club")) class Meta: verbose_name = _("Bank account") - ordering = ['club', 'name'] + ordering = ["club", "name"] def is_owned_by(self, user): """ @@ -121,25 +123,27 @@ class BankAccount(models.Model): if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID): return True m = self.club.get_membership_for(user) - if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:bank_details', kwargs={'b_account_id': self.id}) + return reverse("accounting:bank_details", kwargs={"b_account_id": self.id}) def __str__(self): return self.name class ClubAccount(models.Model): - name = models.CharField(_('name'), max_length=30) + name = models.CharField(_("name"), max_length=30) club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club")) - bank_account = models.ForeignKey(BankAccount, related_name="club_accounts", verbose_name=_("bank account")) + bank_account = models.ForeignKey( + BankAccount, related_name="club_accounts", verbose_name=_("bank account") + ) class Meta: verbose_name = _("Club account") - ordering = ['bank_account', 'name'] + ordering = ["bank_account", "name"] def is_owned_by(self, user): """ @@ -154,7 +158,7 @@ class ClubAccount(models.Model): Method to see if that object can be edited by the given user """ m = self.club.get_membership_for(user) - if m and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -163,7 +167,7 @@ class ClubAccount(models.Model): Method to see if that object can be viewed by the given user """ m = self.club.get_membership_for(user) - if m and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -177,30 +181,36 @@ class ClubAccount(models.Model): return self.journals.filter(closed=False).first() def get_absolute_url(self): - return reverse('accounting:club_details', kwargs={'c_account_id': self.id}) + return reverse("accounting:club_details", kwargs={"c_account_id": self.id}) def __str__(self): return self.name def get_display_name(self): - return _("%(club_account)s on %(bank_account)s") % {"club_account": self.name, "bank_account": self.bank_account} + return _("%(club_account)s on %(bank_account)s") % { + "club_account": self.name, + "bank_account": self.bank_account, + } class GeneralJournal(models.Model): """ Class storing all the operations for a period of time """ - start_date = models.DateField(_('start date')) - end_date = models.DateField(_('end date'), null=True, blank=True, default=None) - name = models.CharField(_('name'), max_length=40) - closed = models.BooleanField(_('is closed'), default=False) - club_account = models.ForeignKey(ClubAccount, related_name="journals", null=False, verbose_name=_("club account")) - amount = CurrencyField(_('amount'), default=0) - effective_amount = CurrencyField(_('effective_amount'), default=0) + + start_date = models.DateField(_("start date")) + end_date = models.DateField(_("end date"), null=True, blank=True, default=None) + name = models.CharField(_("name"), max_length=40) + closed = models.BooleanField(_("is closed"), default=False) + club_account = models.ForeignKey( + ClubAccount, related_name="journals", null=False, verbose_name=_("club account") + ) + amount = CurrencyField(_("amount"), default=0) + effective_amount = CurrencyField(_("effective_amount"), default=0) class Meta: verbose_name = _("General journal") - ordering = ['-start_date'] + ordering = ["-start_date"] def is_owned_by(self, user): """ @@ -226,7 +236,7 @@ class GeneralJournal(models.Model): return self.club_account.can_be_viewed_by(user) def get_absolute_url(self): - return reverse('accounting:journal_details', kwargs={'j_id': self.id}) + return reverse("accounting:journal_details", kwargs={"j_id": self.id}) def __str__(self): return self.name @@ -250,31 +260,79 @@ class Operation(models.Model): """ An operation is a line in the journal, a debit or a credit """ - number = models.IntegerField(_('number')) - journal = models.ForeignKey(GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")) - amount = CurrencyField(_('amount')) - date = models.DateField(_('date')) - remark = models.CharField(_('comment'), max_length=128, null=True, blank=True) - mode = models.CharField(_('payment method'), max_length=255, choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD) - cheque_number = models.CharField(_('cheque number'), max_length=32, default="", null=True, blank=True) - invoice = models.ForeignKey(SithFile, related_name='operations', verbose_name=_("invoice"), null=True, blank=True) - done = models.BooleanField(_('is done'), default=False) - simpleaccounting_type = models.ForeignKey('SimplifiedAccountingType', related_name="operations", - verbose_name=_("simple type"), null=True, blank=True) - accounting_type = models.ForeignKey('AccountingType', related_name="operations", - verbose_name=_("accounting type"), null=True, blank=True) - label = models.ForeignKey('Label', related_name="operations", - verbose_name=_("label"), null=True, blank=True, on_delete=models.SET_NULL) - target_type = models.CharField(_('target type'), max_length=10, - choices=[('USER', _('User')), ('CLUB', _('Club')), ('ACCOUNT', _('Account')), ('COMPANY', _('Company')), ('OTHER', _('Other'))]) - target_id = models.IntegerField(_('target id'), null=True, blank=True) - target_label = models.CharField(_('target label'), max_length=32, default="", blank=True) - linked_operation = models.OneToOneField('self', related_name='operation_linked_to', verbose_name=_("linked operation"), - null=True, blank=True, default=None) + + number = models.IntegerField(_("number")) + journal = models.ForeignKey( + GeneralJournal, related_name="operations", null=False, verbose_name=_("journal") + ) + amount = CurrencyField(_("amount")) + date = models.DateField(_("date")) + remark = models.CharField(_("comment"), max_length=128, null=True, blank=True) + mode = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_ACCOUNTING_PAYMENT_METHOD, + ) + cheque_number = models.CharField( + _("cheque number"), max_length=32, default="", null=True, blank=True + ) + invoice = models.ForeignKey( + SithFile, + related_name="operations", + verbose_name=_("invoice"), + null=True, + blank=True, + ) + done = models.BooleanField(_("is done"), default=False) + simpleaccounting_type = models.ForeignKey( + "SimplifiedAccountingType", + related_name="operations", + verbose_name=_("simple type"), + null=True, + blank=True, + ) + accounting_type = models.ForeignKey( + "AccountingType", + related_name="operations", + verbose_name=_("accounting type"), + null=True, + blank=True, + ) + label = models.ForeignKey( + "Label", + related_name="operations", + verbose_name=_("label"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + target_type = models.CharField( + _("target type"), + max_length=10, + choices=[ + ("USER", _("User")), + ("CLUB", _("Club")), + ("ACCOUNT", _("Account")), + ("COMPANY", _("Company")), + ("OTHER", _("Other")), + ], + ) + target_id = models.IntegerField(_("target id"), null=True, blank=True) + target_label = models.CharField( + _("target label"), max_length=32, default="", blank=True + ) + linked_operation = models.OneToOneField( + "self", + related_name="operation_linked_to", + verbose_name=_("linked operation"), + null=True, + blank=True, + default=None, + ) class Meta: - unique_together = ('number', 'journal') - ordering = ['-number'] + unique_together = ("number", "journal") + ordering = ["-number"] def __getattribute__(self, attr): if attr == "target": @@ -287,14 +345,29 @@ class Operation(models.Model): if self.date is None: raise ValidationError(_("The date must be set.")) elif self.date < self.journal.start_date: - raise ValidationError(_("""The date can not be before the start date of the journal, which is -%(start_date)s.""") % {'start_date': defaultfilters.date(self.journal.start_date, settings.DATE_FORMAT)}) + raise ValidationError( + _( + """The date can not be before the start date of the journal, which is +%(start_date)s.""" + ) + % { + "start_date": defaultfilters.date( + self.journal.start_date, settings.DATE_FORMAT + ) + } + ) if self.target_type != "OTHER" and self.get_target() is None: raise ValidationError(_("Target does not exists")) if self.target_type == "OTHER" and self.target_label == "": - raise ValidationError(_("Please add a target label if you set no existing target")) + raise ValidationError( + _("Please add a target label if you set no existing target") + ) if not self.accounting_type and not self.simpleaccounting_type: - raise ValidationError(_("You need to provide ether a simplified accounting type or a standard accounting type")) + raise ValidationError( + _( + "You need to provide ether a simplified accounting type or a standard accounting type" + ) + ) if self.simpleaccounting_type: self.accounting_type = self.simpleaccounting_type.accounting_type @@ -329,7 +402,7 @@ class Operation(models.Model): if self.journal.closed: return False m = self.journal.club_account.club.get_membership_for(user) - if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role >= settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False @@ -342,16 +415,19 @@ class Operation(models.Model): if self.journal.closed: return False m = self.journal.club_account.club.get_membership_for(user) - if m is not None and m.role == settings.SITH_CLUB_ROLES_ID['Treasurer']: + if m is not None and m.role == settings.SITH_CLUB_ROLES_ID["Treasurer"]: return True return False def get_absolute_url(self): - return reverse('accounting:journal_details', kwargs={'j_id': self.journal.id}) + return reverse("accounting:journal_details", kwargs={"j_id": self.journal.id}) def __str__(self): return "%d € | %s | %s | %s" % ( - self.amount, self.date, self.accounting_type, self.done, + self.amount, + self.date, + self.accounting_type, + self.done, ) @@ -361,18 +437,30 @@ class AccountingType(models.Model): Thoses are numbers used in accounting to classify operations """ - code = models.CharField(_('code'), max_length=16, - validators=[ - validators.RegexValidator(r'^[0-9]*$', _('An accounting type code contains only numbers')), - ], + + code = models.CharField( + _("code"), + max_length=16, + validators=[ + validators.RegexValidator( + r"^[0-9]*$", _("An accounting type code contains only numbers") + ) + ], + ) + label = models.CharField(_("label"), max_length=128) + movement_type = models.CharField( + _("movement type"), + choices=[ + ("CREDIT", _("Credit")), + ("DEBIT", _("Debit")), + ("NEUTRAL", _("Neutral")), + ], + max_length=12, ) - label = models.CharField(_('label'), max_length=128) - movement_type = models.CharField(_('movement type'), choices=[('CREDIT', _('Credit')), ('DEBIT', _('Debit')), - ('NEUTRAL', _('Neutral'))], max_length=12) class Meta: verbose_name = _("accounting type") - ordering = ['movement_type', 'code'] + ordering = ["movement_type", "code"] def is_owned_by(self, user): """ @@ -383,7 +471,7 @@ class AccountingType(models.Model): return False def get_absolute_url(self): - return reverse('accounting:type_list') + return reverse("accounting:type_list") def __str__(self): return self.code + " - " + self.get_movement_type_display() + " - " + self.label @@ -393,13 +481,17 @@ class SimplifiedAccountingType(models.Model): """ Class describing the simplified accounting types. """ - label = models.CharField(_('label'), max_length=128) - accounting_type = models.ForeignKey(AccountingType, related_name="simplified_types", - verbose_name=_("simplified accounting types")) + + label = models.CharField(_("label"), max_length=128) + accounting_type = models.ForeignKey( + AccountingType, + related_name="simplified_types", + verbose_name=_("simplified accounting types"), + ) class Meta: verbose_name = _("simplified type") - ordering = ['accounting_type__movement_type', 'accounting_type__code'] + ordering = ["accounting_type__movement_type", "accounting_type__code"] @property def movement_type(self): @@ -409,25 +501,36 @@ class SimplifiedAccountingType(models.Model): return self.accounting_type.get_movement_type_display() def get_absolute_url(self): - return reverse('accounting:simple_type_list') + return reverse("accounting:simple_type_list") def __str__(self): - return self.get_movement_type_display() + " - " + self.accounting_type.code + " - " + self.label + return ( + self.get_movement_type_display() + + " - " + + self.accounting_type.code + + " - " + + self.label + ) class Label(models.Model): """Label allow a club to sort its operations""" - name = models.CharField(_('label'), max_length=64) - club_account = models.ForeignKey(ClubAccount, related_name="labels", verbose_name=_("club account")) + + name = models.CharField(_("label"), max_length=64) + club_account = models.ForeignKey( + ClubAccount, related_name="labels", verbose_name=_("club account") + ) class Meta: - unique_together = ('name', 'club_account') + unique_together = ("name", "club_account") def __str__(self): return "%s (%s)" % (self.name, self.club_account.name) def get_absolute_url(self): - return reverse('accounting:label_list', kwargs={'clubaccount_id': self.club_account.id}) + return reverse( + "accounting:label_list", kwargs={"clubaccount_id": self.club_account.id} + ) def is_owned_by(self, user): return self.club_account.is_owned_by(user) diff --git a/accounting/tests.py b/accounting/tests.py index ceb31c29..df4fd8d7 100644 --- a/accounting/tests.py +++ b/accounting/tests.py @@ -28,7 +28,13 @@ from django.core.management import call_command from datetime import date from core.models import User -from accounting.models import GeneralJournal, Operation, Label, AccountingType, SimplifiedAccountingType +from accounting.models import ( + GeneralJournal, + Operation, + Label, + AccountingType, + SimplifiedAccountingType, +) class RefoundAccountTest(TestCase): @@ -40,18 +46,20 @@ class RefoundAccountTest(TestCase): self.skia.customer.save() def test_permission_denied(self): - self.client.login(username='guy', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) + self.client.login(username="guy", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) response_get = self.client.get(reverse("accounting:refound_account")) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) def test_root_granteed(self): - self.client.login(username='root', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) - self.skia = User.objects.filter(username='skia').first() + self.client.login(username="root", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) + self.skia = User.objects.filter(username="skia").first() response_get = self.client.get(reverse("accounting:refound_account")) self.assertFalse(response_get.status_code == 403) self.assertTrue('
' in str(response_get.content)) @@ -59,10 +67,11 @@ class RefoundAccountTest(TestCase): self.assertTrue(self.skia.customer.amount == 0) def test_comptable_granteed(self): - self.client.login(username='comptable', password='plop') - response_post = self.client.post(reverse("accounting:refound_account"), - {"user": self.skia.id}) - self.skia = User.objects.filter(username='skia').first() + self.client.login(username="comptable", password="plop") + response_post = self.client.post( + reverse("accounting:refound_account"), {"user": self.skia.id} + ) + self.skia = User.objects.filter(username="skia").first() response_get = self.client.get(reverse("accounting:refound_account")) self.assertFalse(response_get.status_code == 403) self.assertTrue('' in str(response_get.content)) @@ -76,146 +85,217 @@ class JournalTest(TestCase): self.journal = GeneralJournal.objects.filter(id=1).first() def test_permission_granted(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) self.assertTrue(response_get.status_code == 200) - self.assertTrue('M\\xc3\\xa9thode de paiement' in str(response_get.content)) + self.assertTrue( + "M\\xc3\\xa9thode de paiement" in str(response_get.content) + ) def test_permission_not_granted(self): - self.client.login(username='skia', password='plop') - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) + self.client.login(username="skia", password="plop") + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) self.assertTrue(response_get.status_code == 403) - self.assertFalse('M\xc3\xa9thode de paiement' in str(response_get.content)) + self.assertFalse( + "M\xc3\xa9thode de paiement" in str(response_get.content) + ) class OperationTest(TestCase): def setUp(self): call_command("populate") self.journal = GeneralJournal.objects.filter(id=1).first() - self.skia = User.objects.filter(username='skia').first() - at = AccountingType(code='443', label="Ce code n'existe pas", movement_type='CREDIT') + self.skia = User.objects.filter(username="skia").first() + at = AccountingType( + code="443", label="Ce code n'existe pas", movement_type="CREDIT" + ) at.save() - l = Label(club_account=self.journal.club_account, name='bob') + l = Label(club_account=self.journal.club_account, name="bob") l.save() - self.client.login(username='comptable', password='plop') - self.op1 = Operation(journal=self.journal, date=date.today(), amount=1, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.client.login(username="comptable", password="plop") + self.op1 = Operation( + journal=self.journal, + date=date.today(), + amount=1, + remark="Test bilan", + mode="CASH", + done=True, + label=l, + accounting_type=at, + target_type="USER", + target_id=self.skia.id, + ) self.op1.save() - self.op2 = Operation(journal=self.journal, date=date.today(), amount=2, - remark="Test bilan", mode='CASH', done=True, label=l, - accounting_type=at, target_type='USER', target_id=self.skia.id) + self.op2 = Operation( + journal=self.journal, + date=date.today(), + amount=2, + remark="Test bilan", + mode="CASH", + done=True, + label=l, + accounting_type=at, + target_type="USER", + target_id=self.skia.id, + ) self.op2.save() def test_new_operation(self): - self.client.login(username='comptable', password='plop') - at = AccountingType.objects.filter(code='604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome de la nuit", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': '', - 'accounting_type': at.id, - 'label': '', - 'done': False, - }) + self.client.login(username="comptable", password="plop") + at = AccountingType.objects.filter(code="604").first() + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 30, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome de la nuit", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": "", + "accounting_type": at.id, + "label": "", + "done": False, + }, + ) self.assertFalse(response.status_code == 403) - self.assertTrue(self.journal.operations.filter(target_label="Le fantome de la nuit").exists()) - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) - self.assertTrue('Le fantome de la nuit' in str(response_get.content)) + self.assertTrue( + self.journal.operations.filter( + target_label="Le fantome de la nuit" + ).exists() + ) + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) + self.assertTrue("Le fantome de la nuit" in str(response_get.content)) def test_bad_new_operation(self): - self.client.login(username='comptable', password='plop') - AccountingType.objects.filter(code='604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome de la nuit", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': '', - 'accounting_type': '', - 'label': '', - 'done': False, - }) - self.assertTrue('Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard' in str(response.content)) + self.client.login(username="comptable", password="plop") + AccountingType.objects.filter(code="604").first() + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 30, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome de la nuit", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": "", + "accounting_type": "", + "label": "", + "done": False, + }, + ) + self.assertTrue( + "Vous devez fournir soit un type comptable simplifi\\xc3\\xa9 ou un type comptable standard" + in str(response.content) + ) def test_new_operation_not_authorized(self): - self.client.login(username='skia', password='plop') - at = AccountingType.objects.filter(code='604').first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 30, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome du jour", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': '', - 'accounting_type': at.id, - 'label': '', - 'done': False, - }) + self.client.login(username="skia", password="plop") + at = AccountingType.objects.filter(code="604").first() + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 30, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome du jour", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": "", + "accounting_type": at.id, + "label": "", + "done": False, + }, + ) self.assertTrue(response.status_code == 403) - self.assertFalse(self.journal.operations.filter(target_label="Le fantome du jour").exists()) + self.assertFalse( + self.journal.operations.filter(target_label="Le fantome du jour").exists() + ) def test__operation_simple_accounting(self): - self.client.login(username='comptable', password='plop') + self.client.login(username="comptable", password="plop") sat = SimplifiedAccountingType.objects.all().first() - response = self.client.post(reverse('accounting:op_new', - args=[self.journal.id]), - {'amount': 23, - 'remark': "Un gros test", - 'journal': self.journal.id, - 'target_type': 'OTHER', - 'target_id': '', - 'target_label': "Le fantome de l'aurore", - 'date': '04/12/2020', - 'mode': 'CASH', - 'cheque_number': '', - 'invoice': '', - 'simpleaccounting_type': sat.id, - 'accounting_type': '', - 'label': '', - 'done': False, - }) + response = self.client.post( + reverse("accounting:op_new", args=[self.journal.id]), + { + "amount": 23, + "remark": "Un gros test", + "journal": self.journal.id, + "target_type": "OTHER", + "target_id": "", + "target_label": "Le fantome de l'aurore", + "date": "04/12/2020", + "mode": "CASH", + "cheque_number": "", + "invoice": "", + "simpleaccounting_type": sat.id, + "accounting_type": "", + "label": "", + "done": False, + }, + ) self.assertFalse(response.status_code == 403) self.assertTrue(self.journal.operations.filter(amount=23).exists()) - response_get = self.client.get(reverse("accounting:journal_details", args=[self.journal.id])) - self.assertTrue("Le fantome de l'aurore" in str(response_get.content)) - self.assertTrue(self.journal.operations.filter(amount=23).values('accounting_type').first()['accounting_type'] == AccountingType.objects.filter(code=6).values('id').first()['id']) + response_get = self.client.get( + reverse("accounting:journal_details", args=[self.journal.id]) + ) + self.assertTrue( + "Le fantome de l'aurore" in str(response_get.content) + ) + self.assertTrue( + self.journal.operations.filter(amount=23) + .values("accounting_type") + .first()["accounting_type"] + == AccountingType.objects.filter(code=6).values("id").first()["id"] + ) def test_nature_statement(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_nature_statement", args=[self.journal.id])) - self.assertTrue("bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content)) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_nature_statement", args=[self.journal.id]) + ) + self.assertTrue( + "bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content) + ) def test_person_statement(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_person_statement", args=[self.journal.id])) - self.assertTrue("3.00" in str(response_get.content) and 'S' Kia' in str(response_get.content)) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_person_statement", args=[self.journal.id]) + ) + self.assertTrue( + "3.00" in str(response_get.content) + and 'S' Kia' + in str(response_get.content) + ) def test_accounting_statement(self): - self.client.login(username='comptable', password='plop') - response_get = self.client.get(reverse("accounting:journal_accounting_statement", args=[self.journal.id])) - self.assertTrue("443 - Cr\\xc3\\xa9dit - Ce code n'existe pas" in str(response_get.content)) + self.client.login(username="comptable", password="plop") + response_get = self.client.get( + reverse("accounting:journal_accounting_statement", args=[self.journal.id]) + ) + self.assertTrue( + "443 - Cr\\xc3\\xa9dit - Ce code n'existe pas" + in str(response_get.content) + ) diff --git a/accounting/urls.py b/accounting/urls.py index 70e6a3ea..63882d45 100644 --- a/accounting/urls.py +++ b/accounting/urls.py @@ -28,46 +28,125 @@ from accounting.views import * urlpatterns = [ # Accounting types - url(r'^simple_type$', SimplifiedAccountingTypeListView.as_view(), name='simple_type_list'), - url(r'^simple_type/create$', SimplifiedAccountingTypeCreateView.as_view(), name='simple_type_new'), - url(r'^simple_type/(?P[0-9]+)/edit$', SimplifiedAccountingTypeEditView.as_view(), name='simple_type_edit'), + url( + r"^simple_type$", + SimplifiedAccountingTypeListView.as_view(), + name="simple_type_list", + ), + url( + r"^simple_type/create$", + SimplifiedAccountingTypeCreateView.as_view(), + name="simple_type_new", + ), + url( + r"^simple_type/(?P[0-9]+)/edit$", + SimplifiedAccountingTypeEditView.as_view(), + name="simple_type_edit", + ), # Accounting types - url(r'^type$', AccountingTypeListView.as_view(), name='type_list'), - url(r'^type/create$', AccountingTypeCreateView.as_view(), name='type_new'), - url(r'^type/(?P[0-9]+)/edit$', AccountingTypeEditView.as_view(), name='type_edit'), + url(r"^type$", AccountingTypeListView.as_view(), name="type_list"), + url(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"), + url( + r"^type/(?P[0-9]+)/edit$", + AccountingTypeEditView.as_view(), + name="type_edit", + ), # Bank accounts - url(r'^$', BankAccountListView.as_view(), name='bank_list'), - url(r'^bank/create$', BankAccountCreateView.as_view(), name='bank_new'), - url(r'^bank/(?P[0-9]+)$', BankAccountDetailView.as_view(), name='bank_details'), - url(r'^bank/(?P[0-9]+)/edit$', BankAccountEditView.as_view(), name='bank_edit'), - url(r'^bank/(?P[0-9]+)/delete$', BankAccountDeleteView.as_view(), name='bank_delete'), + url(r"^$", BankAccountListView.as_view(), name="bank_list"), + url(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"), + url( + r"^bank/(?P[0-9]+)$", + BankAccountDetailView.as_view(), + name="bank_details", + ), + url( + r"^bank/(?P[0-9]+)/edit$", + BankAccountEditView.as_view(), + name="bank_edit", + ), + url( + r"^bank/(?P[0-9]+)/delete$", + BankAccountDeleteView.as_view(), + name="bank_delete", + ), # Club accounts - url(r'^club/create$', ClubAccountCreateView.as_view(), name='club_new'), - url(r'^club/(?P[0-9]+)$', ClubAccountDetailView.as_view(), name='club_details'), - url(r'^club/(?P[0-9]+)/edit$', ClubAccountEditView.as_view(), name='club_edit'), - url(r'^club/(?P[0-9]+)/delete$', ClubAccountDeleteView.as_view(), name='club_delete'), + url(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"), + url( + r"^club/(?P[0-9]+)$", + ClubAccountDetailView.as_view(), + name="club_details", + ), + url( + r"^club/(?P[0-9]+)/edit$", + ClubAccountEditView.as_view(), + name="club_edit", + ), + url( + r"^club/(?P[0-9]+)/delete$", + ClubAccountDeleteView.as_view(), + name="club_delete", + ), # Journals - url(r'^journal/create$', JournalCreateView.as_view(), name='journal_new'), - url(r'^journal/(?P[0-9]+)$', JournalDetailView.as_view(), name='journal_details'), - url(r'^journal/(?P[0-9]+)/edit$', JournalEditView.as_view(), name='journal_edit'), - url(r'^journal/(?P[0-9]+)/delete$', JournalDeleteView.as_view(), name='journal_delete'), - url(r'^journal/(?P[0-9]+)/statement/nature$', JournalNatureStatementView.as_view(), name='journal_nature_statement'), - url(r'^journal/(?P[0-9]+)/statement/person$', JournalPersonStatementView.as_view(), name='journal_person_statement'), - url(r'^journal/(?P[0-9]+)/statement/accounting$', JournalAccountingStatementView.as_view(), name='journal_accounting_statement'), - + url(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"), + url( + r"^journal/(?P[0-9]+)$", + JournalDetailView.as_view(), + name="journal_details", + ), + url( + r"^journal/(?P[0-9]+)/edit$", + JournalEditView.as_view(), + name="journal_edit", + ), + url( + r"^journal/(?P[0-9]+)/delete$", + JournalDeleteView.as_view(), + name="journal_delete", + ), + url( + r"^journal/(?P[0-9]+)/statement/nature$", + JournalNatureStatementView.as_view(), + name="journal_nature_statement", + ), + url( + r"^journal/(?P[0-9]+)/statement/person$", + JournalPersonStatementView.as_view(), + name="journal_person_statement", + ), + url( + r"^journal/(?P[0-9]+)/statement/accounting$", + JournalAccountingStatementView.as_view(), + name="journal_accounting_statement", + ), # Operations - url(r'^operation/create/(?P[0-9]+)$', OperationCreateView.as_view(), name='op_new'), - url(r'^operation/(?P[0-9]+)$', OperationEditView.as_view(), name='op_edit'), - url(r'^operation/(?P[0-9]+)/pdf$', OperationPDFView.as_view(), name='op_pdf'), + url( + r"^operation/create/(?P[0-9]+)$", + OperationCreateView.as_view(), + name="op_new", + ), + url(r"^operation/(?P[0-9]+)$", OperationEditView.as_view(), name="op_edit"), + url( + r"^operation/(?P[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf" + ), # Companies - url(r'^company/list$', CompanyListView.as_view(), name='co_list'), - url(r'^company/create$', CompanyCreateView.as_view(), name='co_new'), - url(r'^company/(?P[0-9]+)$', CompanyEditView.as_view(), name='co_edit'), + url(r"^company/list$", CompanyListView.as_view(), name="co_list"), + url(r"^company/create$", CompanyCreateView.as_view(), name="co_new"), + url(r"^company/(?P[0-9]+)$", CompanyEditView.as_view(), name="co_edit"), # Labels - url(r'^label/new$', LabelCreateView.as_view(), name='label_new'), - url(r'^label/(?P[0-9]+)$', LabelListView.as_view(), name='label_list'), - url(r'^label/(?P[0-9]+)/edit$', LabelEditView.as_view(), name='label_edit'), - url(r'^label/(?P[0-9]+)/delete$', LabelDeleteView.as_view(), name='label_delete'), + url(r"^label/new$", LabelCreateView.as_view(), name="label_new"), + url( + r"^label/(?P[0-9]+)$", + LabelListView.as_view(), + name="label_list", + ), + url( + r"^label/(?P[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit" + ), + url( + r"^label/(?P[0-9]+)/delete$", + LabelDeleteView.as_view(), + name="label_delete", + ), # User account - url(r'^refound/account$', RefoundAccountView.as_view(), name='refound_account'), + url(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"), ] diff --git a/accounting/views.py b/accounting/views.py index 3de31c99..5ebd0e2a 100644 --- a/accounting/views.py +++ b/accounting/views.py @@ -38,9 +38,24 @@ import collections from ajax_select.fields import AutoCompleteSelectField -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + CanCreateMixin, + TabedViewMixin, +) from core.views.forms import SelectFile, SelectDate -from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company, SimplifiedAccountingType, Label +from accounting.models import ( + BankAccount, + ClubAccount, + GeneralJournal, + Operation, + AccountingType, + Company, + SimplifiedAccountingType, + Label, +) from counter.models import Counter, Selling, Product # Main accounting view @@ -50,185 +65,228 @@ class BankAccountListView(CanViewMixin, ListView): """ A list view for the admins """ + model = BankAccount - template_name = 'accounting/bank_account_list.jinja' - ordering = ['name'] + template_name = "accounting/bank_account_list.jinja" + ordering = ["name"] # Simplified accounting types + class SimplifiedAccountingTypeListView(CanViewMixin, ListView): """ A list view for the admins """ + model = SimplifiedAccountingType - template_name = 'accounting/simplifiedaccountingtype_list.jinja' + template_name = "accounting/simplifiedaccountingtype_list.jinja" class SimplifiedAccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = SimplifiedAccountingType pk_url_kwarg = "type_id" - fields = ['label', 'accounting_type'] - template_name = 'core/edit.jinja' + fields = ["label", "accounting_type"] + template_name = "core/edit.jinja" class SimplifiedAccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) """ + model = SimplifiedAccountingType - fields = ['label', 'accounting_type'] - template_name = 'core/create.jinja' + fields = ["label", "accounting_type"] + template_name = "core/create.jinja" # Accounting types + class AccountingTypeListView(CanViewMixin, ListView): """ A list view for the admins """ + model = AccountingType - template_name = 'accounting/accountingtype_list.jinja' + template_name = "accounting/accountingtype_list.jinja" class AccountingTypeEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = AccountingType pk_url_kwarg = "type_id" - fields = ['code', 'label', 'movement_type'] - template_name = 'core/edit.jinja' + fields = ["code", "label", "movement_type"] + template_name = "core/edit.jinja" class AccountingTypeCreateView(CanCreateMixin, CreateView): """ Create an accounting type (for the admins) """ + model = AccountingType - fields = ['code', 'label', 'movement_type'] - template_name = 'core/create.jinja' + fields = ["code", "label", "movement_type"] + template_name = "core/create.jinja" # BankAccount views + class BankAccountEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = BankAccount pk_url_kwarg = "b_account_id" - fields = ['name', 'iban', 'number', 'club'] - template_name = 'core/edit.jinja' + fields = ["name", "iban", "number", "club"] + template_name = "core/edit.jinja" class BankAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every club account """ + model = BankAccount pk_url_kwarg = "b_account_id" - template_name = 'accounting/bank_account_details.jinja' + template_name = "accounting/bank_account_details.jinja" class BankAccountCreateView(CanCreateMixin, CreateView): """ Create a bank account (for the admins) """ + model = BankAccount - fields = ['name', 'club', 'iban', 'number'] - template_name = 'core/create.jinja' + fields = ["name", "club", "iban", "number"] + template_name = "core/create.jinja" -class BankAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close +class BankAccountDeleteView( + CanEditPropMixin, DeleteView +): # TODO change Delete to Close """ Delete a bank account (for the admins) """ + model = BankAccount pk_url_kwarg = "b_account_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('accounting:bank_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("accounting:bank_list") # ClubAccount views + class ClubAccountEditView(CanViewMixin, UpdateView): """ An edit view for the admins """ + model = ClubAccount pk_url_kwarg = "c_account_id" - fields = ['name', 'club', 'bank_account'] - template_name = 'core/edit.jinja' + fields = ["name", "club", "bank_account"] + template_name = "core/edit.jinja" class ClubAccountDetailView(CanViewMixin, DetailView): """ A detail view, listing every journal """ + model = ClubAccount pk_url_kwarg = "c_account_id" - template_name = 'accounting/club_account_details.jinja' + template_name = "accounting/club_account_details.jinja" class ClubAccountCreateView(CanCreateMixin, CreateView): """ Create a club account (for the admins) """ + model = ClubAccount - fields = ['name', 'club', 'bank_account'] - template_name = 'core/create.jinja' + fields = ["name", "club", "bank_account"] + template_name = "core/create.jinja" def get_initial(self): ret = super(ClubAccountCreateView, self).get_initial() - if 'parent' in self.request.GET.keys(): - obj = BankAccount.objects.filter(id=int(self.request.GET['parent'])).first() + if "parent" in self.request.GET.keys(): + obj = BankAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: - ret['bank_account'] = obj.id + ret["bank_account"] = obj.id return ret -class ClubAccountDeleteView(CanEditPropMixin, DeleteView): # TODO change Delete to Close +class ClubAccountDeleteView( + CanEditPropMixin, DeleteView +): # TODO change Delete to Close """ Delete a club account (for the admins) """ + model = ClubAccount pk_url_kwarg = "c_account_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('accounting:bank_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("accounting:bank_list") # Journal views + class JournalTabsMixin(TabedViewMixin): def get_tabs_title(self): return _("Journal") def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('accounting:journal_details', kwargs={'j_id': self.object.id}), - 'slug': 'journal', - 'name': _("Journal"), - }) - tab_list.append({ - 'url': reverse('accounting:journal_nature_statement', kwargs={'j_id': self.object.id}), - 'slug': 'nature_statement', - 'name': _("Statement by nature"), - }) - tab_list.append({ - 'url': reverse('accounting:journal_person_statement', kwargs={'j_id': self.object.id}), - 'slug': 'person_statement', - 'name': _("Statement by person"), - }) - tab_list.append({ - 'url': reverse('accounting:journal_accounting_statement', kwargs={'j_id': self.object.id}), - 'slug': 'accounting_statement', - 'name': _("Accounting statement"), - }) + tab_list.append( + { + "url": reverse( + "accounting:journal_details", kwargs={"j_id": self.object.id} + ), + "slug": "journal", + "name": _("Journal"), + } + ) + tab_list.append( + { + "url": reverse( + "accounting:journal_nature_statement", + kwargs={"j_id": self.object.id}, + ), + "slug": "nature_statement", + "name": _("Statement by nature"), + } + ) + tab_list.append( + { + "url": reverse( + "accounting:journal_person_statement", + kwargs={"j_id": self.object.id}, + ), + "slug": "person_statement", + "name": _("Statement by person"), + } + ) + tab_list.append( + { + "url": reverse( + "accounting:journal_accounting_statement", + kwargs={"j_id": self.object.id}, + ), + "slug": "accounting_statement", + "name": _("Accounting statement"), + } + ) return tab_list @@ -236,17 +294,21 @@ class JournalCreateView(CanCreateMixin, CreateView): """ Create a general journal """ + model = GeneralJournal - form_class = modelform_factory(GeneralJournal, fields=['name', 'start_date', 'club_account'], - widgets={'start_date': SelectDate, }) - template_name = 'core/create.jinja' + form_class = modelform_factory( + GeneralJournal, + fields=["name", "start_date", "club_account"], + widgets={"start_date": SelectDate}, + ) + template_name = "core/create.jinja" def get_initial(self): ret = super(JournalCreateView, self).get_initial() - if 'parent' in self.request.GET.keys(): - obj = ClubAccount.objects.filter(id=int(self.request.GET['parent'])).first() + if "parent" in self.request.GET.keys(): + obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: - ret['club_account'] = obj.id + ret["club_account"] = obj.id return ret @@ -254,30 +316,33 @@ class JournalDetailView(JournalTabsMixin, CanViewMixin, DetailView): """ A detail view, listing every operation """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_details.jinja' - current_tab = 'journal' + template_name = "accounting/journal_details.jinja" + current_tab = "journal" class JournalEditView(CanEditMixin, UpdateView): """ Update a general journal """ + model = GeneralJournal pk_url_kwarg = "j_id" - fields = ['name', 'start_date', 'end_date', 'club_account', 'closed'] - template_name = 'core/edit.jinja' + fields = ["name", "start_date", "end_date", "club_account", "closed"] + template_name = "core/edit.jinja" class JournalDeleteView(CanEditPropMixin, DeleteView): """ Delete a club account (for the admins) """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('accounting:club_details') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("accounting:club_details") def dispatch(self, request, *args, **kwargs): self.object = self.get_object() @@ -289,64 +354,105 @@ class JournalDeleteView(CanEditPropMixin, DeleteView): # Operation views + class OperationForm(forms.ModelForm): class Meta: model = Operation - fields = ['amount', 'remark', 'journal', 'target_type', 'target_id', 'target_label', 'date', 'mode', - 'cheque_number', 'invoice', 'simpleaccounting_type', 'accounting_type', 'label', 'done'] + fields = [ + "amount", + "remark", + "journal", + "target_type", + "target_id", + "target_label", + "date", + "mode", + "cheque_number", + "invoice", + "simpleaccounting_type", + "accounting_type", + "label", + "done", + ] widgets = { - 'journal': HiddenInput, - 'target_id': HiddenInput, - 'date': SelectDate, - 'invoice': SelectFile, + "journal": HiddenInput, + "target_id": HiddenInput, + "date": SelectDate, + "invoice": SelectFile, } - user = AutoCompleteSelectField('users', help_text=None, required=False) - club_account = AutoCompleteSelectField('club_accounts', help_text=None, required=False) - club = AutoCompleteSelectField('clubs', help_text=None, required=False) - company = AutoCompleteSelectField('companies', help_text=None, required=False) - need_link = forms.BooleanField(label=_("Link this operation to the target account"), required=False, initial=False) + + user = AutoCompleteSelectField("users", help_text=None, required=False) + club_account = AutoCompleteSelectField( + "club_accounts", help_text=None, required=False + ) + club = AutoCompleteSelectField("clubs", help_text=None, required=False) + company = AutoCompleteSelectField("companies", help_text=None, required=False) + need_link = forms.BooleanField( + label=_("Link this operation to the target account"), + required=False, + initial=False, + ) def __init__(self, *args, **kwargs): - club_account = kwargs.pop('club_account', None) + club_account = kwargs.pop("club_account", None) super(OperationForm, self).__init__(*args, **kwargs) if club_account: - self.fields['label'].queryset = club_account.labels.order_by('name').all() + self.fields["label"].queryset = club_account.labels.order_by("name").all() if self.instance.target_type == "USER": - self.fields['user'].initial = self.instance.target_id + self.fields["user"].initial = self.instance.target_id elif self.instance.target_type == "ACCOUNT": - self.fields['club_account'].initial = self.instance.target_id + self.fields["club_account"].initial = self.instance.target_id elif self.instance.target_type == "CLUB": - self.fields['club'].initial = self.instance.target_id + self.fields["club"].initial = self.instance.target_id elif self.instance.target_type == "COMPANY": - self.fields['company'].initial = self.instance.target_id + self.fields["company"].initial = self.instance.target_id def clean(self): self.cleaned_data = super(OperationForm, self).clean() - if 'target_type' in self.cleaned_data.keys(): - if self.cleaned_data.get("user") is None and self.cleaned_data.get("club") is None and self.cleaned_data.get("club_account") is None and self.cleaned_data.get("company") is None and self.cleaned_data.get("target_label") is None: - self.add_error('target_type', ValidationError(_("The target must be set."))) + if "target_type" in self.cleaned_data.keys(): + if ( + self.cleaned_data.get("user") is None + and self.cleaned_data.get("club") is None + and self.cleaned_data.get("club_account") is None + and self.cleaned_data.get("company") is None + and self.cleaned_data.get("target_label") is None + ): + self.add_error( + "target_type", ValidationError(_("The target must be set.")) + ) else: - if self.cleaned_data['target_type'] == "USER": - self.cleaned_data['target_id'] = self.cleaned_data['user'].id - elif self.cleaned_data['target_type'] == "ACCOUNT": - self.cleaned_data['target_id'] = self.cleaned_data['club_account'].id - elif self.cleaned_data['target_type'] == "CLUB": - self.cleaned_data['target_id'] = self.cleaned_data['club'].id - elif self.cleaned_data['target_type'] == "COMPANY": - self.cleaned_data['target_id'] = self.cleaned_data['company'].id + if self.cleaned_data["target_type"] == "USER": + self.cleaned_data["target_id"] = self.cleaned_data["user"].id + elif self.cleaned_data["target_type"] == "ACCOUNT": + self.cleaned_data["target_id"] = self.cleaned_data[ + "club_account" + ].id + elif self.cleaned_data["target_type"] == "CLUB": + self.cleaned_data["target_id"] = self.cleaned_data["club"].id + elif self.cleaned_data["target_type"] == "COMPANY": + self.cleaned_data["target_id"] = self.cleaned_data["company"].id if self.cleaned_data.get("amount") is None: - self.add_error('amount', ValidationError(_("The amount must be set."))) + self.add_error("amount", ValidationError(_("The amount must be set."))) return self.cleaned_data def save(self): ret = super(OperationForm, self).save() - if self.instance.target_type == "ACCOUNT" and not self.instance.linked_operation and self.instance.target.has_open_journal() and self.cleaned_data['need_link']: + if ( + self.instance.target_type == "ACCOUNT" + and not self.instance.linked_operation + and self.instance.target.has_open_journal() + and self.cleaned_data["need_link"] + ): inst = self.instance club_account = inst.target - acc_type = AccountingType.objects.exclude(movement_type="NEUTRAL").exclude( - movement_type=inst.accounting_type.movement_type).order_by('code').first() # Select a random opposite accounting type + acc_type = ( + AccountingType.objects.exclude(movement_type="NEUTRAL") + .exclude(movement_type=inst.accounting_type.movement_type) + .order_by("code") + .first() + ) # Select a random opposite accounting type op = Operation( journal=club_account.get_open_journal(), amount=inst.amount, @@ -373,26 +479,27 @@ class OperationCreateView(CanCreateMixin, CreateView): """ Create an operation """ + model = Operation form_class = OperationForm - template_name = 'accounting/operation_edit.jinja' + template_name = "accounting/operation_edit.jinja" def get_form(self, form_class=None): - self.journal = GeneralJournal.objects.filter(id=self.kwargs['j_id']).first() + self.journal = GeneralJournal.objects.filter(id=self.kwargs["j_id"]).first() ca = self.journal.club_account if self.journal else None return self.form_class(club_account=ca, **self.get_form_kwargs()) def get_initial(self): ret = super(OperationCreateView, self).get_initial() if self.journal is not None: - ret['journal'] = self.journal.id + ret["journal"] = self.journal.id return ret def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(OperationCreateView, self).get_context_data(**kwargs) if self.journal: - kwargs['object'] = self.journal + kwargs["object"] = self.journal return kwargs @@ -400,15 +507,16 @@ class OperationEditView(CanEditMixin, UpdateView): """ An edit view, working as detail for the moment """ + model = Operation pk_url_kwarg = "op_id" form_class = OperationForm - template_name = 'accounting/operation_edit.jinja' + template_name = "accounting/operation_edit.jinja" def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(OperationEditView, self).get_context_data(**kwargs) - kwargs['object'] = self.object.journal + kwargs["object"] = self.object.journal return kwargs @@ -430,7 +538,7 @@ class OperationPDFView(CanViewMixin, DetailView): from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase import pdfmetrics - pdfmetrics.registerFont(TTFont('DejaVu', 'DejaVuSerif.ttf')) + pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf")) self.object = self.get_object() amount = self.object.amount @@ -450,11 +558,15 @@ class OperationPDFView(CanViewMixin, DetailView): else: target = self.object.target.get_display_name() - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'filename="op-%d(%s_on_%s).pdf"' % (num, ti, club_name) + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = 'filename="op-%d(%s_on_%s).pdf"' % ( + num, + ti, + club_name, + ) p = canvas.Canvas(response) - p.setFont('DejaVu', 12) + p.setFont("DejaVu", 12) p.setTitle("%s %d" % (_("Operation"), num)) width, height = letter @@ -466,20 +578,29 @@ class OperationPDFView(CanViewMixin, DetailView): label = Table(labelStr, colWidths=[150], rowHeights=[20]) - label.setStyle(TableStyle([ - ('ALIGN', (0, 0), (-1, -1), 'RIGHT'), - ])) + label.setStyle(TableStyle([("ALIGN", (0, 0), (-1, -1), "RIGHT")])) w, h = label.wrapOn(label, 0, 0) label.drawOn(p, width - 180, height) - p.drawString(90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op)) # Justificatif du libellé - p.drawString(90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name})) - p.drawString(90, height - 160, _("Label: %(op_label)s") % {"op_label": op_label if op_label is not None else ""}) + p.drawString( + 90, height - 100, _("Financial proof: ") + "OP%010d" % (id_op) + ) # Justificatif du libellé + p.drawString( + 90, height - 130, _("Club: %(club_name)s") % ({"club_name": club_name}) + ) + p.drawString( + 90, + height - 160, + _("Label: %(op_label)s") + % {"op_label": op_label if op_label is not None else ""}, + ) p.drawString(90, height - 190, _("Date: %(date)s") % {"date": date}) data = [] - data += [["%s" % (_("Credit").upper() if nature == 'CREDIT' else _("Debit").upper())]] + data += [ + ["%s" % (_("Credit").upper() if nature == "CREDIT" else _("Debit").upper())] + ] data += [[_("Amount: %(amount).2f €") % {"amount": amount}]] @@ -493,33 +614,50 @@ class OperationPDFView(CanViewMixin, DetailView): data += [[payment_mode]] - data += [["%s : %s" % (_("Debtor") if nature == 'CREDIT' else _("Creditor"), target), ""]] + data += [ + [ + "%s : %s" + % (_("Debtor") if nature == "CREDIT" else _("Creditor"), target), + "", + ] + ] data += [["%s \n%s" % (_("Comment:"), remark)]] - t = Table(data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80]) - t.setStyle(TableStyle([ - ('ALIGN', (0, 0), (-1, -1), 'CENTER'), - ('VALIGN', (-2, -1), (-1, -1), 'TOP'), - ('VALIGN', (0, 0), (-1, -2), 'MIDDLE'), - ('INNERGRID', (0, 0), (-1, -1), 0.25, colors.black), - ('SPAN', (0, 0), (1, 0)), # line DEBIT/CREDIT - ('SPAN', (0, 1), (1, 1)), # line amount - ('SPAN', (-2, -1), (-1, -1)), # line comment - ('SPAN', (0, -2), (-1, -2)), # line creditor/debtor - ('SPAN', (0, 2), (1, 2)), # line payment_mode - ('ALIGN', (0, 2), (1, 2), 'LEFT'), # line payment_mode - ('ALIGN', (-2, -1), (-1, -1), 'LEFT'), - ('BOX', (0, 0), (-1, -1), 0.25, colors.black), - ])) + t = Table( + data, colWidths=[(width - 90 * 2) / 2] * 2, rowHeights=[20, 20, 70, 20, 80] + ) + t.setStyle( + TableStyle( + [ + ("ALIGN", (0, 0), (-1, -1), "CENTER"), + ("VALIGN", (-2, -1), (-1, -1), "TOP"), + ("VALIGN", (0, 0), (-1, -2), "MIDDLE"), + ("INNERGRID", (0, 0), (-1, -1), 0.25, colors.black), + ("SPAN", (0, 0), (1, 0)), # line DEBIT/CREDIT + ("SPAN", (0, 1), (1, 1)), # line amount + ("SPAN", (-2, -1), (-1, -1)), # line comment + ("SPAN", (0, -2), (-1, -2)), # line creditor/debtor + ("SPAN", (0, 2), (1, 2)), # line payment_mode + ("ALIGN", (0, 2), (1, 2), "LEFT"), # line payment_mode + ("ALIGN", (-2, -1), (-1, -1), "LEFT"), + ("BOX", (0, 0), (-1, -1), 0.25, colors.black), + ] + ) + ) signature = [] signature += [[_("Signature:")]] tSig = Table(signature, colWidths=[(width - 90 * 2)], rowHeights=[80]) - tSig.setStyle(TableStyle([ - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ('BOX', (0, 0), (-1, -1), 0.25, colors.black)])) + tSig.setStyle( + TableStyle( + [ + ("VALIGN", (0, 0), (-1, -1), "TOP"), + ("BOX", (0, 0), (-1, -1), 0.25, colors.black), + ] + ) + ) w, h = tSig.wrapOn(p, 0, 0) tSig.drawOn(p, 90, 200) @@ -540,18 +678,22 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Display a statement sorted by labels """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_statement_nature.jinja' - current_tab = 'nature_statement' + template_name = "accounting/journal_statement_nature.jinja" + current_tab = "nature_statement" def statement(self, queryset, movement_type): ret = collections.OrderedDict() statement = collections.OrderedDict() total_sum = 0 - for sat in [None] + list(SimplifiedAccountingType.objects.order_by('label').all()): - sum = queryset.filter(accounting_type__movement_type=movement_type, - simpleaccounting_type=sat).aggregate(amount_sum=Sum('amount'))['amount_sum'] + for sat in [None] + list( + SimplifiedAccountingType.objects.order_by("label").all() + ): + sum = queryset.filter( + accounting_type__movement_type=movement_type, simpleaccounting_type=sat + ).aggregate(amount_sum=Sum("amount"))["amount_sum"] if sat: sat = sat.label else: @@ -564,7 +706,9 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): return ret def big_statement(self): - label_list = self.object.operations.order_by('label').values_list('label').distinct() + label_list = ( + self.object.operations.order_by("label").values_list("label").distinct() + ) labels = Label.objects.filter(id__in=label_list).all() statement = collections.OrderedDict() gen_statement = collections.OrderedDict() @@ -572,20 +716,28 @@ class JournalNatureStatementView(JournalTabsMixin, CanViewMixin, DetailView): gen_statement.update(self.statement(self.object.operations.all(), "CREDIT")) gen_statement.update(self.statement(self.object.operations.all(), "DEBIT")) statement[_("General statement")] = gen_statement - no_label_statement.update(self.statement(self.object.operations.filter(label=None).all(), "CREDIT")) - no_label_statement.update(self.statement(self.object.operations.filter(label=None).all(), "DEBIT")) + no_label_statement.update( + self.statement(self.object.operations.filter(label=None).all(), "CREDIT") + ) + no_label_statement.update( + self.statement(self.object.operations.filter(label=None).all(), "DEBIT") + ) statement[_("No label operations")] = no_label_statement for l in labels: l_stmt = collections.OrderedDict() - l_stmt.update(self.statement(self.object.operations.filter(label=l).all(), "CREDIT")) - l_stmt.update(self.statement(self.object.operations.filter(label=l).all(), "DEBIT")) + l_stmt.update( + self.statement(self.object.operations.filter(label=l).all(), "CREDIT") + ) + l_stmt.update( + self.statement(self.object.operations.filter(label=l).all(), "DEBIT") + ) statement[l] = l_stmt return statement def get_context_data(self, **kwargs): """ Add infos to the context """ kwargs = super(JournalNatureStatementView, self).get_context_data(**kwargs) - kwargs['statement'] = self.big_statement() + kwargs["statement"] = self.big_statement() return kwargs @@ -593,20 +745,29 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): """ Calculate a dictionary with operation target and sum of operations """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_statement_person.jinja' - current_tab = 'person_statement' + template_name = "accounting/journal_statement_person.jinja" + current_tab = "person_statement" def sum_by_target(self, target_id, target_type, movement_type): - return self.object.operations.filter(accounting_type__movement_type=movement_type, - target_id=target_id, target_type=target_type).aggregate(amount_sum=Sum('amount'))['amount_sum'] + return self.object.operations.filter( + accounting_type__movement_type=movement_type, + target_id=target_id, + target_type=target_type, + ).aggregate(amount_sum=Sum("amount"))["amount_sum"] def statement(self, movement_type): statement = collections.OrderedDict() - for op in self.object.operations.filter(accounting_type__movement_type=movement_type).order_by('target_type', - 'target_id').distinct(): - statement[op.target] = self.sum_by_target(op.target_id, op.target_type, movement_type) + for op in ( + self.object.operations.filter(accounting_type__movement_type=movement_type) + .order_by("target_type", "target_id") + .distinct() + ): + statement[op.target] = self.sum_by_target( + op.target_id, op.target_type, movement_type + ) return statement def total(self, movement_type): @@ -615,10 +776,10 @@ class JournalPersonStatementView(JournalTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(JournalPersonStatementView, self).get_context_data(**kwargs) - kwargs['credit_statement'] = self.statement("CREDIT") - kwargs['debit_statement'] = self.statement("DEBIT") - kwargs['total_credit'] = self.total("CREDIT") - kwargs['total_debit'] = self.total("DEBIT") + kwargs["credit_statement"] = self.statement("CREDIT") + kwargs["debit_statement"] = self.statement("DEBIT") + kwargs["total_credit"] = self.total("CREDIT") + kwargs["total_debit"] = self.total("DEBIT") return kwargs @@ -626,16 +787,18 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) """ Calculate a dictionary with operation type and sum of operations """ + model = GeneralJournal pk_url_kwarg = "j_id" - template_name = 'accounting/journal_statement_accounting.jinja' + template_name = "accounting/journal_statement_accounting.jinja" current_tab = "accounting_statement" def statement(self): statement = collections.OrderedDict() - for at in AccountingType.objects.order_by('code').all(): + for at in AccountingType.objects.order_by("code").all(): sum_by_type = self.object.operations.filter( - accounting_type__code__startswith=at.code).aggregate(amount_sum=Sum('amount'))['amount_sum'] + accounting_type__code__startswith=at.code + ).aggregate(amount_sum=Sum("amount"))["amount_sum"] if sum_by_type: statement[at] = sum_by_type return statement @@ -643,86 +806,95 @@ class JournalAccountingStatementView(JournalTabsMixin, CanViewMixin, DetailView) def get_context_data(self, **kwargs): """ Add journal to the context """ kwargs = super(JournalAccountingStatementView, self).get_context_data(**kwargs) - kwargs['statement'] = self.statement() + kwargs["statement"] = self.statement() return kwargs + # Company views class CompanyListView(CanViewMixin, ListView): model = Company - template_name = 'accounting/co_list.jinja' + template_name = "accounting/co_list.jinja" class CompanyCreateView(CanCreateMixin, CreateView): """ Create a company """ + model = Company - fields = ['name'] - template_name = 'core/create.jinja' - success_url = reverse_lazy('accounting:co_list') + fields = ["name"] + template_name = "core/create.jinja" + success_url = reverse_lazy("accounting:co_list") class CompanyEditView(CanCreateMixin, UpdateView): """ Edit a company """ + model = Company pk_url_kwarg = "co_id" - fields = ['name'] - template_name = 'core/edit.jinja' - success_url = reverse_lazy('accounting:co_list') + fields = ["name"] + template_name = "core/edit.jinja" + success_url = reverse_lazy("accounting:co_list") # Label views + class LabelListView(CanViewMixin, DetailView): model = ClubAccount pk_url_kwarg = "clubaccount_id" - template_name = 'accounting/label_list.jinja' + template_name = "accounting/label_list.jinja" -class LabelCreateView(CanCreateMixin, CreateView): # FIXME we need to check the rights before creating the object +class LabelCreateView( + CanCreateMixin, CreateView +): # FIXME we need to check the rights before creating the object model = Label - form_class = modelform_factory(Label, fields=['name', 'club_account'], widgets={ - 'club_account': HiddenInput, - }) - template_name = 'core/create.jinja' + form_class = modelform_factory( + Label, fields=["name", "club_account"], widgets={"club_account": HiddenInput} + ) + template_name = "core/create.jinja" def get_initial(self): ret = super(LabelCreateView, self).get_initial() - if 'parent' in self.request.GET.keys(): - obj = ClubAccount.objects.filter(id=int(self.request.GET['parent'])).first() + if "parent" in self.request.GET.keys(): + obj = ClubAccount.objects.filter(id=int(self.request.GET["parent"])).first() if obj is not None: - ret['club_account'] = obj.id + ret["club_account"] = obj.id return ret class LabelEditView(CanEditMixin, UpdateView): model = Label pk_url_kwarg = "label_id" - fields = ['name'] - template_name = 'core/edit.jinja' + fields = ["name"] + template_name = "core/edit.jinja" class LabelDeleteView(CanEditMixin, DeleteView): model = Label pk_url_kwarg = "label_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def get_success_url(self): return self.object.get_absolute_url() class CloseCustomerAccountForm(forms.Form): - user = AutoCompleteSelectField('users', label=_('Refound this account'), help_text=None, required=True) + user = AutoCompleteSelectField( + "users", label=_("Refound this account"), help_text=None, required=True + ) class RefoundAccountView(FormView): """ Create a selling with the same amount than the current user money """ + template_name = "accounting/refound_account.jinja" form_class = CloseCustomerAccountForm @@ -743,21 +915,28 @@ class RefoundAccountView(FormView): return super(RefoundAccountView, self).post(self, request, *arg, **kwargs) def form_valid(self, form): - self.customer = form.cleaned_data['user'] + self.customer = form.cleaned_data["user"] self.create_selling() return super(RefoundAccountView, self).form_valid(form) def get_success_url(self): - return reverse('accounting:refound_account') + return reverse("accounting:refound_account") def create_selling(self): with transaction.atomic(): uprice = self.customer.customer.amount - refound_club_counter = Counter.objects.get(id=settings.SITH_COUNTER_REFOUND_ID) + refound_club_counter = Counter.objects.get( + id=settings.SITH_COUNTER_REFOUND_ID + ) refound_club = refound_club_counter.club - s = Selling(label=_('Refound account'), unit_price=uprice, - quantity=1, seller=self.operator, - customer=self.customer.customer, - club=refound_club, counter=refound_club_counter, - product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID)) + s = Selling( + label=_("Refound account"), + unit_price=uprice, + quantity=1, + seller=self.operator, + customer=self.customer.customer, + club=refound_club, + counter=refound_club_counter, + product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID), + ) s.save() diff --git a/api/__init__.py b/api/__init__.py index 0a9419f8..0ace29c4 100644 --- a/api/__init__.py +++ b/api/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/api/urls.py b/api/urls.py index 1289668f..60a4922c 100644 --- a/api/urls.py +++ b/api/urls.py @@ -29,25 +29,28 @@ from rest_framework import routers # Router config router = routers.DefaultRouter() -router.register(r'counter', CounterViewSet, base_name='api_counter') -router.register(r'user', UserViewSet, base_name='api_user') -router.register(r'club', ClubViewSet, base_name='api_club') -router.register(r'group', GroupViewSet, base_name='api_group') +router.register(r"counter", CounterViewSet, base_name="api_counter") +router.register(r"user", UserViewSet, base_name="api_user") +router.register(r"club", ClubViewSet, base_name="api_club") +router.register(r"group", GroupViewSet, base_name="api_group") # Launderette -router.register(r'launderette/place', LaunderettePlaceViewSet, - base_name='api_launderette_place') -router.register(r'launderette/machine', LaunderetteMachineViewSet, - base_name='api_launderette_machine') -router.register(r'launderette/token', LaunderetteTokenViewSet, - base_name='api_launderette_token') +router.register( + r"launderette/place", LaunderettePlaceViewSet, base_name="api_launderette_place" +) +router.register( + r"launderette/machine", + LaunderetteMachineViewSet, + base_name="api_launderette_machine", +) +router.register( + r"launderette/token", LaunderetteTokenViewSet, base_name="api_launderette_token" +) urlpatterns = [ - # API - url(r'^', include(router.urls)), - url(r'^login/', include('rest_framework.urls', namespace='rest_framework')), - url(r'^markdown$', RenderMarkdown, name='api_markdown'), - url(r'^mailings$', FetchMailingLists, name='mailings_fetch') - + url(r"^", include(router.urls)), + url(r"^login/", include("rest_framework.urls", namespace="rest_framework")), + url(r"^markdown$", RenderMarkdown, name="api_markdown"), + url(r"^mailings$", FetchMailingLists, name="mailings_fetch"), ] diff --git a/api/views/__init__.py b/api/views/__init__.py index a0676bbe..90f3fc0e 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -30,19 +30,21 @@ from django.db.models.query import QuerySet from core.views import can_view, can_edit + def check_if(obj, user, test): """ Detect if it's a single object or a queryset aply a given test on individual object and return global permission """ - if (isinstance(obj, QuerySet)): + if isinstance(obj, QuerySet): for o in obj: - if (test(o, user) is False): + if test(o, user) is False: return False return True else: return test(obj, user) + class ManageModelMixin: @detail_route() def id(self, request, pk=None): @@ -53,19 +55,19 @@ class ManageModelMixin: serializer = self.get_serializer(self.queryset) return Response(serializer.data) -class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet): +class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet): def dispatch(self, request, *arg, **kwargs): - res = super(RightModelViewSet, - self).dispatch(request, *arg, **kwargs) + res = super(RightModelViewSet, self).dispatch(request, *arg, **kwargs) obj = self.queryset user = self.request.user try: - if (request.method == 'GET' and check_if(obj, user, can_view)): + if request.method == "GET" and check_if(obj, user, can_view): return res - if (request.method != 'GET' and check_if(obj, user, can_edit)): + if request.method != "GET" and check_if(obj, user, can_edit): return res - except: pass # To prevent bug with Anonymous user + except: + pass # To prevent bug with Anonymous user raise PermissionDenied @@ -74,4 +76,4 @@ from .counter import * from .user import * from .club import * from .group import * -from .launderette import * \ No newline at end of file +from .launderette import * diff --git a/api/views/api.py b/api/views/api.py index a7d098e3..efd9ec42 100644 --- a/api/views/api.py +++ b/api/views/api.py @@ -30,15 +30,14 @@ from rest_framework.views import APIView from core.templatetags.renderer import markdown -@api_view(['POST']) +@api_view(["POST"]) @renderer_classes((StaticHTMLRenderer,)) def RenderMarkdown(request): """ Render Markdown """ try: - data = markdown(request.POST['text']) + data = markdown(request.POST["text"]) except: - data = 'Error' + data = "Error" return Response(data) - diff --git a/api/views/club.py b/api/views/club.py index e4d4b30a..38ce16be 100644 --- a/api/views/club.py +++ b/api/views/club.py @@ -36,10 +36,9 @@ from api.views import RightModelViewSet class ClubSerializer(serializers.ModelSerializer): - class Meta: model = Club - fields = ('id', 'name', 'unix_name', 'address', 'members') + fields = ("id", "name", "unix_name", "address", "members") class ClubViewSet(RightModelViewSet): @@ -51,13 +50,15 @@ class ClubViewSet(RightModelViewSet): queryset = Club.objects.all() -@api_view(['GET']) +@api_view(["GET"]) @renderer_classes((StaticHTMLRenderer,)) def FetchMailingLists(request): - key = request.GET.get('key', '') + key = request.GET.get("key", "") if key != settings.SITH_MAILING_FETCH_KEY: raise PermissionDenied - data = '' - for mailing in Mailing.objects.filter(is_moderated=True, club__is_active=True).all(): + data = "" + for mailing in Mailing.objects.filter( + is_moderated=True, club__is_active=True + ).all(): data += mailing.fetch_format() + "\n" return Response(data) diff --git a/api/views/counter.py b/api/views/counter.py index 2df78e73..43ac1885 100644 --- a/api/views/counter.py +++ b/api/views/counter.py @@ -35,14 +35,12 @@ class CounterSerializer(serializers.ModelSerializer): is_open = serializers.BooleanField(read_only=True) barman_list = serializers.ListField( - child=serializers.IntegerField(), - read_only=True + child=serializers.IntegerField(), read_only=True ) class Meta: model = Counter - fields = ('id', 'name', 'type', 'club', - 'products', 'is_open', 'barman_list') + fields = ("id", "name", "type", "club", "products", "is_open", "barman_list") class CounterViewSet(RightModelViewSet): diff --git a/api/views/group.py b/api/views/group.py index ec46555e..5e6436af 100644 --- a/api/views/group.py +++ b/api/views/group.py @@ -30,7 +30,6 @@ from api.views import RightModelViewSet class GroupSerializer(serializers.ModelSerializer): - class Meta: model = RealGroup diff --git a/api/views/launderette.py b/api/views/launderette.py index fb4db382..940f8ebd 100644 --- a/api/views/launderette.py +++ b/api/views/launderette.py @@ -30,34 +30,45 @@ from launderette.models import Launderette, Machine, Token from api.views import RightModelViewSet + class LaunderettePlaceSerializer(serializers.ModelSerializer): machine_list = serializers.ListField( - child=serializers.IntegerField(), - read_only=True - ) - token_list = serializers.ListField( - child=serializers.IntegerField(), - read_only=True + child=serializers.IntegerField(), read_only=True ) + token_list = serializers.ListField(child=serializers.IntegerField(), read_only=True) class Meta: model = Launderette - fields = ('id', 'name', 'counter', 'machine_list', - 'token_list', 'get_absolute_url') + fields = ( + "id", + "name", + "counter", + "machine_list", + "token_list", + "get_absolute_url", + ) + class LaunderetteMachineSerializer(serializers.ModelSerializer): - class Meta: model = Machine - fields = ('id', 'name', 'type', 'is_working', 'launderette') + fields = ("id", "name", "type", "is_working", "launderette") + class LaunderetteTokenSerializer(serializers.ModelSerializer): - class Meta: model = Token - fields = ('id', 'name', 'type', 'launderette', 'borrow_date', - 'user', 'is_avaliable') + fields = ( + "id", + "name", + "type", + "launderette", + "borrow_date", + "user", + "is_avaliable", + ) + class LaunderettePlaceViewSet(RightModelViewSet): """ @@ -67,6 +78,7 @@ class LaunderettePlaceViewSet(RightModelViewSet): serializer_class = LaunderettePlaceSerializer queryset = Launderette.objects.all() + class LaunderetteMachineViewSet(RightModelViewSet): """ Manage Washing Machines (api/v1/launderette/machine/) @@ -89,7 +101,7 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all washing tokens (api/v1/launderette/token/washing) """ - self.queryset = self.queryset.filter(type='WASHING') + self.queryset = self.queryset.filter(type="WASHING") serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) @@ -98,7 +110,7 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all drying tokens (api/v1/launderette/token/drying) """ - self.queryset = self.queryset.filter(type='DRYING') + self.queryset = self.queryset.filter(type="DRYING") serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) @@ -107,7 +119,9 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all avaliable tokens (api/v1/launderette/token/avaliable) """ - self.queryset = self.queryset.filter(borrow_date__isnull=True, user__isnull=True) + self.queryset = self.queryset.filter( + borrow_date__isnull=True, user__isnull=True + ) serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) @@ -116,6 +130,8 @@ class LaunderetteTokenViewSet(RightModelViewSet): """ Return all unavaliable tokens (api/v1/launderette/token/unavaliable) """ - self.queryset = self.queryset.filter(borrow_date__isnull=False, user__isnull=False) + self.queryset = self.queryset.filter( + borrow_date__isnull=False, user__isnull=False + ) serializer = self.get_serializer(self.queryset, many=True) return Response(serializer.data) diff --git a/api/views/user.py b/api/views/user.py index cb4d6a24..4724c292 100644 --- a/api/views/user.py +++ b/api/views/user.py @@ -34,11 +34,18 @@ from api.views import RightModelViewSet class UserSerializer(serializers.ModelSerializer): - class Meta: model = User - fields = ('id', 'first_name', 'last_name', 'email', - 'date_of_birth', 'nick_name', 'is_active', 'date_joined') + fields = ( + "id", + "first_name", + "last_name", + "email", + "date_of_birth", + "nick_name", + "is_active", + "date_joined", + ) class UserViewSet(RightModelViewSet): diff --git a/club/__init__.py b/club/__init__.py index 0a9419f8..0ace29c4 100644 --- a/club/__init__.py +++ b/club/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/club/migrations/0001_initial.py b/club/migrations/0001_initial.py index d93ac4c8..0bc1ec7e 100644 --- a/club/migrations/0001_initial.py +++ b/club/migrations/0001_initial.py @@ -7,28 +7,92 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Club', + name="Club", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('unix_name', models.CharField(unique=True, max_length=30, error_messages={'unique': 'A club with that unix name already exists.'}, verbose_name='unix name', validators=[django.core.validators.RegexValidator('^[a-z0-9][a-z0-9._-]*[a-z0-9]$', 'Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.')])), - ('address', models.CharField(max_length=254, verbose_name='address')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "unix_name", + models.CharField( + unique=True, + max_length=30, + error_messages={ + "unique": "A club with that unix name already exists." + }, + verbose_name="unix name", + validators=[ + django.core.validators.RegexValidator( + "^[a-z0-9][a-z0-9._-]*[a-z0-9]$", + "Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.", + ) + ], + ), + ), + ("address", models.CharField(max_length=254, verbose_name="address")), ], ), migrations.CreateModel( - name='Membership', + name="Membership", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateField(verbose_name='start date', auto_now=True)), - ('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), - ('role', models.IntegerField(choices=[(0, 'Curious'), (1, 'Active member'), (2, 'Board member'), (3, 'IT supervisor'), (4, 'Secretary'), (5, 'Communication supervisor'), (7, 'Treasurer'), (9, 'Vice-President'), (10, 'President')], default=0, verbose_name='role')), - ('description', models.CharField(max_length=128, blank=True, verbose_name='description')), - ('club', models.ForeignKey(verbose_name='club', to='club.Club', related_name='members')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "start_date", + models.DateField(verbose_name="start date", auto_now=True), + ), + ( + "end_date", + models.DateField(null=True, verbose_name="end date", blank=True), + ), + ( + "role", + models.IntegerField( + choices=[ + (0, "Curious"), + (1, "Active member"), + (2, "Board member"), + (3, "IT supervisor"), + (4, "Secretary"), + (5, "Communication supervisor"), + (7, "Treasurer"), + (9, "Vice-President"), + (10, "President"), + ], + default=0, + verbose_name="role", + ), + ), + ( + "description", + models.CharField( + max_length=128, blank=True, verbose_name="description" + ), + ), + ( + "club", + models.ForeignKey( + verbose_name="club", to="club.Club", related_name="members" + ), + ), ], ), ] diff --git a/club/migrations/0002_auto_20160824_2152.py b/club/migrations/0002_auto_20160824_2152.py index 8c8a0fc8..b6de6b01 100644 --- a/club/migrations/0002_auto_20160824_2152.py +++ b/club/migrations/0002_auto_20160824_2152.py @@ -9,39 +9,57 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('club', '0001_initial'), - ('core', '0001_initial'), + ("club", "0001_initial"), + ("core", "0001_initial"), ] operations = [ migrations.AddField( - model_name='membership', - name='user', - field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='membership'), + model_name="membership", + name="user", + field=models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="membership", + ), ), migrations.AddField( - model_name='club', - name='edit_groups', - field=models.ManyToManyField(to='core.Group', blank=True, related_name='editable_club'), + model_name="club", + name="edit_groups", + field=models.ManyToManyField( + to="core.Group", blank=True, related_name="editable_club" + ), ), migrations.AddField( - model_name='club', - name='home', - field=models.OneToOneField(blank=True, null=True, related_name='home_of_club', verbose_name='home', to='core.SithFile'), + model_name="club", + name="home", + field=models.OneToOneField( + blank=True, + null=True, + related_name="home_of_club", + verbose_name="home", + to="core.SithFile", + ), ), migrations.AddField( - model_name='club', - name='owner_group', - field=models.ForeignKey(default=1, to='core.Group', related_name='owned_club'), + model_name="club", + name="owner_group", + field=models.ForeignKey( + default=1, to="core.Group", related_name="owned_club" + ), ), migrations.AddField( - model_name='club', - name='parent', - field=models.ForeignKey(null=True, to='club.Club', related_name='children', blank=True), + model_name="club", + name="parent", + field=models.ForeignKey( + null=True, to="club.Club", related_name="children", blank=True + ), ), migrations.AddField( - model_name='club', - name='view_groups', - field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_club'), + model_name="club", + name="view_groups", + field=models.ManyToManyField( + to="core.Group", blank=True, related_name="viewable_club" + ), ), ] diff --git a/club/migrations/0003_auto_20160902_2042.py b/club/migrations/0003_auto_20160902_2042.py index f0d7e014..15eb34ad 100644 --- a/club/migrations/0003_auto_20160902_2042.py +++ b/club/migrations/0003_auto_20160902_2042.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0002_auto_20160824_2152'), - ] + dependencies = [("club", "0002_auto_20160824_2152")] operations = [ migrations.AlterField( - model_name='membership', - name='start_date', - field=models.DateField(verbose_name='start date'), - ), + model_name="membership", + name="start_date", + field=models.DateField(verbose_name="start date"), + ) ] diff --git a/club/migrations/0004_auto_20160915_1057.py b/club/migrations/0004_auto_20160915_1057.py index 05bd4958..6cdde0a6 100644 --- a/club/migrations/0004_auto_20160915_1057.py +++ b/club/migrations/0004_auto_20160915_1057.py @@ -7,14 +7,16 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('club', '0003_auto_20160902_2042'), - ] + dependencies = [("club", "0003_auto_20160902_2042")] operations = [ migrations.AlterField( - model_name='membership', - name='user', - field=models.ForeignKey(verbose_name='user', related_name='memberships', to=settings.AUTH_USER_MODEL), - ), + model_name="membership", + name="user", + field=models.ForeignKey( + verbose_name="user", + related_name="memberships", + to=settings.AUTH_USER_MODEL, + ), + ) ] diff --git a/club/migrations/0005_auto_20161120_1149.py b/club/migrations/0005_auto_20161120_1149.py index df8d8126..93193f50 100644 --- a/club/migrations/0005_auto_20161120_1149.py +++ b/club/migrations/0005_auto_20161120_1149.py @@ -7,14 +7,19 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('club', '0004_auto_20160915_1057'), - ] + dependencies = [("club", "0004_auto_20160915_1057")] operations = [ migrations.AlterField( - model_name='club', - name='home', - field=models.OneToOneField(related_name='home_of_club', blank=True, on_delete=django.db.models.deletion.SET_NULL, verbose_name='home', null=True, to='core.SithFile'), - ), + model_name="club", + name="home", + field=models.OneToOneField( + related_name="home_of_club", + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + verbose_name="home", + null=True, + to="core.SithFile", + ), + ) ] diff --git a/club/migrations/0006_auto_20161229_0040.py b/club/migrations/0006_auto_20161229_0040.py index 58a0f676..6bb03661 100644 --- a/club/migrations/0006_auto_20161229_0040.py +++ b/club/migrations/0006_auto_20161229_0040.py @@ -7,14 +7,14 @@ import django.utils.timezone class Migration(migrations.Migration): - dependencies = [ - ('club', '0005_auto_20161120_1149'), - ] + dependencies = [("club", "0005_auto_20161120_1149")] operations = [ migrations.AlterField( - model_name='membership', - name='start_date', - field=models.DateField(verbose_name='start date', default=django.utils.timezone.now), - ), + model_name="membership", + name="start_date", + field=models.DateField( + verbose_name="start date", default=django.utils.timezone.now + ), + ) ] diff --git a/club/migrations/0007_auto_20170324_0917.py b/club/migrations/0007_auto_20170324_0917.py index dd215472..9faa9e75 100644 --- a/club/migrations/0007_auto_20170324_0917.py +++ b/club/migrations/0007_auto_20170324_0917.py @@ -6,13 +6,10 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0006_auto_20161229_0040'), - ] + dependencies = [("club", "0006_auto_20161229_0040")] operations = [ migrations.AlterModelOptions( - name='club', - options={'ordering': ['name', 'unix_name']}, - ), + name="club", options={"ordering": ["name", "unix_name"]} + ) ] diff --git a/club/migrations/0008_auto_20170515_2214.py b/club/migrations/0008_auto_20170515_2214.py index 48f5b3b6..6b65c629 100644 --- a/club/migrations/0008_auto_20170515_2214.py +++ b/club/migrations/0008_auto_20170515_2214.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0007_auto_20170324_0917'), - ] + dependencies = [("club", "0007_auto_20170324_0917")] operations = [ migrations.AlterField( - model_name='club', - name='id', + model_name="club", + name="id", field=models.AutoField(primary_key=True, serialize=False, db_index=True), - ), + ) ] diff --git a/club/migrations/0009_auto_20170822_2232.py b/club/migrations/0009_auto_20170822_2232.py index 67fc9cf6..a7d84c3a 100644 --- a/club/migrations/0009_auto_20170822_2232.py +++ b/club/migrations/0009_auto_20170822_2232.py @@ -11,31 +11,98 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('club', '0008_auto_20170515_2214'), + ("club", "0008_auto_20170515_2214"), ] operations = [ migrations.CreateModel( - name='Mailing', + name="Mailing", fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), - ('email', models.CharField(max_length=256, unique=True, validators=[django.core.validators.RegexValidator(re.compile('(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*\\Z|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"\\Z)', 34), 'Enter a valid address. Only the root of the address is needed.')], verbose_name='Email address')), - ('is_moderated', models.BooleanField(default=False, verbose_name='is moderated')), - ('club', models.ForeignKey(verbose_name='Club', related_name='mailings', to='club.Club')), - ('moderator', models.ForeignKey(null=True, verbose_name='moderator', related_name='moderated_mailings', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + verbose_name="ID", + serialize=False, + primary_key=True, + ), + ), + ( + "email", + models.CharField( + max_length=256, + unique=True, + validators=[ + django.core.validators.RegexValidator( + re.compile( + "(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\\Z|^\"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*\"\\Z)", + 34, + ), + "Enter a valid address. Only the root of the address is needed.", + ) + ], + verbose_name="Email address", + ), + ), + ( + "is_moderated", + models.BooleanField(default=False, verbose_name="is moderated"), + ), + ( + "club", + models.ForeignKey( + verbose_name="Club", related_name="mailings", to="club.Club" + ), + ), + ( + "moderator", + models.ForeignKey( + null=True, + verbose_name="moderator", + related_name="moderated_mailings", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='MailingSubscription', + name="MailingSubscription", fields=[ - ('id', models.AutoField(auto_created=True, verbose_name='ID', serialize=False, primary_key=True)), - ('email', models.EmailField(max_length=254, verbose_name='Email address')), - ('mailing', models.ForeignKey(verbose_name='Mailing', related_name='subscriptions', to='club.Mailing')), - ('user', models.ForeignKey(null=True, verbose_name='User', related_name='mailing_subscriptions', blank=True, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + verbose_name="ID", + serialize=False, + primary_key=True, + ), + ), + ( + "email", + models.EmailField(max_length=254, verbose_name="Email address"), + ), + ( + "mailing", + models.ForeignKey( + verbose_name="Mailing", + related_name="subscriptions", + to="club.Mailing", + ), + ), + ( + "user", + models.ForeignKey( + null=True, + verbose_name="User", + related_name="mailing_subscriptions", + blank=True, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.AlterUniqueTogether( - name='mailingsubscription', - unique_together=set([('user', 'email', 'mailing')]), + name="mailingsubscription", + unique_together=set([("user", "email", "mailing")]), ), ] diff --git a/club/migrations/0010_auto_20170912_2028.py b/club/migrations/0010_auto_20170912_2028.py index af3e2bdf..ff6f76ec 100644 --- a/club/migrations/0010_auto_20170912_2028.py +++ b/club/migrations/0010_auto_20170912_2028.py @@ -12,34 +12,44 @@ def generate_club_pages(apps, schema_editor): club.make_page() for child in Club.objects.filter(parent=club).all(): recursive_generate_club_page(child) + for club in Club.objects.filter(parent=None).all(): recursive_generate_club_page(club) class Migration(migrations.Migration): - dependencies = [ - ('core', '0024_auto_20170906_1317'), - ('club', '0010_club_logo'), - ] + dependencies = [("core", "0024_auto_20170906_1317"), ("club", "0010_club_logo")] operations = [ migrations.AddField( - model_name='club', - name='is_active', - field=models.BooleanField(default=True, verbose_name='is active'), + model_name="club", + name="is_active", + field=models.BooleanField(default=True, verbose_name="is active"), ), migrations.AddField( - model_name='club', - name='page', - field=models.OneToOneField(related_name='club', blank=True, null=True, to='core.Page'), + model_name="club", + name="page", + field=models.OneToOneField( + related_name="club", blank=True, null=True, to="core.Page" + ), ), migrations.AddField( - model_name='club', - name='short_description', - field=models.CharField(verbose_name='short description', max_length=1000, default='', blank=True, null=True), + model_name="club", + name="short_description", + field=models.CharField( + verbose_name="short description", + max_length=1000, + default="", + blank=True, + null=True, + ), + ), + PsqlRunOnly( + "SET CONSTRAINTS ALL IMMEDIATE", reverse_sql=migrations.RunSQL.noop ), - PsqlRunOnly('SET CONSTRAINTS ALL IMMEDIATE', reverse_sql=migrations.RunSQL.noop), migrations.RunPython(generate_club_pages), - PsqlRunOnly(migrations.RunSQL.noop, reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'), + PsqlRunOnly( + migrations.RunSQL.noop, reverse_sql="SET CONSTRAINTS ALL IMMEDIATE" + ), ] diff --git a/club/migrations/0010_club_logo.py b/club/migrations/0010_club_logo.py index 9e8d4288..6dab697d 100644 --- a/club/migrations/0010_club_logo.py +++ b/club/migrations/0010_club_logo.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('club', '0009_auto_20170822_2232'), - ] + dependencies = [("club", "0009_auto_20170822_2232")] operations = [ migrations.AddField( - model_name='club', - name='logo', - field=models.ImageField(null=True, upload_to='club_logos', blank=True, verbose_name='logo'), - ), + model_name="club", + name="logo", + field=models.ImageField( + null=True, upload_to="club_logos", blank=True, verbose_name="logo" + ), + ) ] diff --git a/club/migrations/0011_auto_20180426_2013.py b/club/migrations/0011_auto_20180426_2013.py index 2c4cfa5e..8d63136e 100644 --- a/club/migrations/0011_auto_20180426_2013.py +++ b/club/migrations/0011_auto_20180426_2013.py @@ -7,14 +7,16 @@ import club.models class Migration(migrations.Migration): - dependencies = [ - ('club', '0010_auto_20170912_2028'), - ] + dependencies = [("club", "0010_auto_20170912_2028")] operations = [ migrations.AlterField( - model_name='club', - name='owner_group', - field=models.ForeignKey(default=club.models.Club.get_default_owner_group, related_name='owned_club', to='core.Group'), - ), + model_name="club", + name="owner_group", + field=models.ForeignKey( + default=club.models.Club.get_default_owner_group, + related_name="owned_club", + to="core.Group", + ), + ) ] diff --git a/club/models.py b/club/models.py index 575c06ad..00c9239d 100644 --- a/club/models.py +++ b/club/models.py @@ -43,40 +43,64 @@ class Club(models.Model): """ The Club class, made as a tree to allow nice tidy organization """ + id = models.AutoField(primary_key=True, db_index=True) - name = models.CharField(_('name'), max_length=64) - parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) - unix_name = models.CharField(_('unix name'), max_length=30, unique=True, - validators=[ - validators.RegexValidator( - r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', - _('Enter a valid unix name. This value may contain only ' - 'letters, numbers ./-/_ characters.') - ), - ], - error_messages={ - 'unique': _("A club with that unix name already exists."), - }, + name = models.CharField(_("name"), max_length=64) + parent = models.ForeignKey("Club", related_name="children", null=True, blank=True) + unix_name = models.CharField( + _("unix name"), + max_length=30, + unique=True, + validators=[ + validators.RegexValidator( + r"^[a-z0-9][a-z0-9._-]*[a-z0-9]$", + _( + "Enter a valid unix name. This value may contain only " + "letters, numbers ./-/_ characters." + ), + ) + ], + error_messages={"unique": _("A club with that unix name already exists.")}, ) - logo = models.ImageField(upload_to='club_logos', verbose_name=_('logo'), null=True, blank=True) - is_active = models.BooleanField(_('is active'), default=True) - short_description = models.CharField(_('short description'), max_length=1000, default='', blank=True, null=True) - address = models.CharField(_('address'), max_length=254) + logo = models.ImageField( + upload_to="club_logos", verbose_name=_("logo"), null=True, blank=True + ) + is_active = models.BooleanField(_("is active"), default=True) + short_description = models.CharField( + _("short description"), max_length=1000, default="", blank=True, null=True + ) + address = models.CharField(_("address"), max_length=254) # This function prevents generating migration upon settings change - def get_default_owner_group(): return settings.SITH_GROUP_ROOT_ID - owner_group = models.ForeignKey(Group, related_name="owned_club", default=get_default_owner_group) - edit_groups = models.ManyToManyField(Group, related_name="editable_club", blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_club", blank=True) - home = models.OneToOneField(SithFile, related_name='home_of_club', verbose_name=_("home"), null=True, blank=True, - on_delete=models.SET_NULL) + def get_default_owner_group(): + return settings.SITH_GROUP_ROOT_ID + + owner_group = models.ForeignKey( + Group, related_name="owned_club", default=get_default_owner_group + ) + edit_groups = models.ManyToManyField( + Group, related_name="editable_club", blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_club", blank=True + ) + home = models.OneToOneField( + SithFile, + related_name="home_of_club", + verbose_name=_("home"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) page = models.OneToOneField(Page, related_name="club", blank=True, null=True) class Meta: - ordering = ['name', 'unix_name'] + ordering = ["name", "unix_name"] @cached_property def president(self): - return self.members.filter(role=settings.SITH_CLUB_ROLES_ID['President'], end_date=None).first() + return self.members.filter( + role=settings.SITH_CLUB_ROLES_ID["President"], end_date=None + ).first() def check_loop(self): """Raise a validation error when a loop is found within the parent list""" @@ -84,7 +108,7 @@ class Club(models.Model): cur = self while cur.parent is not None: if cur in objs: - raise ValidationError(_('You can not make loops in clubs')) + raise ValidationError(_("You can not make loops in clubs")) objs.append(cur) cur = cur.parent @@ -130,7 +154,12 @@ class Club(models.Model): self.page.unset_lock() self.page.name = self.unix_name self.page.save(force_lock=True) - elif self.page and self.parent and self.parent.page and self.page.parent != self.parent.page: + elif ( + self.page + and self.parent + and self.parent.page + and self.page.parent != self.parent.page + ): self.page.unset_lock() self.page.parent = self.parent.page self.page.save(force_lock=True) @@ -150,7 +179,9 @@ class Club(models.Model): board.save() member = MetaGroup(name=self.unix_name + settings.SITH_MEMBER_SUFFIX) member.save() - subscribers = Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() + subscribers = Group.objects.filter( + name=settings.SITH_MAIN_MEMBERS_GROUP + ).first() self.make_home() self.home.edit_groups = [board] self.home.view_groups = [member, subscribers] @@ -161,7 +192,7 @@ class Club(models.Model): return self.name def get_absolute_url(self): - return reverse('club:club_view', kwargs={'club_id': self.id}) + return reverse("club:club_view", kwargs={"club_id": self.id}) def get_display_name(self): return self.name @@ -223,24 +254,48 @@ class Membership(models.Model): A User is currently member of all the Clubs where its Membership has an end_date set to null/None. Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past. """ - user = models.ForeignKey(User, verbose_name=_('user'), related_name="memberships", null=False, blank=False) - club = models.ForeignKey(Club, verbose_name=_('club'), related_name="members", null=False, blank=False) - start_date = models.DateField(_('start date'), default=timezone.now) - end_date = models.DateField(_('end date'), null=True, blank=True) - role = models.IntegerField(_('role'), choices=sorted(settings.SITH_CLUB_ROLES.items()), - default=sorted(settings.SITH_CLUB_ROLES.items())[0][0]) - description = models.CharField(_('description'), max_length=128, null=False, blank=True) + + user = models.ForeignKey( + User, + verbose_name=_("user"), + related_name="memberships", + null=False, + blank=False, + ) + club = models.ForeignKey( + Club, verbose_name=_("club"), related_name="members", null=False, blank=False + ) + start_date = models.DateField(_("start date"), default=timezone.now) + end_date = models.DateField(_("end date"), null=True, blank=True) + role = models.IntegerField( + _("role"), + choices=sorted(settings.SITH_CLUB_ROLES.items()), + default=sorted(settings.SITH_CLUB_ROLES.items())[0][0], + ) + description = models.CharField( + _("description"), max_length=128, null=False, blank=True + ) def clean(self): sub = User.objects.filter(pk=self.user.pk).first() if sub is None or not sub.is_subscribed: - raise ValidationError(_('User must be subscriber to take part to a club')) - if Membership.objects.filter(user=self.user).filter(club=self.club).filter(end_date=None).exists(): - raise ValidationError(_('User is already member of that club')) + raise ValidationError(_("User must be subscriber to take part to a club")) + if ( + Membership.objects.filter(user=self.user) + .filter(club=self.club) + .filter(end_date=None) + .exists() + ): + raise ValidationError(_("User is already member of that club")) def __str__(self): - return self.club.name + ' - ' + self.user.username + ' - ' + str(settings.SITH_CLUB_ROLES[self.role]) + str( - " - " + str(_('past member')) if self.end_date is not None else "" + return ( + self.club.name + + " - " + + self.user.username + + " - " + + str(settings.SITH_CLUB_ROLES[self.role]) + + str(" - " + str(_("past member")) if self.end_date is not None else "") ) def is_owned_by(self, user): @@ -255,11 +310,13 @@ class Membership(models.Model): """ if user.memberships: ms = user.memberships.filter(club=self.club, end_date=None).first() - return (ms and ms.role >= self.role) or user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) + return (ms and ms.role >= self.role) or user.is_in_group( + settings.SITH_MAIN_BOARD_GROUP + ) return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) def get_absolute_url(self): - return reverse('club:club_members', kwargs={'club_id': self.club.id}) + return reverse("club:club_members", kwargs={"club_id": self.club.id}) class Mailing(models.Model): @@ -267,14 +324,27 @@ class Mailing(models.Model): This class correspond to a mailing list Remember that mailing lists should be validated by UTBM """ - club = models.ForeignKey(Club, verbose_name=_('Club'), related_name="mailings", null=False, blank=False) - email = models.CharField(_('Email address'), unique=True, null=False, blank=False, max_length=256, - validators=[ - RegexValidator(validate_email.user_regex, - _('Enter a valid address. Only the root of the address is needed.')) - ]) - is_moderated = models.BooleanField(_('is moderated'), default=False) - moderator = models.ForeignKey(User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True) + + club = models.ForeignKey( + Club, verbose_name=_("Club"), related_name="mailings", null=False, blank=False + ) + email = models.CharField( + _("Email address"), + unique=True, + null=False, + blank=False, + max_length=256, + validators=[ + RegexValidator( + validate_email.user_regex, + _("Enter a valid address. Only the root of the address is needed."), + ) + ], + ) + is_moderated = models.BooleanField(_("is moderated"), default=False) + moderator = models.ForeignKey( + User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True + ) def clean(self): if self.can_moderate(self.moderator): @@ -285,13 +355,17 @@ class Mailing(models.Model): @property def email_full(self): - return self.email + '@' + settings.SITH_MAILING_DOMAIN + return self.email + "@" + settings.SITH_MAILING_DOMAIN def can_moderate(self, user): return user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) def is_owned_by(self, user): - return user.is_in_group(self) or user.is_root or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + return ( + user.is_in_group(self) + or user.is_root + or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + ) def can_view(self, user): return self.club.has_rights_in_club(user) @@ -305,16 +379,26 @@ class Mailing(models.Model): super(Mailing, self).delete() def fetch_format(self): - resp = self.email + ': ' + resp = self.email + ": " for sub in self.subscriptions.all(): resp += sub.fetch_format() return resp def save(self): if not self.is_moderated: - for user in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not user.notifications.filter(type="MAILING_MODERATION", viewed=False).exists(): - Notification(user=user, url=reverse('com:mailing_admin'), type="MAILING_MODERATION").save() + for user in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not user.notifications.filter( + type="MAILING_MODERATION", viewed=False + ).exists(): + Notification( + user=user, + url=reverse("com:mailing_admin"), + type="MAILING_MODERATION", + ).save() super(Mailing, self).save() def __str__(self): @@ -325,12 +409,25 @@ class MailingSubscription(models.Model): """ This class makes the link between user and mailing list """ - mailing = models.ForeignKey(Mailing, verbose_name=_('Mailing'), related_name="subscriptions", null=False, blank=False) - user = models.ForeignKey(User, verbose_name=_('User'), related_name="mailing_subscriptions", null=True, blank=True) - email = models.EmailField(_('Email address'), blank=False, null=False) + + mailing = models.ForeignKey( + Mailing, + verbose_name=_("Mailing"), + related_name="subscriptions", + null=False, + blank=False, + ) + user = models.ForeignKey( + User, + verbose_name=_("User"), + related_name="mailing_subscriptions", + null=True, + blank=True, + ) + email = models.EmailField(_("Email address"), blank=False, null=False) class Meta: - unique_together = (('user', 'email', 'mailing'),) + unique_together = (("user", "email", "mailing"),) def clean(self): if not self.user and not self.email: @@ -338,17 +435,25 @@ class MailingSubscription(models.Model): try: if self.user and not self.email: self.email = self.user.email - if MailingSubscription.objects.filter(mailing=self.mailing, email=self.email).exists(): - raise ValidationError(_("This email is already suscribed in this mailing")) + if MailingSubscription.objects.filter( + mailing=self.mailing, email=self.email + ).exists(): + raise ValidationError( + _("This email is already suscribed in this mailing") + ) except ObjectDoesNotExist: pass super(MailingSubscription, self).clean() def is_owned_by(self, user): - return self.mailing.club.has_rights_in_club(user) or user.is_root or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + return ( + self.mailing.club.has_rights_in_club(user) + or user.is_root + or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + ) def can_be_edited_by(self, user): - return (self.user is not None and user.id == self.user.id) + return self.user is not None and user.id == self.user.id @property def get_email(self): @@ -357,7 +462,7 @@ class MailingSubscription(models.Model): return self.email def fetch_format(self): - return self.get_email + ' ' + return self.get_email + " " def __str__(self): if self.user: diff --git a/club/tests.py b/club/tests.py index 87a2009f..c0409a9f 100644 --- a/club/tests.py +++ b/club/tests.py @@ -41,66 +41,92 @@ class ClubTest(TestCase): self.bdf = Club.objects.filter(unix_name="bdf").first() def test_create_add_user_to_club_from_root_ok(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 3}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 3}, + ) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) self.assertTrue(response.status_code == 200) - self.assertTrue("S' Kia\\n Responsable info" in str(response.content)) + self.assertTrue( + "S' Kia\\n Responsable info" + in str(response.content) + ) def test_create_add_user_to_club_from_root_fail_not_subscriber(self): - self.client.login(username='root', password='plop') - response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.guy.id, - "start_date": "12/06/2016", - "role": 3}) + self.client.login(username="root", password="plop") + response = self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.guy.id, "start_date": "12/06/2016", "role": 3}, + ) self.assertTrue(response.status_code == 200) self.assertTrue('
  • ' in str(response.content)) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) - self.assertFalse("Guy Carlier\\n Responsable info" in str(response.content)) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) + self.assertFalse( + "Guy Carlier\\n Responsable info" + in str(response.content) + ) def test_create_add_user_to_club_from_root_fail_already_in_club(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 3}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) - self.assertTrue("S' Kia\\n Responsable info" in str(response.content)) - response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 4}) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 3}, + ) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) + self.assertTrue( + "S' Kia\\n Responsable info" + in str(response.content) + ) + response = self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 4}, + ) self.assertTrue(response.status_code == 200) - self.assertFalse("S' Kia\\n Secrétaire" in str(response.content)) + self.assertFalse( + "S' Kia\\n Secrétaire" + in str(response.content) + ) def test_create_add_user_to_club_from_skia_ok(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 10}) - self.client.login(username='skia', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.rbatsbak.id, - "start_date": "12/06/2016", - "role": 9}) - response = self.client.get(reverse("club:club_members", kwargs={"club_id": self.bdf.id})) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 10}, + ) + self.client.login(username="skia", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 9}, + ) + response = self.client.get( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}) + ) self.assertTrue(response.status_code == 200) - self.assertTrue("""Richard Batsbak\\n Vice-Pr\\xc3\\xa9sident""" in str(response.content)) + self.assertTrue( + """Richard Batsbak\\n Vice-Pr\\xc3\\xa9sident""" + in str(response.content) + ) def test_create_add_user_to_club_from_richard_fail(self): - self.client.login(username='root', password='plop') - self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.rbatsbak.id, - "start_date": "12/06/2016", - "role": 3}) - self.client.login(username='rbatsbak', password='plop') - response = self.client.post(reverse("club:club_members", kwargs={"club_id": self.bdf.id}), { - "user": self.skia.id, - "start_date": "12/06/2016", - "role": 10}) + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3}, + ) + self.client.login(username="rbatsbak", password="plop") + response = self.client.post( + reverse("club:club_members", kwargs={"club_id": self.bdf.id}), + {"user": self.skia.id, "start_date": "12/06/2016", "role": 10}, + ) self.assertTrue(response.status_code == 200) - self.assertTrue("
  • Vous n'avez pas la permission de faire cela
  • " in str(response.content)) + self.assertTrue( + "
  • Vous n'avez pas la permission de faire cela
  • " + in str(response.content) + ) diff --git a/club/urls.py b/club/urls.py index 4d41e120..8f446d65 100644 --- a/club/urls.py +++ b/club/urls.py @@ -28,30 +28,96 @@ from django.conf.urls import url from club.views import * urlpatterns = [ - url(r'^$', ClubListView.as_view(), name='club_list'), - url(r'^new$', ClubCreateView.as_view(), name='club_new'), - url(r'^stats$', ClubStatView.as_view(), name='club_stats'), - url(r'^(?P[0-9]+)/$', ClubView.as_view(), name='club_view'), - url(r'^(?P[0-9]+)/rev/(?P[0-9]+)/$', ClubRevView.as_view(), name='club_view_rev'), - url(r'^(?P[0-9]+)/hist$', ClubPageHistView.as_view(), name='club_hist'), - url(r'^(?P[0-9]+)/edit$', ClubEditView.as_view(), name='club_edit'), - url(r'^(?P[0-9]+)/edit/page$', ClubPageEditView.as_view(), name='club_edit_page'), - url(r'^(?P[0-9]+)/members$', ClubMembersView.as_view(), name='club_members'), - url(r'^(?P[0-9]+)/elderlies$', ClubOldMembersView.as_view(), name='club_old_members'), - url(r'^(?P[0-9]+)/sellings$', ClubSellingView.as_view(), name='club_sellings'), - url(r'^(?P[0-9]+)/sellings/csv$', ClubSellingCSVView.as_view(), name='sellings_csv'), - url(r'^(?P[0-9]+)/prop$', ClubEditPropView.as_view(), name='club_prop'), - url(r'^(?P[0-9]+)/tools$', ClubToolsView.as_view(), name='tools'), - url(r'^(?P[0-9]+)/mailing$', ClubMailingView.as_view(action="display"), name='mailing'), - url(r'^(?P[0-9]+)/mailing/new/mailing$', ClubMailingView.as_view(action="add_mailing"), name='mailing_create'), - url(r'^(?P[0-9]+)/mailing/new/subscription$', ClubMailingView.as_view(action="add_member"), name='mailing_subscription_create'), - url(r'^(?P[0-9]+)/mailing/generate$', MailingAutoGenerationView.as_view(), name='mailing_generate'), - url(r'^(?P[0-9]+)/mailing/clean$', MailingAutoCleanView.as_view(), name='mailing_clean'), - url(r'^(?P[0-9]+)/mailing/delete$', MailingDeleteView.as_view(), name='mailing_delete'), - url(r'^(?P[0-9]+)/mailing/delete/subscription$', MailingSubscriptionDeleteView.as_view(), name='mailing_subscription_delete'), - url(r'^membership/(?P[0-9]+)/set_old$', MembershipSetOldView.as_view(), name='membership_set_old'), - url(r'^(?P[0-9]+)/poster$', PosterListView.as_view(), name='poster_list'), - url(r'^(?P[0-9]+)/poster/create$', PosterCreateView.as_view(), name='poster_create'), - url(r'^(?P[0-9]+)/poster/(?P[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), - url(r'^(?P[0-9]+)/poster/(?P[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), + url(r"^$", ClubListView.as_view(), name="club_list"), + url(r"^new$", ClubCreateView.as_view(), name="club_new"), + url(r"^stats$", ClubStatView.as_view(), name="club_stats"), + url(r"^(?P[0-9]+)/$", ClubView.as_view(), name="club_view"), + url( + r"^(?P[0-9]+)/rev/(?P[0-9]+)/$", + ClubRevView.as_view(), + name="club_view_rev", + ), + url(r"^(?P[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"), + url(r"^(?P[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"), + url( + r"^(?P[0-9]+)/edit/page$", + ClubPageEditView.as_view(), + name="club_edit_page", + ), + url( + r"^(?P[0-9]+)/members$", ClubMembersView.as_view(), name="club_members" + ), + url( + r"^(?P[0-9]+)/elderlies$", + ClubOldMembersView.as_view(), + name="club_old_members", + ), + url( + r"^(?P[0-9]+)/sellings$", + ClubSellingView.as_view(), + name="club_sellings", + ), + url( + r"^(?P[0-9]+)/sellings/csv$", + ClubSellingCSVView.as_view(), + name="sellings_csv", + ), + url(r"^(?P[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"), + url(r"^(?P[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"), + url( + r"^(?P[0-9]+)/mailing$", + ClubMailingView.as_view(action="display"), + name="mailing", + ), + url( + r"^(?P[0-9]+)/mailing/new/mailing$", + ClubMailingView.as_view(action="add_mailing"), + name="mailing_create", + ), + url( + r"^(?P[0-9]+)/mailing/new/subscription$", + ClubMailingView.as_view(action="add_member"), + name="mailing_subscription_create", + ), + url( + r"^(?P[0-9]+)/mailing/generate$", + MailingAutoGenerationView.as_view(), + name="mailing_generate", + ), + url( + r"^(?P[0-9]+)/mailing/clean$", + MailingAutoCleanView.as_view(), + name="mailing_clean", + ), + url( + r"^(?P[0-9]+)/mailing/delete$", + MailingDeleteView.as_view(), + name="mailing_delete", + ), + url( + r"^(?P[0-9]+)/mailing/delete/subscription$", + MailingSubscriptionDeleteView.as_view(), + name="mailing_subscription_delete", + ), + url( + r"^membership/(?P[0-9]+)/set_old$", + MembershipSetOldView.as_view(), + name="membership_set_old", + ), + url(r"^(?P[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"), + url( + r"^(?P[0-9]+)/poster/create$", + PosterCreateView.as_view(), + name="poster_create", + ), + url( + r"^(?P[0-9]+)/poster/(?P[0-9]+)/edit$", + PosterEditView.as_view(), + name="poster_edit", + ), + url( + r"^(?P[0-9]+)/poster/(?P[0-9]+)/delete$", + PosterDeleteView.as_view(), + name="poster_delete", + ), ] diff --git a/com/__init__.py b/com/__init__.py index 0a9419f8..0ace29c4 100644 --- a/com/__init__.py +++ b/com/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/com/admin.py b/com/admin.py index df553be2..43ca4e1e 100644 --- a/com/admin.py +++ b/com/admin.py @@ -41,4 +41,3 @@ admin.site.register(News, NewsAdmin) admin.site.register(Weekmail, WeekmailAdmin) admin.site.register(Screen) admin.site.register(Poster) - diff --git a/com/migrations/0001_initial.py b/com/migrations/0001_initial.py index 0f4fe6fd..8edde739 100644 --- a/com/migrations/0001_initial.py +++ b/com/migrations/0001_initial.py @@ -6,17 +6,37 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Sith', + name="Sith", fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), - ('alert_msg', models.TextField(default='', verbose_name='alert message', blank=True)), - ('info_msg', models.TextField(default='', verbose_name='info message', blank=True)), - ('index_page', models.TextField(default='', verbose_name='index page', blank=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + auto_created=True, + serialize=False, + primary_key=True, + ), + ), + ( + "alert_msg", + models.TextField( + default="", verbose_name="alert message", blank=True + ), + ), + ( + "info_msg", + models.TextField( + default="", verbose_name="info message", blank=True + ), + ), + ( + "index_page", + models.TextField(default="", verbose_name="index page", blank=True), + ), ], - ), + ) ] diff --git a/com/migrations/0002_news_newsdate.py b/com/migrations/0002_news_newsdate.py index 8dbac143..27241539 100644 --- a/com/migrations/0002_news_newsdate.py +++ b/com/migrations/0002_news_newsdate.py @@ -8,33 +8,100 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('club', '0005_auto_20161120_1149'), + ("club", "0005_auto_20161120_1149"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('com', '0001_initial'), + ("com", "0001_initial"), ] operations = [ migrations.CreateModel( - name='News', + name="News", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('title', models.CharField(max_length=64, verbose_name='title')), - ('summary', models.TextField(verbose_name='summary')), - ('content', models.TextField(verbose_name='content')), - ('type', models.CharField(choices=[('NOTICE', 'Notice'), ('EVENT', 'Event'), ('WEEKLY', 'Weekly'), ('CALL', 'Call')], default='EVENT', max_length=16, verbose_name='type')), - ('is_moderated', models.BooleanField(default=False, verbose_name='is moderated')), - ('author', models.ForeignKey(related_name='owned_news', to=settings.AUTH_USER_MODEL, verbose_name='author')), - ('club', models.ForeignKey(related_name='news', to='club.Club', verbose_name='club')), - ('moderator', models.ForeignKey(related_name='moderated_news', null=True, to=settings.AUTH_USER_MODEL, verbose_name='moderator')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + auto_created=True, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=64, verbose_name="title")), + ("summary", models.TextField(verbose_name="summary")), + ("content", models.TextField(verbose_name="content")), + ( + "type", + models.CharField( + choices=[ + ("NOTICE", "Notice"), + ("EVENT", "Event"), + ("WEEKLY", "Weekly"), + ("CALL", "Call"), + ], + default="EVENT", + max_length=16, + verbose_name="type", + ), + ), + ( + "is_moderated", + models.BooleanField(default=False, verbose_name="is moderated"), + ), + ( + "author", + models.ForeignKey( + related_name="owned_news", + to=settings.AUTH_USER_MODEL, + verbose_name="author", + ), + ), + ( + "club", + models.ForeignKey( + related_name="news", to="club.Club", verbose_name="club" + ), + ), + ( + "moderator", + models.ForeignKey( + related_name="moderated_news", + null=True, + to=settings.AUTH_USER_MODEL, + verbose_name="moderator", + ), + ), ], ), migrations.CreateModel( - name='NewsDate', + name="NewsDate", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('start_date', models.DateTimeField(null=True, blank=True, verbose_name='start_date')), - ('end_date', models.DateTimeField(null=True, blank=True, verbose_name='end_date')), - ('news', models.ForeignKey(related_name='dates', to='com.News', verbose_name='news_date')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + auto_created=True, + verbose_name="ID", + ), + ), + ( + "start_date", + models.DateTimeField( + null=True, blank=True, verbose_name="start_date" + ), + ), + ( + "end_date", + models.DateTimeField( + null=True, blank=True, verbose_name="end_date" + ), + ), + ( + "news", + models.ForeignKey( + related_name="dates", to="com.News", verbose_name="news_date" + ), + ), ], ), ] diff --git a/com/migrations/0003_auto_20170115_2300.py b/com/migrations/0003_auto_20170115_2300.py index 660c7635..d8fd9771 100644 --- a/com/migrations/0003_auto_20170115_2300.py +++ b/com/migrations/0003_auto_20170115_2300.py @@ -8,42 +8,81 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('club', '0006_auto_20161229_0040'), + ("club", "0006_auto_20161229_0040"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('com', '0002_news_newsdate'), + ("com", "0002_news_newsdate"), ] operations = [ migrations.CreateModel( - name='Weekmail', + name="Weekmail", fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), - ('title', models.CharField(max_length=64, verbose_name='title', blank=True)), - ('intro', models.TextField(verbose_name='intro', blank=True)), - ('joke', models.TextField(verbose_name='joke', blank=True)), - ('protip', models.TextField(verbose_name='protip', blank=True)), - ('conclusion', models.TextField(verbose_name='conclusion', blank=True)), - ('sent', models.BooleanField(verbose_name='sent', default=False)), + ( + "id", + models.AutoField( + serialize=False, + primary_key=True, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "title", + models.CharField(max_length=64, verbose_name="title", blank=True), + ), + ("intro", models.TextField(verbose_name="intro", blank=True)), + ("joke", models.TextField(verbose_name="joke", blank=True)), + ("protip", models.TextField(verbose_name="protip", blank=True)), + ("conclusion", models.TextField(verbose_name="conclusion", blank=True)), + ("sent", models.BooleanField(verbose_name="sent", default=False)), ], - options={ - 'ordering': ['-id'], - }, + options={"ordering": ["-id"]}, ), migrations.CreateModel( - name='WeekmailArticle', + name="WeekmailArticle", fields=[ - ('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)), - ('title', models.CharField(max_length=64, verbose_name='title')), - ('content', models.TextField(verbose_name='content')), - ('rank', models.IntegerField(verbose_name='rank', default=-1)), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='author', related_name='owned_weekmail_articles')), - ('club', models.ForeignKey(to='club.Club', verbose_name='club', related_name='weekmail_articles')), - ('weekmail', models.ForeignKey(to='com.Weekmail', verbose_name='weekmail', related_name='articles', null=True)), + ( + "id", + models.AutoField( + serialize=False, + primary_key=True, + verbose_name="ID", + auto_created=True, + ), + ), + ("title", models.CharField(max_length=64, verbose_name="title")), + ("content", models.TextField(verbose_name="content")), + ("rank", models.IntegerField(verbose_name="rank", default=-1)), + ( + "author", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, + verbose_name="author", + related_name="owned_weekmail_articles", + ), + ), + ( + "club", + models.ForeignKey( + to="club.Club", + verbose_name="club", + related_name="weekmail_articles", + ), + ), + ( + "weekmail", + models.ForeignKey( + to="com.Weekmail", + verbose_name="weekmail", + related_name="articles", + null=True, + ), + ), ], ), migrations.AddField( - model_name='sith', - name='weekmail_destinations', - field=models.TextField(verbose_name='weekmail destinations', default=''), + model_name="sith", + name="weekmail_destinations", + field=models.TextField(verbose_name="weekmail destinations", default=""), ), ] diff --git a/com/migrations/0004_auto_20171221_1614.py b/com/migrations/0004_auto_20171221_1614.py index e8d17887..1ebd9b86 100644 --- a/com/migrations/0004_auto_20171221_1614.py +++ b/com/migrations/0004_auto_20171221_1614.py @@ -9,36 +9,78 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('club', '0010_auto_20170912_2028'), + ("club", "0010_auto_20170912_2028"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('com', '0003_auto_20170115_2300'), + ("com", "0003_auto_20170115_2300"), ] operations = [ migrations.CreateModel( - name='Poster', + name="Poster", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(verbose_name='name', max_length=128, default='')), - ('file', models.ImageField(verbose_name='file', upload_to='com/posters')), - ('date_begin', models.DateTimeField(default=django.utils.timezone.now)), - ('date_end', models.DateTimeField(blank=True, null=True)), - ('display_time', models.IntegerField(verbose_name='display time', default=30)), - ('is_moderated', models.BooleanField(verbose_name='is moderated', default=False)), - ('club', models.ForeignKey(verbose_name='club', related_name='posters', to='club.Club')), - ('moderator', models.ForeignKey(verbose_name='moderator', blank=True, null=True, related_name='moderated_posters', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ( + "name", + models.CharField(verbose_name="name", max_length=128, default=""), + ), + ( + "file", + models.ImageField(verbose_name="file", upload_to="com/posters"), + ), + ("date_begin", models.DateTimeField(default=django.utils.timezone.now)), + ("date_end", models.DateTimeField(blank=True, null=True)), + ( + "display_time", + models.IntegerField(verbose_name="display time", default=30), + ), + ( + "is_moderated", + models.BooleanField(verbose_name="is moderated", default=False), + ), + ( + "club", + models.ForeignKey( + verbose_name="club", related_name="posters", to="club.Club" + ), + ), + ( + "moderator", + models.ForeignKey( + verbose_name="moderator", + blank=True, + null=True, + related_name="moderated_posters", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='Screen', + name="Screen", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(verbose_name='name', max_length=128)), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(verbose_name="name", max_length=128)), ], ), migrations.AddField( - model_name='poster', - name='screens', - field=models.ManyToManyField(related_name='posters', to='com.Screen'), + model_name="poster", + name="screens", + field=models.ManyToManyField(related_name="posters", to="com.Screen"), ), ] diff --git a/com/migrations/0005_auto_20180318_2227.py b/com/migrations/0005_auto_20180318_2227.py index 3b0cb351..9c6a3711 100644 --- a/com/migrations/0005_auto_20180318_2227.py +++ b/com/migrations/0005_auto_20180318_2227.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('com', '0004_auto_20171221_1614'), - ] + dependencies = [("com", "0004_auto_20171221_1614")] operations = [ migrations.AlterField( - model_name='poster', - name='display_time', - field=models.IntegerField(verbose_name='display time', default=15), - ), + model_name="poster", + name="display_time", + field=models.IntegerField(verbose_name="display time", default=15), + ) ] diff --git a/com/models.py b/com/models.py index 1b39563a..9f2065ca 100644 --- a/com/models.py +++ b/com/models.py @@ -40,10 +40,9 @@ from core.models import User, Preferences, RealGroup, Notification, SithFile from club.models import Club - - class Sith(models.Model): """A one instance class storing all the modifiable infos""" + alert_msg = models.TextField(_("alert message"), default="", blank=True) info_msg = models.TextField(_("info message"), default="", blank=True) index_page = models.TextField(_("index page"), default="", blank=True) @@ -57,23 +56,30 @@ class Sith(models.Model): NEWS_TYPES = [ - ('NOTICE', _('Notice')), - ('EVENT', _('Event')), - ('WEEKLY', _('Weekly')), - ('CALL', _('Call')), + ("NOTICE", _("Notice")), + ("EVENT", _("Event")), + ("WEEKLY", _("Weekly")), + ("CALL", _("Call")), ] class News(models.Model): """The news class""" + title = models.CharField(_("title"), max_length=64) summary = models.TextField(_("summary")) content = models.TextField(_("content")) - type = models.CharField(_("type"), max_length=16, choices=NEWS_TYPES, default="EVENT") + type = models.CharField( + _("type"), max_length=16, choices=NEWS_TYPES, default="EVENT" + ) club = models.ForeignKey(Club, related_name="news", verbose_name=_("club")) - author = models.ForeignKey(User, related_name="owned_news", verbose_name=_("author")) + author = models.ForeignKey( + User, related_name="owned_news", verbose_name=_("author") + ) is_moderated = models.BooleanField(_("is moderated"), default=False) - moderator = models.ForeignKey(User, related_name="moderated_news", verbose_name=_("moderator"), null=True) + moderator = models.ForeignKey( + User, related_name="moderated_news", verbose_name=_("moderator"), null=True + ) def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or user == self.author @@ -85,7 +91,7 @@ class News(models.Model): return self.is_moderated or user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) def get_absolute_url(self): - return reverse('com:news_detail', kwargs={'news_id': self.id}) + return reverse("com:news_detail", kwargs={"news_id": self.id}) def get_full_url(self): return "https://%s%s" % (settings.SITH_URL, self.get_absolute_url()) @@ -95,15 +101,28 @@ class News(models.Model): def save(self, *args, **kwargs): super(News, self).save(*args, **kwargs) - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - Notification(user=u, url=reverse("com:news_admin_list"), - type="NEWS_MODERATION", param="1").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("com:news_admin_list"), + type="NEWS_MODERATION", + param="1", + ).save() + def news_notification_callback(notif): - count = News.objects.filter( - Q(dates__start_date__gt=timezone.now(), is_moderated=False) | - Q(type="NOTICE", is_moderated=False) - ).distinct().count() + count = ( + News.objects.filter( + Q(dates__start_date__gt=timezone.now(), is_moderated=False) + | Q(type="NOTICE", is_moderated=False) + ) + .distinct() + .count() + ) if count: notif.viewed = False notif.param = "%s" % count @@ -111,6 +130,7 @@ def news_notification_callback(notif): else: notif.viewed = True + class NewsDate(models.Model): """ A date class, useful for weekly events, or for events that just have no date @@ -118,9 +138,10 @@ class NewsDate(models.Model): This class allows more flexibilty managing the dates related to a news, particularly when this news is weekly, since we don't have to make copies """ + news = models.ForeignKey(News, related_name="dates", verbose_name=_("news_date")) - start_date = models.DateTimeField(_('start_date'), null=True, blank=True) - end_date = models.DateTimeField(_('end_date'), null=True, blank=True) + start_date = models.DateTimeField(_("start_date"), null=True, blank=True) + end_date = models.DateTimeField(_("end_date"), null=True, blank=True) def __str__(self): return "%s: %s - %s" % (self.news.title, self.start_date, self.end_date) @@ -130,6 +151,7 @@ class Weekmail(models.Model): """ The weekmail class """ + title = models.CharField(_("title"), max_length=64, blank=True) intro = models.TextField(_("intro"), blank=True) joke = models.TextField(_("joke"), blank=True) @@ -138,16 +160,21 @@ class Weekmail(models.Model): sent = models.BooleanField(_("sent"), default=False) class Meta: - ordering = ['-id'] + ordering = ["-id"] def send(self): - dest = [i[0] for i in Preferences.objects.filter(receive_weekmail=True).values_list('user__email')] + dest = [ + i[0] + for i in Preferences.objects.filter(receive_weekmail=True).values_list( + "user__email" + ) + ] with transaction.atomic(): email = EmailMultiAlternatives( subject=self.title, body=self.render_text(), from_email=settings.SITH_COM_EMAIL, - to=Sith.objects.first().weekmail_destinations.split(' '), + to=Sith.objects.first().weekmail_destinations.split(" "), bcc=dest, ) email.attach_alternative(self.render_html(), "text/html") @@ -157,14 +184,14 @@ class Weekmail(models.Model): Weekmail().save() def render_text(self): - return render(None, "com/weekmail_renderer_text.jinja", context={ - 'weekmail': self, - }).content.decode('utf-8') + return render( + None, "com/weekmail_renderer_text.jinja", context={"weekmail": self} + ).content.decode("utf-8") def render_html(self): - return render(None, "com/weekmail_renderer_html.jinja", context={ - 'weekmail': self, - }).content.decode('utf-8') + return render( + None, "com/weekmail_renderer_html.jinja", context={"weekmail": self} + ).content.decode("utf-8") def get_banner(self): return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg") @@ -180,12 +207,18 @@ class Weekmail(models.Model): class WeekmailArticle(models.Model): - weekmail = models.ForeignKey(Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True) + weekmail = models.ForeignKey( + Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True + ) title = models.CharField(_("title"), max_length=64) content = models.TextField(_("content")) - author = models.ForeignKey(User, related_name="owned_weekmail_articles", verbose_name=_("author")) - club = models.ForeignKey(Club, related_name="weekmail_articles", verbose_name=_("club")) - rank = models.IntegerField(_('rank'), default=-1) + author = models.ForeignKey( + User, related_name="owned_weekmail_articles", verbose_name=_("author") + ) + club = models.ForeignKey( + Club, related_name="weekmail_articles", verbose_name=_("club") + ) + rank = models.IntegerField(_("rank"), default=-1) def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) @@ -199,7 +232,9 @@ class Screen(models.Model): def active_posters(self): now = timezone.now() - return self.posters.filter(is_moderated=True, date_begin__lte=now).filter(Q(date_end__isnull=True) | Q(date_end__gte=now)) + return self.posters.filter(is_moderated=True, date_begin__lte=now).filter( + Q(date_end__isnull=True) | Q(date_end__gte=now) + ) def is_owned_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) @@ -209,21 +244,40 @@ class Screen(models.Model): class Poster(models.Model): - name = models.CharField(_("name"), blank=False, null=False, max_length=128, default="") + name = models.CharField( + _("name"), blank=False, null=False, max_length=128, default="" + ) file = models.ImageField(_("file"), null=False, upload_to="com/posters") - club = models.ForeignKey(Club, related_name="posters", verbose_name=_("club"), null=False) + club = models.ForeignKey( + Club, related_name="posters", verbose_name=_("club"), null=False + ) screens = models.ManyToManyField(Screen, related_name="posters") date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now) date_end = models.DateTimeField(blank=True, null=True) - display_time = models.IntegerField(_("display time"), blank=False, null=False, default=15) + display_time = models.IntegerField( + _("display time"), blank=False, null=False, default=15 + ) is_moderated = models.BooleanField(_("is moderated"), default=False) - moderator = models.ForeignKey(User, related_name="moderated_posters", verbose_name=_("moderator"), null=True, blank=True) + moderator = models.ForeignKey( + User, + related_name="moderated_posters", + verbose_name=_("moderator"), + null=True, + blank=True, + ) def save(self, *args, **kwargs): if not self.is_moderated: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - Notification(user=u, url=reverse("com:poster_moderate_list"), - type="POSTER_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("com:poster_moderate_list"), + type="POSTER_MODERATION", + ).save() return super(Poster, self).save(*args, **kwargs) def clean(self, *args, **kwargs): @@ -231,7 +285,9 @@ class Poster(models.Model): raise ValidationError(_("Begin date should be before end date")) def is_owned_by(self, user): - return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or Club.objects.filter(id__in=user.clubs_with_rights) + return user.is_in_group( + settings.SITH_GROUP_COM_ADMIN_ID + ) or Club.objects.filter(id__in=user.clubs_with_rights) def can_be_moderated_by(self, user): return user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) diff --git a/com/tests.py b/com/tests.py index 4bcdcbdb..7272228a 100644 --- a/com/tests.py +++ b/com/tests.py @@ -34,25 +34,43 @@ class ComTest(TestCase): def setUp(self): call_command("populate") self.skia = User.objects.filter(username="skia").first() - self.com_group = RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first() + self.com_group = RealGroup.objects.filter( + id=settings.SITH_GROUP_COM_ADMIN_ID + ).first() self.skia.groups = [self.com_group] self.skia.save() - self.client.login(username=self.skia.username, password='plop') + self.client.login(username=self.skia.username, password="plop") def test_alert_msg(self): - response = self.client.post(reverse("com:alert_edit"), {"alert_msg": """ + response = self.client.post( + reverse("com:alert_edit"), + { + "alert_msg": """ ### ALERTE! **Caaaataaaapuuuulte!!!!** -"""}) +""" + }, + ) r = self.client.get(reverse("core:index")) self.assertTrue(r.status_code == 200) - self.assertTrue("""
    \\n

    ALERTE!

    \\n

    Caaaataaaapuuuulte!!!!

    """ in str(r.content)) + self.assertTrue( + """
    \\n

    ALERTE!

    \\n

    Caaaataaaapuuuulte!!!!

    """ + in str(r.content) + ) def test_info_msg(self): - response = self.client.post(reverse("com:info_edit"), {"info_msg": """ + response = self.client.post( + reverse("com:info_edit"), + { + "info_msg": """ ### INFO: **Caaaataaaapuuuulte!!!!** -"""}) +""" + }, + ) r = self.client.get(reverse("core:index")) self.assertTrue(r.status_code == 200) - self.assertTrue("""
    \\n

    INFO: Caaaataaaapuuuulte!!!!

    """ in str(r.content)) + self.assertTrue( + """
    \\n

    INFO: Caaaataaaapuuuulte!!!!

    """ + in str(r.content) + ) diff --git a/com/urls.py b/com/urls.py index 9f5b0d97..380ea8b1 100644 --- a/com/urls.py +++ b/com/urls.py @@ -28,35 +28,94 @@ from com.views import * from club.views import MailingDeleteView urlpatterns = [ - url(r'^sith/edit/alert$', AlertMsgEditView.as_view(), name='alert_edit'), - url(r'^sith/edit/info$', InfoMsgEditView.as_view(), name='info_edit'), - url(r'^sith/edit/index$', IndexEditView.as_view(), name='index_edit'), - url(r'^sith/edit/weekmail_destinations$', WeekmailDestinationEditView.as_view(), name='weekmail_destinations'), - url(r'^weekmail$', WeekmailEditView.as_view(), name='weekmail'), - url(r'^weekmail/preview$', WeekmailPreviewView.as_view(), name='weekmail_preview'), - url(r'^weekmail/new_article$', WeekmailArticleCreateView.as_view(), name='weekmail_article'), - url(r'^weekmail/article/(?P[0-9]+)/delete$', WeekmailArticleDeleteView.as_view(), name='weekmail_article_delete'), - url(r'^weekmail/article/(?P[0-9]+)/edit$', WeekmailArticleEditView.as_view(), name='weekmail_article_edit'), - url(r'^news$', NewsListView.as_view(), name='news_list'), - url(r'^news/admin$', NewsAdminListView.as_view(), name='news_admin_list'), - url(r'^news/create$', NewsCreateView.as_view(), name='news_new'), - url(r'^news/(?P[0-9]+)/delete$', NewsDeleteView.as_view(), name='news_delete'), - url(r'^news/(?P[0-9]+)/moderate$', NewsModerateView.as_view(), name='news_moderate'), - url(r'^news/(?P[0-9]+)/edit$', NewsEditView.as_view(), name='news_edit'), - url(r'^news/(?P[0-9]+)$', NewsDetailView.as_view(), name='news_detail'), - url(r'^mailings$', MailingListAdminView.as_view(), name='mailing_admin'), - url(r'^mailings/(?P[0-9]+)/moderate$', MailingModerateView.as_view(), name='mailing_moderate'), - url(r'^mailings/(?P[0-9]+)/delete$', MailingDeleteView.as_view(redirect_page='com:mailing_admin'), name='mailing_delete'), - url(r'^poster$', PosterListView.as_view(), name='poster_list'), - url(r'^poster/create$', PosterCreateView.as_view(), name='poster_create'), - url(r'^poster/(?P[0-9]+)/edit$', PosterEditView.as_view(), name='poster_edit'), - url(r'^poster/(?P[0-9]+)/delete$', PosterDeleteView.as_view(), name='poster_delete'), - url(r'^poster/moderate$', PosterModerateListView.as_view(), name='poster_moderate_list'), - url(r'^poster/(?P[0-9]+)/moderate$', PosterModerateView.as_view(), name='poster_moderate'), - url(r'^screen$', ScreenListView.as_view(), name='screen_list'), - url(r'^screen/create$', ScreenCreateView.as_view(), name='screen_create'), - url(r'^screen/(?P[0-9]+)/slideshow$', ScreenSlideshowView.as_view(), name='screen_slideshow'), - url(r'^screen/(?P[0-9]+)/edit$', ScreenEditView.as_view(), name='screen_edit'), - url(r'^screen/(?P[0-9]+)/delete$', ScreenDeleteView.as_view(), name='screen_delete'), + url(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"), + url(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"), + url(r"^sith/edit/index$", IndexEditView.as_view(), name="index_edit"), + url( + r"^sith/edit/weekmail_destinations$", + WeekmailDestinationEditView.as_view(), + name="weekmail_destinations", + ), + url(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"), + url(r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"), + url( + r"^weekmail/new_article$", + WeekmailArticleCreateView.as_view(), + name="weekmail_article", + ), + url( + r"^weekmail/article/(?P[0-9]+)/delete$", + WeekmailArticleDeleteView.as_view(), + name="weekmail_article_delete", + ), + url( + r"^weekmail/article/(?P[0-9]+)/edit$", + WeekmailArticleEditView.as_view(), + name="weekmail_article_edit", + ), + url(r"^news$", NewsListView.as_view(), name="news_list"), + url(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"), + url(r"^news/create$", NewsCreateView.as_view(), name="news_new"), + url( + r"^news/(?P[0-9]+)/delete$", + NewsDeleteView.as_view(), + name="news_delete", + ), + url( + r"^news/(?P[0-9]+)/moderate$", + NewsModerateView.as_view(), + name="news_moderate", + ), + url(r"^news/(?P[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"), + url(r"^news/(?P[0-9]+)$", NewsDetailView.as_view(), name="news_detail"), + url(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"), + url( + r"^mailings/(?P[0-9]+)/moderate$", + MailingModerateView.as_view(), + name="mailing_moderate", + ), + url( + r"^mailings/(?P[0-9]+)/delete$", + MailingDeleteView.as_view(redirect_page="com:mailing_admin"), + name="mailing_delete", + ), + url(r"^poster$", PosterListView.as_view(), name="poster_list"), + url(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"), + url( + r"^poster/(?P[0-9]+)/edit$", + PosterEditView.as_view(), + name="poster_edit", + ), + url( + r"^poster/(?P[0-9]+)/delete$", + PosterDeleteView.as_view(), + name="poster_delete", + ), + url( + r"^poster/moderate$", + PosterModerateListView.as_view(), + name="poster_moderate_list", + ), + url( + r"^poster/(?P[0-9]+)/moderate$", + PosterModerateView.as_view(), + name="poster_moderate", + ), + url(r"^screen$", ScreenListView.as_view(), name="screen_list"), + url(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"), + url( + r"^screen/(?P[0-9]+)/slideshow$", + ScreenSlideshowView.as_view(), + name="screen_slideshow", + ), + url( + r"^screen/(?P[0-9]+)/edit$", + ScreenEditView.as_view(), + name="screen_edit", + ), + url( + r"^screen/(?P[0-9]+)/delete$", + ScreenDeleteView.as_view(), + name="screen_delete", + ), ] - diff --git a/com/views.py b/com/views.py index b4313167..c0fd10c0 100644 --- a/com/views.py +++ b/com/views.py @@ -41,7 +41,14 @@ from django import forms from datetime import timedelta from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + CanCreateMixin, + QuickNotifMixin, +) from core.views.forms import SelectDateTime from core.models import Notification, RealGroup, User from club.models import Club, Mailing @@ -55,23 +62,40 @@ sith = Sith.objects.first class PosterForm(forms.ModelForm): class Meta: model = Poster - fields = ['name', 'file', 'club', 'screens', 'date_begin', 'date_end', 'display_time'] - widgets = { - 'screens': forms.CheckboxSelectMultiple, - } + fields = [ + "name", + "file", + "club", + "screens", + "date_begin", + "date_end", + "display_time", + ] + widgets = {"screens": forms.CheckboxSelectMultiple} - date_begin = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), - widget=SelectDateTime, required=True, initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S")) - date_end = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), - widget=SelectDateTime, required=False) + date_begin = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=True, + initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"), + ) + date_end = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + widget=SelectDateTime, + required=False, + ) def __init__(self, *args, **kwargs): - self.user = kwargs.pop('user', None) + self.user = kwargs.pop("user", None) super(PosterForm, self).__init__(*args, **kwargs) if self.user: if not self.user.is_com_admin: - self.fields['club'].queryset = Club.objects.filter(id__in=self.user.clubs_with_rights) - self.fields.pop('display_time') + self.fields["club"].queryset = Club.objects.filter( + id__in=self.user.clubs_with_rights + ) + self.fields.pop("display_time") class ComTabsMixin(TabedViewMixin): @@ -80,51 +104,54 @@ class ComTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('com:weekmail'), - 'slug': 'weekmail', - 'name': _("Weekmail"), - }) - tab_list.append({ - 'url': reverse('com:weekmail_destinations'), - 'slug': 'weekmail_destinations', - 'name': _("Weekmail destinations"), - }) - tab_list.append({ - 'url': reverse('com:index_edit'), - 'slug': 'index', - 'name': _("Index page"), - }) - tab_list.append({ - 'url': reverse('com:info_edit'), - 'slug': 'info', - 'name': _("Info message"), - }) - tab_list.append({ - 'url': reverse('com:alert_edit'), - 'slug': 'alert', - 'name': _("Alert message"), - }) - tab_list.append({ - 'url': reverse('com:mailing_admin'), - 'slug': 'mailings', - 'name': _("Mailing lists administration"), - }) - tab_list.append({ - 'url': reverse('com:poster_list'), - 'slug': 'posters', - 'name': _("Posters list"), - }) - tab_list.append({ - 'url': reverse('com:screen_list'), - 'slug': 'screens', - 'name': _("Screens list"), - }) + tab_list.append( + {"url": reverse("com:weekmail"), "slug": "weekmail", "name": _("Weekmail")} + ) + tab_list.append( + { + "url": reverse("com:weekmail_destinations"), + "slug": "weekmail_destinations", + "name": _("Weekmail destinations"), + } + ) + tab_list.append( + {"url": reverse("com:index_edit"), "slug": "index", "name": _("Index page")} + ) + tab_list.append( + {"url": reverse("com:info_edit"), "slug": "info", "name": _("Info message")} + ) + tab_list.append( + { + "url": reverse("com:alert_edit"), + "slug": "alert", + "name": _("Alert message"), + } + ) + tab_list.append( + { + "url": reverse("com:mailing_admin"), + "slug": "mailings", + "name": _("Mailing lists administration"), + } + ) + tab_list.append( + { + "url": reverse("com:poster_list"), + "slug": "posters", + "name": _("Posters list"), + } + ) + tab_list.append( + { + "url": reverse("com:screen_list"), + "slug": "screens", + "name": _("Screens list"), + } + ) return tab_list class IsComAdminMixin(View): - def dispatch(self, request, *args, **kwargs): if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)): raise PermissionDenied @@ -133,34 +160,35 @@ class IsComAdminMixin(View): class ComEditView(ComTabsMixin, CanEditPropMixin, UpdateView): model = Sith - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" def get_object(self, queryset=None): return Sith.objects.first() class AlertMsgEditView(ComEditView): - fields = ['alert_msg'] + fields = ["alert_msg"] current_tab = "alert" - success_url = reverse_lazy('com:alert_edit') + success_url = reverse_lazy("com:alert_edit") class InfoMsgEditView(ComEditView): - fields = ['info_msg'] + fields = ["info_msg"] current_tab = "info" - success_url = reverse_lazy('com:info_edit') + success_url = reverse_lazy("com:info_edit") class IndexEditView(ComEditView): - fields = ['index_page'] + fields = ["index_page"] current_tab = "index" - success_url = reverse_lazy('com:index_edit') + success_url = reverse_lazy("com:index_edit") class WeekmailDestinationEditView(ComEditView): - fields = ['weekmail_destinations'] + fields = ["weekmail_destinations"] current_tab = "weekmail_destinations" - success_url = reverse_lazy('com:weekmail_destinations') + success_url = reverse_lazy("com:weekmail_destinations") + # News @@ -168,43 +196,64 @@ class WeekmailDestinationEditView(ComEditView): class NewsForm(forms.ModelForm): class Meta: model = News - fields = ['type', 'title', 'club', 'summary', 'content', 'author'] - widgets = { - 'author': forms.HiddenInput, - 'type': forms.RadioSelect, - } - start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), widget=SelectDateTime, required=False) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), widget=SelectDateTime, required=False) - until = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Until"), widget=SelectDateTime, required=False) + fields = ["type", "title", "club", "summary", "content", "author"] + widgets = {"author": forms.HiddenInput, "type": forms.RadioSelect} + + start_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=False, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + widget=SelectDateTime, + required=False, + ) + until = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], label=_("Until"), widget=SelectDateTime, required=False + ) automoderation = forms.BooleanField(label=_("Automoderation"), required=False) def clean(self): self.cleaned_data = super(NewsForm, self).clean() - if self.cleaned_data['type'] != "NOTICE": - if not self.cleaned_data['start_date']: - self.add_error('start_date', ValidationError(_("This field is required."))) - if not self.cleaned_data['end_date']: - self.add_error('end_date', ValidationError(_("This field is required."))) - if self.cleaned_data['start_date'] > self.cleaned_data['end_date']: - self.add_error('end_date', ValidationError(_("You crazy? You can not finish an event before starting it."))) - if self.cleaned_data['type'] == "WEEKLY" and not self.cleaned_data['until']: - self.add_error('until', ValidationError(_("This field is required."))) + if self.cleaned_data["type"] != "NOTICE": + if not self.cleaned_data["start_date"]: + self.add_error( + "start_date", ValidationError(_("This field is required.")) + ) + if not self.cleaned_data["end_date"]: + self.add_error( + "end_date", ValidationError(_("This field is required.")) + ) + if self.cleaned_data["start_date"] > self.cleaned_data["end_date"]: + self.add_error( + "end_date", + ValidationError( + _("You crazy? You can not finish an event before starting it.") + ), + ) + if self.cleaned_data["type"] == "WEEKLY" and not self.cleaned_data["until"]: + self.add_error("until", ValidationError(_("This field is required."))) return self.cleaned_data def save(self): ret = super(NewsForm, self).save() self.instance.dates.all().delete() if self.instance.type == "EVENT" or self.instance.type == "CALL": - NewsDate(start_date=self.cleaned_data['start_date'], - end_date=self.cleaned_data['end_date'], - news=self.instance).save() + NewsDate( + start_date=self.cleaned_data["start_date"], + end_date=self.cleaned_data["end_date"], + news=self.instance, + ).save() elif self.instance.type == "WEEKLY": - start_date = self.cleaned_data['start_date'] - end_date = self.cleaned_data['end_date'] - while start_date <= self.cleaned_data['until']: - NewsDate(start_date=start_date, - end_date=end_date, - news=self.instance).save() + start_date = self.cleaned_data["start_date"] + end_date = self.cleaned_data["end_date"] + while start_date <= self.cleaned_data["until"]: + NewsDate( + start_date=start_date, end_date=end_date, news=self.instance + ).save() start_date += timedelta(days=7) end_date += timedelta(days=7) return ret @@ -213,59 +262,81 @@ class NewsForm(forms.ModelForm): class NewsEditView(CanEditMixin, UpdateView): model = News form_class = NewsForm - template_name = 'com/news_edit.jinja' - pk_url_kwarg = 'news_id' + template_name = "com/news_edit.jinja" + pk_url_kwarg = "news_id" def get_initial(self): init = {} try: - init['start_date'] = self.object.dates.order_by('id').first().start_date.strftime('%Y-%m-%d %H:%M:%S') + init["start_date"] = ( + self.object.dates.order_by("id") + .first() + .start_date.strftime("%Y-%m-%d %H:%M:%S") + ) except: pass try: - init['end_date'] = self.object.dates.order_by('id').first().end_date.strftime('%Y-%m-%d %H:%M:%S') + init["end_date"] = ( + self.object.dates.order_by("id") + .first() + .end_date.strftime("%Y-%m-%d %H:%M:%S") + ) except: pass return init def post(self, request, *args, **kwargs): form = self.get_form() - if form.is_valid() and 'preview' not in request.POST.keys(): + if form.is_valid() and "preview" not in request.POST.keys(): return self.form_valid(form) else: return self.form_invalid(form) def form_valid(self, form): self.object = form.save() - if form.cleaned_data['automoderation'] and self.request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): + if form.cleaned_data["automoderation"] and self.request.user.is_in_group( + settings.SITH_GROUP_COM_ADMIN_ID + ): self.object.moderator = self.request.user self.object.is_moderated = True self.object.save() else: self.object.is_moderated = False self.object.save() - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not u.notifications.filter(type="NEWS_MODERATION", viewed=False).exists(): - Notification(user=u, url=reverse("com:news_detail", kwargs={'news_id': self.object.id}), type="NEWS_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not u.notifications.filter( + type="NEWS_MODERATION", viewed=False + ).exists(): + Notification( + user=u, + url=reverse( + "com:news_detail", kwargs={"news_id": self.object.id} + ), + type="NEWS_MODERATION", + ).save() return super(NewsEditView, self).form_valid(form) class NewsCreateView(CanCreateMixin, CreateView): model = News form_class = NewsForm - template_name = 'com/news_edit.jinja' + template_name = "com/news_edit.jinja" def get_initial(self): - init = {'author': self.request.user} + init = {"author": self.request.user} try: - init['club'] = Club.objects.filter(id=self.request.GET['club']).first() + init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() except: pass return init def post(self, request, *args, **kwargs): form = self.get_form() - if form.is_valid() and 'preview' not in request.POST.keys(): + if form.is_valid() and "preview" not in request.POST.keys(): return self.form_valid(form) else: self.object = form.instance @@ -273,176 +344,216 @@ class NewsCreateView(CanCreateMixin, CreateView): def form_valid(self, form): self.object = form.save() - if form.cleaned_data['automoderation'] and self.request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): + if form.cleaned_data["automoderation"] and self.request.user.is_in_group( + settings.SITH_GROUP_COM_ADMIN_ID + ): self.object.moderator = self.request.user self.object.is_moderated = True self.object.save() else: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not u.notifications.filter(type="NEWS_MODERATION", viewed=False).exists(): - Notification(user=u, url=reverse("com:news_admin_list"), type="NEWS_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not u.notifications.filter( + type="NEWS_MODERATION", viewed=False + ).exists(): + Notification( + user=u, + url=reverse("com:news_admin_list"), + type="NEWS_MODERATION", + ).save() return super(NewsCreateView, self).form_valid(form) class NewsDeleteView(CanEditMixin, DeleteView): model = News - pk_url_kwarg = 'news_id' - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('com:news_admin_list') + pk_url_kwarg = "news_id" + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("com:news_admin_list") class NewsModerateView(CanEditMixin, SingleObjectMixin): model = News - pk_url_kwarg = 'news_id' + pk_url_kwarg = "news_id" def get(self, request, *args, **kwargs): self.object = self.get_object() - if 'remove' in request.GET.keys(): + if "remove" in request.GET.keys(): self.object.is_moderated = False else: self.object.is_moderated = True self.object.moderator = request.user self.object.save() - if 'next' in self.request.GET.keys(): - return redirect(self.request.GET['next']) - return redirect('com:news_admin_list') + if "next" in self.request.GET.keys(): + return redirect(self.request.GET["next"]) + return redirect("com:news_admin_list") class NewsAdminListView(CanEditMixin, ListView): model = News - template_name = 'com/news_admin_list.jinja' + template_name = "com/news_admin_list.jinja" queryset = News.objects.all() class NewsListView(CanViewMixin, ListView): model = News - template_name = 'com/news_list.jinja' + template_name = "com/news_list.jinja" queryset = News.objects.filter(is_moderated=True) def get_context_data(self, **kwargs): kwargs = super(NewsListView, self).get_context_data(**kwargs) - kwargs['NewsDate'] = NewsDate - kwargs['timedelta'] = timedelta - kwargs['birthdays'] = User.objects\ - .filter(date_of_birth__month=timezone.now().month, date_of_birth__day=timezone.now().day)\ - .filter(role__in=['STUDENT', 'FORMER STUDENT'])\ - .order_by('-date_of_birth') + kwargs["NewsDate"] = NewsDate + kwargs["timedelta"] = timedelta + kwargs["birthdays"] = ( + User.objects.filter( + date_of_birth__month=timezone.now().month, + date_of_birth__day=timezone.now().day, + ) + .filter(role__in=["STUDENT", "FORMER STUDENT"]) + .order_by("-date_of_birth") + ) return kwargs class NewsDetailView(CanViewMixin, DetailView): model = News - template_name = 'com/news_detail.jinja' - pk_url_kwarg = 'news_id' + template_name = "com/news_detail.jinja" + pk_url_kwarg = "news_id" + # Weekmail class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView): model = Weekmail - template_name = 'com/weekmail_preview.jinja' - success_url = reverse_lazy('com:weekmail') + template_name = "com/weekmail_preview.jinja" + success_url = reverse_lazy("com:weekmail") current_tab = "weekmail" def post(self, request, *args, **kwargs): self.object = self.get_object() try: - if request.POST['send'] == "validate": + if request.POST["send"] == "validate": self.object.send() - return HttpResponseRedirect(reverse('com:weekmail') + "?qn_weekmail_send_success") + return HttpResponseRedirect( + reverse("com:weekmail") + "?qn_weekmail_send_success" + ) except: pass return super(WeekmailEditView, self).get(request, *args, **kwargs) def get_object(self, queryset=None): - return self.model.objects.filter(sent=False).order_by('-id').first() + return self.model.objects.filter(sent=False).order_by("-id").first() def get_context_data(self, **kwargs): """Add rendered weekmail""" kwargs = super(WeekmailPreviewView, self).get_context_data(**kwargs) - kwargs['weekmail_rendered'] = self.object.render_html() + kwargs["weekmail_rendered"] = self.object.render_html() return kwargs class WeekmailEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): model = Weekmail - template_name = 'com/weekmail.jinja' - form_class = modelform_factory(Weekmail, fields=['title', 'intro', 'joke', 'protip', 'conclusion'], - help_texts={'title': _("Delete and save to regenerate")}) - success_url = reverse_lazy('com:weekmail') + template_name = "com/weekmail.jinja" + form_class = modelform_factory( + Weekmail, + fields=["title", "intro", "joke", "protip", "conclusion"], + help_texts={"title": _("Delete and save to regenerate")}, + ) + success_url = reverse_lazy("com:weekmail") current_tab = "weekmail" def get_object(self, queryset=None): - weekmail = self.model.objects.filter(sent=False).order_by('-id').first() + weekmail = self.model.objects.filter(sent=False).order_by("-id").first() if not weekmail.title: now = timezone.now() - weekmail.title = _("Weekmail of the ") + (now + timedelta(days=6 - now.weekday())).strftime('%d/%m/%Y') + weekmail.title = _("Weekmail of the ") + ( + now + timedelta(days=6 - now.weekday()) + ).strftime("%d/%m/%Y") weekmail.save() return weekmail def get(self, request, *args, **kwargs): self.object = self.get_object() - if 'up_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['up_article'], weekmail=self.object) - prev_art = self.object.articles.order_by('rank').filter(rank__lt=art.rank).last() + if "up_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["up_article"], weekmail=self.object + ) + prev_art = ( + self.object.articles.order_by("rank").filter(rank__lt=art.rank).last() + ) if prev_art: art.rank, prev_art.rank = prev_art.rank, art.rank art.save() prev_art.save() - self.quick_notif_list += ['qn_success'] - if 'down_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['down_article'], weekmail=self.object) - next_art = self.object.articles.order_by('rank').filter(rank__gt=art.rank).first() + self.quick_notif_list += ["qn_success"] + if "down_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["down_article"], weekmail=self.object + ) + next_art = ( + self.object.articles.order_by("rank").filter(rank__gt=art.rank).first() + ) if next_art: art.rank, next_art.rank = next_art.rank, art.rank art.save() next_art.save() - self.quick_notif_list += ['qn_success'] - if 'add_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['add_article'], weekmail=None) + self.quick_notif_list += ["qn_success"] + if "add_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["add_article"], weekmail=None + ) art.weekmail = self.object - art.rank = self.object.articles.aggregate(Max('rank'))['rank__max'] or 0 + art.rank = self.object.articles.aggregate(Max("rank"))["rank__max"] or 0 art.rank += 1 art.save() - self.quick_notif_list += ['qn_success'] - if 'del_article' in request.GET.keys(): - art = get_object_or_404(WeekmailArticle, id=request.GET['del_article'], weekmail=self.object) + self.quick_notif_list += ["qn_success"] + if "del_article" in request.GET.keys(): + art = get_object_or_404( + WeekmailArticle, id=request.GET["del_article"], weekmail=self.object + ) art.weekmail = None art.rank = -1 art.save() - self.quick_notif_list += ['qn_success'] + self.quick_notif_list += ["qn_success"] return super(WeekmailEditView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): """Add orphan articles """ kwargs = super(WeekmailEditView, self).get_context_data(**kwargs) - kwargs['orphans'] = WeekmailArticle.objects.filter(weekmail=None) + kwargs["orphans"] = WeekmailArticle.objects.filter(weekmail=None) return kwargs -class WeekmailArticleEditView(ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView): +class WeekmailArticleEditView( + ComTabsMixin, QuickNotifMixin, CanEditPropMixin, UpdateView +): """Edit an article""" + model = WeekmailArticle - fields = ['title', 'club', 'content'] + fields = ["title", "club", "content"] pk_url_kwarg = "article_id" - template_name = 'core/edit.jinja' - success_url = reverse_lazy('com:weekmail') + template_name = "core/edit.jinja" + success_url = reverse_lazy("com:weekmail") quick_notif_url_arg = "qn_weekmail_article_edit" current_tab = "weekmail" class WeekmailArticleCreateView(QuickNotifMixin, CreateView): """Post an article""" + model = WeekmailArticle - fields = ['title', 'club', 'content'] - template_name = 'core/create.jinja' - success_url = reverse_lazy('core:user_tools') + fields = ["title", "club", "content"] + template_name = "core/create.jinja" + success_url = reverse_lazy("core:user_tools") quick_notif_url_arg = "qn_weekmail_new_article" def get_initial(self): init = {} try: - init['club'] = Club.objects.filter(id=self.request.GET['club']).first() + init["club"] = Club.objects.filter(id=self.request.GET["club"]).first() except: pass return init @@ -456,8 +567,15 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): if m.role <= settings.SITH_MAXIMUM_FREE_ROLE: raise except: - form.add_error('club', ValidationError(_("You must be a board member of the selected club to post in the Weekmail."))) - if form.is_valid() and not 'preview' in request.POST.keys(): + form.add_error( + "club", + ValidationError( + _( + "You must be a board member of the selected club to post in the Weekmail." + ) + ), + ) + if form.is_valid() and not "preview" in request.POST.keys(): return self.form_valid(form) else: return self.form_invalid(form) @@ -469,9 +587,10 @@ class WeekmailArticleCreateView(QuickNotifMixin, CreateView): class WeekmailArticleDeleteView(CanEditPropMixin, DeleteView): """Delete an article""" + model = WeekmailArticle - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('com:weekmail') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("com:weekmail") pk_url_kwarg = "article_id" @@ -481,40 +600,43 @@ class MailingListAdminView(ComTabsMixin, ListView): current_tab = "mailings" def dispatch(self, request, *args, **kwargs): - if not (request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) or request.user.is_root): + if not ( + request.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) + or request.user.is_root + ): raise PermissionDenied return super(MailingListAdminView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(MailingListAdminView, self).get_context_data(**kwargs) - kwargs['moderated'] = self.get_queryset().filter(is_moderated=True).all() - kwargs['unmoderated'] = self.get_queryset().filter(is_moderated=False).all() - kwargs['has_moderated'] = len(kwargs['moderated']) > 0 - kwargs['has_unmoderated'] = len(kwargs['unmoderated']) > 0 + kwargs["moderated"] = self.get_queryset().filter(is_moderated=True).all() + kwargs["unmoderated"] = self.get_queryset().filter(is_moderated=False).all() + kwargs["has_moderated"] = len(kwargs["moderated"]) > 0 + kwargs["has_unmoderated"] = len(kwargs["unmoderated"]) > 0 return kwargs class MailingModerateView(View): - def get(self, request, *args, **kwargs): - mailing = get_object_or_404(Mailing, pk=kwargs['mailing_id']) + mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"]) if mailing.can_moderate(request.user): mailing.is_moderated = True mailing.moderator = request.user mailing.save() - return redirect('com:mailing_admin') + return redirect("com:mailing_admin") raise PermissionDenied class PosterListBaseView(ListView): """List communication posters""" + current_tab = "posters" model = Poster - template_name = 'com/poster_list.jinja' + template_name = "com/poster_list.jinja" def dispatch(self, request, *args, **kwargs): - club_id = kwargs.pop('club_id', None) + club_id = kwargs.pop("club_id", None) self.club = None if club_id: self.club = get_object_or_404(Club, pk=club_id) @@ -522,40 +644,41 @@ class PosterListBaseView(ListView): def get_queryset(self): if self.request.user.is_com_admin: - return Poster.objects.all().order_by('-date_begin') + return Poster.objects.all().order_by("-date_begin") else: return Poster.objects.filter(club=self.club.id) def get_context_data(self, **kwargs): kwargs = super(PosterListBaseView, self).get_context_data(**kwargs) if not self.request.user.is_com_admin: - kwargs['club'] = self.club + kwargs["club"] = self.club return kwargs class PosterCreateBaseView(CreateView): """Create communication poster""" + current_tab = "posters" form_class = PosterForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def get_queryset(self): return Poster.objects.all() def dispatch(self, request, *args, **kwargs): - if 'club_id' in kwargs: - self.club = get_object_or_404(Club, pk=kwargs['club_id']) + if "club_id" in kwargs: + self.club = get_object_or_404(Club, pk=kwargs["club_id"]) return super(PosterCreateBaseView, self).dispatch(request, *args, **kwargs) def get_form_kwargs(self): kwargs = super(PosterCreateBaseView, self).get_form_kwargs() - kwargs.update({'user': self.request.user}) + kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): kwargs = super(PosterCreateBaseView, self).get_context_data(**kwargs) if not self.request.user.is_com_admin: - kwargs['club'] = self.club + kwargs["club"] = self.club return kwargs def form_valid(self, form): @@ -566,27 +689,28 @@ class PosterCreateBaseView(CreateView): class PosterEditBaseView(UpdateView): """Edit communication poster""" + pk_url_kwarg = "poster_id" current_tab = "posters" form_class = PosterForm - template_name = 'com/poster_edit.jinja' + template_name = "com/poster_edit.jinja" def get_initial(self): init = {} try: - init['date_begin'] = self.object.date_begin.strftime('%Y-%m-%d %H:%M:%S') + init["date_begin"] = self.object.date_begin.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass try: - init['date_end'] = self.object.date_end.strftime('%Y-%m-%d %H:%M:%S') + init["date_end"] = self.object.date_end.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass return init def dispatch(self, request, *args, **kwargs): - if 'club_id' in kwargs and kwargs['club_id']: + if "club_id" in kwargs and kwargs["club_id"]: try: - self.club = Club.objects.get(pk=kwargs['club_id']) + self.club = Club.objects.get(pk=kwargs["club_id"]) except Club.DoesNotExist: raise PermissionDenied return super(PosterEditBaseView, self).dispatch(request, *args, **kwargs) @@ -596,13 +720,13 @@ class PosterEditBaseView(UpdateView): def get_form_kwargs(self): kwargs = super(PosterEditBaseView, self).get_form_kwargs() - kwargs.update({'user': self.request.user}) + kwargs.update({"user": self.request.user}) return kwargs def get_context_data(self, **kwargs): kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs) if not self.request.user.is_com_admin: - kwargs['club'] = self.club + kwargs["club"] = self.club return kwargs def form_valid(self, form): @@ -613,15 +737,16 @@ class PosterEditBaseView(UpdateView): class PosterDeleteBaseView(DeleteView): """Edit communication poster""" + pk_url_kwarg = "poster_id" current_tab = "posters" model = Poster - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): - if 'club_id' in kwargs and kwargs['club_id']: + if "club_id" in kwargs and kwargs["club_id"]: try: - self.club = Club.objects.get(pk=kwargs['club_id']) + self.club = Club.objects.get(pk=kwargs["club_id"]) except Club.DoesNotExist: raise PermissionDenied return super(PosterDeleteBaseView, self).dispatch(request, *args, **kwargs) @@ -632,107 +757,117 @@ class PosterListView(IsComAdminMixin, ComTabsMixin, PosterListBaseView): def get_context_data(self, **kwargs): kwargs = super(PosterListView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterCreateView(IsComAdminMixin, ComTabsMixin, PosterCreateBaseView): """Create communication poster""" - success_url = reverse_lazy('com:poster_list') + + success_url = reverse_lazy("com:poster_list") def get_context_data(self, **kwargs): kwargs = super(PosterCreateView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterEditView(IsComAdminMixin, ComTabsMixin, PosterEditBaseView): """Edit communication poster""" - success_url = reverse_lazy('com:poster_list') + + success_url = reverse_lazy("com:poster_list") def get_context_data(self, **kwargs): kwargs = super(PosterEditView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterDeleteView(IsComAdminMixin, ComTabsMixin, PosterDeleteBaseView): """Delete communication poster""" - success_url = reverse_lazy('com:poster_list') + + success_url = reverse_lazy("com:poster_list") class PosterModerateListView(IsComAdminMixin, ComTabsMixin, ListView): """Moderate list communication poster""" + current_tab = "posters" model = Poster - template_name = 'com/poster_moderate.jinja' + template_name = "com/poster_moderate.jinja" queryset = Poster.objects.filter(is_moderated=False).all() def get_context_data(self, **kwargs): kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class PosterModerateView(IsComAdminMixin, ComTabsMixin, View): """Moderate communication poster""" + def get(self, request, *args, **kwargs): - obj = get_object_or_404(Poster, pk=kwargs['object_id']) + obj = get_object_or_404(Poster, pk=kwargs["object_id"]) if obj.can_be_moderated_by(request.user): obj.is_moderated = True obj.moderator = request.user obj.save() - return redirect('com:poster_moderate_list') + return redirect("com:poster_moderate_list") raise PermissionDenied def get_context_data(self, **kwargs): kwargs = super(PosterModerateListView, self).get_context_data(**kwargs) - kwargs['app'] = "com" + kwargs["app"] = "com" return kwargs class ScreenListView(IsComAdminMixin, ComTabsMixin, ListView): """List communication screens""" + current_tab = "screens" model = Screen - template_name = 'com/screen_list.jinja' + template_name = "com/screen_list.jinja" class ScreenSlideshowView(DetailView): """Slideshow of actives posters""" + pk_url_kwarg = "screen_id" model = Screen - template_name = 'com/screen_slideshow.jinja' + template_name = "com/screen_slideshow.jinja" def get_context_data(self, **kwargs): kwargs = super(ScreenSlideshowView, self).get_context_data(**kwargs) - kwargs['posters'] = self.object.active_posters() + kwargs["posters"] = self.object.active_posters() return kwargs class ScreenCreateView(IsComAdminMixin, ComTabsMixin, CreateView): """Create communication screen""" + current_tab = "screens" model = Screen - fields = ['name', ] - template_name = 'core/create.jinja' - success_url = reverse_lazy('com:screen_list') + fields = ["name"] + template_name = "core/create.jinja" + success_url = reverse_lazy("com:screen_list") class ScreenEditView(IsComAdminMixin, ComTabsMixin, UpdateView): """Edit communication screen""" + pk_url_kwarg = "screen_id" current_tab = "screens" model = Screen - fields = ['name', ] - template_name = 'com/screen_edit.jinja' - success_url = reverse_lazy('com:screen_list') + fields = ["name"] + template_name = "com/screen_edit.jinja" + success_url = reverse_lazy("com:screen_list") class ScreenDeleteView(IsComAdminMixin, ComTabsMixin, DeleteView): """Delete communication screen""" + pk_url_kwarg = "screen_id" current_tab = "screens" model = Screen - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('com:screen_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("com:screen_list") diff --git a/core/__init__.py b/core/__init__.py index ffb9c1e7..e5fb0a2d 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -22,4 +22,4 @@ # # -default_app_config = 'core.apps.SithConfig' +default_app_config = "core.apps.SithConfig" diff --git a/core/admin.py b/core/admin.py index eec0e90b..5b7c9c97 100644 --- a/core/admin.py +++ b/core/admin.py @@ -32,30 +32,38 @@ from haystack.admin import SearchModelAdmin admin.site.unregister(AuthGroup) admin.site.register(RealGroup) + class UserAdmin(SearchModelAdmin): list_display = ["first_name", "last_name", "username", "email", "nick_name"] - form = make_ajax_form(User, { - 'godfathers': 'users', - 'home': 'files', # ManyToManyField - 'profile_pict': 'files', # ManyToManyField - 'avatar_pict': 'files', # ManyToManyField - 'scrub_pict': 'files', # ManyToManyField - }) + form = make_ajax_form( + User, + { + "godfathers": "users", + "home": "files", # ManyToManyField + "profile_pict": "files", # ManyToManyField + "avatar_pict": "files", # ManyToManyField + "scrub_pict": "files", # ManyToManyField + }, + ) search_fields = ["first_name", "last_name", "username"] + admin.site.register(User, UserAdmin) + @admin.register(Page) class PageAdmin(admin.ModelAdmin): - form = make_ajax_form(Page, { - 'lock_user': 'users', - 'owner_group': 'groups', - 'edit_groups': 'groups', - 'view_groups': 'groups', - }) + form = make_ajax_form( + Page, + { + "lock_user": "users", + "owner_group": "groups", + "edit_groups": "groups", + "view_groups": "groups", + }, + ) + @admin.register(SithFile) class SithFileAdmin(admin.ModelAdmin): - form = make_ajax_form(SithFile, { - 'parent': 'files', # ManyToManyField - }) + form = make_ajax_form(SithFile, {"parent": "files"}) # ManyToManyField diff --git a/core/apps.py b/core/apps.py index 75643556..4f3358d5 100644 --- a/core/apps.py +++ b/core/apps.py @@ -29,7 +29,7 @@ from django.core.signals import request_started class SithConfig(AppConfig): - name = 'core' + name = "core" verbose_name = "Core app of the Sith" def ready(self): @@ -47,6 +47,12 @@ class SithConfig(AppConfig): Forum._club_memberships = {} print("Connecting signals!", file=sys.stderr) - request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups") - request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships") + request_started.connect( + clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups" + ) + request_started.connect( + clear_cached_memberships, + weak=False, + dispatch_uid="clear_cached_memberships", + ) # TODO: there may be a need to add more cache clearing diff --git a/core/lookups.py b/core/lookups.py index 3a77e915..477ef95e 100644 --- a/core/lookups.py +++ b/core/lookups.py @@ -33,9 +33,11 @@ from accounting.models import ClubAccount, Company def check_token(request): - return ('counter_token' in request.session.keys() and - request.session['counter_token'] and - Counter.objects.filter(token=request.session['counter_token']).exists()) + return ( + "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter(token=request.session["counter_token"]).exists() + ) class RightManagedLookupChannel(LookupChannel): @@ -44,7 +46,7 @@ class RightManagedLookupChannel(LookupChannel): raise PermissionDenied -@register('users') +@register("users") class UsersLookup(RightManagedLookupChannel): model = User @@ -58,7 +60,7 @@ class UsersLookup(RightManagedLookupChannel): return item.get_display_name() -@register('groups') +@register("groups") class GroupsLookup(RightManagedLookupChannel): model = Group @@ -72,7 +74,7 @@ class GroupsLookup(RightManagedLookupChannel): return item.name -@register('clubs') +@register("clubs") class ClubLookup(RightManagedLookupChannel): model = Club @@ -86,7 +88,7 @@ class ClubLookup(RightManagedLookupChannel): return item.name -@register('counters') +@register("counters") class CountersLookup(RightManagedLookupChannel): model = Counter @@ -97,19 +99,21 @@ class CountersLookup(RightManagedLookupChannel): return item.name -@register('products') +@register("products") class ProductsLookup(RightManagedLookupChannel): model = Product def get_query(self, q, request): - return (self.model.objects.filter(name__icontains=q) | - self.model.objects.filter(code__icontains=q)).filter(archived=False)[:50] + return ( + self.model.objects.filter(name__icontains=q) + | self.model.objects.filter(code__icontains=q) + ).filter(archived=False)[:50] def format_item_display(self, item): return "%s (%s)" % (item.name, item.code) -@register('files') +@register("files") class SithFileLookup(RightManagedLookupChannel): model = SithFile @@ -117,7 +121,7 @@ class SithFileLookup(RightManagedLookupChannel): return self.model.objects.filter(name__icontains=q)[:50] -@register('club_accounts') +@register("club_accounts") class ClubAccountLookup(RightManagedLookupChannel): model = ClubAccount @@ -128,7 +132,7 @@ class ClubAccountLookup(RightManagedLookupChannel): return item.name -@register('companies') +@register("companies") class CompaniesLookup(RightManagedLookupChannel): model = Company diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py index 0a9419f8..0ace29c4 100644 --- a/core/management/commands/__init__.py +++ b/core/management/commands/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/core/management/commands/check_fs.py b/core/management/commands/check_fs.py index 19943e02..7ebc076d 100644 --- a/core/management/commands/check_fs.py +++ b/core/management/commands/check_fs.py @@ -33,10 +33,14 @@ class Command(BaseCommand): help = "Recursively check the file system with respect to the DB" def add_arguments(self, parser): - parser.add_argument('ids', metavar='ID', type=int, nargs='+', help="The file IDs to process") + parser.add_argument( + "ids", metavar="ID", type=int, nargs="+", help="The file IDs to process" + ) def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - files = SithFile.objects.filter(id__in=options['ids']).all() + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) + files = SithFile.objects.filter(id__in=options["ids"]).all() for f in files: f._check_fs() diff --git a/core/management/commands/compilestatic.py b/core/management/commands/compilestatic.py index c634bc35..fd956333 100644 --- a/core/management/commands/compilestatic.py +++ b/core/management/commands/compilestatic.py @@ -33,15 +33,13 @@ class Command(BaseCommand): """ Compiles scss in static folder for production """ + help = "Compile scss files from static folder" def compile(self, filename): - args = { - "filename": filename, - "include_paths": settings.STATIC_ROOT, - } + args = {"filename": filename, "include_paths": settings.STATIC_ROOT} if settings.SASS_PRECISION: - args['precision'] = settings.SASS_PRECISION + args["precision"] = settings.SASS_PRECISION return sass.compile(**args) def is_compilable(self, file, ext_list): @@ -54,7 +52,7 @@ class Command(BaseCommand): file = os.path.join(folder, file) if os.path.isdir(file): self.exec_on_folder(file, func) - elif self.is_compilable(file, ['.scss']): + elif self.is_compilable(file, [".scss"]): to_exec.append(file) for file in to_exec: @@ -62,7 +60,7 @@ class Command(BaseCommand): def compilescss(self, file): print("compiling %s" % file) - with(open(file.replace('.scss', '.css'), "w")) as newfile: + with (open(file.replace(".scss", ".css"), "w")) as newfile: newfile.write(self.compile(file)) def removescss(self, file): @@ -77,4 +75,6 @@ class Command(BaseCommand): print("---- Removing scss files ----") self.exec_on_folder(settings.STATIC_ROOT, self.removescss) else: - print("No static folder avalaible, please use collectstatic before compiling scss") + print( + "No static folder avalaible, please use collectstatic before compiling scss" + ) diff --git a/core/management/commands/markdown.py b/core/management/commands/markdown.py index d0fad56f..1b5a6855 100644 --- a/core/management/commands/markdown.py +++ b/core/management/commands/markdown.py @@ -27,11 +27,14 @@ from django.core.management.base import BaseCommand from core.markdown import markdown + class Command(BaseCommand): help = "Output the fully rendered doc/SYNTAX.md file" def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md: + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md: result = markdown(md.read()) - print(result, end='') + print(result, end="") diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index e76dfb02..d3356d3e 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -36,7 +36,15 @@ from django.utils import timezone from PIL import Image from core.models import Group, User, Page, PageRev, SithFile -from accounting.models import GeneralJournal, BankAccount, ClubAccount, Operation, AccountingType, SimplifiedAccountingType, Company +from accounting.models import ( + GeneralJournal, + BankAccount, + ClubAccount, + Operation, + AccountingType, + SimplifiedAccountingType, + Company, +) from core.utils import resize_image from club.models import Club, Membership from subscription.models import Subscription @@ -50,7 +58,7 @@ class Command(BaseCommand): help = "Populate a new instance of the Sith AE" def add_arguments(self, parser): - parser.add_argument('--prod', action="store_true") + parser.add_argument("--prod", action="store_true") def reset_index(self, *args): sqlcmd = StringIO() @@ -59,9 +67,11 @@ class Command(BaseCommand): cursor.execute(sqlcmd.getvalue()) def handle(self, *args, **options): - os.environ['DJANGO_COLORS'] = 'nocolor' + os.environ["DJANGO_COLORS"] = "nocolor" Site(id=4000, domain=settings.SITH_URL, name=settings.SITH_NAME).save() - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) Group(name="Root").save() Group(name="Public").save() Group(name="Subscribers").save() @@ -75,13 +85,21 @@ class Command(BaseCommand): Group(name="SAS admin").save() Group(name="Forum admin").save() self.reset_index("core", "auth") - root = User(id=0, username='root', last_name="", first_name="Bibou", - email="ae.info@utbm.fr", - date_of_birth="1942-06-12", - is_superuser=True, is_staff=True) + root = User( + id=0, + username="root", + last_name="", + first_name="Bibou", + email="ae.info@utbm.fr", + date_of_birth="1942-06-12", + is_superuser=True, + is_staff=True, + ) root.set_password("plop") root.save() - profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root) + profiles_root = SithFile( + parent=None, name="profiles", is_folder=True, owner=root + ) profiles_root.save() home_root = SithFile(parent=None, name="users", is_folder=True, owner=root) home_root.save() @@ -94,250 +112,395 @@ class Command(BaseCommand): club_root = SithFile(parent=None, name="clubs", is_folder=True, owner=root) club_root.save() SithFile(parent=None, name="SAS", is_folder=True, owner=root).save() - main_club = Club(id=1, name=settings.SITH_MAIN_CLUB['name'], unix_name=settings.SITH_MAIN_CLUB['unix_name'], - address=settings.SITH_MAIN_CLUB['address']) + main_club = Club( + id=1, + name=settings.SITH_MAIN_CLUB["name"], + unix_name=settings.SITH_MAIN_CLUB["unix_name"], + address=settings.SITH_MAIN_CLUB["address"], + ) main_club.save() - bar_club = Club(id=2, name=settings.SITH_BAR_MANAGER['name'], unix_name=settings.SITH_BAR_MANAGER['unix_name'], - address=settings.SITH_BAR_MANAGER['address']) + bar_club = Club( + id=2, + name=settings.SITH_BAR_MANAGER["name"], + unix_name=settings.SITH_BAR_MANAGER["unix_name"], + address=settings.SITH_BAR_MANAGER["address"], + ) bar_club.save() - launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'], - unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'], - address=settings.SITH_LAUNDERETTE_MANAGER['address']) + launderette_club = Club( + id=84, + name=settings.SITH_LAUNDERETTE_MANAGER["name"], + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"], + address=settings.SITH_LAUNDERETTE_MANAGER["address"], + ) launderette_club.save() self.reset_index("club") for b in settings.SITH_COUNTER_BARS: g = Group(name=b[1] + " admin") g.save() - c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR') + c = Counter(id=b[0], name=b[1], club=bar_club, type="BAR") c.save() c.edit_groups = [g] c.save() self.reset_index("counter") - Counter(name="Eboutic", club=main_club, type='EBOUTIC').save() - Counter(name="AE", club=main_club, type='OFFICE').save() + Counter(name="Eboutic", club=main_club, type="EBOUTIC").save() + Counter(name="AE", club=main_club, type="OFFICE").save() - home_root.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()] - club_root.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()] + home_root.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() + ] + club_root.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first() + ] home_root.save() club_root.save() Sith(weekmail_destinations="etudiants@git.an personnel@git.an").save() Weekmail().save() - p = Page(name='Index') + p = Page(name="Index") p.set_lock(root) p.save() p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.set_lock(root) p.save() - PageRev(page=p, title="Wiki index", author=root, content=""" + PageRev( + page=p, + title="Wiki index", + author=root, + content=""" Welcome to the wiki page! -""").save() +""", + ).save() p = Page(name="services") p.set_lock(root) p.save() p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.set_lock(root) - PageRev(page=p, title="Services", author=root, content=""" + PageRev( + page=p, + title="Services", + author=root, + content=""" | | | | | :---: | :---: | :---: | :---: | | [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | [Fichiers](/file) | | SAS | Weekmail | Forum | | -""").save() +""", + ).save() p = Page(name="launderette") p.set_lock(root) p.save() p.set_lock(root) - PageRev(page=p, title="Laverie", author=root, content="Fonctionnement de la laverie").save() + PageRev( + page=p, title="Laverie", author=root, content="Fonctionnement de la laverie" + ).save() # Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment - if not options['prod']: + if not options["prod"]: # Adding user Skia - skia = User(username='skia', last_name="Kia", first_name="S'", - email="skia@git.an", - date_of_birth="1942-06-12") + skia = User( + username="skia", + last_name="Kia", + first_name="S'", + email="skia@git.an", + date_of_birth="1942-06-12", + ) skia.set_password("plop") skia.save() - skia.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + skia.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] skia.save() - skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg') - with open(skia_profile_path, 'rb') as f: + skia_profile_path = os.path.join(root_path, "core/fixtures/images/3.jpg") + with open(skia_profile_path, "rb") as f: name = str(skia.id) + "_profile.jpg" - skia_profile = SithFile(parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), - owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path)) + skia_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=skia, + is_folder=False, + mime_type="image/jpeg", + size=os.path.getsize(skia_profile_path), + ) skia_profile.file.name = name skia_profile.save() skia.profile_pict = skia_profile skia.save() # Adding user public - public = User(username='public', last_name="Not subscribed", first_name="Public", - email="public@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + public = User( + username="public", + last_name="Not subscribed", + first_name="Public", + email="public@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) public.set_password("plop") public.save() - public.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + public.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] public.save() # Adding user Subscriber - subscriber = User(username='subscriber', last_name="User", first_name="Subscribed", - email="Subscribed@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + subscriber = User( + username="subscriber", + last_name="User", + first_name="Subscribed", + email="Subscribed@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) subscriber.set_password("plop") subscriber.save() - subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + subscriber.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] subscriber.save() # Adding user old Subscriber - old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old", - email="old_subscriber@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + old_subscriber = User( + username="old_subscriber", + last_name="Subscriber", + first_name="Old", + email="old_subscriber@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) old_subscriber.set_password("plop") old_subscriber.save() - old_subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + old_subscriber.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] old_subscriber.save() # Adding user Counter admin - counter = User(username='counter', last_name="Ter", first_name="Coun", - email="counter@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + counter = User( + username="counter", + last_name="Ter", + first_name="Coun", + email="counter@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) counter.set_password("plop") counter.save() - counter.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] - counter.groups = [Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id] + counter.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] + counter.groups = [ + Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id + ] counter.save() # Adding user Comptable - comptable = User(username='comptable', last_name="Able", first_name="Compte", - email="compta@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + comptable = User( + username="comptable", + last_name="Able", + first_name="Compte", + email="compta@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) comptable.set_password("plop") comptable.save() - comptable.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] - comptable.groups = [Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID).first().id] + comptable.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] + comptable.groups = [ + Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) + .first() + .id + ] comptable.save() # Adding user Guy - u = User(username='guy', last_name="Carlier", first_name="Guy", - email="guy@git.an", - date_of_birth="1942-06-12", - is_superuser=False, is_staff=False) + u = User( + username="guy", + last_name="Carlier", + first_name="Guy", + email="guy@git.an", + date_of_birth="1942-06-12", + is_superuser=False, + is_staff=False, + ) u.set_password("plop") u.save() - u.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + u.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] u.save() # Adding user Richard Batsbak - r = User(username='rbatsbak', last_name="Batsbak", first_name="Richard", - email="richard@git.an", - date_of_birth="1982-06-12") + r = User( + username="rbatsbak", + last_name="Batsbak", + first_name="Richard", + email="richard@git.an", + date_of_birth="1982-06-12", + ) r.set_password("plop") r.save() - r.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + r.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] r.save() # Adding syntax help page - p = Page(name='Aide_sur_la_syntaxe') + p = Page(name="Aide_sur_la_syntaxe") p.save(force_lock=True) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as rm: - PageRev(page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()).save() + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as rm: + PageRev( + page=p, title="Aide sur la syntaxe", author=skia, content=rm.read() + ).save() p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.save(force_lock=True) - p = Page(name='Services') + p = Page(name="Services") p.save(force_lock=True) p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.save(force_lock=True) - PageRev(page=p, title="Services", author=skia, content=""" + PageRev( + page=p, + title="Services", + author=skia, + content=""" | | | | | :---: | :---: | :---: | | [Eboutic](/eboutic) | [Laverie](/launderette) | Matmat | | SAS | Weekmail | Forum| -""").save() +""", + ).save() # Adding README - p = Page(name='README') + p = Page(name="README") p.save(force_lock=True) p.view_groups = [settings.SITH_GROUP_PUBLIC_ID] p.save(force_lock=True) - with open(os.path.join(root_path) + '/README.md', 'r') as rm: + with open(os.path.join(root_path) + "/README.md", "r") as rm: PageRev(page=p, title="README", author=skia, content=rm.read()).save() # Subscription - default_subscription = 'un-semestre' + default_subscription = "un-semestre" # Root - s = Subscription(member=User.objects.filter(pk=root.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=root.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Skia - s = Subscription(member=User.objects.filter(pk=skia.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=skia.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Counter admin - s = Subscription(member=User.objects.filter(pk=counter.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=counter.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Comptable - s = Subscription(member=User.objects.filter(pk=comptable.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=comptable.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Richard - s = Subscription(member=User.objects.filter(pk=r.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=r.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # User - s = Subscription(member=User.objects.filter(pk=subscriber.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=subscriber.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Old subscriber - s = Subscription(member=User.objects.filter(pk=old_subscriber.pk).first(), subscription_type=default_subscription, - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=old_subscriber.pk).first(), + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4)) s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Clubs - Club(name="Bibo'UT", unix_name="bibout", - address="46 de la Boustifaille", parent=main_club).save() - guyut = Club(name="Guy'UT", unix_name="guyut", - address="42 de la Boustifaille", parent=main_club) + Club( + name="Bibo'UT", + unix_name="bibout", + address="46 de la Boustifaille", + parent=main_club, + ).save() + guyut = Club( + name="Guy'UT", + unix_name="guyut", + address="42 de la Boustifaille", + parent=main_club, + ) guyut.save() - Club(name="Woenzel'UT", unix_name="woenzel", - address="Woenzel", parent=guyut).save() + Club( + name="Woenzel'UT", unix_name="woenzel", address="Woenzel", parent=guyut + ).save() Membership(user=skia, club=main_club, role=3, description="").save() - troll = Club(name="Troll Penché", unix_name="troll", - address="Terre Du Milieu", parent=main_club) + troll = Club( + name="Troll Penché", + unix_name="troll", + address="Terre Du Milieu", + parent=main_club, + ) troll.save() - refound = Club(name="Carte AE", unix_name="carte_ae", - address="Jamais imprimée", parent=main_club) + refound = Club( + name="Carte AE", + unix_name="carte_ae", + address="Jamais imprimée", + parent=main_club, + ) refound.save() # Counters @@ -351,33 +514,96 @@ Welcome to the wiki page! r.save() verre = ProductType(name="Verre") verre.save() - cotis = Product(name="Cotis 1 semestre", code="1SCOTIZ", product_type=c, purchase_price="15", selling_price="15", - special_selling_price="15", club=main_club) + cotis = Product( + name="Cotis 1 semestre", + code="1SCOTIZ", + product_type=c, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) cotis.save() - cotis2 = Product(name="Cotis 2 semestres", code="2SCOTIZ", product_type=c, purchase_price="28", selling_price="28", - special_selling_price="28", club=main_club) + cotis2 = Product( + name="Cotis 2 semestres", + code="2SCOTIZ", + product_type=c, + purchase_price="28", + selling_price="28", + special_selling_price="28", + club=main_club, + ) cotis2.save() - refill = Product(name="Rechargement 15 €", code="15REFILL", product_type=r, purchase_price="15", selling_price="15", - special_selling_price="15", club=main_club) + refill = Product( + name="Rechargement 15 €", + code="15REFILL", + product_type=r, + purchase_price="15", + selling_price="15", + special_selling_price="15", + club=main_club, + ) refill.save() - barb = Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club) + barb = Product( + name="Barbar", + code="BARB", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ) barb.save() - cble = Product(name="Chimay Bleue", code="CBLE", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club) + cble = Product( + name="Chimay Bleue", + code="CBLE", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ) cble.save() - cons = Product(name="Consigne Eco-cup", code="CONS", product_type=verre, purchase_price="1", selling_price="1", - special_selling_price="1", club=main_club) + cons = Product( + name="Consigne Eco-cup", + code="CONS", + product_type=verre, + purchase_price="1", + selling_price="1", + special_selling_price="1", + club=main_club, + ) cons.id = 1152 cons.save() - dcons = Product(name="Déconsigne Eco-cup", code="DECO", product_type=verre, purchase_price="-1", selling_price="-1", - special_selling_price="-1", club=main_club) + dcons = Product( + name="Déconsigne Eco-cup", + code="DECO", + product_type=verre, + purchase_price="-1", + selling_price="-1", + special_selling_price="-1", + club=main_club, + ) dcons.id = 1151 dcons.save() - Product(name="Corsendonk", code="CORS", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club).save() - Product(name="Carolus", code="CARO", product_type=p, purchase_price="1.50", selling_price="1.7", - special_selling_price="1.6", club=main_club).save() + Product( + name="Corsendonk", + code="CORS", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ).save() + Product( + name="Carolus", + code="CARO", + product_type=p, + purchase_price="1.50", + selling_price="1.7", + special_selling_price="1.6", + club=main_club, + ).save() mde = Counter.objects.filter(name="MDE").first() mde.products.add(barb) mde.products.add(cble) @@ -393,10 +619,16 @@ Welcome to the wiki page! eboutic.products.add(refill) eboutic.save() - refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE') + refound_counter = Counter(name="Carte AE", club=refound, type="OFFICE") refound_counter.save() - refound_product = Product(name="remboursement", code="REMBOURS", purchase_price="0", selling_price="0", - special_selling_price="0", club=refound) + refound_product = Product( + name="remboursement", + code="REMBOURS", + purchase_price="0", + selling_price="0", + special_selling_price="0", + club=refound, + ) refound_product.save() # Accounting test values: @@ -408,94 +640,238 @@ Welcome to the wiki page! ca.save() gj = GeneralJournal(name="A16", start_date=date.today(), club_account=ca) gj.save() - credit = AccountingType(code='74', label="Subventions d'exploitation", movement_type='CREDIT') + credit = AccountingType( + code="74", label="Subventions d'exploitation", movement_type="CREDIT" + ) credit.save() - debit = AccountingType(code='606', label="Achats non stockés de matières et fournitures(*1)", movement_type='DEBIT') + debit = AccountingType( + code="606", + label="Achats non stockés de matières et fournitures(*1)", + movement_type="DEBIT", + ) debit.save() - debit2 = AccountingType(code='604', label="Achats d'études et prestations de services(*2)", movement_type='DEBIT') + debit2 = AccountingType( + code="604", + label="Achats d'études et prestations de services(*2)", + movement_type="DEBIT", + ) debit2.save() - buying = AccountingType(code='60', label="Achats (sauf 603)", movement_type='DEBIT') + buying = AccountingType( + code="60", label="Achats (sauf 603)", movement_type="DEBIT" + ) buying.save() - comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT') + comptes = AccountingType( + code="6", label="Comptes de charge", movement_type="DEBIT" + ) comptes.save() - simple = SimplifiedAccountingType(label='Je fais du simple 6', accounting_type=comptes) + simple = SimplifiedAccountingType( + label="Je fais du simple 6", accounting_type=comptes + ) simple.save() woenzco = Company(name="Woenzel & co") woenzco.save() operation_list = [ - (27, "J'avais trop de bière", 'CASH', None, buying, 'USER', skia.id, "", None), - (4000, "Ceci n'est pas une opération... en fait si mais non", 'CHECK', None, debit, 'COMPANY', woenzco.id, "", 23), - (22, "C'est de l'argent ?", 'CARD', None, credit, 'CLUB', troll.id, "", None), - (37, "Je paye CASH", 'CASH', None, debit2, 'OTHER', None, "tous les étudiants <3", None), - (300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None), - (32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None), - (46.42, "Allumette", 'CHECK', None, credit, 'CLUB', main_club.id, "", 57), - (666.42, "Subvention de far far away", 'CASH', None, comptes, 'CLUB', main_club.id, "", None), - (496, "Ça, c'est un 6", 'CARD', simple, None, 'USER', skia.id, "", None), - (17, "La Gargotte du Korrigan", 'CASH', None, debit2, 'CLUB', bar_club.id, "", None), + ( + 27, + "J'avais trop de bière", + "CASH", + None, + buying, + "USER", + skia.id, + "", + None, + ), + ( + 4000, + "Ceci n'est pas une opération... en fait si mais non", + "CHECK", + None, + debit, + "COMPANY", + woenzco.id, + "", + 23, + ), + ( + 22, + "C'est de l'argent ?", + "CARD", + None, + credit, + "CLUB", + troll.id, + "", + None, + ), + ( + 37, + "Je paye CASH", + "CASH", + None, + debit2, + "OTHER", + None, + "tous les étudiants <3", + None, + ), + (300, "Paiement Guy", "CASH", None, buying, "USER", skia.id, "", None), + (32.3, "Essence", "CASH", None, buying, "OTHER", None, "station", None), + ( + 46.42, + "Allumette", + "CHECK", + None, + credit, + "CLUB", + main_club.id, + "", + 57, + ), + ( + 666.42, + "Subvention de far far away", + "CASH", + None, + comptes, + "CLUB", + main_club.id, + "", + None, + ), + ( + 496, + "Ça, c'est un 6", + "CARD", + simple, + None, + "USER", + skia.id, + "", + None, + ), + ( + 17, + "La Gargotte du Korrigan", + "CASH", + None, + debit2, + "CLUB", + bar_club.id, + "", + None, + ), ] for op in operation_list: - operation = Operation(journal=gj, date=date.today(), amount=op[0], - remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3], - accounting_type=op[4], target_type=op[5], target_id=op[6], - target_label=op[7], cheque_number=op[8]) + operation = Operation( + journal=gj, + date=date.today(), + amount=op[0], + remark=op[1], + mode=op[2], + done=True, + simpleaccounting_type=op[3], + accounting_type=op[4], + target_type=op[5], + target_id=op[6], + target_label=op[7], + cheque_number=op[8], + ) operation.clean() operation.save() # Adding user sli - sli = User(username='sli', last_name="Li", first_name="S", - email="sli@git.an", - date_of_birth="1942-06-12") + sli = User( + username="sli", + last_name="Li", + first_name="S", + email="sli@git.an", + date_of_birth="1942-06-12", + ) sli.set_password("plop") sli.save() - sli.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] + sli.view_groups = [ + Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id + ] sli.save() - sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg') - with open(sli_profile_path, 'rb') as f: + sli_profile_path = os.path.join(root_path, "core/fixtures/images/5.jpg") + with open(sli_profile_path, "rb") as f: name = str(sli.id) + "_profile.jpg" - sli_profile = SithFile(parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), - owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path)) + sli_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=sli, + is_folder=False, + mime_type="image/jpeg", + size=os.path.getsize(sli_profile_path), + ) sli_profile.file.name = name sli_profile.save() sli.profile_pict = sli_profile sli.save() # Adding user Krophil - krophil = User(username='krophil', last_name="Phil'", first_name="Kro", - email="krophil@git.an", - date_of_birth="1942-06-12") + krophil = User( + username="krophil", + last_name="Phil'", + first_name="Kro", + email="krophil@git.an", + date_of_birth="1942-06-12", + ) krophil.set_password("plop") krophil.save() - krophil_profile_path = os.path.join(root_path, 'core/fixtures/images/6.jpg') - with open(krophil_profile_path, 'rb') as f: + krophil_profile_path = os.path.join(root_path, "core/fixtures/images/6.jpg") + with open(krophil_profile_path, "rb") as f: name = str(krophil.id) + "_profile.jpg" - krophil_profile = SithFile(parent=profiles_root, name=name, - file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), - owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path)) + krophil_profile = SithFile( + parent=profiles_root, + name=name, + file=resize_image(Image.open(BytesIO(f.read())), 400, "JPEG"), + owner=krophil, + is_folder=False, + mime_type="image/jpeg", + size=os.path.getsize(krophil_profile_path), + ) krophil_profile.file.name = name krophil_profile.save() krophil.profile_pict = krophil_profile krophil.save() # Adding subscription for sli - s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=sli.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() # Adding subscription for Krophil - s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=krophil.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = s.compute_start() s.subscription_end = s.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() - Selling(label=dcons.name, product=dcons, counter=mde, unit_price=dcons.selling_price, club=main_club, - quantity=settings.SITH_ECOCUP_LIMIT + 3, seller=skia, customer=krophil.customer).save() + Selling( + label=dcons.name, + product=dcons, + counter=mde, + unit_price=dcons.selling_price, + club=main_club, + quantity=settings.SITH_ECOCUP_LIMIT + 3, + seller=skia, + customer=krophil.customer, + ).save() # Add barman to counter c = Counter.objects.get(id=2) @@ -506,9 +882,14 @@ Welcome to the wiki page! public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) - el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01', - end_candidature='2042-06-12 10:28:45+01', start_date='1942-06-12 10:28:45+01', - end_date='7942-06-12 10:28:45+01') + el = Election( + title="Élection 2017", + description="La roue tourne", + start_candidature="1942-06-12 10:28:45+01", + end_candidature="2042-06-12 10:28:45+01", + start_date="1942-06-12 10:28:45+01", + end_date="7942-06-12 10:28:45+01", + ) el.save() el.view_groups.add(public_group) el.edit_groups.add(ae_board_group) @@ -521,27 +902,55 @@ Welcome to the wiki page! listeT.save() pres = Role(election=el, title="Président AE", description="Roi de l'AE") pres.save() - resp = Role(election=el, title="Co Respo Info", max_choice=2, description="Ghetto++") + resp = Role( + election=el, title="Co Respo Info", max_choice=2, description="Ghetto++" + ) resp.save() - cand = Candidature(role=resp, user=skia, election_list=liste, program="Refesons le site AE") + cand = Candidature( + role=resp, user=skia, election_list=liste, program="Refesons le site AE" + ) cand.save() - cand = Candidature(role=resp, user=sli, election_list=liste, program="Vasy je deviens mon propre adjoint") + cand = Candidature( + role=resp, + user=sli, + election_list=liste, + program="Vasy je deviens mon propre adjoint", + ) cand.save() - cand = Candidature(role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !") + cand = Candidature( + role=resp, user=krophil, election_list=listeT, program="Le Pôle Troll !" + ) cand.save() - cand = Candidature(role=pres, user=sli, election_list=listeT, program="En fait j'aime pas l'info, je voulais faire GMC") + cand = Candidature( + role=pres, + user=sli, + election_list=listeT, + program="En fait j'aime pas l'info, je voulais faire GMC", + ) cand.save() # Forum - room = Forum(name="Salon de discussions", description="Pour causer de tout", is_category=True) + room = Forum( + name="Salon de discussions", + description="Pour causer de tout", + is_category=True, + ) room.save() Forum(name="AE", description="Réservé au bureau AE", parent=room).save() Forum(name="BdF", description="Réservé au bureau BdF", parent=room).save() - hall = Forum(name="Hall de discussions", description="Pour toutes les discussions", parent=room) + hall = Forum( + name="Hall de discussions", + description="Pour toutes les discussions", + parent=room, + ) hall.save() - various = Forum(name="Divers", description="Pour causer de rien", is_category=True) + various = Forum( + name="Divers", description="Pour causer de rien", is_category=True + ) various.save() - Forum(name="Promos", description="Réservé aux Promos", parent=various).save() + Forum( + name="Promos", description="Réservé aux Promos", parent=various + ).save() ForumTopic(forum=hall) # News @@ -550,39 +959,87 @@ Welcome to the wiki page! friday += timedelta(hours=6) friday.replace(hour=20, minute=0, second=0) # Event - n = News(title="Apero barman", summary="Viens boire un coup avec les barmans", - content="Glou glou glou glou glou glou glou" , type="EVENT", - club=bar_club, author=subscriber, is_moderated=True, moderator=skia) + n = News( + title="Apero barman", + summary="Viens boire un coup avec les barmans", + content="Glou glou glou glou glou glou glou", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=timezone.now()+timedelta(hours=70), - end_date=timezone.now()+timedelta(hours=72)).save() - n = News(title="Repas barman", summary="Enjoy la fin du semestre!", - content="Viens donc t'enjailler avec les autres barmans aux " - "frais du BdF! \o/", type="EVENT", club=bar_club, - author=subscriber, is_moderated=True, moderator=skia) + NewsDate( + news=n, + start_date=timezone.now() + timedelta(hours=70), + end_date=timezone.now() + timedelta(hours=72), + ).save() + n = News( + title="Repas barman", + summary="Enjoy la fin du semestre!", + content="Viens donc t'enjailler avec les autres barmans aux " + "frais du BdF! \o/", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=timezone.now()+timedelta(hours=72), - end_date=timezone.now()+timedelta(hours=84)).save() - n = News(title="Repas fromager", summary="Wien manger du l'bon fromeug'", - content="Fô viendre mangey d'la bonne fondue!", - type="EVENT", club=bar_club, author=subscriber, - is_moderated=True, moderator=skia) + NewsDate( + news=n, + start_date=timezone.now() + timedelta(hours=72), + end_date=timezone.now() + timedelta(hours=84), + ).save() + n = News( + title="Repas fromager", + summary="Wien manger du l'bon fromeug'", + content="Fô viendre mangey d'la bonne fondue!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=timezone.now()+timedelta(hours=96), - end_date=timezone.now()+timedelta(hours=100)).save() - n = News(title="SdF", summary="Enjoy la fin des finaux!", - content="Viens faire la fête avec tout plein de gens!", - type="EVENT", club=bar_club, author=subscriber, - is_moderated=True, moderator=skia) + NewsDate( + news=n, + start_date=timezone.now() + timedelta(hours=96), + end_date=timezone.now() + timedelta(hours=100), + ).save() + n = News( + title="SdF", + summary="Enjoy la fin des finaux!", + content="Viens faire la fête avec tout plein de gens!", + type="EVENT", + club=bar_club, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() - NewsDate(news=n, start_date=friday+timedelta(hours=24*7+1), - end_date=timezone.now()+timedelta(hours=24*7+9)).save() + NewsDate( + news=n, + start_date=friday + timedelta(hours=24 * 7 + 1), + end_date=timezone.now() + timedelta(hours=24 * 7 + 9), + ).save() # Weekly - n = News(title="Jeux sans faim", summary="Viens jouer!", - content="Rejoins la fine équipe du Troll Penché et viens " - "d'amuser le Vendredi soir!", type="WEEKLY", club=troll, - author=subscriber, is_moderated=True, moderator=skia) + n = News( + title="Jeux sans faim", + summary="Viens jouer!", + content="Rejoins la fine équipe du Troll Penché et viens " + "d'amuser le Vendredi soir!", + type="WEEKLY", + club=troll, + author=subscriber, + is_moderated=True, + moderator=skia, + ) n.save() for i in range(10): - NewsDate(news=n, start_date=friday+timedelta(hours=24*7*i), - end_date=friday+timedelta(hours=24*7*i+8)).save() + NewsDate( + news=n, + start_date=friday + timedelta(hours=24 * 7 * i), + end_date=friday + timedelta(hours=24 * 7 * i + 8), + ).save() diff --git a/core/management/commands/repair_fs.py b/core/management/commands/repair_fs.py index 8bad0a33..055dbc9b 100644 --- a/core/management/commands/repair_fs.py +++ b/core/management/commands/repair_fs.py @@ -33,10 +33,14 @@ class Command(BaseCommand): help = "Recursively repair the file system with respect to the DB" def add_arguments(self, parser): - parser.add_argument('ids', metavar='ID', type=int, nargs='+', help="The file IDs to process") + parser.add_argument( + "ids", metavar="ID", type=int, nargs="+", help="The file IDs to process" + ) def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) - files = SithFile.objects.filter(id__in=options['ids']).all() + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) + files = SithFile.objects.filter(id__in=options["ids"]).all() for f in files: f._repair_fs() diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index 88be8377..128b762d 100644 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -31,22 +31,24 @@ class Command(BaseCommand): help = "Set up a new instance of the Sith AE" def add_arguments(self, parser): - parser.add_argument('--prod', action="store_true") + parser.add_argument("--prod", action="store_true") def handle(self, *args, **options): - root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) + root_path = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + ) try: - os.mkdir(os.path.join(root_path) + '/data') + os.mkdir(os.path.join(root_path) + "/data") print("Data dir created") except Exception as e: repr(e) try: - os.remove(os.path.join(root_path, 'db.sqlite3')) + os.remove(os.path.join(root_path, "db.sqlite3")) print("db.sqlite3 deleted") except Exception as e: repr(e) - call_command('migrate') - if options['prod']: - call_command('populate', '--prod') + call_command("migrate") + if options["prod"]: + call_command("populate", "--prod") else: - call_command('populate') + call_command("populate") diff --git a/core/markdown.py b/core/markdown.py index e9ce814c..c5d69a6c 100644 --- a/core/markdown.py +++ b/core/markdown.py @@ -30,7 +30,7 @@ from django.core.urlresolvers import reverse class SithRenderer(Renderer): def file_link(self, id, suffix): - return reverse('core:file_detail', kwargs={'file_id': id}) + suffix + return reverse("core:file_detail", kwargs={"file_id": id}) + suffix def exposant(self, text): return """%s""" % text @@ -48,19 +48,19 @@ class SithRenderer(Renderer): :param text: alt text of the image. """ style = None - if '?' in original_src: - src, params = original_src.rsplit('?', maxsplit=1) - m = re.search(r'(\d+%?)(x(\d+%?))?', params) + if "?" in original_src: + src, params = original_src.rsplit("?", maxsplit=1) + m = re.search(r"(\d+%?)(x(\d+%?))?", params) if not m: src = original_src else: width = m.group(1) - if not width.endswith('%'): + if not width.endswith("%"): width += "px" style = "width: %s; " % width try: height = m.group(3) - if not height.endswith('%'): + if not height.endswith("%"): height += "px" style += "height: %s; " % height except: @@ -77,67 +77,57 @@ class SithRenderer(Renderer): html = '%s' % html - return '%s>' % html + if self.options.get("use_xhtml"): + return "%s />" % html + return "%s>" % html class SithInlineGrammar(InlineGrammar): - double_emphasis = re.compile( - r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** - ) - emphasis = re.compile( - r'^\*((?:\*\*|[^\*])+?)\*(?!\*)' # *word* - ) - underline = re.compile( - r'^_{2}([\s\S]+?)_{2}(?!_)' # __word__ - ) - exposant = re.compile( - r'^([\s\S]+?)' # text - ) - indice = re.compile( - r'^([\s\S]+?)' # text - ) + double_emphasis = re.compile(r"^\*{2}([\s\S]+?)\*{2}(?!\*)") # **word** + emphasis = re.compile(r"^\*((?:\*\*|[^\*])+?)\*(?!\*)") # *word* + underline = re.compile(r"^_{2}([\s\S]+?)_{2}(?!_)") # __word__ + exposant = re.compile(r"^([\s\S]+?)") # text + indice = re.compile(r"^([\s\S]+?)") # text class SithInlineLexer(InlineLexer): grammar_class = SithInlineGrammar default_rules = [ - 'escape', + "escape", # 'inline_html', - 'autolink', - 'url', - 'footnote', - 'link', - 'reflink', - 'nolink', - 'exposant', - 'double_emphasis', - 'emphasis', - 'underline', - 'indice', - 'code', - 'linebreak', - 'strikethrough', - 'text', + "autolink", + "url", + "footnote", + "link", + "reflink", + "nolink", + "exposant", + "double_emphasis", + "emphasis", + "underline", + "indice", + "code", + "linebreak", + "strikethrough", + "text", ] inline_html_rules = [ - 'escape', - 'autolink', - 'url', - 'link', - 'reflink', - 'nolink', - 'exposant', - 'double_emphasis', - 'emphasis', - 'underline', - 'indice', - 'code', - 'linebreak', - 'strikethrough', - 'text', + "escape", + "autolink", + "url", + "link", + "reflink", + "nolink", + "exposant", + "double_emphasis", + "emphasis", + "underline", + "indice", + "code", + "linebreak", + "strikethrough", + "text", ] def output_underline(self, m): @@ -166,22 +156,18 @@ class SithInlineLexer(InlineLexer): def _process_link(self, m, link, title=None): try: # Add page:// support for links - page = re.compile( - r'^page://(\S*)' # page://nom_de_ma_page - ) + page = re.compile(r"^page://(\S*)") # page://nom_de_ma_page match = page.search(link) page = match.group(1) or "" - link = reverse('core:page', kwargs={'page_name': page}) + link = reverse("core:page", kwargs={"page_name": page}) except: pass try: # Add file:// support for links - file_link = re.compile( - r'^file://(\d*)/?(\S*)?' # file://4000/download - ) + file_link = re.compile(r"^file://(\d*)/?(\S*)?") # file://4000/download match = file_link.search(link) id = match.group(1) suffix = match.group(2) or "" - link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix + link = reverse("core:file_detail", kwargs={"file_id": id}) + suffix except: pass return super(SithInlineLexer, self)._process_link(m, link, title) @@ -194,6 +180,6 @@ markdown = Markdown(renderer, inline=inline) if __name__ == "__main__": root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md: + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md: result = markdown(md.read()) - print(result, end='') + print(result, end="") diff --git a/core/middleware.py b/core/middleware.py index 36c91bbf..37c01ff9 100644 --- a/core/middleware.py +++ b/core/middleware.py @@ -26,14 +26,16 @@ import importlib from django.conf import settings from django.utils.functional import SimpleLazyObject from django.contrib.auth import get_user -from django.contrib.auth.middleware import AuthenticationMiddleware as DjangoAuthenticationMiddleware +from django.contrib.auth.middleware import ( + AuthenticationMiddleware as DjangoAuthenticationMiddleware, +) -module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit('.', 1) +module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) AnonymousUser = getattr(importlib.import_module(module), klass) def get_cached_user(request): - if not hasattr(request, '_cached_user'): + if not hasattr(request, "_cached_user"): user = get_user(request) if user.is_anonymous(): user = AnonymousUser(request) @@ -45,7 +47,7 @@ def get_cached_user(request): class AuthenticationMiddleware(DjangoAuthenticationMiddleware): def process_request(self, request): - assert hasattr(request, 'session'), ( + assert hasattr(request, "session"), ( "The Django authentication middleware requires session middleware " "to be installed. Edit your MIDDLEWARE_CLASSES setting to insert " "'django.contrib.sessions.middleware.SessionMiddleware' before " diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 4f611246..190ace89 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -12,169 +12,559 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('auth', '0006_require_contenttypes_0002'), - ] + dependencies = [("auth", "0006_require_contenttypes_0002")] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), - ('username', models.CharField(help_text='Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=254, error_messages={'unique': 'A user with that username already exists.'}, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')])), - ('first_name', models.CharField(max_length=64, verbose_name='first name')), - ('last_name', models.CharField(max_length=64, verbose_name='last name')), - ('email', models.EmailField(unique=True, max_length=254, verbose_name='email address')), - ('date_of_birth', models.DateField(null=True, verbose_name='date of birth', blank=True)), - ('nick_name', models.CharField(max_length=64, null=True, verbose_name='nick name', blank=True)), - ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), - ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), - ('date_joined', models.DateField(auto_now_add=True, verbose_name='date joined')), - ('last_update', models.DateField(verbose_name='last update', auto_now=True)), - ('is_superuser', models.BooleanField(help_text='Designates whether this user is a superuser. ', verbose_name='superuser', default=False)), - ('sex', models.CharField(choices=[('MAN', 'Man'), ('WOMAN', 'Woman')], max_length=10, default='MAN', verbose_name='sex')), - ('tshirt_size', models.CharField(choices=[('-', '-'), ('XS', 'XS'), ('S', 'S'), ('M', 'M'), ('L', 'L'), ('XL', 'XL'), ('XXL', 'XXL'), ('XXXL', 'XXXL')], max_length=5, default='-', verbose_name='tshirt size')), - ('role', models.CharField(choices=[('STUDENT', 'Student'), ('ADMINISTRATIVE', 'Administrative agent'), ('TEACHER', 'Teacher'), ('AGENT', 'Agent'), ('DOCTOR', 'Doctor'), ('FORMER STUDENT', 'Former student'), ('SERVICE', 'Service')], max_length=15, blank=True, verbose_name='role', default='')), - ('department', models.CharField(choices=[('TC', 'TC'), ('IMSI', 'IMSI'), ('IMAP', 'IMAP'), ('INFO', 'INFO'), ('GI', 'GI'), ('E', 'E'), ('EE', 'EE'), ('GESC', 'GESC'), ('GMC', 'GMC'), ('MC', 'MC'), ('EDIM', 'EDIM'), ('HUMA', 'Humanities'), ('NA', 'N/A')], max_length=15, blank=True, verbose_name='department', default='NA')), - ('dpt_option', models.CharField(max_length=32, blank=True, verbose_name='dpt option', default='')), - ('semester', models.CharField(max_length=5, blank=True, verbose_name='semester', default='')), - ('quote', models.CharField(max_length=256, blank=True, verbose_name='quote', default='')), - ('school', models.CharField(max_length=80, blank=True, verbose_name='school', default='')), - ('promo', models.IntegerField(null=True, verbose_name='promo', validators=[core.models.validate_promo], blank=True)), - ('forum_signature', models.TextField(max_length=256, blank=True, verbose_name='forum signature', default='')), - ('second_email', models.EmailField(max_length=254, null=True, verbose_name='second email address', blank=True)), - ('phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, verbose_name='phone', blank=True)), - ('parent_phone', phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, verbose_name='parent phone', blank=True)), - ('address', models.CharField(max_length=128, blank=True, verbose_name='address', default='')), - ('parent_address', models.CharField(max_length=128, blank=True, verbose_name='parent address', default='')), - ('is_subscriber_viewable', models.BooleanField(verbose_name='is subscriber viewable', default=True)), - ], - options={ - 'abstract': False, - }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + null=True, verbose_name="last login", blank=True + ), + ), + ( + "username", + models.CharField( + help_text="Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.", + unique=True, + max_length=254, + error_messages={ + "unique": "A user with that username already exists." + }, + verbose_name="username", + validators=[ + django.core.validators.RegexValidator( + "^[\\w.@+-]+$", + "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.", + ) + ], + ), + ), + ( + "first_name", + models.CharField(max_length=64, verbose_name="first name"), + ), + ( + "last_name", + models.CharField(max_length=64, verbose_name="last name"), + ), + ( + "email", + models.EmailField( + unique=True, max_length=254, verbose_name="email address" + ), + ), + ( + "date_of_birth", + models.DateField( + null=True, verbose_name="date of birth", blank=True + ), + ), + ( + "nick_name", + models.CharField( + max_length=64, null=True, verbose_name="nick name", blank=True + ), + ), + ( + "is_staff", + models.BooleanField( + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + default=False, + ), + ), + ( + "is_active", + models.BooleanField( + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + default=True, + ), + ), + ( + "date_joined", + models.DateField(auto_now_add=True, verbose_name="date joined"), + ), + ( + "last_update", + models.DateField(verbose_name="last update", auto_now=True), + ), + ( + "is_superuser", + models.BooleanField( + help_text="Designates whether this user is a superuser. ", + verbose_name="superuser", + default=False, + ), + ), + ( + "sex", + models.CharField( + choices=[("MAN", "Man"), ("WOMAN", "Woman")], + max_length=10, + default="MAN", + verbose_name="sex", + ), + ), + ( + "tshirt_size", + models.CharField( + choices=[ + ("-", "-"), + ("XS", "XS"), + ("S", "S"), + ("M", "M"), + ("L", "L"), + ("XL", "XL"), + ("XXL", "XXL"), + ("XXXL", "XXXL"), + ], + max_length=5, + default="-", + verbose_name="tshirt size", + ), + ), + ( + "role", + models.CharField( + choices=[ + ("STUDENT", "Student"), + ("ADMINISTRATIVE", "Administrative agent"), + ("TEACHER", "Teacher"), + ("AGENT", "Agent"), + ("DOCTOR", "Doctor"), + ("FORMER STUDENT", "Former student"), + ("SERVICE", "Service"), + ], + max_length=15, + blank=True, + verbose_name="role", + default="", + ), + ), + ( + "department", + models.CharField( + choices=[ + ("TC", "TC"), + ("IMSI", "IMSI"), + ("IMAP", "IMAP"), + ("INFO", "INFO"), + ("GI", "GI"), + ("E", "E"), + ("EE", "EE"), + ("GESC", "GESC"), + ("GMC", "GMC"), + ("MC", "MC"), + ("EDIM", "EDIM"), + ("HUMA", "Humanities"), + ("NA", "N/A"), + ], + max_length=15, + blank=True, + verbose_name="department", + default="NA", + ), + ), + ( + "dpt_option", + models.CharField( + max_length=32, blank=True, verbose_name="dpt option", default="" + ), + ), + ( + "semester", + models.CharField( + max_length=5, blank=True, verbose_name="semester", default="" + ), + ), + ( + "quote", + models.CharField( + max_length=256, blank=True, verbose_name="quote", default="" + ), + ), + ( + "school", + models.CharField( + max_length=80, blank=True, verbose_name="school", default="" + ), + ), + ( + "promo", + models.IntegerField( + null=True, + verbose_name="promo", + validators=[core.models.validate_promo], + blank=True, + ), + ), + ( + "forum_signature", + models.TextField( + max_length=256, + blank=True, + verbose_name="forum signature", + default="", + ), + ), + ( + "second_email", + models.EmailField( + max_length=254, + null=True, + verbose_name="second email address", + blank=True, + ), + ), + ( + "phone", + phonenumber_field.modelfields.PhoneNumberField( + max_length=128, null=True, verbose_name="phone", blank=True + ), + ), + ( + "parent_phone", + phonenumber_field.modelfields.PhoneNumberField( + max_length=128, + null=True, + verbose_name="parent phone", + blank=True, + ), + ), + ( + "address", + models.CharField( + max_length=128, blank=True, verbose_name="address", default="" + ), + ), + ( + "parent_address", + models.CharField( + max_length=128, + blank=True, + verbose_name="parent address", + default="", + ), + ), + ( + "is_subscriber_viewable", + models.BooleanField( + verbose_name="is subscriber viewable", default=True + ), + ), ], + options={"abstract": False}, + managers=[("objects", django.contrib.auth.models.UserManager())], ), migrations.CreateModel( - name='Group', + name="Group", fields=[ - ('group_ptr', models.OneToOneField(primary_key=True, parent_link=True, serialize=False, to='auth.Group', auto_created=True)), - ('is_meta', models.BooleanField(help_text='Whether a group is a meta group or not', verbose_name='meta group status', default=False)), - ('description', models.CharField(max_length=60, verbose_name='description')), + ( + "group_ptr", + models.OneToOneField( + primary_key=True, + parent_link=True, + serialize=False, + to="auth.Group", + auto_created=True, + ), + ), + ( + "is_meta", + models.BooleanField( + help_text="Whether a group is a meta group or not", + verbose_name="meta group status", + default=False, + ), + ), + ( + "description", + models.CharField(max_length=60, verbose_name="description"), + ), ], - bases=('auth.group',), + bases=("auth.group",), ), migrations.CreateModel( - name='Page', + name="Page", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='page name')), - ('_full_name', models.CharField(max_length=255, blank=True, verbose_name='page name')), - ('edit_groups', models.ManyToManyField(related_name='editable_page', to='core.Group', blank=True, verbose_name='edit group')), - ('owner_group', models.ForeignKey(default=1, related_name='owned_page', verbose_name='owner group', to='core.Group')), - ('parent', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children', verbose_name='parent', to='core.Page', blank=True)), - ('view_groups', models.ManyToManyField(related_name='viewable_page', to='core.Group', blank=True, verbose_name='view group')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="page name")), + ( + "_full_name", + models.CharField( + max_length=255, blank=True, verbose_name="page name" + ), + ), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_page", + to="core.Group", + blank=True, + verbose_name="edit group", + ), + ), + ( + "owner_group", + models.ForeignKey( + default=1, + related_name="owned_page", + verbose_name="owner group", + to="core.Group", + ), + ), + ( + "parent", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="children", + verbose_name="parent", + to="core.Page", + blank=True, + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_page", + to="core.Group", + blank=True, + verbose_name="view group", + ), + ), ], options={ - 'permissions': (('change_prop_page', "Can change the page's properties (groups, ...)"), ('view_page', 'Can view the page')), + "permissions": ( + ( + "change_prop_page", + "Can change the page's properties (groups, ...)", + ), + ("view_page", "Can view the page"), + ) }, ), migrations.CreateModel( - name='PageRev', + name="PageRev", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('revision', models.IntegerField(verbose_name='revision')), - ('title', models.CharField(max_length=255, blank=True, verbose_name='page title')), - ('content', models.TextField(blank=True, verbose_name='page content')), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='page_rev')), - ('page', models.ForeignKey(to='core.Page', related_name='revisions')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("revision", models.IntegerField(verbose_name="revision")), + ( + "title", + models.CharField( + max_length=255, blank=True, verbose_name="page title" + ), + ), + ("content", models.TextField(blank=True, verbose_name="page content")), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "author", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, related_name="page_rev" + ), + ), + ("page", models.ForeignKey(to="core.Page", related_name="revisions")), ], - options={ - 'ordering': ['date'], - }, + options={"ordering": ["date"]}, ), migrations.CreateModel( - name='Preferences', + name="Preferences", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('show_my_stats', models.BooleanField(help_text='Show your account statistics to others', verbose_name='define if we show a users stats', default=False)), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='preferences')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "show_my_stats", + models.BooleanField( + help_text="Show your account statistics to others", + verbose_name="define if we show a users stats", + default=False, + ), + ), + ( + "user", + models.OneToOneField( + to=settings.AUTH_USER_MODEL, related_name="preferences" + ), + ), ], ), migrations.CreateModel( - name='SithFile', + name="SithFile", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='file name')), - ('file', models.FileField(upload_to=core.models.get_directory, null=True, verbose_name='file', blank=True)), - ('is_folder', models.BooleanField(verbose_name='is folder', default=True)), - ('mime_type', models.CharField(max_length=30, verbose_name='mime type')), - ('size', models.IntegerField(default=0, verbose_name='size')), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('edit_groups', models.ManyToManyField(related_name='editable_files', to='core.Group', blank=True, verbose_name='edit group')), - ('owner', models.ForeignKey(verbose_name='owner', to=settings.AUTH_USER_MODEL, related_name='owned_files')), - ('parent', models.ForeignKey(null=True, related_name='children', verbose_name='parent', to='core.SithFile', blank=True)), - ('view_groups', models.ManyToManyField(related_name='viewable_files', to='core.Group', blank=True, verbose_name='view group')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="file name")), + ( + "file", + models.FileField( + upload_to=core.models.get_directory, + null=True, + verbose_name="file", + blank=True, + ), + ), + ( + "is_folder", + models.BooleanField(verbose_name="is folder", default=True), + ), + ( + "mime_type", + models.CharField(max_length=30, verbose_name="mime type"), + ), + ("size", models.IntegerField(default=0, verbose_name="size")), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_files", + to="core.Group", + blank=True, + verbose_name="edit group", + ), + ), + ( + "owner", + models.ForeignKey( + verbose_name="owner", + to=settings.AUTH_USER_MODEL, + related_name="owned_files", + ), + ), + ( + "parent", + models.ForeignKey( + null=True, + related_name="children", + verbose_name="parent", + to="core.SithFile", + blank=True, + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_files", + to="core.Group", + blank=True, + verbose_name="view group", + ), + ), ], - options={ - 'verbose_name': 'file', - }, + options={"verbose_name": "file"}, ), migrations.AddField( - model_name='user', - name='avatar_pict', - field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='avatar_of', verbose_name='avatar', to='core.SithFile'), + model_name="user", + name="avatar_pict", + field=models.OneToOneField( + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="avatar_of", + verbose_name="avatar", + to="core.SithFile", + ), ), migrations.AddField( - model_name='user', - name='home', - field=models.OneToOneField(blank=True, null=True, related_name='home_of', verbose_name='home', to='core.SithFile'), + model_name="user", + name="home", + field=models.OneToOneField( + blank=True, + null=True, + related_name="home_of", + verbose_name="home", + to="core.SithFile", + ), ), migrations.AddField( - model_name='user', - name='profile_pict', - field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='profile_of', verbose_name='profile', to='core.SithFile'), + model_name="user", + name="profile_pict", + field=models.OneToOneField( + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="profile_of", + verbose_name="profile", + to="core.SithFile", + ), ), migrations.AddField( - model_name='user', - name='scrub_pict', - field=models.OneToOneField(blank=True, on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='scrub_of', verbose_name='scrub', to='core.SithFile'), + model_name="user", + name="scrub_pict", + field=models.OneToOneField( + blank=True, + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="scrub_of", + verbose_name="scrub", + to="core.SithFile", + ), ), migrations.CreateModel( - name='MetaGroup', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.group',), - managers=[ - ('objects', core.models.MetaGroupManager()), - ], + name="MetaGroup", + fields=[], + options={"proxy": True}, + bases=("core.group",), + managers=[("objects", core.models.MetaGroupManager())], ), migrations.CreateModel( - name='RealGroup', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.group',), - managers=[ - ('objects', core.models.RealGroupManager()), - ], + name="RealGroup", + fields=[], + options={"proxy": True}, + bases=("core.group",), + managers=[("objects", core.models.RealGroupManager())], ), migrations.AlterUniqueTogether( - name='page', - unique_together=set([('name', 'parent')]), + name="page", unique_together=set([("name", "parent")]) ), migrations.AddField( - model_name='user', - name='groups', - field=models.ManyToManyField(to='core.RealGroup', blank=True, related_name='users'), + model_name="user", + name="groups", + field=models.ManyToManyField( + to="core.RealGroup", blank=True, related_name="users" + ), ), ] diff --git a/core/migrations/0002_auto_20160831_0144.py b/core/migrations/0002_auto_20160831_0144.py index a2b49e2b..cc5fa3f1 100644 --- a/core/migrations/0002_auto_20160831_0144.py +++ b/core/migrations/0002_auto_20160831_0144.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0001_initial'), - ] + dependencies = [("core", "0001_initial")] operations = [ migrations.AlterField( - model_name='sithfile', - name='name', - field=models.CharField(verbose_name='file name', max_length=256), - ), + model_name="sithfile", + name="name", + field=models.CharField(verbose_name="file name", max_length=256), + ) ] diff --git a/core/migrations/0003_auto_20160902_1914.py b/core/migrations/0003_auto_20160902_1914.py index f5d2c28b..fa6274c4 100644 --- a/core/migrations/0003_auto_20160902_1914.py +++ b/core/migrations/0003_auto_20160902_1914.py @@ -7,14 +7,24 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ('core', '0002_auto_20160831_0144'), - ] + dependencies = [("core", "0002_auto_20160831_0144")] operations = [ migrations.AlterField( - model_name='user', - name='username', - field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=254, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.+-]+$', 'Enter a valid username. This value may contain only letters, numbers and ./+/-/_ characters.')], help_text='Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.', verbose_name='username'), - ), + model_name="user", + name="username", + field=models.CharField( + error_messages={"unique": "A user with that username already exists."}, + max_length=254, + unique=True, + validators=[ + django.core.validators.RegexValidator( + "^[\\w.+-]+$", + "Enter a valid username. This value may contain only letters, numbers and ./+/-/_ characters.", + ) + ], + help_text="Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.", + verbose_name="username", + ), + ) ] diff --git a/core/migrations/0004_user_godfathers.py b/core/migrations/0004_user_godfathers.py index 3bb1f8e6..e3e33d2c 100644 --- a/core/migrations/0004_user_godfathers.py +++ b/core/migrations/0004_user_godfathers.py @@ -7,14 +7,14 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0003_auto_20160902_1914'), - ] + dependencies = [("core", "0003_auto_20160902_1914")] operations = [ migrations.AddField( - model_name='user', - name='godfathers', - field=models.ManyToManyField(to=settings.AUTH_USER_MODEL, related_name='godchildren', blank=True), - ), + model_name="user", + name="godfathers", + field=models.ManyToManyField( + to=settings.AUTH_USER_MODEL, related_name="godchildren", blank=True + ), + ) ] diff --git a/core/migrations/0005_auto_20161105_1035.py b/core/migrations/0005_auto_20161105_1035.py index da6241d4..803b373a 100644 --- a/core/migrations/0005_auto_20161105_1035.py +++ b/core/migrations/0005_auto_20161105_1035.py @@ -7,19 +7,26 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0004_user_godfathers'), - ] + dependencies = [("core", "0004_user_godfathers")] operations = [ migrations.AddField( - model_name='page', - name='lock_timeout', - field=models.DateTimeField(verbose_name='lock_timeout', null=True, blank=True, default=None), + model_name="page", + name="lock_timeout", + field=models.DateTimeField( + verbose_name="lock_timeout", null=True, blank=True, default=None + ), ), migrations.AddField( - model_name='page', - name='lock_user', - field=models.ForeignKey(verbose_name='lock user', default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True, related_name='locked_pages'), + model_name="page", + name="lock_user", + field=models.ForeignKey( + verbose_name="lock user", + default=None, + blank=True, + to=settings.AUTH_USER_MODEL, + null=True, + related_name="locked_pages", + ), ), ] diff --git a/core/migrations/0006_auto_20161108_1703.py b/core/migrations/0006_auto_20161108_1703.py index e9c2a04d..21b5a969 100644 --- a/core/migrations/0006_auto_20161108_1703.py +++ b/core/migrations/0006_auto_20161108_1703.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0005_auto_20161105_1035'), - ] + dependencies = [("core", "0005_auto_20161105_1035")] operations = [ migrations.AddField( - model_name='sithfile', - name='is_moderated', - field=models.BooleanField(verbose_name='is moderated', default=False), - ), + model_name="sithfile", + name="is_moderated", + field=models.BooleanField(verbose_name="is moderated", default=False), + ) ] diff --git a/core/migrations/0008_sithfile_asked_for_removal.py b/core/migrations/0008_sithfile_asked_for_removal.py index 79ae887b..db3bc23d 100644 --- a/core/migrations/0008_sithfile_asked_for_removal.py +++ b/core/migrations/0008_sithfile_asked_for_removal.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0006_auto_20161108_1703'), - ] + dependencies = [("core", "0006_auto_20161108_1703")] operations = [ migrations.AddField( - model_name='sithfile', - name='asked_for_removal', - field=models.BooleanField(default=False, verbose_name='asked for removal'), - ), + model_name="sithfile", + name="asked_for_removal", + field=models.BooleanField(default=False, verbose_name="asked for removal"), + ) ] diff --git a/core/migrations/0009_auto_20161120_1155.py b/core/migrations/0009_auto_20161120_1155.py index 524e29da..b2fccdf4 100644 --- a/core/migrations/0009_auto_20161120_1155.py +++ b/core/migrations/0009_auto_20161120_1155.py @@ -8,24 +8,39 @@ import core.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0008_sithfile_asked_for_removal'), - ] + dependencies = [("core", "0008_sithfile_asked_for_removal")] operations = [ migrations.AddField( - model_name='sithfile', - name='compressed', - field=models.FileField(upload_to=core.models.get_compressed_directory, null=True, verbose_name='compressed file', blank=True), + model_name="sithfile", + name="compressed", + field=models.FileField( + upload_to=core.models.get_compressed_directory, + null=True, + verbose_name="compressed file", + blank=True, + ), ), migrations.AddField( - model_name='sithfile', - name='thumbnail', - field=models.FileField(upload_to=core.models.get_thumbnail_directory, null=True, verbose_name='thumbnail', blank=True), + model_name="sithfile", + name="thumbnail", + field=models.FileField( + upload_to=core.models.get_thumbnail_directory, + null=True, + verbose_name="thumbnail", + blank=True, + ), ), migrations.AlterField( - model_name='user', - name='home', - field=models.OneToOneField(verbose_name='home', related_name='home_of', on_delete=django.db.models.deletion.SET_NULL, null=True, to='core.SithFile', blank=True), + model_name="user", + name="home", + field=models.OneToOneField( + verbose_name="home", + related_name="home_of", + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="core.SithFile", + blank=True, + ), ), ] diff --git a/core/migrations/0010_sithfile_is_in_sas.py b/core/migrations/0010_sithfile_is_in_sas.py index 2a3237be..38664c6f 100644 --- a/core/migrations/0010_sithfile_is_in_sas.py +++ b/core/migrations/0010_sithfile_is_in_sas.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0009_auto_20161120_1155'), - ] + dependencies = [("core", "0009_auto_20161120_1155")] operations = [ migrations.AddField( - model_name='sithfile', - name='is_in_sas', - field=models.BooleanField(verbose_name='is in the SAS', default=False), - ), + model_name="sithfile", + name="is_in_sas", + field=models.BooleanField(verbose_name="is in the SAS", default=False), + ) ] diff --git a/core/migrations/0011_auto_20161124_0848.py b/core/migrations/0011_auto_20161124_0848.py index 8baba108..a972bff6 100644 --- a/core/migrations/0011_auto_20161124_0848.py +++ b/core/migrations/0011_auto_20161124_0848.py @@ -8,29 +8,47 @@ import core.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0010_sithfile_is_in_sas'), - ] + dependencies = [("core", "0010_sithfile_is_in_sas")] operations = [ migrations.AlterField( - model_name='sithfile', - name='compressed', - field=models.FileField(verbose_name='compressed file', upload_to=core.models.get_compressed_directory, null=True, blank=True, max_length=256), + model_name="sithfile", + name="compressed", + field=models.FileField( + verbose_name="compressed file", + upload_to=core.models.get_compressed_directory, + null=True, + blank=True, + max_length=256, + ), ), migrations.AlterField( - model_name='sithfile', - name='date', - field=models.DateTimeField(verbose_name='date', default=django.utils.timezone.now), + model_name="sithfile", + name="date", + field=models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), ), migrations.AlterField( - model_name='sithfile', - name='file', - field=models.FileField(verbose_name='file', upload_to=core.models.get_directory, null=True, blank=True, max_length=256), + model_name="sithfile", + name="file", + field=models.FileField( + verbose_name="file", + upload_to=core.models.get_directory, + null=True, + blank=True, + max_length=256, + ), ), migrations.AlterField( - model_name='sithfile', - name='thumbnail', - field=models.FileField(verbose_name='thumbnail', upload_to=core.models.get_thumbnail_directory, null=True, blank=True, max_length=256), + model_name="sithfile", + name="thumbnail", + field=models.FileField( + verbose_name="thumbnail", + upload_to=core.models.get_thumbnail_directory, + null=True, + blank=True, + max_length=256, + ), ), ] diff --git a/core/migrations/0012_notification.py b/core/migrations/0012_notification.py index 01a9308b..f48e4063 100644 --- a/core/migrations/0012_notification.py +++ b/core/migrations/0012_notification.py @@ -8,20 +8,49 @@ import django.utils.timezone class Migration(migrations.Migration): - dependencies = [ - ('core', '0011_auto_20161124_0848'), - ] + dependencies = [("core", "0011_auto_20161124_0848")] operations = [ migrations.CreateModel( - name='Notification', + name="Notification", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('url', models.CharField(max_length=255, verbose_name='url')), - ('text', models.CharField(max_length=512, verbose_name='text')), - ('type', models.CharField(max_length=16, choices=[('FILE_MODERATION', 'File moderation'), ('SAS_MODERATION', 'SAS moderation'), ('NEW_PICTURES', 'New pictures')], verbose_name='text', null=True, blank=True)), - ('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), - ('user', models.ForeignKey(related_name='notifications', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + auto_created=True, + serialize=False, + ), + ), + ("url", models.CharField(max_length=255, verbose_name="url")), + ("text", models.CharField(max_length=512, verbose_name="text")), + ( + "type", + models.CharField( + max_length=16, + choices=[ + ("FILE_MODERATION", "File moderation"), + ("SAS_MODERATION", "SAS moderation"), + ("NEW_PICTURES", "New pictures"), + ], + verbose_name="text", + null=True, + blank=True, + ), + ), + ( + "date", + models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), + ), + ( + "user", + models.ForeignKey( + related_name="notifications", to=settings.AUTH_USER_MODEL + ), + ), ], - ), + ) ] diff --git a/core/migrations/0013_auto_20161209_2338.py b/core/migrations/0013_auto_20161209_2338.py index e0c6cff8..e0475c5c 100644 --- a/core/migrations/0013_auto_20161209_2338.py +++ b/core/migrations/0013_auto_20161209_2338.py @@ -6,23 +6,18 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0012_notification'), - ] + dependencies = [("core", "0012_notification")] operations = [ - migrations.RemoveField( - model_name='notification', - name='text', + migrations.RemoveField(model_name="notification", name="text"), + migrations.AddField( + model_name="notification", + name="param", + field=models.CharField(verbose_name="param", default="", max_length=128), ), migrations.AddField( - model_name='notification', - name='param', - field=models.CharField(verbose_name='param', default='', max_length=128), - ), - migrations.AddField( - model_name='notification', - name='viewed', - field=models.BooleanField(verbose_name='viewed', default=False), + model_name="notification", + name="viewed", + field=models.BooleanField(verbose_name="viewed", default=False), ), ] diff --git a/core/migrations/0014_auto_20161210_0009.py b/core/migrations/0014_auto_20161210_0009.py index c6f8db7b..0c17aec7 100644 --- a/core/migrations/0014_auto_20161210_0009.py +++ b/core/migrations/0014_auto_20161210_0009.py @@ -6,14 +6,24 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0013_auto_20161209_2338'), - ] + dependencies = [("core", "0013_auto_20161209_2338")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(verbose_name='type', max_length=32, default='GENERIC', choices=[('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), - ), + model_name="notification", + name="type", + field=models.CharField( + verbose_name="type", + max_length=32, + default="GENERIC", + choices=[ + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + ), + ) ] diff --git a/core/migrations/0015_sithfile_moderator.py b/core/migrations/0015_sithfile_moderator.py index 753c82f7..00e2f1f9 100644 --- a/core/migrations/0015_sithfile_moderator.py +++ b/core/migrations/0015_sithfile_moderator.py @@ -7,15 +7,18 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0014_auto_20161210_0009'), - ] + dependencies = [("core", "0014_auto_20161210_0009")] operations = [ migrations.AddField( - model_name='sithfile', - name='moderator', - field=models.ForeignKey(related_name='moderated_files', verbose_name='owner', default=0, to=settings.AUTH_USER_MODEL), + model_name="sithfile", + name="moderator", + field=models.ForeignKey( + related_name="moderated_files", + verbose_name="owner", + default=0, + to=settings.AUTH_USER_MODEL, + ), preserve_default=False, - ), + ) ] diff --git a/core/migrations/0016_auto_20161212_1922.py b/core/migrations/0016_auto_20161212_1922.py index 6785eb32..e98f06d6 100644 --- a/core/migrations/0016_auto_20161212_1922.py +++ b/core/migrations/0016_auto_20161212_1922.py @@ -7,14 +7,18 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0015_sithfile_moderator'), - ] + dependencies = [("core", "0015_sithfile_moderator")] operations = [ migrations.AlterField( - model_name='sithfile', - name='moderator', - field=models.ForeignKey(related_name='moderated_files', blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='owner'), - ), + model_name="sithfile", + name="moderator", + field=models.ForeignKey( + related_name="moderated_files", + blank=True, + null=True, + to=settings.AUTH_USER_MODEL, + verbose_name="owner", + ), + ) ] diff --git a/core/migrations/0017_auto_20161220_1626.py b/core/migrations/0017_auto_20161220_1626.py index 89e9bd3a..51af77e5 100644 --- a/core/migrations/0017_auto_20161220_1626.py +++ b/core/migrations/0017_auto_20161220_1626.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0016_auto_20161212_1922'), - ] + dependencies = [("core", "0016_auto_20161212_1922")] operations = [ migrations.AlterField( - model_name='user', - name='last_update', - field=models.DateTimeField(verbose_name='last update', auto_now=True), - ), + model_name="user", + name="last_update", + field=models.DateTimeField(verbose_name="last update", auto_now=True), + ) ] diff --git a/core/migrations/0018_auto_20161224_0211.py b/core/migrations/0018_auto_20161224_0211.py index ed5db107..a3daf6f6 100644 --- a/core/migrations/0018_auto_20161224_0211.py +++ b/core/migrations/0018_auto_20161224_0211.py @@ -6,14 +6,25 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0017_auto_20161220_1626'), - ] + dependencies = [("core", "0017_auto_20161220_1626")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], default='GENERIC', max_length=32, verbose_name='type'), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("NEWS_MODERATION", "A fresh new to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + default="GENERIC", + max_length=32, + verbose_name="type", + ), + ) ] diff --git a/core/migrations/0019_preferences_receive_weekmail.py b/core/migrations/0019_preferences_receive_weekmail.py index 7f7b117a..951488aa 100644 --- a/core/migrations/0019_preferences_receive_weekmail.py +++ b/core/migrations/0019_preferences_receive_weekmail.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0018_auto_20161224_0211'), - ] + dependencies = [("core", "0018_auto_20161224_0211")] operations = [ migrations.AddField( - model_name='preferences', - name='receive_weekmail', - field=models.BooleanField(default=False, verbose_name='do you want to receive the weekmail'), - ), + model_name="preferences", + name="receive_weekmail", + field=models.BooleanField( + default=False, verbose_name="do you want to receive the weekmail" + ), + ) ] diff --git a/core/migrations/0020_auto_20170324_0917.py b/core/migrations/0020_auto_20170324_0917.py index 8867e399..09b390f3 100644 --- a/core/migrations/0020_auto_20170324_0917.py +++ b/core/migrations/0020_auto_20170324_0917.py @@ -7,18 +7,22 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ('core', '0019_preferences_receive_weekmail'), - ] + dependencies = [("core", "0019_preferences_receive_weekmail")] operations = [ - migrations.AlterModelOptions( - name='group', - options={'ordering': ['name']}, - ), + migrations.AlterModelOptions(name="group", options={"ordering": ["name"]}), migrations.AlterField( - model_name='page', - name='name', - field=models.CharField(validators=[django.core.validators.RegexValidator('^[A-z.+-]+$', 'Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.')], max_length=30, verbose_name='page unix name'), + model_name="page", + name="name", + field=models.CharField( + validators=[ + django.core.validators.RegexValidator( + "^[A-z.+-]+$", + "Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.", + ) + ], + max_length=30, + verbose_name="page unix name", + ), ), ] diff --git a/core/migrations/0021_auto_20170822_1529.py b/core/migrations/0021_auto_20170822_1529.py index b38e737d..105d280c 100644 --- a/core/migrations/0021_auto_20170822_1529.py +++ b/core/migrations/0021_auto_20170822_1529.py @@ -6,14 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0020_auto_20170324_0917'), - ] + dependencies = [("core", "0020_auto_20170324_0917")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(verbose_name='type', default='GENERIC', max_length=32, choices=[('MAILING_MODERATION', 'A new mailing list neet to be moderated'), ('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), - ), + model_name="notification", + name="type", + field=models.CharField( + verbose_name="type", + default="GENERIC", + max_length=32, + choices=[ + ("MAILING_MODERATION", "A new mailing list neet to be moderated"), + ("NEWS_MODERATION", "A fresh new to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + ), + ) ] diff --git a/core/migrations/0022_auto_20170822_2232.py b/core/migrations/0022_auto_20170822_2232.py index 111ea439..787efb81 100644 --- a/core/migrations/0022_auto_20170822_2232.py +++ b/core/migrations/0022_auto_20170822_2232.py @@ -6,14 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0021_auto_20170822_1529'), - ] + dependencies = [("core", "0021_auto_20170822_1529")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'A fresh new to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], default='GENERIC', max_length=32, verbose_name='type'), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "A fresh new to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + default="GENERIC", + max_length=32, + verbose_name="type", + ), + ) ] diff --git a/core/migrations/0023_auto_20170902_1226.py b/core/migrations/0023_auto_20170902_1226.py index 0398f09e..f932cba9 100644 --- a/core/migrations/0023_auto_20170902_1226.py +++ b/core/migrations/0023_auto_20170902_1226.py @@ -7,29 +7,35 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - ('core', '0022_auto_20170822_2232'), - ] + dependencies = [("core", "0022_auto_20170822_2232")] operations = [ migrations.AddField( - model_name='preferences', - name='notify_on_click', - field=models.BooleanField(verbose_name='get a notification for every click', default=False), + model_name="preferences", + name="notify_on_click", + field=models.BooleanField( + verbose_name="get a notification for every click", default=False + ), ), migrations.AddField( - model_name='preferences', - name='notify_on_refill', - field=models.BooleanField(verbose_name='get a notification for every refilling', default=False), + model_name="preferences", + name="notify_on_refill", + field=models.BooleanField( + verbose_name="get a notification for every refilling", default=False + ), ), migrations.AlterField( - model_name='preferences', - name='show_my_stats', - field=models.BooleanField(verbose_name='show your stats to others', default=False), + model_name="preferences", + name="show_my_stats", + field=models.BooleanField( + verbose_name="show your stats to others", default=False + ), ), migrations.AlterField( - model_name='preferences', - name='user', - field=models.OneToOneField(related_name='_preferences', to=settings.AUTH_USER_MODEL), + model_name="preferences", + name="user", + field=models.OneToOneField( + related_name="_preferences", to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/core/migrations/0024_auto_20170906_1317.py b/core/migrations/0024_auto_20170906_1317.py index a226d268..1bd51690 100644 --- a/core/migrations/0024_auto_20170906_1317.py +++ b/core/migrations/0024_auto_20170906_1317.py @@ -6,14 +6,26 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0023_auto_20170902_1226'), - ] + dependencies = [("core", "0023_auto_20170902_1226")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'New pictures/album to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], verbose_name='type', default='GENERIC', max_length=32), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "There are %s fresh news to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ("SAS_MODERATION", "New pictures/album to be moderated in the SAS"), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + verbose_name="type", + default="GENERIC", + max_length=32, + ), + ) ] diff --git a/core/migrations/0025_auto_20170919_1521.py b/core/migrations/0025_auto_20170919_1521.py index 039f9dd2..36370264 100644 --- a/core/migrations/0025_auto_20170919_1521.py +++ b/core/migrations/0025_auto_20170919_1521.py @@ -7,14 +7,21 @@ import django.core.validators class Migration(migrations.Migration): - dependencies = [ - ('core', '0024_auto_20170906_1317'), - ] + dependencies = [("core", "0024_auto_20170906_1317")] operations = [ migrations.AlterField( - model_name='page', - name='name', - field=models.CharField(max_length=30, verbose_name='page unix name', validators=[django.core.validators.RegexValidator('^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$', 'Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.')]), - ), + model_name="page", + name="name", + field=models.CharField( + max_length=30, + verbose_name="page unix name", + validators=[ + django.core.validators.RegexValidator( + "^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$", + "Enter a valid page name. This value may contain only unaccented letters, numbers and ./+/-/_ characters.", + ) + ], + ), + ) ] diff --git a/core/migrations/0026_auto_20170926_1512.py b/core/migrations/0026_auto_20170926_1512.py index 03e90c4b..02ddbad5 100644 --- a/core/migrations/0026_auto_20170926_1512.py +++ b/core/migrations/0026_auto_20170926_1512.py @@ -6,14 +6,29 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0025_auto_20170919_1521'), - ] + dependencies = [("core", "0025_auto_20170919_1521")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(choices=[('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')], verbose_name='type', max_length=32, default='GENERIC'), - ), + model_name="notification", + name="type", + field=models.CharField( + choices=[ + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "There are %s fresh news to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ( + "SAS_MODERATION", + "There are %s pictures to be moderated in the SAS", + ), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + verbose_name="type", + max_length=32, + default="GENERIC", + ), + ) ] diff --git a/core/migrations/0027_gift.py b/core/migrations/0027_gift.py index a6c604e8..bfac2175 100644 --- a/core/migrations/0027_gift.py +++ b/core/migrations/0027_gift.py @@ -8,18 +8,34 @@ import django.utils.timezone class Migration(migrations.Migration): - dependencies = [ - ('core', '0026_auto_20170926_1512'), - ] + dependencies = [("core", "0026_auto_20170926_1512")] operations = [ migrations.CreateModel( - name='Gift', + name="Gift", fields=[ - ('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), - ('label', models.CharField(max_length=255, verbose_name='label')), - ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date')), - ('user', models.ForeignKey(related_name='gifts', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + auto_created=True, + verbose_name="ID", + serialize=False, + ), + ), + ("label", models.CharField(max_length=255, verbose_name="label")), + ( + "date", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date" + ), + ), + ( + "user", + models.ForeignKey( + related_name="gifts", to=settings.AUTH_USER_MODEL + ), + ), ], - ), + ) ] diff --git a/core/migrations/0028_auto_20171216_2044.py b/core/migrations/0028_auto_20171216_2044.py index 28403d6b..f54df0e2 100644 --- a/core/migrations/0028_auto_20171216_2044.py +++ b/core/migrations/0028_auto_20171216_2044.py @@ -6,14 +6,30 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0027_gift'), - ] + dependencies = [("core", "0027_gift")] operations = [ migrations.AlterField( - model_name='notification', - name='type', - field=models.CharField(default='GENERIC', verbose_name='type', max_length=32, choices=[('POSTER_MODERATION', 'A new poster needs to be moderated'), ('MAILING_MODERATION', 'A new mailing list needs to be moderated'), ('NEWS_MODERATION', 'There are %s fresh news to be moderated'), ('FILE_MODERATION', 'New files to be moderated'), ('SAS_MODERATION', 'There are %s pictures to be moderated in the SAS'), ('NEW_PICTURES', "You've been identified on some pictures"), ('REFILLING', 'You just refilled of %s €'), ('SELLING', 'You just bought %s'), ('GENERIC', 'You have a notification')]), - ), + model_name="notification", + name="type", + field=models.CharField( + default="GENERIC", + verbose_name="type", + max_length=32, + choices=[ + ("POSTER_MODERATION", "A new poster needs to be moderated"), + ("MAILING_MODERATION", "A new mailing list needs to be moderated"), + ("NEWS_MODERATION", "There are %s fresh news to be moderated"), + ("FILE_MODERATION", "New files to be moderated"), + ( + "SAS_MODERATION", + "There are %s pictures to be moderated in the SAS", + ), + ("NEW_PICTURES", "You've been identified on some pictures"), + ("REFILLING", "You just refilled of %s €"), + ("SELLING", "You just bought %s"), + ("GENERIC", "You have a notification"), + ], + ), + ) ] diff --git a/core/migrations/0029_auto_20180426_2013.py b/core/migrations/0029_auto_20180426_2013.py index fd783652..4f80245d 100644 --- a/core/migrations/0029_auto_20180426_2013.py +++ b/core/migrations/0029_auto_20180426_2013.py @@ -7,14 +7,17 @@ import core.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0028_auto_20171216_2044'), - ] + dependencies = [("core", "0028_auto_20171216_2044")] operations = [ migrations.AlterField( - model_name='page', - name='owner_group', - field=models.ForeignKey(verbose_name='owner group', default=core.models.Page.get_default_owner_group, related_name='owned_page', to='core.Group'), - ), + model_name="page", + name="owner_group", + field=models.ForeignKey( + verbose_name="owner group", + default=core.models.Page.get_default_owner_group, + related_name="owned_page", + to="core.Group", + ), + ) ] diff --git a/core/models.py b/core/models.py index b94ae1ed..0c1b900e 100644 --- a/core/models.py +++ b/core/models.py @@ -26,7 +26,14 @@ import importlib from django.db import models from django.core.mail import send_mail -from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager, Group as AuthGroup, GroupManager as AuthGroupManager, AnonymousUser as AuthAnonymousUser +from django.contrib.auth.models import ( + AbstractBaseUser, + PermissionsMixin, + UserManager, + Group as AuthGroup, + GroupManager as AuthGroupManager, + AnonymousUser as AuthAnonymousUser, +) from django.utils.translation import ugettext_lazy as _ from django.utils import timezone from django.core import validators @@ -59,20 +66,20 @@ class MetaGroupManager(AuthGroupManager): class Group(AuthGroup): is_meta = models.BooleanField( - _('meta group status'), + _("meta group status"), default=False, - help_text=_('Whether a group is a meta group or not'), + help_text=_("Whether a group is a meta group or not"), ) - description = models.CharField(_('description'), max_length=60) + description = models.CharField(_("description"), max_length=60) class Meta: - ordering = ['name'] + ordering = ["name"] def get_absolute_url(self): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:group_list') + return reverse("core:group_list") class MetaGroup(Group): @@ -98,8 +105,8 @@ def validate_promo(value): delta = (date.today() + timedelta(days=180)).year - start_year if value < 0 or delta < value: raise ValidationError( - _('%(value)s is not a valid promo (between 0 and %(end)s)'), - params={'value': value, 'end': delta}, + _("%(value)s is not a valid promo (between 0 and %(end)s)"), + params={"value": value, "end": delta}, ) @@ -114,97 +121,154 @@ class User(AbstractBaseUser): Added field: nick_name, date_of_birth Required fields: email, first_name, last_name, date_of_birth """ + username = models.CharField( - _('username'), + _("username"), max_length=254, unique=True, - help_text=_('Required. 254 characters or fewer. Letters, digits and ./+/-/_ only.'), + help_text=_( + "Required. 254 characters or fewer. Letters, digits and ./+/-/_ only." + ), validators=[ validators.RegexValidator( - r'^[\w.+-]+$', - _('Enter a valid username. This value may contain only ' - 'letters, numbers ' 'and ./+/-/_ characters.') - ), + r"^[\w.+-]+$", + _( + "Enter a valid username. This value may contain only " + "letters, numbers " + "and ./+/-/_ characters." + ), + ) ], - error_messages={ - 'unique': _("A user with that username already exists."), - }, + error_messages={"unique": _("A user with that username already exists.")}, ) - first_name = models.CharField(_('first name'), max_length=64) - last_name = models.CharField(_('last name'), max_length=64) - email = models.EmailField(_('email address'), unique=True) - date_of_birth = models.DateField(_('date of birth'), blank=True, null=True) - nick_name = models.CharField(_('nick name'), max_length=64, null=True, blank=True) + first_name = models.CharField(_("first name"), max_length=64) + last_name = models.CharField(_("last name"), max_length=64) + email = models.EmailField(_("email address"), unique=True) + date_of_birth = models.DateField(_("date of birth"), blank=True, null=True) + nick_name = models.CharField(_("nick name"), max_length=64, null=True, blank=True) is_staff = models.BooleanField( - _('staff status'), + _("staff status"), default=False, - help_text=_('Designates whether the user can log into this admin site.'), + help_text=_("Designates whether the user can log into this admin site."), ) is_active = models.BooleanField( - _('active'), + _("active"), default=True, help_text=_( - 'Designates whether this user should be treated as active. ' - 'Unselect this instead of deleting accounts.' + "Designates whether this user should be treated as active. " + "Unselect this instead of deleting accounts." ), ) - date_joined = models.DateField(_('date joined'), auto_now_add=True) - last_update = models.DateTimeField(_('last update'), auto_now=True) + date_joined = models.DateField(_("date joined"), auto_now_add=True) + last_update = models.DateTimeField(_("last update"), auto_now=True) is_superuser = models.BooleanField( - _('superuser'), + _("superuser"), default=False, - help_text=_( - 'Designates whether this user is a superuser. ' - ), + help_text=_("Designates whether this user is a superuser. "), + ) + groups = models.ManyToManyField(RealGroup, related_name="users", blank=True) + home = models.OneToOneField( + "SithFile", + related_name="home_of", + verbose_name=_("home"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + profile_pict = models.OneToOneField( + "SithFile", + related_name="profile_of", + verbose_name=_("profile"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + avatar_pict = models.OneToOneField( + "SithFile", + related_name="avatar_of", + verbose_name=_("avatar"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + scrub_pict = models.OneToOneField( + "SithFile", + related_name="scrub_of", + verbose_name=_("scrub"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + sex = models.CharField( + _("sex"), + max_length=10, + choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], + default="MAN", + ) + tshirt_size = models.CharField( + _("tshirt size"), + max_length=5, + choices=[ + ("-", _("-")), + ("XS", _("XS")), + ("S", _("S")), + ("M", _("M")), + ("L", _("L")), + ("XL", _("XL")), + ("XXL", _("XXL")), + ("XXXL", _("XXXL")), + ], + default="-", + ) + role = models.CharField( + _("role"), + max_length=15, + choices=[ + ("STUDENT", _("Student")), + ("ADMINISTRATIVE", _("Administrative agent")), + ("TEACHER", _("Teacher")), + ("AGENT", _("Agent")), + ("DOCTOR", _("Doctor")), + ("FORMER STUDENT", _("Former student")), + ("SERVICE", _("Service")), + ], + blank=True, + default="", + ) + department = models.CharField( + _("department"), + max_length=15, + choices=settings.SITH_PROFILE_DEPARTMENTS, + default="NA", + blank=True, + ) + dpt_option = models.CharField( + _("dpt option"), max_length=32, blank=True, default="" ) - groups = models.ManyToManyField(RealGroup, related_name='users', blank=True) - home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True, - on_delete=models.SET_NULL) - profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, - blank=True, on_delete=models.SET_NULL) - avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, - blank=True, on_delete=models.SET_NULL) - scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True, - blank=True, on_delete=models.SET_NULL) - sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN") - tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[ - ("-", _("-")), - ("XS", _("XS")), - ("S", _("S")), - ("M", _("M")), - ("L", _("L")), - ("XL", _("XL")), - ("XXL", _("XXL")), - ("XXXL", _("XXXL")), - ], default="-") - role = models.CharField(_("role"), max_length=15, choices=[ - ("STUDENT", _("Student")), - ("ADMINISTRATIVE", _("Administrative agent")), - ("TEACHER", _("Teacher")), - ("AGENT", _("Agent")), - ("DOCTOR", _("Doctor")), - ("FORMER STUDENT", _("Former student")), - ("SERVICE", _("Service")), - ], blank=True, default="") - department = models.CharField(_("department"), max_length=15, choices=settings.SITH_PROFILE_DEPARTMENTS, - default="NA", blank=True) - dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="") semester = models.CharField(_("semester"), max_length=5, blank=True, default="") quote = models.CharField(_("quote"), max_length=256, blank=True, default="") school = models.CharField(_("school"), max_length=80, blank=True, default="") - promo = models.IntegerField(_("promo"), validators=[validate_promo], null=True, blank=True) - forum_signature = models.TextField(_("forum signature"), max_length=256, blank=True, default="") - second_email = models.EmailField(_('second email address'), null=True, blank=True) + promo = models.IntegerField( + _("promo"), validators=[validate_promo], null=True, blank=True + ) + forum_signature = models.TextField( + _("forum signature"), max_length=256, blank=True, default="" + ) + second_email = models.EmailField(_("second email address"), null=True, blank=True) phone = PhoneNumberField(_("phone"), null=True, blank=True) parent_phone = PhoneNumberField(_("parent phone"), null=True, blank=True) address = models.CharField(_("address"), max_length=128, blank=True, default="") - parent_address = models.CharField(_("parent address"), max_length=128, blank=True, default="") - is_subscriber_viewable = models.BooleanField(_("is subscriber viewable"), default=True) - godfathers = models.ManyToManyField('User', related_name='godchildren', blank=True) + parent_address = models.CharField( + _("parent address"), max_length=128, blank=True, default="" + ) + is_subscriber_viewable = models.BooleanField( + _("is subscriber viewable"), default=True + ) + godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True) objects = UserManager() - USERNAME_FIELD = 'username' + USERNAME_FIELD = "username" # REQUIRED_FIELDS = ['email'] def has_module_perms(self, package_name): @@ -217,7 +281,7 @@ class User(AbstractBaseUser): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:user_profile', kwargs={'user_id': self.pk}) + return reverse("core:user_profile", kwargs={"user_id": self.pk}) def __str__(self): return self.get_display_name() @@ -231,7 +295,9 @@ class User(AbstractBaseUser): @cached_property def is_subscribed(self): - s = self.subscriptions.filter(subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now()) + s = self.subscriptions.filter( + subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now() + ) return s.exists() _club_memberships = {} @@ -265,26 +331,33 @@ class User(AbstractBaseUser): return self.is_subscribed if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID: return self.was_subscribed - if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked + if ( + group_name == settings.SITH_MAIN_MEMBERS_GROUP + ): # We check the subscription if asked return self.is_subscribed - if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX: - name = group_name[:-len(settings.SITH_BOARD_SUFFIX)] + if group_name[-len(settings.SITH_BOARD_SUFFIX) :] == settings.SITH_BOARD_SUFFIX: + name = group_name[: -len(settings.SITH_BOARD_SUFFIX)] if name in User._club_memberships.keys(): mem = User._club_memberships[name] else: from club.models import Club + c = Club.objects.filter(unix_name=name).first() mem = c.get_membership_for(self) User._club_memberships[name] = mem if mem: return mem.role > settings.SITH_MAXIMUM_FREE_ROLE return False - if group_name[-len(settings.SITH_MEMBER_SUFFIX):] == settings.SITH_MEMBER_SUFFIX: - name = group_name[:-len(settings.SITH_MEMBER_SUFFIX)] + if ( + group_name[-len(settings.SITH_MEMBER_SUFFIX) :] + == settings.SITH_MEMBER_SUFFIX + ): + name = group_name[: -len(settings.SITH_MEMBER_SUFFIX)] if name in User._club_memberships.keys(): mem = User._club_memberships[name] else: from club.models import Club + c = Club.objects.filter(unix_name=name).first() mem = c.get_membership_for(self) User._club_memberships[name] = mem @@ -297,17 +370,28 @@ class User(AbstractBaseUser): @cached_property def is_root(self): - return self.is_superuser or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists() + return ( + self.is_superuser + or self.groups.filter(id=settings.SITH_GROUP_ROOT_ID).exists() + ) @cached_property def is_board_member(self): from club.models import Club - return Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB['unix_name']).first().has_rights_in_club(self) + + return ( + Club.objects.filter(unix_name=settings.SITH_MAIN_CLUB["unix_name"]) + .first() + .has_rights_in_club(self) + ) @cached_property def can_create_subscription(self): from club.models import Club - for club in Club.objects.filter(id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS).all(): + + for club in Club.objects.filter( + id__in=settings.SITH_CAN_CREATE_SUBSCRIPTIONS + ).all(): if club.has_rights_in_club(self): return True return False @@ -315,7 +399,14 @@ class User(AbstractBaseUser): @cached_property def is_launderette_manager(self): from club.models import Club - return Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first().get_membership_for(self) + + return ( + Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ) + .first() + .get_membership_for(self) + ) @cached_property def is_banned_alcohol(self): @@ -335,18 +426,34 @@ class User(AbstractBaseUser): else: create = True super(User, self).save(*args, **kwargs) - if create and settings.IS_OLD_MYSQL_PRESENT: # Create user on the old site: TODO remove me! + if ( + create and settings.IS_OLD_MYSQL_PRESENT + ): # Create user on the old site: TODO remove me! import MySQLdb + try: db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) c = db.cursor() - c.execute("""INSERT INTO utilisateurs (id_utilisateur, nom_utl, prenom_utl, email_utl, hash_utl, ae_utl) VALUES - (%s, %s, %s, %s, %s, %s)""", (self.id, self.last_name, self.first_name, self.email, "valid", "0")) + c.execute( + """INSERT INTO utilisateurs (id_utilisateur, nom_utl, prenom_utl, email_utl, hash_utl, ae_utl) VALUES + (%s, %s, %s, %s, %s, %s)""", + ( + self.id, + self.last_name, + self.first_name, + self.email, + "valid", + "0", + ), + ) db.commit() except Exception as e: with open(settings.BASE_DIR + "/user_fail.log", "a") as f: - print("FAIL to add user %s (%s %s - %s) to old site" % (self.id, self.first_name, self.last_name, - self.email), file=f) + print( + "FAIL to add user %s (%s %s - %s) to old site" + % (self.id, self.first_name, self.last_name, self.email), + file=f, + ) print("Reason: %s" % (repr(e)), file=f) db.rollback() @@ -380,7 +487,7 @@ class User(AbstractBaseUser): """ Returns the first_name plus the last_name, with a space in between. """ - full_name = '%s %s' % (self.first_name, self.last_name) + full_name = "%s %s" % (self.first_name, self.last_name) return full_name.strip() def get_short_name(self): @@ -404,7 +511,9 @@ class User(AbstractBaseUser): """ today = timezone.now() born = self.date_of_birth - return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + return ( + today.year - born.year - ((today.month, today.day) < (born.month, born.day)) + ) def email_user(self, subject, message, from_email=None, **kwargs): """ @@ -420,10 +529,19 @@ class User(AbstractBaseUser): For example: Guy Carlier gives gcarlier, and gcarlier1 if the first one exists Returns the generated username """ + def remove_accents(data): - return ''.join(x for x in unicodedata.normalize('NFKD', data) if - unicodedata.category(x)[0] == 'L').lower() - user_name = remove_accents(self.first_name[0] + self.last_name).encode('ascii', 'ignore').decode('utf-8') + return "".join( + x + for x in unicodedata.normalize("NFKD", data) + if unicodedata.category(x)[0] == "L" + ).lower() + + user_name = ( + remove_accents(self.first_name[0] + self.last_name) + .encode("ascii", "ignore") + .decode("utf-8") + ) un_set = [u.username for u in User.objects.all()] if user_name in un_set: i = 1 @@ -490,7 +608,9 @@ class User(AbstractBaseUser): %s """ % ( - self.profile_pict.get_download_url() if self.profile_pict else staticfiles_storage.url("core/img/unknown.jpg"), + self.profile_pict.get_download_url() + if self.profile_pict + else staticfiles_storage.url("core/img/unknown.jpg"), _("Profile"), escape(self.get_display_name()), ) @@ -514,13 +634,20 @@ class User(AbstractBaseUser): return self._forum_infos except: from forum.models import ForumUserInfo + infos = ForumUserInfo(user=self) infos.save() return infos @cached_property def clubs_with_rights(self): - return [m.club.id for m in self.memberships.filter(models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now())).all() if m.club.has_rights_in_club(self)] + return [ + m.club.id + for m in self.memberships.filter( + models.Q(end_date__isnull=True) | models.Q(end_date__gte=timezone.now()) + ).all() + if m.club.has_rights_in_club(self) + ] @cached_property def is_com_admin(self): @@ -594,9 +721,12 @@ class AnonymousUser(AuthAnonymousUser): return False def can_view(self, obj): - if hasattr(obj, 'view_groups') and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists(): + if ( + hasattr(obj, "view_groups") + and obj.view_groups.filter(id=settings.SITH_GROUP_PUBLIC_ID).exists() + ): return True - if hasattr(obj, 'can_be_viewed_by') and obj.can_be_viewed_by(self): + if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self): return True return False @@ -607,20 +737,14 @@ class AnonymousUser(AuthAnonymousUser): class Preferences(models.Model): user = models.OneToOneField(User, related_name="_preferences") receive_weekmail = models.BooleanField( - _('do you want to receive the weekmail'), - default=False, - ) - show_my_stats = models.BooleanField( - _('show your stats to others'), - default=False, + _("do you want to receive the weekmail"), default=False ) + show_my_stats = models.BooleanField(_("show your stats to others"), default=False) notify_on_click = models.BooleanField( - _('get a notification for every click'), - default=False, + _("get a notification for every click"), default=False ) notify_on_refill = models.BooleanField( - _('get a notification for every refilling'), - default=False, + _("get a notification for every refilling"), default=False ) def get_display_name(self): @@ -631,40 +755,74 @@ class Preferences(models.Model): def get_directory(instance, filename): - return '.{0}/{1}'.format(instance.get_parent_path(), filename) + return ".{0}/{1}".format(instance.get_parent_path(), filename) def get_compressed_directory(instance, filename): - return './.compressed/{0}/{1}'.format(instance.get_parent_path(), filename) + return "./.compressed/{0}/{1}".format(instance.get_parent_path(), filename) def get_thumbnail_directory(instance, filename): - return './.thumbnails/{0}/{1}'.format(instance.get_parent_path(), filename) + return "./.thumbnails/{0}/{1}".format(instance.get_parent_path(), filename) class SithFile(models.Model): - name = models.CharField(_('file name'), max_length=256, blank=False) - parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True) - file = models.FileField(upload_to=get_directory, verbose_name=_("file"), max_length=256, null=True, blank=True) - compressed = models.FileField(upload_to=get_compressed_directory, verbose_name=_("compressed file"), max_length=256, null=True, blank=True) - thumbnail = models.FileField(upload_to=get_thumbnail_directory, verbose_name=_("thumbnail"), max_length=256, null=True, blank=True) + name = models.CharField(_("file name"), max_length=256, blank=False) + parent = models.ForeignKey( + "self", related_name="children", verbose_name=_("parent"), null=True, blank=True + ) + file = models.FileField( + upload_to=get_directory, + verbose_name=_("file"), + max_length=256, + null=True, + blank=True, + ) + compressed = models.FileField( + upload_to=get_compressed_directory, + verbose_name=_("compressed file"), + max_length=256, + null=True, + blank=True, + ) + thumbnail = models.FileField( + upload_to=get_thumbnail_directory, + verbose_name=_("thumbnail"), + max_length=256, + null=True, + blank=True, + ) owner = models.ForeignKey(User, related_name="owned_files", verbose_name=_("owner")) - edit_groups = models.ManyToManyField(Group, related_name="editable_files", verbose_name=_("edit group"), blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_files", verbose_name=_("view group"), blank=True) + edit_groups = models.ManyToManyField( + Group, related_name="editable_files", verbose_name=_("edit group"), blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_files", verbose_name=_("view group"), blank=True + ) is_folder = models.BooleanField(_("is folder"), default=True) - mime_type = models.CharField(_('mime type'), max_length=30) + mime_type = models.CharField(_("mime type"), max_length=30) size = models.IntegerField(_("size"), default=0) - date = models.DateTimeField(_('date'), default=timezone.now) + date = models.DateTimeField(_("date"), default=timezone.now) is_moderated = models.BooleanField(_("is moderated"), default=False) - moderator = models.ForeignKey(User, related_name="moderated_files", verbose_name=_("owner"), null=True, blank=True) + moderator = models.ForeignKey( + User, + related_name="moderated_files", + verbose_name=_("owner"), + null=True, + blank=True, + ) asked_for_removal = models.BooleanField(_("asked for removal"), default=False) - is_in_sas = models.BooleanField(_("is in the SAS"), default=False) # Allows to query this flag, updated at each call to save() + is_in_sas = models.BooleanField( + _("is in the SAS"), default=False + ) # Allows to query this flag, updated at each call to save() class Meta: verbose_name = _("file") def is_owned_by(self, user): - if hasattr(self, 'profile_of') and user.is_in_group(settings.SITH_MAIN_BOARD_GROUP): + if hasattr(self, "profile_of") and user.is_in_group( + settings.SITH_MAIN_BOARD_GROUP + ): return True if user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID): return True @@ -673,11 +831,11 @@ class SithFile(models.Model): return user.id == self.owner.id def can_be_viewed_by(self, user): - if hasattr(self, 'profile_of'): + if hasattr(self, "profile_of"): return user.can_view(self.profile_of) - if hasattr(self, 'avatar_of'): + if hasattr(self, "avatar_of"): return user.can_view(self.avatar_of) - if hasattr(self, 'scrub_of'): + if hasattr(self, "scrub_of"): return user.can_view(self.scrub_of) return False @@ -696,31 +854,38 @@ class SithFile(models.Model): Cleans up the file """ super(SithFile, self).clean() - if '/' in self.name: + if "/" in self.name: raise ValidationError(_("Character '/' not authorized in name")) if self == self.parent: - raise ValidationError( - _('Loop in folder tree'), - code='loop', - ) - if (self == self.parent or (self.parent is not None and self in self.get_parent_list())): - raise ValidationError( - _('Loop in folder tree'), - code='loop', - ) + raise ValidationError(_("Loop in folder tree"), code="loop") + if self == self.parent or ( + self.parent is not None and self in self.get_parent_list() + ): + raise ValidationError(_("Loop in folder tree"), code="loop") if self.parent and self.parent.is_file: - raise ValidationError(_('You can not make a file be a children of a non folder file')) - if ((self.parent is None and SithFile.objects.exclude(id=self.id).filter(parent=None, name=self.name).exists()) or - (self.parent and self.parent.children.exclude(id=self.id).filter(name=self.name).exists())): raise ValidationError( - _('Duplicate file'), - code='duplicate', + _("You can not make a file be a children of a non folder file") ) + if ( + self.parent is None + and SithFile.objects.exclude(id=self.id) + .filter(parent=None, name=self.name) + .exists() + ) or ( + self.parent + and self.parent.children.exclude(id=self.id).filter(name=self.name).exists() + ): + raise ValidationError(_("Duplicate file"), code="duplicate") if self.is_folder: if self.file: try: import imghdr - if imghdr.what(None, self.file.read()) not in ['gif', 'png', 'jpeg']: + + if imghdr.what(None, self.file.read()) not in [ + "gif", + "png", + "jpeg", + ]: self.file.delete() self.file = None except: @@ -739,9 +904,17 @@ class SithFile(models.Model): if copy_rights: self.copy_rights() if self.is_in_sas: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID).first().users.all(): - Notification(user=u, url=reverse("sas:moderation"), - type="SAS_MODERATION", param="1").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_SAS_ADMIN_ID) + .first() + .users.all() + ): + Notification( + user=u, + url=reverse("sas:moderation"), + type="SAS_MODERATION", + param="1", + ).save() def apply_rights_recursively(self, only_folders=False): children = self.children.all() @@ -789,7 +962,7 @@ class SithFile(models.Model): parent_full_path = settings.MEDIA_ROOT + parent_path print("Parent full path: %s" % parent_full_path) os.makedirs(parent_full_path, exist_ok=True) - old_path = self.file.name # Should be relative: "./users/skia/bleh.jpg" + old_path = self.file.name # Should be relative: "./users/skia/bleh.jpg" new_path = "." + self.get_full_path() print("Old path: %s " % old_path) print("New path: %s " % new_path) @@ -802,14 +975,17 @@ class SithFile(models.Model): print("New file path: %s " % self.file.path) # Really move at the FS level if os.path.exists(parent_full_path): - os.rename(settings.MEDIA_ROOT + old_path, settings.MEDIA_ROOT + new_path) + os.rename( + settings.MEDIA_ROOT + old_path, + settings.MEDIA_ROOT + new_path, + ) # Empty directories may remain, but that's not really a # problem, and that can be solved with a simple shell # command: `find . -type d -empty -delete` except Exception as e: print("This file likely had a problem. Here is the exception:") print(repr(e)) - print('-'*80) + print("-" * 80) def _check_path_consistence(self): file_path = str(self.file) @@ -817,12 +993,12 @@ class SithFile(models.Model): db_path = ".%s" % self.get_full_path() if not os.path.exists(file_full_path): print("%s: WARNING: real file does not exists!" % self.id) - print("file path: %s" % file_path, end='') + print("file path: %s" % file_path, end="") print(" db path: %s" % db_path) return False if file_path != db_path: - print("%s: " % self.id, end='') - print("file path: %s" % file_path, end='') + print("%s: " % self.id, end="") + print("file path: %s" % file_path, end="") print(" db path: %s" % db_path) return False print("%s OK (%s)" % (self.id, file_path)) @@ -845,11 +1021,13 @@ class SithFile(models.Model): @cached_property def as_picture(self): from sas.models import Picture + return Picture.objects.filter(id=self.id).first() @cached_property def as_album(self): from sas.models import Album + return Album.objects.filter(id=self.id).first() def __str__(self): @@ -867,16 +1045,16 @@ class SithFile(models.Model): return l def get_parent_path(self): - return '/' + '/'.join([p.name for p in self.get_parent_list()[::-1]]) + return "/" + "/".join([p.name for p in self.get_parent_list()[::-1]]) def get_full_path(self): - return self.get_parent_path() + '/' + self.name + return self.get_parent_path() + "/" + self.name def get_display_name(self): return self.name def get_download_url(self): - return reverse('core:download', kwargs={'file_id': self.id}) + return reverse("core:download", kwargs={"file_id": self.id}) def __str__(self): return self.get_parent_path() + "/" + self.name @@ -884,16 +1062,19 @@ class SithFile(models.Model): class LockError(Exception): """There was a lock error on the object""" + pass class AlreadyLocked(LockError): """The object is already locked""" + pass class NotLocked(LockError): """The object is not locked""" + pass @@ -908,29 +1089,63 @@ class Page(models.Model): Be careful with the _full_name attribute: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when playing with a Page object, use get_full_name() instead! """ - name = models.CharField(_('page unix name'), max_length=30, - validators=[ - validators.RegexValidator( - r'^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$', - _('Enter a valid page name. This value may contain only ' - 'unaccented letters, numbers ' 'and ./+/-/_ characters.') - ), ], - blank=False) - parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True, on_delete=models.SET_NULL) + + name = models.CharField( + _("page unix name"), + max_length=30, + validators=[ + validators.RegexValidator( + r"^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$", + _( + "Enter a valid page name. This value may contain only " + "unaccented letters, numbers " + "and ./+/-/_ characters." + ), + ) + ], + blank=False, + ) + parent = models.ForeignKey( + "self", + related_name="children", + verbose_name=_("parent"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) # Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when # playing with a Page object, use get_full_name() instead! - _full_name = models.CharField(_('page name'), max_length=255, blank=True) + _full_name = models.CharField(_("page name"), max_length=255, blank=True) # This function prevents generating migration upon settings change - def get_default_owner_group(): return settings.SITH_GROUP_ROOT_ID - owner_group = models.ForeignKey(Group, related_name="owned_page", verbose_name=_("owner group"), - default=get_default_owner_group) - edit_groups = models.ManyToManyField(Group, related_name="editable_page", verbose_name=_("edit group"), blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_page", verbose_name=_("view group"), blank=True) - lock_user = models.ForeignKey(User, related_name="locked_pages", verbose_name=_("lock user"), blank=True, null=True, default=None) - lock_timeout = models.DateTimeField(_('lock_timeout'), null=True, blank=True, default=None) + def get_default_owner_group(): + return settings.SITH_GROUP_ROOT_ID + + owner_group = models.ForeignKey( + Group, + related_name="owned_page", + verbose_name=_("owner group"), + default=get_default_owner_group, + ) + edit_groups = models.ManyToManyField( + Group, related_name="editable_page", verbose_name=_("edit group"), blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_page", verbose_name=_("view group"), blank=True + ) + lock_user = models.ForeignKey( + User, + related_name="locked_pages", + verbose_name=_("lock user"), + blank=True, + null=True, + default=None, + ) + lock_timeout = models.DateTimeField( + _("lock_timeout"), null=True, blank=True, default=None + ) class Meta: - unique_together = ('name', 'parent') + unique_together = ("name", "parent") permissions = ( ("change_prop_page", "Can change the page's properties (groups, ...)"), ("view_page", "Can view the page"), @@ -950,22 +1165,20 @@ class Page(models.Model): """ Cleans up only the name for the moment, but this can be used to make any treatment before saving the object """ - if '/' in self.name: - self.name = self.name.split('/')[-1] - if Page.objects.exclude(pk=self.pk).filter(_full_name=self.get_full_name()).exists(): - raise ValidationError( - _('Duplicate page'), - code='duplicate', - ) + if "/" in self.name: + self.name = self.name.split("/")[-1] + if ( + Page.objects.exclude(pk=self.pk) + .filter(_full_name=self.get_full_name()) + .exists() + ): + raise ValidationError(_("Duplicate page"), code="duplicate") super(Page, self).clean() if self.parent is not None and self in self.get_parent_list(): - raise ValidationError( - _('Loop in page tree'), - code='loop', - ) + raise ValidationError(_("Loop in page tree"), code="loop") def can_be_edited_by(self, user): - if hasattr(self, 'club') and self.club.can_be_edited_by(user): + if hasattr(self, "club") and self.club.can_be_edited_by(user): # Override normal behavior for clubs return True if self.name == settings.SITH_CLUB_ROOT_PAGE and user.is_board_member: @@ -989,14 +1202,16 @@ class Page(models.Model): """ Performs some needed actions before and after saving a page in database """ - locked = kwargs.pop('force_lock', False) + locked = kwargs.pop("force_lock", False) if not locked: locked = self.is_locked() if not locked: raise NotLocked("The page is not locked and thus can not be saved") self.full_clean() if not self.id: - super(Page, self).save(*args, **kwargs) # Save a first time to correctly set _full_name + super(Page, self).save( + *args, **kwargs + ) # Save a first time to correctly set _full_name # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the # recursive method # It also update all the children to maintain correct names @@ -1012,10 +1227,16 @@ class Page(models.Model): This is where the timeout is handled, so a locked page for which the timeout is reach will be unlocked and this function will return False """ - if self.lock_timeout and (timezone.now() - self.lock_timeout > timedelta(minutes=5)): + if self.lock_timeout and ( + timezone.now() - self.lock_timeout > timedelta(minutes=5) + ): # print("Lock timed out") self.unset_lock() - return self.lock_user and self.lock_timeout and (timezone.now() - self.lock_timeout < timedelta(minutes=5)) + return ( + self.lock_user + and self.lock_timeout + and (timezone.now() - self.lock_timeout < timedelta(minutes=5)) + ) def set_lock(self, user): """ @@ -1063,7 +1284,7 @@ class Page(models.Model): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:page', kwargs={'page_name': self._full_name}) + return reverse("core:page", kwargs={"page_name": self._full_name}) def __str__(self): return self.get_full_name() @@ -1076,7 +1297,7 @@ class Page(models.Model): """ if self.parent is None: return self.name - return '/'.join([self.parent.get_full_name(), self.name]) + return "/".join([self.parent.get_full_name(), self.name]) def get_display_name(self): try: @@ -1087,7 +1308,9 @@ class Page(models.Model): @cached_property def is_club_page(self): club_root_page = Page.objects.filter(name=settings.SITH_CLUB_ROOT_PAGE).first() - return club_root_page is not None and (self == club_root_page or club_root_page in self.get_parent_list()) + return club_root_page is not None and ( + self == club_root_page or club_root_page in self.get_parent_list() + ) @cached_property def need_club_redirection(self): @@ -1111,21 +1334,22 @@ class PageRev(models.Model): is the real content of the page. The content is in PageRev.title and PageRev.content . """ + revision = models.IntegerField(_("revision")) title = models.CharField(_("page title"), max_length=255, blank=True) content = models.TextField(_("page content"), blank=True) - date = models.DateTimeField(_('date'), auto_now=True) - author = models.ForeignKey(User, related_name='page_rev') - page = models.ForeignKey(Page, related_name='revisions') + date = models.DateTimeField(_("date"), auto_now=True) + author = models.ForeignKey(User, related_name="page_rev") + page = models.ForeignKey(Page, related_name="revisions") class Meta: - ordering = ['date', ] + ordering = ["date"] def get_absolute_url(self): """ This is needed for black magic powered UpdateView's children """ - return reverse('core:page', kwargs={'page_name': self.page._full_name}) + return reverse("core:page", kwargs={"page_name": self.page._full_name}) def __str__(self): return str(self.__dict__) @@ -1154,12 +1378,14 @@ class PageRev(models.Model): class Notification(models.Model): - user = models.ForeignKey(User, related_name='notifications') + user = models.ForeignKey(User, related_name="notifications") url = models.CharField(_("url"), max_length=255) param = models.CharField(_("param"), max_length=128, default="") - type = models.CharField(_("type"), max_length=32, choices=settings.SITH_NOTIFICATIONS, default="GENERIC") - date = models.DateTimeField(_('date'), default=timezone.now) - viewed = models.BooleanField(_('viewed'), default=False) + type = models.CharField( + _("type"), max_length=32, choices=settings.SITH_NOTIFICATIONS, default="GENERIC" + ) + date = models.DateTimeField(_("date"), default=timezone.now) + viewed = models.BooleanField(_("viewed"), default=False) def __str__(self): if self.param: @@ -1169,7 +1395,9 @@ class Notification(models.Model): def callback(self): # Get the callback defined in settings to update existing # notifications - mod_name, func_name = settings.SITH_PERMANENT_NOTIFICATIONS[self.type].rsplit('.',1) + mod_name, func_name = settings.SITH_PERMANENT_NOTIFICATIONS[self.type].rsplit( + ".", 1 + ) mod = importlib.import_module(mod_name) getattr(mod, func_name)(self) @@ -1184,16 +1412,18 @@ class Notification(models.Model): class Gift(models.Model): - label = models.CharField(_('label'), max_length=255) - date = models.DateTimeField(_('date'), default=timezone.now) - user = models.ForeignKey(User, related_name='gifts') + label = models.CharField(_("label"), max_length=255) + date = models.DateTimeField(_("date"), default=timezone.now) + user = models.ForeignKey(User, related_name="gifts") def __str__(self): - return "%s - %s" % (self.translated_label, self.date.strftime('%d %b %Y')) + return "%s - %s" % (self.translated_label, self.date.strftime("%d %b %Y")) @property def translated_label(self): - translations = [label[1] for label in settings.SITH_GIFT_LIST if label[0] == self.label] + translations = [ + label[1] for label in settings.SITH_GIFT_LIST if label[0] == self.label + ] if len(translations) > 0: return translations[0] return self.label diff --git a/core/operations.py b/core/operations.py index ea847fd9..3c6eae9b 100644 --- a/core/operations.py +++ b/core/operations.py @@ -46,5 +46,5 @@ class PsqlRunOnly(migrations.RunSQL): """ def _run_sql(self, schema_editor, sqls): - if connection.vendor == 'postgresql': + if connection.vendor == "postgresql": super(PsqlRunOnly, self)._run_sql(schema_editor, sqls) diff --git a/core/scss/finder.py b/core/scss/finder.py index c9ef0859..3e25279f 100644 --- a/core/scss/finder.py +++ b/core/scss/finder.py @@ -34,21 +34,20 @@ class ScssFinder(FileSystemFinder): """ Find static *.css files compiled on the fly """ + locations = [] def __init__(self, apps=None, *args, **kwargs): location = settings.STATIC_ROOT if not os.path.isdir(location): return - self.locations = [ - ('', location), - ] + self.locations = [("", location)] self.storages = OrderedDict() filesystem_storage = FileSystemStorage(location=location) filesystem_storage.prefix = self.locations[0][0] self.storages[location] = filesystem_storage def find(self, path, all=False): - if path.endswith('.css'): + if path.endswith(".css"): return super(ScssFinder, self).find(path, all) return [] diff --git a/core/scss/processor.py b/core/scss/processor.py index dab0de4c..39f099f9 100644 --- a/core/scss/processor.py +++ b/core/scss/processor.py @@ -39,7 +39,8 @@ class ScssProcessor(object): Else : give the path of the corresponding css supposed to already be compiled Don't forget to use compilestatics to compile scss for production """ - prefix = iri_to_uri(getattr(settings, 'STATIC_URL', '/static/')) + + prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/")) storage = ScssFileStorage() scss_extensions = [".scss"] @@ -63,7 +64,7 @@ class ScssProcessor(object): "include_paths": settings.SASS_INCLUDE_FOLDERS, } if settings.SASS_PRECISION: - compile_args['precision'] = settings.SASS_PRECISION + compile_args["precision"] = settings.SASS_PRECISION content = sass.compile(**compile_args) content = force_bytes(content) diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py index 0a9419f8..0ace29c4 100644 --- a/core/templatetags/__init__.py +++ b/core/templatetags/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 5d2a061c..fe982c60 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -38,11 +38,11 @@ register = template.Library() @register.filter(is_safe=False) @stringfilter def markdown(text): - return mark_safe("
    %s
    " % md(text)) + return mark_safe('
    %s
    ' % md(text)) -@register.filter(name='phonenumber') -def phonenumber(value, country='FR', - format=phonenumbers.PhoneNumberFormat.NATIONAL): + +@register.filter(name="phonenumber") +def phonenumber(value, country="FR", format=phonenumbers.PhoneNumberFormat.NATIONAL): """ This filter is kindly borrowed from https://github.com/foundertherapy/django-phonenumber-filter """ @@ -53,13 +53,37 @@ def phonenumber(value, country='FR', except phonenumbers.NumberParseException as e: return value + @register.filter() @stringfilter def datetime_format_python_to_PHP(python_format_string): """ Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible. """ - python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e"} + python2PHP = { + "%a": "D", + "%a": "D", + "%A": "l", + "%b": "M", + "%B": "F", + "%c": "", + "%d": "d", + "%H": "H", + "%I": "h", + "%j": "z", + "%m": "m", + "%M": "i", + "%p": "A", + "%S": "s", + "%U": "", + "%w": "w", + "%W": "W", + "%x": "", + "%X": "", + "%y": "y", + "%Y": "Y", + "%Z": "e", + } php_format_string = python_format_string for py, php in python2PHP.items(): diff --git a/core/tests.py b/core/tests.py index 5b93fd71..92e41a98 100644 --- a/core/tests.py +++ b/core/tests.py @@ -49,203 +49,261 @@ class UserRegistrationTest(TestCase): Should register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'guy@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "guy@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_OK" in str(response.content)) def test_register_user_form_fail_password(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop2', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop2", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_email(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou.git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou.git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_missing_name(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': '', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_missing_date_of_birth(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': '', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_missing_first_name(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': '', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_wrong_captcha(self): """ Should not register a user correctly """ c = Client() - response = c.post(reverse('core:register'), {'first_name': 'Bibou', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'WRONG_CAPTCHA' - }) + response = c.post( + reverse("core:register"), + { + "first_name": "Bibou", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "WRONG_CAPTCHA", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_register_user_form_fail_already_exists(self): """ Should not register a user correctly """ c = Client() - c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) - response = c.post(reverse('core:register'), {'first_name': 'Bibou', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) + c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) + response = c.post( + reverse("core:register"), + { + "first_name": "Bibou", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) self.assertTrue(response.status_code == 200) - self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) + self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) def test_login_success(self): """ Should login a user correctly """ c = Client() - c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) - response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'}) + c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) + response = c.post( + reverse("core:login"), {"username": "gcarlier", "password": "plop"} + ) self.assertTrue(response.status_code == 302) - #self.assertTrue('Hello, world' in str(response.content)) + # self.assertTrue('Hello, world' in str(response.content)) def test_login_fail(self): """ Should not login a user correctly """ c = Client() - c.post(reverse('core:register'), {'first_name': 'Guy', - 'last_name': 'Carlier', - 'email': 'bibou@git.an', - 'date_of_birth': '12/6/1942', - 'password1': 'plop', - 'password2': 'plop', - 'captcha_0': 'dummy-value', - 'captcha_1': 'PASSED' - }) - response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'}) + c.post( + reverse("core:register"), + { + "first_name": "Guy", + "last_name": "Carlier", + "email": "bibou@git.an", + "date_of_birth": "12/6/1942", + "password1": "plop", + "password2": "plop", + "captcha_0": "dummy-value", + "captcha_1": "PASSED", + }, + ) + response = c.post( + reverse("core:login"), {"username": "gcarlier", "password": "guy"} + ) self.assertTrue(response.status_code == 200) - self.assertTrue("""

    Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

    """ in str(response.content)) + self.assertTrue( + """

    Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.

    """ + in str(response.content) + ) + class MarkdownTest(TestCase): def test_full_markdown_syntax(self): root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as md_file: + with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file: md = md_file.read() - with open(os.path.join(root_path) + '/doc/SYNTAX.html', 'r') as html_file: + with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file: html = html_file.read() result = markdown(md) self.assertTrue(result == html) + class PageHandlingTest(TestCase): def setUp(self): try: Group.objects.create(name="root") - u = User(username='root', last_name="", first_name="Bibou", - email="ae.info@utbm.fr", - date_of_birth="1942-06-12", - is_superuser=True, is_staff=True) + u = User( + username="root", + last_name="", + first_name="Bibou", + email="ae.info@utbm.fr", + date_of_birth="1942-06-12", + is_superuser=True, + is_staff=True, + ) u.set_password("plop") u.save() - self.client.login(username='root', password='plop') + self.client.login(username="root", password="plop") except Exception as e: print(e) @@ -253,12 +311,10 @@ class PageHandlingTest(TestCase): """ Should create a page correctly """ - self.client.post(reverse('core:page_new'), { - 'parent': '', - 'name': 'guy', - 'owner_group': 1, - }) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) + self.client.post( + reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": 1} + ) + response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) self.assertTrue(response.status_code == 200) self.assertTrue('' in str(response.content)) @@ -266,17 +322,16 @@ class PageHandlingTest(TestCase): """ Should create a page correctly """ - self.client.post(reverse('core:page_new'), { - 'parent': '', - 'name': 'guy', - 'owner_group': '1', - }) - response = self.client.post(reverse('core:page_new'), { - 'parent': '1', - 'name': 'bibou', - 'owner_group': '1', - }) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) + self.client.post( + reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"} + ) + response = self.client.post( + reverse("core:page_new"), + {"parent": "1", "name": "bibou", "owner_group": "1"}, + ) + response = self.client.get( + reverse("core:page", kwargs={"page_name": "guy/bibou"}) + ) self.assertTrue(response.status_code == 200) self.assertTrue('' in str(response.content)) @@ -286,17 +341,24 @@ class PageHandlingTest(TestCase): """ parent = Page(name="guy", owner_group=Group.objects.filter(id=1).first()) parent.save(force_lock=True) - page = Page(name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent) + page = Page( + name="bibou", owner_group=Group.objects.filter(id=1).first(), parent=parent + ) page.save(force_lock=True) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) + response = self.client.get( + reverse("core:page", kwargs={"page_name": "guy/bibou"}) + ) self.assertTrue(response.status_code == 200) - self.assertTrue('\\xc3\\x89diter' in str(response.content)) + self.assertTrue( + '\\xc3\\x89diter' + in str(response.content) + ) def test_access_page_not_found(self): """ Should not display a page correctly """ - response = self.client.get(reverse('core:page', kwargs={'page_name': 'swagg'})) + response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"})) response = self.client.get("/page/swagg/") self.assertTrue(response.status_code == 200) self.assertTrue('' in str(response.content)) @@ -305,15 +367,14 @@ class PageHandlingTest(TestCase): """ Should format the markdown and escape html correctly """ - self.client.post(reverse('core:page_new'), { - 'parent': '', - 'name': 'guy', - 'owner_group': '1', - }) - self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), { - 'title': 'Bibou', - 'content': - '''Guy *bibou* + self.client.post( + reverse("core:page_new"), {"parent": "", "name": "guy", "owner_group": "1"} + ) + self.client.post( + reverse("core:page_edit", kwargs={"page_name": "guy"}), + { + "title": "Bibou", + "content": """Guy *bibou* http://git.an @@ -322,13 +383,18 @@ http://git.an Bibou -''', - }) - response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) +""", + }, + ) + response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) self.assertTrue(response.status_code == 200) - self.assertTrue('

    Guy bibou

    \\n

    http://git.an

    \\n' + - '

    Swag

    \\n<guy>Bibou</guy>' + - "<script>alert(\\'Guy\\');</script>" in str(response.content)) + self.assertTrue( + '

    Guy bibou

    \\n

    http://git.an

    \\n' + + "

    Swag

    \\n<guy>Bibou</guy>" + + "<script>alert(\\'Guy\\');</script>" + in str(response.content) + ) + # TODO: many tests on the pages: # - renaming a page @@ -341,23 +407,33 @@ class FileHandlingTest(TestCase): try: call_command("populate") self.subscriber = User.objects.filter(username="subscriber").first() - self.client.login(username='subscriber', password='plop') + self.client.login(username="subscriber", password="plop") except Exception as e: print(e) def test_create_folder_home(self): - response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), - {"folder_name": "GUY_folder_test"}) + response = self.client.post( + reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), + {"folder_name": "GUY_folder_test"}, + ) self.assertTrue(response.status_code == 302) - response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})) + response = self.client.get( + reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) + ) self.assertTrue(response.status_code == 200) self.assertTrue("GUY_folder_test" in str(response.content)) def test_upload_file_home(self): with open("/bin/ls", "rb") as f: - response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), - {"file_field": f}) + response = self.client.post( + reverse( + "core:file_detail", kwargs={"file_id": self.subscriber.home.id} + ), + {"file_field": f}, + ) self.assertTrue(response.status_code == 302) - response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})) + response = self.client.get( + reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) + ) self.assertTrue(response.status_code == 200) self.assertTrue("ls" in str(response.content)) diff --git a/core/urls.py b/core/urls.py index afbcbf40..be18ff8e 100644 --- a/core/urls.py +++ b/core/urls.py @@ -28,73 +28,181 @@ from django.conf.urls import url from core.views import * urlpatterns = [ - url(r'^$', index, name='index'), - url(r'^to_markdown$', ToMarkdownView.as_view(), name='to_markdown'), - url(r'^notifications$', NotificationList.as_view(), name='notification_list'), - url(r'^notification/(?P[0-9]+)$', notification, name='notification'), - + url(r"^$", index, name="index"), + url(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"), + url(r"^notifications$", NotificationList.as_view(), name="notification_list"), + url(r"^notification/(?P[0-9]+)$", notification, name="notification"), # Search - url(r'^search/$', search_view, name='search'), - url(r'^search_json/$', search_json, name='search_json'), - url(r'^search_user/$', search_user_json, name='search_user'), - + url(r"^search/$", search_view, name="search"), + url(r"^search_json/$", search_json, name="search_json"), + url(r"^search_user/$", search_user_json, name="search_user"), # Login and co - url(r'^login/$', login, name='login'), - url(r'^logout/$', logout, name='logout'), - url(r'^password_change/$', password_change, name='password_change'), - url(r'^password_change/(?P[0-9]+)$', password_root_change, name='password_root_change'), - url(r'^password_change/done$', password_change_done, name='password_change_done'), - url(r'^password_reset/$', password_reset, name='password_reset'), - url(r'^password_reset/done$', password_reset_done, name='password_reset_done'), - url(r'^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', password_reset_confirm, name='password_reset_confirm'), - url(r'^reset/done/$', password_reset_complete, name='password_reset_complete'), - url(r'^register$', register, name='register'), - + url(r"^login/$", login, name="login"), + url(r"^logout/$", logout, name="logout"), + url(r"^password_change/$", password_change, name="password_change"), + url( + r"^password_change/(?P[0-9]+)$", + password_root_change, + name="password_root_change", + ), + url(r"^password_change/done$", password_change_done, name="password_change_done"), + url(r"^password_reset/$", password_reset, name="password_reset"), + url(r"^password_reset/done$", password_reset_done, name="password_reset_done"), + url( + r"^reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$", + password_reset_confirm, + name="password_reset_confirm", + ), + url(r"^reset/done/$", password_reset_complete, name="password_reset_complete"), + url(r"^register$", register, name="register"), # Group handling - url(r'^group/$', GroupListView.as_view(), name='group_list'), - url(r'^group/new$', GroupCreateView.as_view(), name='group_new'), - url(r'^group/(?P[0-9]+)/$', GroupEditView.as_view(), name='group_edit'), - url(r'^group/(?P[0-9]+)/delete$', GroupDeleteView.as_view(), name='group_delete'), - + url(r"^group/$", GroupListView.as_view(), name="group_list"), + url(r"^group/new$", GroupCreateView.as_view(), name="group_new"), + url(r"^group/(?P[0-9]+)/$", GroupEditView.as_view(), name="group_edit"), + url( + r"^group/(?P[0-9]+)/delete$", + GroupDeleteView.as_view(), + name="group_delete", + ), # User views - url(r'^user/$', UserListView.as_view(), name='user_list'), - url(r'^user/(?P[0-9]+)/mini$', UserMiniView.as_view(), name='user_profile_mini'), - url(r'^user/(?P[0-9]+)/$', UserView.as_view(), name='user_profile'), - url(r'^user/(?P[0-9]+)/pictures$', UserPicturesView.as_view(), name='user_pictures'), - url(r'^user/(?P[0-9]+)/godfathers$', UserGodfathersView.as_view(), name='user_godfathers'), - url(r'^user/(?P[0-9]+)/godfathers/tree$', UserGodfathersTreeView.as_view(), name='user_godfathers_tree'), - url(r'^user/(?P[0-9]+)/godfathers/tree/pict$', UserGodfathersTreePictureView.as_view(), name='user_godfathers_tree_pict'), - url(r'^user/(?P[0-9]+)/godfathers/(?P[0-9]+)/(?P(True)|(False))/delete$', DeleteUserGodfathers, name='user_godfathers_delete'), - url(r'^user/(?P[0-9]+)/edit$', UserUpdateProfileView.as_view(), name='user_edit'), - url(r'^user/(?P[0-9]+)/profile_upload$', UserUploadProfilePictView.as_view(), name='user_profile_upload'), - url(r'^user/(?P[0-9]+)/clubs$', UserClubView.as_view(), name='user_clubs'), - url(r'^user/(?P[0-9]+)/prefs$', UserPreferencesView.as_view(), name='user_prefs'), - url(r'^user/(?P[0-9]+)/groups$', UserUpdateGroupView.as_view(), name='user_groups'), - url(r'^user/tools/$', UserToolsView.as_view(), name='user_tools'), - url(r'^user/(?P[0-9]+)/account$', UserAccountView.as_view(), name='user_account'), - url(r'^user/(?P[0-9]+)/account/(?P[0-9]+)/(?P[0-9]+)$', UserAccountDetailView.as_view(), name='user_account_detail'), - url(r'^user/(?P[0-9]+)/stats$', UserStatsView.as_view(), name='user_stats'), - url(r'^user/(?P[0-9]+)/gift/create$', GiftCreateView.as_view(), name='user_gift_create'), - url(r'^user/(?P[0-9]+)/gift/delete/(?P[0-9]+)/$', GiftDeleteView.as_view(), name='user_gift_delete'), - + url(r"^user/$", UserListView.as_view(), name="user_list"), + url( + r"^user/(?P[0-9]+)/mini$", + UserMiniView.as_view(), + name="user_profile_mini", + ), + url(r"^user/(?P[0-9]+)/$", UserView.as_view(), name="user_profile"), + url( + r"^user/(?P[0-9]+)/pictures$", + UserPicturesView.as_view(), + name="user_pictures", + ), + url( + r"^user/(?P[0-9]+)/godfathers$", + UserGodfathersView.as_view(), + name="user_godfathers", + ), + url( + r"^user/(?P[0-9]+)/godfathers/tree$", + UserGodfathersTreeView.as_view(), + name="user_godfathers_tree", + ), + url( + r"^user/(?P[0-9]+)/godfathers/tree/pict$", + UserGodfathersTreePictureView.as_view(), + name="user_godfathers_tree_pict", + ), + url( + r"^user/(?P[0-9]+)/godfathers/(?P[0-9]+)/(?P(True)|(False))/delete$", + DeleteUserGodfathers, + name="user_godfathers_delete", + ), + url( + r"^user/(?P[0-9]+)/edit$", + UserUpdateProfileView.as_view(), + name="user_edit", + ), + url( + r"^user/(?P[0-9]+)/profile_upload$", + UserUploadProfilePictView.as_view(), + name="user_profile_upload", + ), + url(r"^user/(?P[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"), + url( + r"^user/(?P[0-9]+)/prefs$", + UserPreferencesView.as_view(), + name="user_prefs", + ), + url( + r"^user/(?P[0-9]+)/groups$", + UserUpdateGroupView.as_view(), + name="user_groups", + ), + url(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"), + url( + r"^user/(?P[0-9]+)/account$", + UserAccountView.as_view(), + name="user_account", + ), + url( + r"^user/(?P[0-9]+)/account/(?P[0-9]+)/(?P[0-9]+)$", + UserAccountDetailView.as_view(), + name="user_account_detail", + ), + url( + r"^user/(?P[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats" + ), + url( + r"^user/(?P[0-9]+)/gift/create$", + GiftCreateView.as_view(), + name="user_gift_create", + ), + url( + r"^user/(?P[0-9]+)/gift/delete/(?P[0-9]+)/$", + GiftDeleteView.as_view(), + name="user_gift_delete", + ), # File views # url(r'^file/add/(?Ppopup)?$', FileCreateView.as_view(), name='file_new'), - url(r'^file/(?Ppopup)?$', FileListView.as_view(), name='file_list'), - url(r'^file/(?P[0-9]+)/(?Ppopup)?$', FileView.as_view(), name='file_detail'), - url(r'^file/(?P[0-9]+)/edit/(?Ppopup)?$', FileEditView.as_view(), name='file_edit'), - url(r'^file/(?P[0-9]+)/prop/(?Ppopup)?$', FileEditPropView.as_view(), name='file_prop'), - url(r'^file/(?P[0-9]+)/delete/(?Ppopup)?$', FileDeleteView.as_view(), name='file_delete'), - url(r'^file/moderation$', FileModerationView.as_view(), name='file_moderation'), - url(r'^file/(?P[0-9]+)/moderate$', FileModerateView.as_view(), name='file_moderate'), - url(r'^file/(?P[0-9]+)/download$', send_file, name='download'), - + url(r"^file/(?Ppopup)?$", FileListView.as_view(), name="file_list"), + url( + r"^file/(?P[0-9]+)/(?Ppopup)?$", + FileView.as_view(), + name="file_detail", + ), + url( + r"^file/(?P[0-9]+)/edit/(?Ppopup)?$", + FileEditView.as_view(), + name="file_edit", + ), + url( + r"^file/(?P[0-9]+)/prop/(?Ppopup)?$", + FileEditPropView.as_view(), + name="file_prop", + ), + url( + r"^file/(?P[0-9]+)/delete/(?Ppopup)?$", + FileDeleteView.as_view(), + name="file_delete", + ), + url(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"), + url( + r"^file/(?P[0-9]+)/moderate$", + FileModerateView.as_view(), + name="file_moderate", + ), + url(r"^file/(?P[0-9]+)/download$", send_file, name="download"), # Page views - url(r'^page/$', PageListView.as_view(), name='page_list'), - url(r'^page/create$', PageCreateView.as_view(), name='page_new'), - url(r'^page/(?P[0-9]*)/delete$', PageDeleteView.as_view(), name='page_delete'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$', PageEditView.as_view(), name='page_edit'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$', PagePropView.as_view(), name='page_prop'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$', PageHistView.as_view(), name='page_hist'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P[0-9]+)/', PageRevView.as_view(), name='page_rev'), - url(r'^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$', PageView.as_view(), name='page'), + url(r"^page/$", PageListView.as_view(), name="page_list"), + url(r"^page/create$", PageCreateView.as_view(), name="page_new"), + url( + r"^page/(?P[0-9]*)/delete$", + PageDeleteView.as_view(), + name="page_delete", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$", + PageEditView.as_view(), + name="page_edit", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$", + PagePropView.as_view(), + name="page_prop", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$", + PageHistView.as_view(), + name="page_hist", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P[0-9]+)/", + PageRevView.as_view(), + name="page_rev", + ), + url( + r"^page/(?P([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$", + PageView.as_view(), + name="page", + ), ] diff --git a/core/utils.py b/core/utils.py index f375a522..6267802c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -30,6 +30,7 @@ from io import BytesIO from datetime import date from PIL import ExifTags + # from exceptions import IOError import PIL @@ -71,9 +72,9 @@ def get_semester(d=date.today()): def scale_dimension(width, height, long_edge): if width > height: - ratio = long_edge * 1. / width + ratio = long_edge * 1.0 / width else: - ratio = long_edge * 1. / height + ratio = long_edge * 1.0 / height return int(width * ratio), int(height * ratio) @@ -83,16 +84,28 @@ def resize_image(im, edge, format): content = BytesIO() im = im.resize((width, height), PIL.Image.ANTIALIAS) try: - im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) + im.save( + fp=content, + format=format.upper(), + quality=90, + optimize=True, + progressive=True, + ) except IOError: PIL.ImageFile.MAXBLOCK = im.size[0] * im.size[1] - im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) + im.save( + fp=content, + format=format.upper(), + quality=90, + optimize=True, + progressive=True, + ) return ContentFile(content.getvalue()) def exif_auto_rotate(image): for orientation in ExifTags.TAGS.keys(): - if ExifTags.TAGS[orientation] == 'Orientation': + if ExifTags.TAGS[orientation] == "Orientation": break exif = dict(image._getexif().items()) @@ -108,54 +121,66 @@ def exif_auto_rotate(image): def doku_to_markdown(text): """This is a quite correct doku translator""" - text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict) - text = re.sub(r'(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) - text = re.sub(r'(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used) - text = re.sub(r'(.*?)<\/sub>', r'_\1_', text) # Subscript (idem) + text = re.sub( + r"([^:]|^)\/\/(.*?)\/\/", r"*\2*", text + ) # Italic (prevents protocol:// conflict) + text = re.sub( + r"(.*?)<\/del>", r"~~\1~~", text, flags=re.DOTALL + ) # Strike (may be multiline) + text = re.sub( + r"(.*?)<\/sup>", r"^\1^", text + ) # Superscript (multiline not supported, because almost never used) + text = re.sub(r"(.*?)<\/sub>", r"_\1_", text) # Subscript (idem) - text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles - text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE) - text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE) - text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE) - text = re.sub(r'^==(.*?)==', r'#####\1', text, flags=re.MULTILINE) - text = re.sub(r'^=(.*?)=', r'######\1', text, flags=re.MULTILINE) + text = re.sub(r"^======(.*?)======", r"#\1", text, flags=re.MULTILINE) # Titles + text = re.sub(r"^=====(.*?)=====", r"##\1", text, flags=re.MULTILINE) + text = re.sub(r"^====(.*?)====", r"###\1", text, flags=re.MULTILINE) + text = re.sub(r"^===(.*?)===", r"####\1", text, flags=re.MULTILINE) + text = re.sub(r"^==(.*?)==", r"#####\1", text, flags=re.MULTILINE) + text = re.sub(r"^=(.*?)=", r"######\1", text, flags=re.MULTILINE) - text = re.sub(r'', r'', text) - text = re.sub(r'', r'', text) - text = re.sub(r'', r'```\n', text) - text = re.sub(r'', r'\n```', text) - text = re.sub(r'article://', r'page://', text) - text = re.sub(r'dfile://', r'file://', text) + text = re.sub(r"", r"", text) + text = re.sub(r"", r"", text) + text = re.sub(r"", r"```\n", text) + text = re.sub(r"", r"\n```", text) + text = re.sub(r"article://", r"page://", text) + text = re.sub(r"dfile://", r"file://", text) i = 1 - for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes - text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1) + for fn in re.findall(r"\(\((.*?)\)\)", text): # Footnotes + text = re.sub(r"\(\((.*?)\)\)", r"[^%s]" % i, text, count=1) text += "\n[^%s]: %s\n" % (i, fn) i += 1 - text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return + text = re.sub(r"\\{2,}[\s]", r" \n", text) # Carriage return - text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links - text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2 - text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images - text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2 - text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them) + text = re.sub(r"\[\[(.*?)\|(.*?)\]\]", r"[\2](\1)", text) # Links + text = re.sub(r"\[\[(.*?)\]\]", r"[\1](\1)", text) # Links 2 + text = re.sub(r"{{(.*?)\|(.*?)}}", r'![\2](\1 "\2")', text) # Images + text = re.sub(r"{{(.*?)(\|(.*?))?}}", r'![\1](\1 "\1")', text) # Images 2 + text = re.sub( + r"{\[(.*?)(\|(.*?))?\]}", r"[\1](\1)", text + ) # Video (transform to classic links, since we can't integrate them) - text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar + text = re.sub(r"###(\d*?)###", r"[[[\1]]]", text) # Progress bar - text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists + text = re.sub( + r"(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)", r"```\1\n```", text, flags=re.DOTALL + ) # Block code without lists - text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists + text = re.sub(r"( +)-(.*)", r"1.\2", text) # Ordered lists new_text = [] quote_level = 0 for line in text.splitlines(): # Tables and quotes - enter = re.finditer(r'\[quote(=(.+?))?\]', line) - quit = re.finditer(r'\[/quote\]', line) - if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part - line = line.replace('^', '|') + enter = re.finditer(r"\[quote(=(.+?))?\]", line) + quit = re.finditer(r"\[/quote\]", line) + if re.search(r"\A\s*\^(([^\^]*?)\^)*", line): # Table part + line = line.replace("^", "|") new_text.append("> " * quote_level + line) - new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth + new_text.append( + "> " * quote_level + "|---|" + ) # Don't keep the text alignement in tables it's really too complex for what it's worth elif enter or quit: # Quote part for quote in enter: # Enter quotes (support multiple at a time) quote_level += 1 @@ -163,16 +188,20 @@ def doku_to_markdown(text): new_text.append("> " * quote_level + "##### " + quote.group(2)) except: new_text.append("> " * quote_level) - line = line.replace(quote.group(0), '') - final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration + line = line.replace(quote.group(0), "") + final_quote_level = ( + quote_level + ) # Store quote_level to use at the end, since it will be modified during quit iteration final_newline = False for quote in quit: # Quit quotes (support multiple at a time) - line = line.replace(quote.group(0), '') + line = line.replace(quote.group(0), "") quote_level -= 1 final_newline = True new_text.append("> " * final_quote_level + line) # Finally append the line if final_newline: - new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text + new_text.append( + "\n" + ) # Add a new line to ensure the separation between the quote and the following text else: new_text.append(line) @@ -181,24 +210,28 @@ def doku_to_markdown(text): def bbcode_to_markdown(text): """This is a very basic BBcode translator""" - text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold - text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic - text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline - text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) - text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2 + text = re.sub(r"\[b\](.*?)\[\/b\]", r"**\1**", text, flags=re.DOTALL) # Bold + text = re.sub(r"\[i\](.*?)\[\/i\]", r"*\1*", text, flags=re.DOTALL) # Italic + text = re.sub(r"\[u\](.*?)\[\/u\]", r"__\1__", text, flags=re.DOTALL) # Underline + text = re.sub( + r"\[s\](.*?)\[\/s\]", r"~~\1~~", text, flags=re.DOTALL + ) # Strike (may be multiline) + text = re.sub( + r"\[strike\](.*?)\[\/strike\]", r"~~\1~~", text, flags=re.DOTALL + ) # Strike 2 - text = re.sub(r'article://', r'page://', text) - text = re.sub(r'dfile://', r'file://', text) + text = re.sub(r"article://", r"page://", text) + text = re.sub(r"dfile://", r"file://", text) - text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links - text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2 - text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images + text = re.sub(r"\[url=(.*?)\](.*)\[\/url\]", r"[\2](\1)", text) # Links + text = re.sub(r"\[url\](.*)\[\/url\]", r"\1", text) # Links 2 + text = re.sub(r"\[img\](.*)\[\/img\]", r'![\1](\1 "\1")', text) # Images new_text = [] quote_level = 0 for line in text.splitlines(): # Tables and quotes - enter = re.finditer(r'\[quote(=(.+?))?\]', line) - quit = re.finditer(r'\[/quote\]', line) + enter = re.finditer(r"\[quote(=(.+?))?\]", line) + quit = re.finditer(r"\[/quote\]", line) if enter or quit: # Quote part for quote in enter: # Enter quotes (support multiple at a time) quote_level += 1 @@ -206,16 +239,20 @@ def bbcode_to_markdown(text): new_text.append("> " * quote_level + "##### " + quote.group(2)) except: new_text.append("> " * quote_level) - line = line.replace(quote.group(0), '') - final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration + line = line.replace(quote.group(0), "") + final_quote_level = ( + quote_level + ) # Store quote_level to use at the end, since it will be modified during quit iteration final_newline = False for quote in quit: # Quit quotes (support multiple at a time) - line = line.replace(quote.group(0), '') + line = line.replace(quote.group(0), "") quote_level -= 1 final_newline = True new_text.append("> " * final_quote_level + line) # Finally append the line if final_newline: - new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text + new_text.append( + "\n" + ) # Add a new line to ensure the separation between the quote and the following text else: new_text.append(line) diff --git a/core/views/__init__.py b/core/views/__init__.py index c27470f5..7045b7f7 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -26,43 +26,69 @@ import types from django.shortcuts import render from django.http import HttpResponseForbidden, HttpResponseNotFound -from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ImproperlyConfigured +from django.core.exceptions import ( + PermissionDenied, + ObjectDoesNotExist, + ImproperlyConfigured, +) from django.views.generic.base import View from django.db.models import Count from core.models import Group from core.views.forms import LoginForm + def forbidden(request): try: - return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': - LoginForm(), 'popup': request.resolver_match.kwargs['popup'] or ""})) + return HttpResponseForbidden( + render( + request, + "core/403.jinja", + context={ + "next": request.path, + "form": LoginForm(), + "popup": request.resolver_match.kwargs["popup"] or "", + }, + ) + ) except: - return HttpResponseForbidden(render(request, "core/403.jinja", context={'next': request.path, 'form': LoginForm()})) + return HttpResponseForbidden( + render( + request, + "core/403.jinja", + context={"next": request.path, "form": LoginForm()}, + ) + ) + def not_found(request): return HttpResponseNotFound(render(request, "core/404.jinja")) + def can_edit_prop(obj, user): if obj is None or user.is_owner(obj): return True return False + def can_edit(obj, user): if obj is None or user.can_edit(obj): return True return can_edit_prop(obj, user) + def can_view(obj, user): if obj is None or user.can_view(obj): return True return can_edit(obj, user) + class CanCreateMixin(View): """ This view is made to protect any child view that would create an object, and thus, that can not be protected by any of the following mixin """ + def dispatch(self, request, *arg, **kwargs): res = super(CanCreateMixin, self).dispatch(request, *arg, **kwargs) if not request.user.is_authenticated(): @@ -75,6 +101,7 @@ class CanCreateMixin(View): return super(CanCreateMixin, self).form_valid(form) raise PermissionDenied + class CanEditPropMixin(View): """ This view is made to protect any child view that would be showing some properties of an object that are restricted @@ -82,64 +109,78 @@ class CanEditPropMixin(View): In other word, you can make a view with this view as parent, and it would be retricted to the users that are in the object's owner_group """ + def dispatch(self, request, *arg, **kwargs): try: self.object = self.get_object() if can_edit_prop(self.object, request.user): return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) return forbidden(request) - except: pass + except: + pass # If we get here, it's a ListView l_id = [o.id for o in self.get_queryset() if can_edit_prop(o, request.user)] if not l_id and self.get_queryset().count() != 0: raise PermissionDenied self._get_queryset = self.get_queryset + def get_qs(self2): return self2._get_queryset().filter(id__in=l_id) + self.get_queryset = types.MethodType(get_qs, self) return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs) + class CanEditMixin(View): """ This view makes exactly the same thing as its direct parent, but checks the group on the edit_groups field of the object """ + def dispatch(self, request, *arg, **kwargs): try: self.object = self.get_object() if can_edit(self.object, request.user): return super(CanEditMixin, self).dispatch(request, *arg, **kwargs) return forbidden(request) - except: pass + except: + pass # If we get here, it's a ListView l_id = [o.id for o in self.get_queryset() if can_edit(o, request.user)] if not l_id and self.get_queryset().count() != 0: raise PermissionDenied self._get_queryset = self.get_queryset + def get_qs(self2): return self2._get_queryset().filter(id__in=l_id) + self.get_queryset = types.MethodType(get_qs, self) return super(CanEditMixin, self).dispatch(request, *arg, **kwargs) + class CanViewMixin(View): """ This view still makes exactly the same thing as its direct parent, but checks the group on the view_groups field of the object """ + def dispatch(self, request, *arg, **kwargs): try: self.object = self.get_object() if can_view(self.object, request.user): return super(CanViewMixin, self).dispatch(request, *arg, **kwargs) return forbidden(request) - except: pass + except: + pass # If we get here, it's a ListView l_id = [o.id for o in self.get_queryset() if can_view(o, request.user)] if not l_id and self.get_queryset().count() != 0: raise PermissionDenied self._get_queryset = self.get_queryset + def get_qs(self2): return self2._get_queryset().filter(id__in=l_id) + self.get_queryset = types.MethodType(get_qs, self) return super(CanViewMixin, self).dispatch(request, *arg, **kwargs) @@ -148,6 +189,7 @@ class FormerSubscriberMixin(View): """ This view check if the user was at least an old subscriber """ + def dispatch(self, request, *args, **kwargs): if not request.user.was_subscribed: raise PermissionDenied @@ -158,6 +200,7 @@ class TabedViewMixin(View): """ This view provide the basic functions for displaying tabs in the template """ + def get_tabs_title(self): try: return self.tabs_title @@ -178,38 +221,42 @@ class TabedViewMixin(View): def get_context_data(self, **kwargs): kwargs = super(TabedViewMixin, self).get_context_data(**kwargs) - kwargs['tabs_title'] = self.get_tabs_title() - kwargs['current_tab'] = self.get_current_tab() - kwargs['list_of_tabs'] = self.get_list_of_tabs() + kwargs["tabs_title"] = self.get_tabs_title() + kwargs["current_tab"] = self.get_current_tab() + kwargs["list_of_tabs"] = self.get_list_of_tabs() return kwargs + class QuickNotifMixin: quick_notif_list = [] def dispatch(self, request, *arg, **kwargs): - self.quick_notif_list = [] # In some cases, the class can stay instanciated, so we need to reset the list + self.quick_notif_list = ( + [] + ) # In some cases, the class can stay instanciated, so we need to reset the list return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs) def get_success_url(self): ret = super(QuickNotifMixin, self).get_success_url() try: - if '?' in ret: - ret += '&' + self.quick_notif_url_arg + if "?" in ret: + ret += "&" + self.quick_notif_url_arg else: - ret += '?' + self.quick_notif_url_arg - except: pass + ret += "?" + self.quick_notif_url_arg + except: + pass return ret def get_context_data(self, **kwargs): """Add quick notifications to context""" kwargs = super(QuickNotifMixin, self).get_context_data(**kwargs) - kwargs['quick_notifs'] = [] + kwargs["quick_notifs"] = [] for n in self.quick_notif_list: - kwargs['quick_notifs'].append(settings.SITH_QUICK_NOTIF[n]) - for k,v in settings.SITH_QUICK_NOTIF.items(): + kwargs["quick_notifs"].append(settings.SITH_QUICK_NOTIF[n]) + for k, v in settings.SITH_QUICK_NOTIF.items(): for gk in self.request.GET.keys(): if k == gk: - kwargs['quick_notifs'].append(v) + kwargs["quick_notifs"].append(v) return kwargs @@ -218,5 +265,3 @@ from .page import * from .files import * from .site import * from .group import * - - diff --git a/core/views/files.py b/core/views/files.py index 5df973be..60902cfe 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -54,55 +54,93 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"): f = file_class.objects.filter(id=file_id).first() if f is None or not f.file: return not_found(request) - if not (can_view(f, request.user) or - ('counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()) - ): + if not ( + can_view(f, request.user) + or ( + "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ) + ): raise PermissionDenied name = f.__getattribute__(file_attr).name filepath = os.path.join(settings.MEDIA_ROOT, name) - with open(filepath.encode('utf-8'), 'rb') as filename: + with open(filepath.encode("utf-8"), "rb") as filename: wrapper = FileWrapper(filename) response = HttpResponse(wrapper, content_type=f.mime_type) - response['Content-Length'] = os.path.getsize(filepath.encode('utf-8')) - response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8') + response["Content-Length"] = os.path.getsize(filepath.encode("utf-8")) + response["Content-Disposition"] = ('inline; filename="%s"' % f.name).encode( + "utf-8" + ) return response class AddFilesForm(forms.Form): - folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False) - file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"), - required=False) + folder_name = forms.CharField( + label=_("Add a new folder"), max_length=30, required=False + ) + file_field = forms.FileField( + widget=forms.ClearableFileInput(attrs={"multiple": True}), + label=_("Files"), + required=False, + ) def process(self, parent, owner, files): notif = False try: - if self.cleaned_data['folder_name'] != "": - folder = SithFile(parent=parent, name=self.cleaned_data['folder_name'], owner=owner) + if self.cleaned_data["folder_name"] != "": + folder = SithFile( + parent=parent, name=self.cleaned_data["folder_name"], owner=owner + ) folder.clean() folder.save() notif = True except Exception as e: - self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") % - {'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)}) + self.add_error( + None, + _("Error creating folder %(folder_name)s: %(msg)s") + % {"folder_name": self.cleaned_data["folder_name"], "msg": repr(e)}, + ) for f in files: - new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False, - mime_type=f.content_type, size=f._size) + new_file = SithFile( + parent=parent, + name=f.name, + file=f, + owner=owner, + is_folder=False, + mime_type=f.content_type, + size=f._size, + ) try: new_file.clean() new_file.save() notif = True except Exception as e: - self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)}) + self.add_error( + None, + _("Error uploading file %(file_name)s: %(msg)s") + % {"file_name": f, "msg": repr(e)}, + ) if notif: - for u in RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID).first().users.all(): - if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists(): - Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save() + for u in ( + RealGroup.objects.filter(id=settings.SITH_GROUP_COM_ADMIN_ID) + .first() + .users.all() + ): + if not u.notifications.filter( + type="FILE_MODERATION", viewed=False + ).exists(): + Notification( + user=u, + url=reverse("core:file_moderation"), + type="FILE_MODERATION", + ).save() class FileListView(ListView): - template_name = 'core/file_list.jinja' + template_name = "core/file_list.jinja" context_object_name = "file_list" def get_queryset(self): @@ -110,81 +148,94 @@ class FileListView(ListView): def get_context_data(self, **kwargs): kwargs = super(FileListView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs class FileEditView(CanEditMixin, UpdateView): model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_edit.jinja' + template_name = "core/file_edit.jinja" context_object_name = "file" def get_form_class(self): - fields = ['name', 'is_moderated'] + fields = ["name", "is_moderated"] if self.object.is_file: - fields = ['file'] + fields + fields = ["file"] + fields return modelform_factory(SithFile, fields=fields) def get_success_url(self): - if self.kwargs['popup']: - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': "popup"}) - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': ""}) + if self.kwargs["popup"]: + return reverse( + "core:file_detail", kwargs={"file_id": self.object.id, "popup": "popup"} + ) + return reverse( + "core:file_detail", kwargs={"file_id": self.object.id, "popup": ""} + ) def get_context_data(self, **kwargs): kwargs = super(FileEditView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs class FileEditPropForm(forms.ModelForm): class Meta: model = SithFile - fields = ['parent', 'owner', 'edit_groups', 'view_groups'] - parent = make_ajax_field(SithFile, 'parent', 'files', help_text="") - edit_groups = make_ajax_field(SithFile, 'edit_groups', 'groups', help_text="", label=_("edit group")) - view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group")) + fields = ["parent", "owner", "edit_groups", "view_groups"] + + parent = make_ajax_field(SithFile, "parent", "files", help_text="") + edit_groups = make_ajax_field( + SithFile, "edit_groups", "groups", help_text="", label=_("edit group") + ) + view_groups = make_ajax_field( + SithFile, "view_groups", "groups", help_text="", label=_("view group") + ) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) class FileEditPropView(CanEditPropMixin, UpdateView): model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_edit.jinja' + template_name = "core/file_edit.jinja" context_object_name = "file" form_class = FileEditPropForm def get_form(self, form_class=None): form = super(FileEditPropView, self).get_form(form_class) - form.fields['parent'].queryset = SithFile.objects.filter(is_folder=True) + form.fields["parent"].queryset = SithFile.objects.filter(is_folder=True) return form def form_valid(self, form): ret = super(FileEditPropView, self).form_valid(form) - if form.cleaned_data['recursive']: + if form.cleaned_data["recursive"]: self.object.apply_rights_recursively() return ret def get_success_url(self): - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': self.kwargs['popup'] or ""}) + return reverse( + "core:file_detail", + kwargs={"file_id": self.object.id, "popup": self.kwargs["popup"] or ""}, + ) def get_context_data(self, **kwargs): kwargs = super(FileEditPropView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs class FileView(CanViewMixin, DetailView, FormMixin): """This class handle the upload of new files into a folder""" + model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_detail.jinja' + template_name = "core/file_detail.jinja" context_object_name = "file" form_class = AddFilesForm @@ -201,79 +252,99 @@ class FileView(CanViewMixin, DetailView, FormMixin): `object` is the SithFile object you want to put in the clipboard, or where you want to paste the clipboard """ - if 'delete' in request.POST.keys(): - for f_id in request.POST.getlist('file_list'): + if "delete" in request.POST.keys(): + for f_id in request.POST.getlist("file_list"): sf = SithFile.objects.filter(id=f_id).first() if sf: sf.delete() - if 'clear' in request.POST.keys(): - request.session['clipboard'] = [] - if 'cut' in request.POST.keys(): - for f_id in request.POST.getlist('file_list'): + if "clear" in request.POST.keys(): + request.session["clipboard"] = [] + if "cut" in request.POST.keys(): + for f_id in request.POST.getlist("file_list"): f_id = int(f_id) - if f_id in [c.id for c in object.children.all()] and f_id not in request.session['clipboard']: - request.session['clipboard'].append(f_id) - if 'paste' in request.POST.keys(): - for f_id in request.session['clipboard']: + if ( + f_id in [c.id for c in object.children.all()] + and f_id not in request.session["clipboard"] + ): + request.session["clipboard"].append(f_id) + if "paste" in request.POST.keys(): + for f_id in request.session["clipboard"]: sf = SithFile.objects.filter(id=f_id).first() if sf: sf.move_to(object) - request.session['clipboard'] = [] + request.session["clipboard"] = [] request.session.modified = True def get(self, request, *args, **kwargs): self.form = self.get_form() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] return super(FileView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): self.object = self.get_object() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] if request.user.can_edit(self.object): # XXX this call can fail! FileView.handle_clipboard(request, self.object) self.form = self.get_form() # The form handle only the file upload - files = request.FILES.getlist('file_field') - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + files = request.FILES.getlist("file_field") + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): self.form.process(parent=self.object, owner=request.user, files=files) if self.form.is_valid(): return super(FileView, self).form_valid(self.form) return self.form_invalid(self.form) def get_success_url(self): - return reverse('core:file_detail', kwargs={'file_id': self.object.id, 'popup': self.kwargs['popup'] or ""}) + return reverse( + "core:file_detail", + kwargs={"file_id": self.object.id, "popup": self.kwargs["popup"] or ""}, + ) def get_context_data(self, **kwargs): kwargs = super(FileView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - kwargs['form'] = self.form - if self.kwargs['popup']: - kwargs['popup'] = 'popup' - kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) + kwargs["popup"] = "" + kwargs["form"] = self.form + if self.kwargs["popup"]: + kwargs["popup"] = "popup" + kwargs["clipboard"] = SithFile.objects.filter( + id__in=self.request.session["clipboard"] + ) return kwargs class FileDeleteView(CanEditPropMixin, DeleteView): model = SithFile pk_url_kwarg = "file_id" - template_name = 'core/file_delete_confirm.jinja' + template_name = "core/file_delete_confirm.jinja" context_object_name = "file" def get_success_url(self): self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here - if 'next' in self.request.GET.keys(): - return self.request.GET['next'] + if "next" in self.request.GET.keys(): + return self.request.GET["next"] if self.object.parent is None: - return reverse('core:file_list', kwargs={'popup': self.kwargs['popup'] or ""}) - return reverse('core:file_detail', kwargs={'file_id': self.object.parent.id, 'popup': self.kwargs['popup'] or ""}) + return reverse( + "core:file_list", kwargs={"popup": self.kwargs["popup"] or ""} + ) + return reverse( + "core:file_detail", + kwargs={ + "file_id": self.object.parent.id, + "popup": self.kwargs["popup"] or "", + }, + ) def get_context_data(self, **kwargs): kwargs = super(FileDeleteView, self).get_context_data(**kwargs) - kwargs['popup'] = "" - if self.kwargs['popup']: - kwargs['popup'] = 'popup' + kwargs["popup"] = "" + if self.kwargs["popup"]: + kwargs["popup"] = "popup" return kwargs @@ -282,7 +353,7 @@ class FileModerationView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(FileModerationView, self).get_context_data(**kwargs) - kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100] + kwargs["files"] = SithFile.objects.filter(is_moderated=False)[:100] return kwargs @@ -295,6 +366,6 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin): self.object.is_moderated = True self.object.moderator = request.user self.object.save() - if 'next' in self.request.GET.keys(): - return redirect(self.request.GET['next']) - return redirect('core:file_moderation') + if "next" in self.request.GET.keys(): + return redirect(self.request.GET["next"]) + return redirect("core:file_moderation") diff --git a/core/views/forms.py b/core/views/forms.py index 957bf498..a0290182 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -27,7 +27,14 @@ from django import forms from django.conf import settings from django.db import transaction from django.core.exceptions import ValidationError -from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput, DateTimeInput, Textarea +from django.forms import ( + CheckboxSelectMultiple, + Select, + DateInput, + TextInput, + DateTimeInput, + Textarea, +) from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget @@ -45,114 +52,144 @@ from PIL import Image # Widgets + class SelectSingle(Select): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_single" + attrs["class"] = "select_single" else: - attrs = {'class': "select_single"} + attrs = {"class": "select_single"} return super(SelectSingle, self).render(name, value, attrs) class SelectMultiple(Select): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_multiple" + attrs["class"] = "select_multiple" else: - attrs = {'class': "select_multiple"} + attrs = {"class": "select_multiple"} return super(SelectMultiple, self).render(name, value, attrs) class SelectDateTime(DateTimeInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_datetime" + attrs["class"] = "select_datetime" else: - attrs = {'class': "select_datetime"} + attrs = {"class": "select_datetime"} return super(SelectDateTime, self).render(name, value, attrs) class SelectDate(DateInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_date" + attrs["class"] = "select_date" else: - attrs = {'class': "select_date"} + attrs = {"class": "select_date"} return super(SelectDate, self).render(name, value, attrs) class MarkdownInput(Textarea): def render(self, name, value, attrs=None): - output = '

    %(help_text)s

    '\ - '
    %(content)s
    ' % { - 'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(), - 'help_text': _("Help on the syntax"), - 'content': super(MarkdownInput, self).render(name, value, attrs), - } + output = ( + '

    %(help_text)s

    ' + '
    %(content)s
    ' + % { + "syntax_url": Page.get_page_by_full_name( + settings.SITH_CORE_PAGE_SYNTAX + ).get_absolute_url(), + "help_text": _("Help on the syntax"), + "content": super(MarkdownInput, self).render(name, value, attrs), + } + ) return output class SelectFile(TextInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_file" + attrs["class"] = "select_file" else: - attrs = {'class': "select_file"} - output = '%(content)s
    ' % { - 'content': super(SelectFile, self).render(name, value, attrs), - 'title': _("Choose file"), - 'name': name, - } - output += '' + ugettext("Choose file") + '' + attrs = {"class": "select_file"} + output = ( + '%(content)s
    ' + % { + "content": super(SelectFile, self).render(name, value, attrs), + "title": _("Choose file"), + "name": name, + } + ) + output += ( + '' + + ugettext("Choose file") + + "" + ) return output class SelectUser(TextInput): def render(self, name, value, attrs=None): if attrs: - attrs['class'] = "select_user" + attrs["class"] = "select_user" else: - attrs = {'class': "select_user"} - output = '%(content)s
    ' % { - 'content': super(SelectUser, self).render(name, value, attrs), - 'title': _("Choose user"), - 'name': name, - } - output += '' + ugettext("Choose user") + '' + attrs = {"class": "select_user"} + output = ( + '%(content)s
    ' + % { + "content": super(SelectUser, self).render(name, value, attrs), + "title": _("Choose user"), + "name": name, + } + ) + output += ( + '' + + ugettext("Choose user") + + "" + ) return output + # Forms class LoginForm(AuthenticationForm): def __init__(self, *arg, **kwargs): - if 'data' in kwargs.keys(): + if "data" in kwargs.keys(): from counter.models import Customer - data = kwargs['data'].copy() + + data = kwargs["data"].copy() account_code = re.compile(r"^[0-9]+[A-Za-z]$") try: - if account_code.match(data['username']): - user = Customer.objects.filter(account_id__iexact=data['username']).first().user - elif '@' in data['username']: - user = User.objects.filter(email__iexact=data['username']).first() + if account_code.match(data["username"]): + user = ( + Customer.objects.filter(account_id__iexact=data["username"]) + .first() + .user + ) + elif "@" in data["username"]: + user = User.objects.filter(email__iexact=data["username"]).first() else: - user = User.objects.filter(username=data['username']).first() - data['username'] = user.username + user = User.objects.filter(username=data["username"]).first() + data["username"] = user.username except: pass - kwargs['data'] = data + kwargs["data"] = data super(LoginForm, self).__init__(*arg, **kwargs) - self.fields['username'].label = _("Username, email, or account number") + self.fields["username"].label = _("Username, email, or account number") class RegisteringForm(UserCreationForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" captcha = CaptchaField() class Meta: model = User - fields = ('first_name', 'last_name', 'email') + fields = ("first_name", "last_name", "email") def save(self, commit=True): user = super(RegisteringForm, self).save(commit=False) @@ -169,24 +206,49 @@ class UserProfileForm(forms.ModelForm): This form is actually pretty bad and was made in the rush before the migration. It should be refactored. TODO: refactor this form """ + class Meta: model = User - fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict', - 'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone', - 'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo', - 'forum_signature', 'is_subscriber_viewable'] + fields = [ + "first_name", + "last_name", + "nick_name", + "email", + "date_of_birth", + "profile_pict", + "avatar_pict", + "scrub_pict", + "sex", + "second_email", + "address", + "parent_address", + "phone", + "parent_phone", + "tshirt_size", + "role", + "department", + "dpt_option", + "semester", + "quote", + "school", + "promo", + "forum_signature", + "is_subscriber_viewable", + ] widgets = { - 'date_of_birth': SelectDate, - 'profile_pict': forms.ClearableFileInput, - 'avatar_pict': forms.ClearableFileInput, - 'scrub_pict': forms.ClearableFileInput, - 'phone': PhoneNumberInternationalFallbackWidget, - 'parent_phone': PhoneNumberInternationalFallbackWidget, + "date_of_birth": SelectDate, + "profile_pict": forms.ClearableFileInput, + "avatar_pict": forms.ClearableFileInput, + "scrub_pict": forms.ClearableFileInput, + "phone": PhoneNumberInternationalFallbackWidget, + "parent_phone": PhoneNumberInternationalFallbackWidget, } labels = { - 'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"), - 'avatar_pict': _("Avatar: used on the forum"), - 'scrub_pict': _("Scrub: let other know how your scrub looks like!"), + "profile_pict": _( + "Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)" + ), + "avatar_pict": _("Avatar: used on the forum"), + "scrub_pict": _("Scrub: let other know how your scrub looks like!"), } def __init__(self, *arg, **kwargs): @@ -197,27 +259,36 @@ class UserProfileForm(forms.ModelForm): def generate_name(self, field_name, f): field_name = field_name[:-4] - return field_name + str(self.instance.id) + "." + f.content_type.split('/')[-1] + return field_name + str(self.instance.id) + "." + f.content_type.split("/")[-1] def process(self, files): avatar = self.instance.avatar_pict profile = self.instance.profile_pict scrub = self.instance.scrub_pict self.full_clean() - self.cleaned_data['avatar_pict'] = avatar - self.cleaned_data['profile_pict'] = profile - self.cleaned_data['scrub_pict'] = scrub + self.cleaned_data["avatar_pict"] = avatar + self.cleaned_data["profile_pict"] = profile + self.cleaned_data["scrub_pict"] = scrub parent = SithFile.objects.filter(parent=None, name="profiles").first() for field, f in files: with transaction.atomic(): try: im = Image.open(BytesIO(f.read())) - new_file = SithFile(parent=parent, name=self.generate_name(field, f), - file=resize_image(im, 400, f.content_type.split('/')[-1]), - owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size, - moderator=self.instance, is_moderated=True) + new_file = SithFile( + parent=parent, + name=self.generate_name(field, f), + file=resize_image(im, 400, f.content_type.split("/")[-1]), + owner=self.instance, + is_folder=False, + mime_type=f.content_type, + size=f._size, + moderator=self.instance, + is_moderated=True, + ) new_file.file.name = new_file.name - old = SithFile.objects.filter(parent=parent, name=new_file.name).first() + old = SithFile.objects.filter( + parent=parent, name=new_file.name + ).first() if old: old.delete() new_file.clean() @@ -226,73 +297,101 @@ class UserProfileForm(forms.ModelForm): self._errors.pop(field, None) except ValidationError as e: self._errors.pop(field, None) - self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % - {'file_name': f, 'msg': str(e.message)}) + self.add_error( + field, + _("Error uploading file %(file_name)s: %(msg)s") + % {"file_name": f, "msg": str(e.message)}, + ) except IOError: self._errors.pop(field, None) - self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % - {'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")}) + self.add_error( + field, + _("Error uploading file %(file_name)s: %(msg)s") + % { + "file_name": f, + "msg": _( + "Bad image format, only jpeg, png, and gif are accepted" + ), + }, + ) self._post_clean() class UserPropForm(forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" class Meta: model = User - fields = ['groups'] - help_texts = { - 'groups': "Which groups this user belongs to", - } - widgets = { - 'groups': CheckboxSelectMultiple, - } + fields = ["groups"] + help_texts = {"groups": "Which groups this user belongs to"} + widgets = {"groups": CheckboxSelectMultiple} class UserGodfathersForm(forms.Form): - type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add")) - user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) + type = forms.ChoiceField( + choices=[("godfather", _("Godfather")), ("godchild", _("Godchild"))], + label=_("Add"), + ) + user = AutoCompleteSelectField( + "users", required=True, label=_("Select user"), help_text=None + ) class PagePropForm(forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' + error_css_class = "error" + required_css_class = "required" class Meta: model = Page - fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ] - edit_groups = make_ajax_field(Page, 'edit_groups', 'groups', help_text="", label=_("edit groups")) - view_groups = make_ajax_field(Page, 'view_groups', 'groups', help_text="", label=_("view groups")) + fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"] + + edit_groups = make_ajax_field( + Page, "edit_groups", "groups", help_text="", label=_("edit groups") + ) + view_groups = make_ajax_field( + Page, "view_groups", "groups", help_text="", label=_("view groups") + ) def __init__(self, *arg, **kwargs): super(PagePropForm, self).__init__(*arg, **kwargs) - self.fields['edit_groups'].required = False - self.fields['view_groups'].required = False + self.fields["edit_groups"].required = False + self.fields["view_groups"].required = False class PageForm(forms.ModelForm): class Meta: model = Page - fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups'] - edit_groups = make_ajax_field(Page, 'edit_groups', 'groups', help_text="", label=_("edit groups")) - view_groups = make_ajax_field(Page, 'view_groups', 'groups', help_text="", label=_("view groups")) + fields = ["parent", "name", "owner_group", "edit_groups", "view_groups"] + + edit_groups = make_ajax_field( + Page, "edit_groups", "groups", help_text="", label=_("edit groups") + ) + view_groups = make_ajax_field( + Page, "view_groups", "groups", help_text="", label=_("view groups") + ) def __init__(self, *args, **kwargs): super(PageForm, self).__init__(*args, **kwargs) - self.fields['parent'].queryset = self.fields['parent'].queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE).filter(club=None) + self.fields["parent"].queryset = ( + self.fields["parent"] + .queryset.exclude(name=settings.SITH_CLUB_ROOT_PAGE) + .filter(club=None) + ) class GiftForm(forms.ModelForm): class Meta: model = Gift - fields = ['label', 'user'] + fields = ["label", "user"] label = forms.ChoiceField(choices=settings.SITH_GIFT_LIST) def __init__(self, *args, **kwargs): - user_id = kwargs.pop('user_id', None) + user_id = kwargs.pop("user_id", None) super(GiftForm, self).__init__(*args, **kwargs) if user_id: - self.fields['user'].queryset = self.fields['user'].queryset.filter(id=user_id) - self.fields['user'].widget = forms.HiddenInput() + self.fields["user"].queryset = self.fields["user"].queryset.filter( + id=user_id + ) + self.fields["user"].widget = forms.HiddenInput() diff --git a/core/views/group.py b/core/views/group.py index 8dc54905..29864dbc 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -34,6 +34,7 @@ class GroupListView(CanEditMixin, ListView): """ Displays the group list """ + model = RealGroup template_name = "core/group_list.jinja" @@ -42,17 +43,17 @@ class GroupEditView(CanEditMixin, UpdateView): model = RealGroup pk_url_kwarg = "group_id" template_name = "core/group_edit.jinja" - fields = ['name', 'description'] + fields = ["name", "description"] class GroupCreateView(CanEditMixin, CreateView): model = RealGroup template_name = "core/group_edit.jinja" - fields = ['name', 'description'] + fields = ["name", "description"] class GroupDeleteView(CanEditMixin, DeleteView): model = RealGroup pk_url_kwarg = "group_id" template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy('core:group_list') + success_url = reverse_lazy("core:group_list") diff --git a/core/views/page.py b/core/views/page.py index 14d50187..92b9b381 100644 --- a/core/views/page.py +++ b/core/views/page.py @@ -36,7 +36,6 @@ from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMi class CanEditPagePropMixin(CanEditPropMixin): - def dispatch(self, request, *args, **kwargs): res = super(CanEditPagePropMixin, self).dispatch(request, *args, **kwargs) if self.object.is_club_page: @@ -46,93 +45,95 @@ class CanEditPagePropMixin(CanEditPropMixin): class PageListView(CanViewMixin, ListView): model = Page - template_name = 'core/page_list.jinja' + template_name = "core/page_list.jinja" class PageView(CanViewMixin, DetailView): model = Page - template_name = 'core/page_detail.jinja' + template_name = "core/page_detail.jinja" def dispatch(self, request, *args, **kwargs): res = super(PageView, self).dispatch(request, *args, **kwargs) if self.object and self.object.need_club_redirection: - return redirect('club:club_view', club_id=self.object.club.id) + return redirect("club:club_view", club_id=self.object.club.id) return res def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self.page def get_context_data(self, **kwargs): context = super(PageView, self).get_context_data(**kwargs) if "page" not in context.keys(): - context['new_page'] = self.kwargs['page_name'] + context["new_page"] = self.kwargs["page_name"] return context class PageHistView(CanViewMixin, DetailView): model = Page - template_name = 'core/page_hist.jinja' + template_name = "core/page_hist.jinja" def dispatch(self, request, *args, **kwargs): res = super(PageHistView, self).dispatch(request, *args, **kwargs) if self.object.need_club_redirection: - return redirect('club:club_hist', club_id=self.object.club.id) + return redirect("club:club_hist", club_id=self.object.club.id) return res def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self.page class PageRevView(CanViewMixin, DetailView): model = Page - template_name = 'core/page_detail.jinja' + template_name = "core/page_detail.jinja" def dispatch(self, request, *args, **kwargs): res = super(PageRevView, self).dispatch(request, *args, **kwargs) if self.object.need_club_redirection: - return redirect('club:club_view_rev', club_id=self.object.club.id, rev_id=kwargs['rev']) + return redirect( + "club:club_view_rev", club_id=self.object.club.id, rev_id=kwargs["rev"] + ) return res def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self.page def get_context_data(self, **kwargs): context = super(PageRevView, self).get_context_data(**kwargs) if self.page is not None: - context['page'] = self.page + context["page"] = self.page try: - rev = self.page.revisions.get(id=self.kwargs['rev']) - context['rev'] = rev + rev = self.page.revisions.get(id=self.kwargs["rev"]) + context["rev"] = rev except: # By passing, the template will just display the normal page without taking revision into account pass else: - context['new_page'] = self.kwargs['page_name'] + context["new_page"] = self.kwargs["page_name"] return context class PageCreateView(CanCreateMixin, CreateView): model = Page form_class = PageForm - template_name = 'core/page_prop.jinja' + template_name = "core/page_prop.jinja" def get_initial(self): init = {} - if 'page' in self.request.GET.keys(): - page_name = self.request.GET['page'] - parent_name = '/'.join(page_name.split('/')[:-1]) + if "page" in self.request.GET.keys(): + page_name = self.request.GET["page"] + parent_name = "/".join(page_name.split("/")[:-1]) parent = Page.get_page_by_full_name(parent_name) if parent is not None: - init['parent'] = parent.id - init['name'] = page_name.split('/')[-1] + init["parent"] = parent.id + init["name"] = page_name.split("/")[-1] return init def get_context_data(self, **kwargs): context = super(PageCreateView, self).get_context_data(**kwargs) - context['new_page'] = True + context["new_page"] = True return context def form_valid(self, form): @@ -144,9 +145,9 @@ class PageCreateView(CanCreateMixin, CreateView): class PagePropView(CanEditPagePropMixin, UpdateView): model = Page form_class = PagePropForm - template_name = 'core/page_prop.jinja' - slug_field = '_full_name' - slug_url_kwarg = 'page_name' + template_name = "core/page_prop.jinja" + slug_field = "_full_name" + slug_url_kwarg = "page_name" def get_object(self): o = super(PagePropView, self).get_object() @@ -169,11 +170,13 @@ class PagePropView(CanEditPagePropMixin, UpdateView): class PageEditViewBase(CanEditMixin, UpdateView): model = PageRev - form_class = modelform_factory(model=PageRev, fields=['title', 'content', ], widgets={'content': MarkdownInput}) - template_name = 'core/pagerev_edit.jinja' + form_class = modelform_factory( + model=PageRev, fields=["title", "content"], widgets={"content": MarkdownInput} + ) + template_name = "core/pagerev_edit.jinja" def get_object(self): - self.page = Page.get_page_by_full_name(self.kwargs['page_name']) + self.page = Page.get_page_by_full_name(self.kwargs["page_name"]) return self._get_revision() def _get_revision(self): @@ -193,17 +196,15 @@ class PageEditViewBase(CanEditMixin, UpdateView): def get_context_data(self, **kwargs): context = super(PageEditViewBase, self).get_context_data(**kwargs) if self.page is not None: - context['page'] = self.page + context["page"] = self.page else: - context['new_page'] = self.kwargs['page_name'] + context["new_page"] = self.kwargs["page_name"] return context def form_valid(self, form): # TODO : factor that, but first make some tests rev = form.instance - new_rev = PageRev(title=rev.title, - content=rev.content, - ) + new_rev = PageRev(title=rev.title, content=rev.content) new_rev.author = self.request.user new_rev.page = self.page form.instance = new_rev @@ -211,18 +212,17 @@ class PageEditViewBase(CanEditMixin, UpdateView): class PageEditView(PageEditViewBase): - def dispatch(self, request, *args, **kwargs): res = super(PageEditView, self).dispatch(request, *args, **kwargs) if self.object and self.object.page.need_club_redirection: - return redirect('club:club_edit_page', club_id=self.object.page.club.id) + return redirect("club:club_edit_page", club_id=self.object.page.club.id) return res class PageDeleteView(CanEditPagePropMixin, DeleteView): model = Page - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'page_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "page_id" def get_success_url(self, **kwargs): - return reverse_lazy('core:page_list') + return reverse_lazy("core:page_list") diff --git a/core/views/site.py b/core/views/site.py index a4084e90..55c26608 100644 --- a/core/views/site.py +++ b/core/views/site.py @@ -41,6 +41,7 @@ from club.models import Club def index(request, context=None): if request.user.is_authenticated(): from com.views import NewsListView + return NewsListView.as_view()(request) return render(request, "core/index.jinja") @@ -50,11 +51,11 @@ class NotificationList(ListView): template_name = "core/notification_list.jinja" def get_queryset(self): - if 'see_all' in self.request.GET.keys(): + if "see_all" in self.request.GET.keys(): for n in self.request.user.notifications.all(): n.viewed = True n.save() - return self.request.user.notifications.order_by('-date')[:20] + return self.request.user.notifications.order_by("-date")[:20] def notification(request, notif_id): @@ -70,7 +71,12 @@ def notification(request, notif_id): def search_user(query, as_json=False): - res = SearchQuerySet().models(User).filter(text=query).filter_or(text__contains=query)[:20] + res = ( + SearchQuerySet() + .models(User) + .filter(text=query) + .filter_or(text__contains=query)[:20] + ) return [r.object for r in res] @@ -79,8 +85,10 @@ def search_club(query, as_json=False): if query: clubs = Club.objects.filter(name__icontains=query).all() clubs = clubs[:5] - if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers - clubs = json.loads(serializers.serialize('json', clubs, fields=('name'))) + if ( + as_json + ): # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers + clubs = json.loads(serializers.serialize("json", clubs, fields=("name"))) else: clubs = list(clubs) return clubs @@ -89,25 +97,23 @@ def search_club(query, as_json=False): @login_required def search_view(request): result = { - 'users': search_user(request.GET.get('query', '')), - 'clubs': search_club(request.GET.get('query', '')), + "users": search_user(request.GET.get("query", "")), + "clubs": search_club(request.GET.get("query", "")), } - return render(request, "core/search.jinja", context={'result': result}) + return render(request, "core/search.jinja", context={"result": result}) @login_required def search_user_json(request): - result = { - 'users': search_user(request.GET.get('query', ''), True), - } + result = {"users": search_user(request.GET.get("query", ""), True)} return JsonResponse(result) @login_required def search_json(request): result = { - 'users': search_user(request.GET.get('query', ''), True), - 'clubs': search_club(request.GET.get('query', ''), True), + "users": search_user(request.GET.get("query", ""), True), + "clubs": search_club(request.GET.get("query", ""), True), } return JsonResponse(result) @@ -116,8 +122,8 @@ class ToMarkdownView(TemplateView): template_name = "core/to_markdown.jinja" def post(self, request, *args, **kwargs): - self.text = request.POST['text'] - if request.POST['syntax'] == "doku": + self.text = request.POST["text"] + if request.POST["syntax"] == "doku": self.text_md = doku_to_markdown(self.text) else: self.text_md = bbcode_to_markdown(self.text) @@ -127,9 +133,9 @@ class ToMarkdownView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(ToMarkdownView, self).get_context_data(**kwargs) try: - kwargs['text'] = self.text - kwargs['text_md'] = self.text_md + kwargs["text"] = self.text + kwargs["text_md"] = self.text_md except: - kwargs['text'] = "" - kwargs['text_md'] = "" + kwargs["text"] = "" + kwargs["text_md"] = "" return kwargs diff --git a/core/views/user.py b/core/views/user.py index ad441650..fb9e6455 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -31,7 +31,13 @@ from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied, ValidationError from django.http import Http404, HttpResponse from django.views.generic.edit import UpdateView -from django.views.generic import ListView, DetailView, TemplateView, CreateView, DeleteView +from django.views.generic import ( + ListView, + DetailView, + TemplateView, + CreateView, + DeleteView, +) from django.forms.models import modelform_factory from django.forms import CheckboxSelectMultiple from django.core.urlresolvers import reverse_lazy @@ -42,8 +48,20 @@ from django.views.generic.dates import YearMixin, MonthMixin from datetime import timedelta, date import logging -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin -from core.views.forms import RegisteringForm, UserProfileForm, LoginForm, UserGodfathersForm, GiftForm +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + QuickNotifMixin, +) +from core.views.forms import ( + RegisteringForm, + UserProfileForm, + LoginForm, + UserGodfathersForm, + GiftForm, +) from core.models import User, SithFile, Preferences, Gift from subscription.models import Subscription from trombi.views import UserTrombiForm @@ -55,7 +73,9 @@ def login(request): Needs to be improve with correct handling of form exceptions """ - return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm) + return views.login( + request, template_name="core/login.jinja", authentication_form=LoginForm + ) def logout(request): @@ -69,14 +89,20 @@ def password_change(request): """ Allows a user to change its password """ - return views.password_change(request, template_name="core/password_change.jinja", post_change_redirect=reverse("core:password_change_done")) + return views.password_change( + request, + template_name="core/password_change.jinja", + post_change_redirect=reverse("core:password_change_done"), + ) def password_change_done(request): """ Allows a user to change its password """ - return views.password_change_done(request, template_name="core/password_change_done.jinja") + return views.password_change_done( + request, template_name="core/password_change_done.jinja" + ) def password_root_change(request, user_id): @@ -95,62 +121,74 @@ def password_root_change(request, user_id): return redirect("core:password_change_done") else: form = views.SetPasswordForm(user=user) - return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user}) + return TemplateResponse( + request, "core/password_change.jinja", {"form": form, "target": user} + ) def password_reset(request): """ Allows someone to enter an email adresse for resetting password """ - return views.password_reset(request, - template_name="core/password_reset.jinja", - email_template_name="core/password_reset_email.jinja", - post_reset_redirect="core:password_reset_done", - ) + return views.password_reset( + request, + template_name="core/password_reset.jinja", + email_template_name="core/password_reset_email.jinja", + post_reset_redirect="core:password_reset_done", + ) def password_reset_done(request): """ Confirm that the reset email has been sent """ - return views.password_reset_done(request, template_name="core/password_reset_done.jinja") + return views.password_reset_done( + request, template_name="core/password_reset_done.jinja" + ) def password_reset_confirm(request, uidb64=None, token=None): """ Provide a reset password formular """ - return views.password_reset_confirm(request, uidb64=uidb64, token=token, - post_reset_redirect="core:password_reset_complete", - template_name="core/password_reset_confirm.jinja", - ) + return views.password_reset_confirm( + request, + uidb64=uidb64, + token=token, + post_reset_redirect="core:password_reset_complete", + template_name="core/password_reset_confirm.jinja", + ) def password_reset_complete(request): """ Confirm the password has sucessfully been reset """ - return views.password_reset_complete(request, - template_name="core/password_reset_complete.jinja", - ) + return views.password_reset_complete( + request, template_name="core/password_reset_complete.jinja" + ) def register(request): context = {} - if request.method == 'POST': + if request.method == "POST": form = RegisteringForm(request.POST) if form.is_valid(): - logging.debug("Registering " + form.cleaned_data['first_name'] + form.cleaned_data['last_name']) + logging.debug( + "Registering " + + form.cleaned_data["first_name"] + + form.cleaned_data["last_name"] + ) u = form.save() - context['user_registered'] = u - context['tests'] = 'TEST_REGISTER_USER_FORM_OK' + context["user_registered"] = u + context["tests"] = "TEST_REGISTER_USER_FORM_OK" form = RegisteringForm() else: - context['error'] = 'Erreur' - context['tests'] = 'TEST_REGISTER_USER_FORM_FAIL' + context["error"] = "Erreur" + context["tests"] = "TEST_REGISTER_USER_FORM_FAIL" else: form = RegisteringForm() - context['form'] = form.as_p() + context["form"] = form.as_p() return render(request, "core/register.jinja", context) @@ -160,65 +198,103 @@ class UserTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}), - 'slug': 'infos', - 'name': _("Infos"), - }) - tab_list.append({ - 'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}), - 'slug': 'godfathers', - 'name': _("Godfathers"), - }) - tab_list.append({ - 'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}), - 'slug': 'pictures', - 'name': _("Pictures"), - }) + tab_list.append( + { + "url": reverse("core:user_profile", kwargs={"user_id": self.object.id}), + "slug": "infos", + "name": _("Infos"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_godfathers", kwargs={"user_id": self.object.id} + ), + "slug": "godfathers", + "name": _("Godfathers"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_pictures", kwargs={"user_id": self.object.id} + ), + "slug": "pictures", + "name": _("Pictures"), + } + ) if self.request.user == self.object: - tab_list.append({ - 'url': reverse('core:user_tools'), - 'slug': 'tools', - 'name': _("Tools"), - }) + tab_list.append( + {"url": reverse("core:user_tools"), "slug": "tools", "name": _("Tools")} + ) if self.request.user.can_edit(self.object): - tab_list.append({ - 'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}), - 'slug': 'edit', - 'name': _("Edit"), - }) - tab_list.append({ - 'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}), - 'slug': 'prefs', - 'name': _("Preferences"), - }) + tab_list.append( + { + "url": reverse( + "core:user_edit", kwargs={"user_id": self.object.id} + ), + "slug": "edit", + "name": _("Edit"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_prefs", kwargs={"user_id": self.object.id} + ), + "slug": "prefs", + "name": _("Preferences"), + } + ) if self.request.user.can_view(self.object): - tab_list.append({ - 'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}), - 'slug': 'clubs', - 'name': _("Clubs"), - }) + tab_list.append( + { + "url": reverse( + "core:user_clubs", kwargs={"user_id": self.object.id} + ), + "slug": "clubs", + "name": _("Clubs"), + } + ) if self.request.user.is_owner(self.object): - tab_list.append({ - 'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}), - 'slug': 'groups', - 'name': _("Groups"), - }) + tab_list.append( + { + "url": reverse( + "core:user_groups", kwargs={"user_id": self.object.id} + ), + "slug": "groups", + "name": _("Groups"), + } + ) try: - if (self.object.customer and (self.object == self.request.user - or self.request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - or self.request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX) - or self.request.user.is_root)): - tab_list.append({ - 'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}), - 'slug': 'stats', - 'name': _("Stats"), - }) - tab_list.append({ - 'url': reverse('core:user_account', kwargs={'user_id': self.object.id}), - 'slug': 'account', - 'name': _("Account") + " (%s €)" % self.object.customer.amount, - }) + if self.object.customer and ( + self.object == self.request.user + or self.request.user.is_in_group( + settings.SITH_GROUP_ACCOUNTING_ADMIN_ID + ) + or self.request.user.is_in_group( + settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX + ) + or self.request.user.is_root + ): + tab_list.append( + { + "url": reverse( + "core:user_stats", kwargs={"user_id": self.object.id} + ), + "slug": "stats", + "name": _("Stats"), + } + ) + tab_list.append( + { + "url": reverse( + "core:user_account", kwargs={"user_id": self.object.id} + ), + "slug": "account", + "name": _("Account") + " (%s €)" % self.object.customer.amount, + } + ) except: pass return tab_list @@ -228,15 +304,18 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's profile """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_detail.jinja" - current_tab = 'infos' + current_tab = "infos" def get_context_data(self, **kwargs): kwargs = super(UserView, self).get_context_data(**kwargs) - kwargs['gift_form'] = GiftForm(user_id=self.object.id, initial={'user': self.object}) + kwargs["gift_form"] = GiftForm( + user_id=self.object.id, initial={"user": self.object} + ) return kwargs @@ -244,33 +323,36 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's pictures """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_pictures.jinja" - current_tab = 'pictures' + current_tab = "pictures" def get_context_data(self, **kwargs): kwargs = super(UserPicturesView, self).get_context_data(**kwargs) - kwargs['albums'] = [] - kwargs['pictures'] = {} - picture_qs = self.object.pictures.exclude(picture=None).order_by('-picture__parent__date', 'id').select_related('picture__parent') + kwargs["albums"] = [] + kwargs["pictures"] = {} + picture_qs = ( + self.object.pictures.exclude(picture=None) + .order_by("-picture__parent__date", "id") + .select_related("picture__parent") + ) last_album = None for pict_relation in picture_qs: album = pict_relation.picture.parent if album.id != last_album: - kwargs['albums'].append(album) - kwargs['pictures'][album.id] = [] + kwargs["albums"].append(album) + kwargs["pictures"][album.id] = [] last_album = album.id - kwargs['pictures'][album.id].append(pict_relation.picture) + kwargs["pictures"][album.id].append(pict_relation.picture) return kwargs def DeleteUserGodfathers(request, user_id, godfather_id, is_father): user = User.objects.get(id=user_id) - if ((user == request.user) or - request.user.is_root or - request.user.is_board_member): + if (user == request.user) or request.user.is_root or request.user.is_board_member: ud = get_object_or_404(User, id=godfather_id) if is_father == "True": user.godfathers.remove(ud) @@ -278,28 +360,29 @@ def DeleteUserGodfathers(request, user_id, godfather_id, is_father): user.godchildren.remove(ud) else: raise PermissionDenied - return redirect('core:user_godfathers', user_id=user_id) + return redirect("core:user_godfathers", user_id=user_id) class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's godfathers """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_godfathers.jinja" - current_tab = 'godfathers' + current_tab = "godfathers" def post(self, request, *args, **kwargs): self.object = self.get_object() self.form = UserGodfathersForm(request.POST) - if self.form.is_valid() and self.form.cleaned_data['user'] != self.object: - if self.form.cleaned_data['type'] == 'godfather': - self.object.godfathers.add(self.form.cleaned_data['user']) + if self.form.is_valid() and self.form.cleaned_data["user"] != self.object: + if self.form.cleaned_data["type"] == "godfather": + self.object.godfathers.add(self.form.cleaned_data["user"]) self.object.save() else: - self.object.godchildren.add(self.form.cleaned_data['user']) + self.object.godchildren.add(self.form.cleaned_data["user"]) self.object.save() self.form = UserGodfathersForm() return super(UserGodfathersView, self).get(request, *args, **kwargs) @@ -307,39 +390,44 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): kwargs = super(UserGodfathersView, self).get_context_data(**kwargs) try: - kwargs['form'] = self.form + kwargs["form"] = self.form except: - kwargs['form'] = UserGodfathersForm() + kwargs["form"] = UserGodfathersForm() return kwargs + class UserGodfathersTreeView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's family tree """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_godfathers_tree.jinja" - current_tab = 'godfathers' + current_tab = "godfathers" def get_context_data(self, **kwargs): kwargs = super(UserGodfathersTreeView, self).get_context_data(**kwargs) if "descent" in self.request.GET: - kwargs['param'] = "godchildren" + kwargs["param"] = "godchildren" else: - kwargs['param'] = "godfathers" - kwargs['members_set'] = set() + kwargs["param"] = "godfathers" + kwargs["members_set"] = set() return kwargs + class UserGodfathersTreePictureView(CanViewMixin, DetailView): """ Display a user's tree as a picture """ + model = User pk_url_kwarg = "user_id" def build_complex_graph(self): import pygraphviz as pgv + self.depth = int(self.request.GET.get("depth", 4)) if self.param == "godfathers": self.graph = pgv.AGraph(strict=False, directed=True, rankdir="BT") @@ -349,7 +437,8 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView): self.level = 1 # Since the tree isn't very deep, we can build it recursively def crawl_family(user): - if self.level > self.depth: return + if self.level > self.depth: + return self.level += 1 for u in user.__getattribute__(self.param).all(): self.graph.add_edge(user.get_short_name(), u.get_short_name()) @@ -357,12 +446,14 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView): family.add(u) crawl_family(u) self.level -= 1 + self.graph.add_node(self.object.get_short_name()) family.add(self.object) crawl_family(self.object) def build_family_graph(self): import pygraphviz as pgv + self.graph = pgv.AGraph(strict=False, directed=True) self.graph.add_node(self.object.get_short_name()) for u in self.object.godfathers.all(): @@ -384,29 +475,31 @@ class UserGodfathersTreePictureView(CanViewMixin, DetailView): else: self.build_complex_graph() # Pimp the graph before display - self.graph.node_attr['color'] = "lightblue" - self.graph.node_attr['style'] = "filled" + self.graph.node_attr["color"] = "lightblue" + self.graph.node_attr["style"] = "filled" main_node = self.graph.get_node(self.object.get_short_name()) - main_node.attr['color'] = "sandybrown" - main_node.attr['shape'] = "rect" + main_node.attr["color"] = "sandybrown" + main_node.attr["shape"] = "rect" if self.param == "godchildren": - self.graph.graph_attr['label'] = _("Godchildren") + self.graph.graph_attr["label"] = _("Godchildren") elif self.param == "godfathers": - self.graph.graph_attr['label'] = _("Godfathers") + self.graph.graph_attr["label"] = _("Godfathers") else: - self.graph.graph_attr['label'] = _("Family") + self.graph.graph_attr["label"] = _("Family") img = self.graph.draw(format="png", prog="dot") return HttpResponse(img, content_type="image/png") + class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): """ Display a user's stats """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" template_name = "core/user_stats.jinja" - current_tab = 'stats' + current_tab = "stats" def dispatch(self, request, *arg, **kwargs): profile = self.get_object() @@ -414,10 +507,14 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): if not hasattr(profile, "customer"): raise Http404 - if not (profile == request.user - or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX) - or request.user.is_root): + if not ( + profile == request.user + or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) + or request.user.is_in_group( + settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX + ) + or request.user.is_root + ): raise PermissionDenied return super(UserStatsView, self).dispatch(request, *arg, **kwargs) @@ -426,22 +523,71 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): kwargs = super(UserStatsView, self).get_context_data(**kwargs) from counter.models import Counter from django.db.models import Sum + foyer = Counter.objects.filter(name="Foyer").first() mde = Counter.objects.filter(name="MDE").first() gommette = Counter.objects.filter(name="La Gommette").first() semester_start = Subscription.compute_start(d=date.today(), duration=3) - kwargs['total_perm_time'] = sum([p.end - p.start for p in self.object.permanencies.exclude(end=None)], timedelta()) - kwargs['total_foyer_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=foyer).exclude(end=None)], timedelta()) - kwargs['total_mde_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=mde).exclude(end=None)], timedelta()) - kwargs['total_gommette_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=gommette).exclude(end=None)], timedelta()) - kwargs['total_foyer_buyings'] = sum([b.unit_price * b.quantity for b in - self.object.customer.buyings.filter(counter=foyer, date__gte=semester_start)]) - kwargs['total_mde_buyings'] = sum([b.unit_price * b.quantity for b in self.object.customer.buyings.filter(counter=mde, - date__gte=semester_start)]) - kwargs['total_gommette_buyings'] = sum([b.unit_price * b.quantity for b in - self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)]) - kwargs['top_product'] = self.object.customer.buyings.values('product__name').annotate( - product_sum=Sum('quantity')).exclude(product_sum=None).order_by('-product_sum').all()[:10] + kwargs["total_perm_time"] = sum( + [p.end - p.start for p in self.object.permanencies.exclude(end=None)], + timedelta(), + ) + kwargs["total_foyer_time"] = sum( + [ + p.end - p.start + for p in self.object.permanencies.filter(counter=foyer).exclude( + end=None + ) + ], + timedelta(), + ) + kwargs["total_mde_time"] = sum( + [ + p.end - p.start + for p in self.object.permanencies.filter(counter=mde).exclude(end=None) + ], + timedelta(), + ) + kwargs["total_gommette_time"] = sum( + [ + p.end - p.start + for p in self.object.permanencies.filter(counter=gommette).exclude( + end=None + ) + ], + timedelta(), + ) + kwargs["total_foyer_buyings"] = sum( + [ + b.unit_price * b.quantity + for b in self.object.customer.buyings.filter( + counter=foyer, date__gte=semester_start + ) + ] + ) + kwargs["total_mde_buyings"] = sum( + [ + b.unit_price * b.quantity + for b in self.object.customer.buyings.filter( + counter=mde, date__gte=semester_start + ) + ] + ) + kwargs["total_gommette_buyings"] = sum( + [ + b.unit_price * b.quantity + for b in self.object.customer.buyings.filter( + counter=gommette, date__gte=semester_start + ) + ] + ) + kwargs["top_product"] = ( + self.object.customer.buyings.values("product__name") + .annotate(product_sum=Sum("quantity")) + .exclude(product_sum=None) + .order_by("-product_sum") + .all()[:10] + ) return kwargs @@ -449,6 +595,7 @@ class UserMiniView(CanViewMixin, DetailView): """ Display a user's profile """ + model = User pk_url_kwarg = "user_id" context_object_name = "profile" @@ -459,6 +606,7 @@ class UserListView(ListView, CanEditPropMixin): """ Displays the user list """ + model = User template_name = "core/user_list.jinja" @@ -467,6 +615,7 @@ class UserUploadProfilePictView(CanEditMixin, DetailView): """ Handle the upload of the profile picture taken with webcam in navigator """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_edit.jinja" @@ -475,16 +624,23 @@ class UserUploadProfilePictView(CanEditMixin, DetailView): from core.utils import resize_image from io import BytesIO from PIL import Image + self.object = self.get_object() if self.object.profile_pict: raise ValidationError(_("User already has a profile picture")) - f = request.FILES['new_profile_pict'] + f = request.FILES["new_profile_pict"] parent = SithFile.objects.filter(parent=None, name="profiles").first() name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs im = Image.open(BytesIO(f.read())) - new_file = SithFile(parent=parent, name=name, - file=resize_image(im, 400, f.content_type.split('/')[-1]), - owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size) + new_file = SithFile( + parent=parent, + name=name, + file=resize_image(im, 400, f.content_type.split("/")[-1]), + owner=self.object, + is_folder=False, + mime_type=f.content_type, + size=f._size, + ) new_file.file.name = name new_file.save() self.object.profile_pict = new_file @@ -496,12 +652,13 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): """ Edit a user's profile """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_edit.jinja" form_class = UserProfileForm current_tab = "edit" - edit_once = ['profile_pict', 'date_of_birth', 'first_name', 'last_name'] + edit_once = ["profile_pict", "date_of_birth", "first_name", "last_name"] board_only = [] def remove_restricted_fields(self, request): @@ -509,7 +666,9 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): Removes edit_once and board_only fields """ for i in self.edit_once: - if getattr(self.form.instance, i) and not (request.user.is_board_member or request.user.is_root): + if getattr(self.form.instance, i) and not ( + request.user.is_board_member or request.user.is_root + ): self.form.fields.pop(i, None) for i in self.board_only: if not (request.user.is_board_member or request.user.is_root): @@ -527,14 +686,18 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): self.remove_restricted_fields(request) files = request.FILES.items() self.form.process(files) - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): return super(UserUpdateProfileView, self).form_valid(self.form) return self.form_invalid(self.form) def get_context_data(self, **kwargs): kwargs = super(UserUpdateProfileView, self).get_context_data(**kwargs) - kwargs['profile'] = self.form.instance - kwargs['form'] = self.form + kwargs["profile"] = self.form.instance + kwargs["form"] = self.form return kwargs @@ -542,6 +705,7 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView): """ Display the user's club(s) """ + model = User context_object_name = "profile" pk_url_kwarg = "user_id" @@ -553,28 +717,30 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): """ Edit a user's preferences """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_preferences.jinja" - form_class = modelform_factory(Preferences, fields=['receive_weekmail', - 'notify_on_click', 'notify_on_refill']) + form_class = modelform_factory( + Preferences, fields=["receive_weekmail", "notify_on_click", "notify_on_refill"] + ) context_object_name = "profile" current_tab = "prefs" def get_object(self, queryset=None): - user = get_object_or_404(User, pk=self.kwargs['user_id']) + user = get_object_or_404(User, pk=self.kwargs["user_id"]) return user def get_form_kwargs(self): kwargs = super(UserPreferencesView, self).get_form_kwargs() pref = self.object.preferences - kwargs.update({'instance': pref}) + kwargs.update({"instance": pref}) return kwargs def get_context_data(self, **kwargs): kwargs = super(UserPreferencesView, self).get_context_data(**kwargs) - if not hasattr(self.object, 'trombi_user'): - kwargs['trombi_form'] = UserTrombiForm() + if not hasattr(self.object, "trombi_user"): + kwargs["trombi_form"] = UserTrombiForm() return kwargs @@ -582,11 +748,13 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView): """ Edit a user's groups """ + model = User pk_url_kwarg = "user_id" template_name = "core/user_group.jinja" - form_class = modelform_factory(User, fields=['groups'], - widgets={'groups': CheckboxSelectMultiple}) + form_class = modelform_factory( + User, fields=["groups"], widgets={"groups": CheckboxSelectMultiple} + ) context_object_name = "profile" current_tab = "groups" @@ -595,16 +763,18 @@ class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView): """ Displays the logged user's tools """ + template_name = "core/user_tools.jinja" current_tab = "tools" def get_context_data(self, **kwargs): self.object = self.request.user from launderette.models import Launderette + kwargs = super(UserToolsView, self).get_context_data(**kwargs) - kwargs['launderettes'] = Launderette.objects.all() - kwargs['profile'] = self.request.user - kwargs['object'] = self.request.user + kwargs["launderettes"] = Launderette.objects.all() + kwargs["profile"] = self.request.user + kwargs["object"] = self.request.user return kwargs @@ -612,16 +782,21 @@ class UserAccountBase(UserTabsMixin, DetailView): """ Base class for UserAccount """ + model = User pk_url_kwarg = "user_id" current_tab = "account" def dispatch(self, request, *arg, **kwargs): # Manually validates the rights res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs) - if (self.object == request.user - or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) - or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX) - or request.user.is_root): + if ( + self.object == request.user + or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) + or request.user.is_in_group( + settings.SITH_BAR_MANAGER["unix_name"] + settings.SITH_BOARD_SUFFIX + ) + or request.user.is_root + ): return res raise PermissionDenied @@ -630,24 +805,20 @@ class UserAccountView(UserAccountBase): """ Display a user's account """ + template_name = "core/user_account.jinja" def expense_by_month(self, obj, calc): stats = [] - for year in obj.datetimes('date', 'year', order='DESC'): + for year in obj.datetimes("date", "year", order="DESC"): stats.append([]) i = 0 for month in obj.filter(date__year=year.year).datetimes( - 'date', 'month', order='DESC'): - q = obj.filter( - date__year=month.year, - date__month=month.month - ) - stats[i].append({ - 'sum': sum([calc(p) for p in q]), - 'date': month - }) + "date", "month", order="DESC" + ): + q = obj.filter(date__year=month.year, date__month=month.month) + stats[i].append({"sum": sum([calc(p) for p in q]), "date": month}) i += 1 return stats @@ -659,22 +830,21 @@ class UserAccountView(UserAccountBase): def get_context_data(self, **kwargs): kwargs = super(UserAccountView, self).get_context_data(**kwargs) - kwargs['profile'] = self.object + kwargs["profile"] = self.object try: - kwargs['customer'] = self.object.customer - kwargs['buyings_month'] = self.expense_by_month( - self.object.customer.buyings, - (lambda q: q.unit_price * q.quantity) + kwargs["customer"] = self.object.customer + kwargs["buyings_month"] = self.expense_by_month( + self.object.customer.buyings, (lambda q: q.unit_price * q.quantity) ) - kwargs['invoices_month'] = self.expense_by_month( - self.object.customer.user.invoices, - self.invoices_calc + kwargs["invoices_month"] = self.expense_by_month( + self.object.customer.user.invoices, self.invoices_calc ) - kwargs['refilling_month'] = self.expense_by_month( - self.object.customer.refillings, - (lambda q: q.amount) + kwargs["refilling_month"] = self.expense_by_month( + self.object.customer.refillings, (lambda q: q.amount) ) - kwargs['etickets'] = self.object.customer.buyings.exclude(product__eticket=None).all() + kwargs["etickets"] = self.object.customer.buyings.exclude( + product__eticket=None + ).all() except Exception as e: print(repr(e)) return kwargs @@ -684,51 +854,52 @@ class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin): """ Display a user's account for month """ + template_name = "core/user_account_detail.jinja" def get_context_data(self, **kwargs): kwargs = super(UserAccountDetailView, self).get_context_data(**kwargs) - kwargs['profile'] = self.object - kwargs['year'] = self.get_year() - kwargs['month'] = self.get_month() + kwargs["profile"] = self.object + kwargs["year"] = self.get_year() + kwargs["month"] = self.get_month() try: - kwargs['customer'] = self.object.customer + kwargs["customer"] = self.object.customer except: pass - kwargs['tab'] = "account" + kwargs["tab"] = "account" return kwargs class GiftCreateView(CreateView): form_class = GiftForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *args, **kwargs): if not (request.user.is_board_member or request.user.is_root): raise PermissionDenied - self.user = get_object_or_404(User, pk=kwargs['user_id']) + self.user = get_object_or_404(User, pk=kwargs["user_id"]) return super(GiftCreateView, self).dispatch(request, *args, **kwargs) def get_initial(self): - return {'user': self.user} + return {"user": self.user} def get_form_kwargs(self): kwargs = super(GiftCreateView, self).get_form_kwargs() - kwargs['user_id'] = self.user.id + kwargs["user_id"] = self.user.id return kwargs def get_success_url(self): - return reverse_lazy('core:user_profile', kwargs={'user_id': self.user.id}) + return reverse_lazy("core:user_profile", kwargs={"user_id": self.user.id}) class GiftDeleteView(CanEditPropMixin, DeleteView): model = Gift pk_url_kwarg = "gift_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): - self.user = get_object_or_404(User, pk=kwargs['user_id']) + self.user = get_object_or_404(User, pk=kwargs["user_id"]) return super(GiftDeleteView, self).dispatch(request, *args, **kwargs) def get_success_url(self): - return reverse_lazy('core:user_profile', kwargs={'user_id': self.user.id}) + return reverse_lazy("core:user_profile", kwargs={"user_id": self.user.id}) diff --git a/counter/__init__.py b/counter/__init__.py index 0a9419f8..0ace29c4 100644 --- a/counter/__init__.py +++ b/counter/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/counter/migrations/0001_initial.py b/counter/migrations/0001_initial.py index 0051def2..06f01d59 100644 --- a/counter/migrations/0001_initial.py +++ b/counter/migrations/0001_initial.py @@ -10,140 +10,400 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('subscription', '0001_initial'), + ("subscription", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('core', '0001_initial'), - ('club', '0002_auto_20160824_2152'), + ("core", "0001_initial"), + ("club", "0002_auto_20160824_2152"), ] operations = [ migrations.CreateModel( - name='Counter', + name="Counter", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('type', models.CharField(choices=[('BAR', 'Bar'), ('OFFICE', 'Office'), ('EBOUTIC', 'Eboutic')], max_length=255, verbose_name='counter type')), - ('club', models.ForeignKey(to='club.Club', related_name='counters')), - ('edit_groups', models.ManyToManyField(to='core.Group', blank=True, related_name='editable_counters')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "type", + models.CharField( + choices=[ + ("BAR", "Bar"), + ("OFFICE", "Office"), + ("EBOUTIC", "Eboutic"), + ], + max_length=255, + verbose_name="counter type", + ), + ), + ("club", models.ForeignKey(to="club.Club", related_name="counters")), + ( + "edit_groups", + models.ManyToManyField( + to="core.Group", blank=True, related_name="editable_counters" + ), + ), + ], + options={"verbose_name": "counter"}, + ), + migrations.CreateModel( + name="Customer", + fields=[ + ( + "user", + models.OneToOneField( + primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL + ), + ), + ( + "account_id", + models.CharField( + unique=True, max_length=10, verbose_name="account id" + ), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), ], options={ - 'verbose_name': 'counter', + "verbose_name": "customer", + "ordering": ["account_id"], + "verbose_name_plural": "customers", }, ), migrations.CreateModel( - name='Customer', + name="Permanency", fields=[ - ('user', models.OneToOneField(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), - ('account_id', models.CharField(unique=True, max_length=10, verbose_name='account id')), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start", models.DateTimeField(verbose_name="start date")), + ("end", models.DateTimeField(verbose_name="end date")), + ( + "counter", + models.ForeignKey( + to="counter.Counter", related_name="permanencies" + ), + ), + ( + "user", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, related_name="permanencies" + ), + ), ], - options={ - 'verbose_name': 'customer', - 'ordering': ['account_id'], - 'verbose_name_plural': 'customers', - }, + options={"verbose_name": "permanency"}, ), migrations.CreateModel( - name='Permanency', + name="Product", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start', models.DateTimeField(verbose_name='start date')), - ('end', models.DateTimeField(verbose_name='end date')), - ('counter', models.ForeignKey(to='counter.Counter', related_name='permanencies')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='permanencies')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "description", + models.TextField(blank=True, verbose_name="description"), + ), + ( + "code", + models.CharField(max_length=16, blank=True, verbose_name="code"), + ), + ( + "purchase_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="purchase price" + ), + ), + ( + "selling_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="selling price" + ), + ), + ( + "special_selling_price", + accounting.models.CurrencyField( + decimal_places=2, + max_digits=12, + verbose_name="special selling price", + ), + ), + ( + "icon", + models.ImageField( + upload_to="products", null=True, verbose_name="icon", blank=True + ), + ), + ("limit_age", models.IntegerField(default=0, verbose_name="limit age")), + ("tray", models.BooleanField(verbose_name="tray price", default=False)), + ( + "buying_groups", + models.ManyToManyField( + related_name="products", + to="core.Group", + verbose_name="buying groups", + ), + ), + ( + "club", + models.ForeignKey( + verbose_name="club", to="club.Club", related_name="products" + ), + ), + ( + "parent_product", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="children_products", + verbose_name="parent product", + to="counter.Product", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'permanency', - }, + options={"verbose_name": "product"}, ), migrations.CreateModel( - name='Product', + name="ProductType", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('description', models.TextField(blank=True, verbose_name='description')), - ('code', models.CharField(max_length=16, blank=True, verbose_name='code')), - ('purchase_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='purchase price')), - ('selling_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='selling price')), - ('special_selling_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='special selling price')), - ('icon', models.ImageField(upload_to='products', null=True, verbose_name='icon', blank=True)), - ('limit_age', models.IntegerField(default=0, verbose_name='limit age')), - ('tray', models.BooleanField(verbose_name='tray price', default=False)), - ('buying_groups', models.ManyToManyField(related_name='products', to='core.Group', verbose_name='buying groups')), - ('club', models.ForeignKey(verbose_name='club', to='club.Club', related_name='products')), - ('parent_product', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children_products', verbose_name='parent product', to='counter.Product', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "description", + models.TextField(null=True, verbose_name="description", blank=True), + ), + ( + "icon", + models.ImageField(upload_to="products", null=True, blank=True), + ), ], - options={ - 'verbose_name': 'product', - }, + options={"verbose_name": "product type"}, ), migrations.CreateModel( - name='ProductType', + name="Refilling", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('description', models.TextField(null=True, verbose_name='description', blank=True)), - ('icon', models.ImageField(upload_to='products', null=True, blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "amount", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="amount" + ), + ), + ("date", models.DateTimeField(verbose_name="date")), + ( + "payment_method", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CASH", "Cash"), + ("CARD", "Credit card"), + ], + max_length=255, + default="CASH", + verbose_name="payment method", + ), + ), + ( + "bank", + models.CharField( + choices=[ + ("OTHER", "Autre"), + ("SOCIETE-GENERALE", "Société générale"), + ("BANQUE-POPULAIRE", "Banque populaire"), + ("BNP", "BNP"), + ("CAISSE-EPARGNE", "Caisse d'épargne"), + ("CIC", "CIC"), + ("CREDIT-AGRICOLE", "Crédit Agricole"), + ("CREDIT-MUTUEL", "Credit Mutuel"), + ("CREDIT-LYONNAIS", "Credit Lyonnais"), + ("LA-POSTE", "La Poste"), + ], + max_length=255, + default="OTHER", + verbose_name="bank", + ), + ), + ( + "is_validated", + models.BooleanField(verbose_name="is validated", default=False), + ), + ( + "counter", + models.ForeignKey(to="counter.Counter", related_name="refillings"), + ), + ( + "customer", + models.ForeignKey(to="counter.Customer", related_name="refillings"), + ), + ( + "operator", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="refillings_as_operator", + ), + ), ], - options={ - 'verbose_name': 'product type', - }, + options={"verbose_name": "refilling"}, ), migrations.CreateModel( - name='Refilling', + name="Selling", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('amount', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='amount')), - ('date', models.DateTimeField(verbose_name='date')), - ('payment_method', models.CharField(choices=[('CHECK', 'Check'), ('CASH', 'Cash'), ('CARD', 'Credit card')], max_length=255, default='CASH', verbose_name='payment method')), - ('bank', models.CharField(choices=[('OTHER', 'Autre'), ('SOCIETE-GENERALE', 'Société générale'), ('BANQUE-POPULAIRE', 'Banque populaire'), ('BNP', 'BNP'), ('CAISSE-EPARGNE', "Caisse d'épargne"), ('CIC', 'CIC'), ('CREDIT-AGRICOLE', 'Crédit Agricole'), ('CREDIT-MUTUEL', 'Credit Mutuel'), ('CREDIT-LYONNAIS', 'Credit Lyonnais'), ('LA-POSTE', 'La Poste')], max_length=255, default='OTHER', verbose_name='bank')), - ('is_validated', models.BooleanField(verbose_name='is validated', default=False)), - ('counter', models.ForeignKey(to='counter.Counter', related_name='refillings')), - ('customer', models.ForeignKey(to='counter.Customer', related_name='refillings')), - ('operator', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='refillings_as_operator')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("label", models.CharField(max_length=64, verbose_name="label")), + ( + "unit_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="unit price" + ), + ), + ("quantity", models.IntegerField(verbose_name="quantity")), + ("date", models.DateTimeField(verbose_name="date")), + ( + "payment_method", + models.CharField( + choices=[ + ("SITH_ACCOUNT", "Sith account"), + ("CARD", "Credit card"), + ], + max_length=255, + default="SITH_ACCOUNT", + verbose_name="payment method", + ), + ), + ( + "is_validated", + models.BooleanField(verbose_name="is validated", default=False), + ), + ( + "club", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="club.Club", + related_name="sellings", + ), + ), + ( + "counter", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="counter.Counter", + related_name="sellings", + ), + ), + ( + "customer", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="counter.Customer", + related_name="buyings", + ), + ), + ( + "product", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to="counter.Product", + related_name="sellings", + blank=True, + ), + ), + ( + "seller", + models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + to=settings.AUTH_USER_MODEL, + related_name="sellings_as_operator", + ), + ), ], - options={ - 'verbose_name': 'refilling', - }, - ), - migrations.CreateModel( - name='Selling', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('label', models.CharField(max_length=64, verbose_name='label')), - ('unit_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='unit price')), - ('quantity', models.IntegerField(verbose_name='quantity')), - ('date', models.DateTimeField(verbose_name='date')), - ('payment_method', models.CharField(choices=[('SITH_ACCOUNT', 'Sith account'), ('CARD', 'Credit card')], max_length=255, default='SITH_ACCOUNT', verbose_name='payment method')), - ('is_validated', models.BooleanField(verbose_name='is validated', default=False)), - ('club', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='club.Club', related_name='sellings')), - ('counter', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='counter.Counter', related_name='sellings')), - ('customer', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='counter.Customer', related_name='buyings')), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to='counter.Product', related_name='sellings', blank=True)), - ('seller', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, to=settings.AUTH_USER_MODEL, related_name='sellings_as_operator')), - ], - options={ - 'verbose_name': 'selling', - }, + options={"verbose_name": "selling"}, ), migrations.AddField( - model_name='product', - name='product_type', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='products', verbose_name='product type', to='counter.ProductType', blank=True), + model_name="product", + name="product_type", + field=models.ForeignKey( + on_delete=django.db.models.deletion.SET_NULL, + null=True, + related_name="products", + verbose_name="product type", + to="counter.ProductType", + blank=True, + ), ), migrations.AddField( - model_name='counter', - name='products', - field=models.ManyToManyField(to='counter.Product', blank=True, related_name='counters'), + model_name="counter", + name="products", + field=models.ManyToManyField( + to="counter.Product", blank=True, related_name="counters" + ), ), migrations.AddField( - model_name='counter', - name='sellers', - field=models.ManyToManyField(related_name='counters', to='core.User', blank=True, verbose_name='sellers'), + model_name="counter", + name="sellers", + field=models.ManyToManyField( + related_name="counters", + to="core.User", + blank=True, + verbose_name="sellers", + ), ), migrations.AddField( - model_name='counter', - name='view_groups', - field=models.ManyToManyField(to='core.Group', blank=True, related_name='viewable_counters'), + model_name="counter", + name="view_groups", + field=models.ManyToManyField( + to="core.Group", blank=True, related_name="viewable_counters" + ), ), ] diff --git a/counter/migrations/0002_auto_20160826_1342.py b/counter/migrations/0002_auto_20160826_1342.py index 9bcd4906..721f5c8e 100644 --- a/counter/migrations/0002_auto_20160826_1342.py +++ b/counter/migrations/0002_auto_20160826_1342.py @@ -10,45 +10,94 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('counter', '0001_initial'), + ("counter", "0001_initial"), ] operations = [ migrations.CreateModel( - name='CashRegisterSummary', + name="CashRegisterSummary", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('date', models.DateTimeField(verbose_name='date')), - ('comment', models.TextField(null=True, verbose_name='comment', blank=True)), - ('emptied', models.BooleanField(default=False, verbose_name='emptied')), - ('counter', models.ForeignKey(to='counter.Counter', related_name='cash_summaries', verbose_name='counter')), - ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='cash_summaries', verbose_name='user')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date")), + ( + "comment", + models.TextField(null=True, verbose_name="comment", blank=True), + ), + ("emptied", models.BooleanField(default=False, verbose_name="emptied")), + ( + "counter", + models.ForeignKey( + to="counter.Counter", + related_name="cash_summaries", + verbose_name="counter", + ), + ), + ( + "user", + models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="cash_summaries", + verbose_name="user", + ), + ), ], - options={ - 'verbose_name': 'cash register summary', - }, + options={"verbose_name": "cash register summary"}, ), migrations.CreateModel( - name='CashRegisterSummaryItem', + name="CashRegisterSummaryItem", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('value', accounting.models.CurrencyField(max_digits=12, verbose_name='value', decimal_places=2)), - ('quantity', models.IntegerField(default=0, verbose_name='quantity')), - ('check', models.BooleanField(default=False, verbose_name='check')), - ('cash_summary', models.ForeignKey(to='counter.CashRegisterSummary', related_name='items', verbose_name='cash summary')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ( + "value", + accounting.models.CurrencyField( + max_digits=12, verbose_name="value", decimal_places=2 + ), + ), + ("quantity", models.IntegerField(default=0, verbose_name="quantity")), + ("check", models.BooleanField(default=False, verbose_name="check")), + ( + "cash_summary", + models.ForeignKey( + to="counter.CashRegisterSummary", + related_name="items", + verbose_name="cash summary", + ), + ), ], - options={ - 'verbose_name': 'cash register summary item', - }, + options={"verbose_name": "cash register summary item"}, ), migrations.AlterField( - model_name='permanency', - name='counter', - field=models.ForeignKey(to='counter.Counter', related_name='permanencies', verbose_name='counter'), + model_name="permanency", + name="counter", + field=models.ForeignKey( + to="counter.Counter", + related_name="permanencies", + verbose_name="counter", + ), ), migrations.AlterField( - model_name='permanency', - name='user', - field=models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='permanencies', verbose_name='user'), + model_name="permanency", + name="user", + field=models.ForeignKey( + to=settings.AUTH_USER_MODEL, + related_name="permanencies", + verbose_name="user", + ), ), ] diff --git a/counter/migrations/0003_permanency_activity.py b/counter/migrations/0003_permanency_activity.py index 21a14717..05707b20 100644 --- a/counter/migrations/0003_permanency_activity.py +++ b/counter/migrations/0003_permanency_activity.py @@ -8,15 +8,17 @@ from django.utils.timezone import utc class Migration(migrations.Migration): - dependencies = [ - ('counter', '0002_auto_20160826_1342'), - ] + dependencies = [("counter", "0002_auto_20160826_1342")] operations = [ migrations.AddField( - model_name='permanency', - name='activity', - field=models.DateTimeField(verbose_name='activity time', auto_now=True, default=datetime.datetime(2016, 8, 26, 17, 5, 31, 202824, tzinfo=utc)), + model_name="permanency", + name="activity", + field=models.DateTimeField( + verbose_name="activity time", + auto_now=True, + default=datetime.datetime(2016, 8, 26, 17, 5, 31, 202824, tzinfo=utc), + ), preserve_default=False, - ), + ) ] diff --git a/counter/migrations/0004_auto_20160826_1907.py b/counter/migrations/0004_auto_20160826_1907.py index 94cc1e1a..6e2ddd21 100644 --- a/counter/migrations/0004_auto_20160826_1907.py +++ b/counter/migrations/0004_auto_20160826_1907.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0003_permanency_activity'), - ] + dependencies = [("counter", "0003_permanency_activity")] operations = [ migrations.AlterField( - model_name='permanency', - name='end', - field=models.DateTimeField(verbose_name='end date', null=True), - ), + model_name="permanency", + name="end", + field=models.DateTimeField(verbose_name="end date", null=True), + ) ] diff --git a/counter/migrations/0005_auto_20160826_2330.py b/counter/migrations/0005_auto_20160826_2330.py index dd8a34e5..4fa5ebea 100644 --- a/counter/migrations/0005_auto_20160826_2330.py +++ b/counter/migrations/0005_auto_20160826_2330.py @@ -6,24 +6,31 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0004_auto_20160826_1907'), - ] + dependencies = [("counter", "0004_auto_20160826_1907")] operations = [ migrations.AlterField( - model_name='counter', - name='club', - field=models.ForeignKey(verbose_name='club', to='club.Club', related_name='counters'), + model_name="counter", + name="club", + field=models.ForeignKey( + verbose_name="club", to="club.Club", related_name="counters" + ), ), migrations.AlterField( - model_name='counter', - name='products', - field=models.ManyToManyField(blank=True, related_name='counters', to='counter.Product', verbose_name='products'), + model_name="counter", + name="products", + field=models.ManyToManyField( + blank=True, + related_name="counters", + to="counter.Product", + verbose_name="products", + ), ), migrations.AlterField( - model_name='permanency', - name='activity', - field=models.DateTimeField(auto_now=True, verbose_name='last activity date'), + model_name="permanency", + name="activity", + field=models.DateTimeField( + auto_now=True, verbose_name="last activity date" + ), ), ] diff --git a/counter/migrations/0006_auto_20160831_1304.py b/counter/migrations/0006_auto_20160831_1304.py index 3db83884..15b7b169 100644 --- a/counter/migrations/0006_auto_20160831_1304.py +++ b/counter/migrations/0006_auto_20160831_1304.py @@ -6,14 +6,17 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0005_auto_20160826_2330'), - ] + dependencies = [("counter", "0005_auto_20160826_2330")] operations = [ migrations.AlterField( - model_name='product', - name='buying_groups', - field=models.ManyToManyField(related_name='products', verbose_name='buying groups', blank=True, to='core.Group'), - ), + model_name="product", + name="buying_groups", + field=models.ManyToManyField( + related_name="products", + verbose_name="buying groups", + blank=True, + to="core.Group", + ), + ) ] diff --git a/counter/migrations/0007_product_archived.py b/counter/migrations/0007_product_archived.py index 918e3268..521eda48 100644 --- a/counter/migrations/0007_product_archived.py +++ b/counter/migrations/0007_product_archived.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0006_auto_20160831_1304'), - ] + dependencies = [("counter", "0006_auto_20160831_1304")] operations = [ migrations.AddField( - model_name='product', - name='archived', - field=models.BooleanField(verbose_name='archived', default=False), - ), + model_name="product", + name="archived", + field=models.BooleanField(verbose_name="archived", default=False), + ) ] diff --git a/counter/migrations/0008_counter_token.py b/counter/migrations/0008_counter_token.py index 28398fed..a02b5a5d 100644 --- a/counter/migrations/0008_counter_token.py +++ b/counter/migrations/0008_counter_token.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0007_product_archived'), - ] + dependencies = [("counter", "0007_product_archived")] operations = [ migrations.AddField( - model_name='counter', - name='token', - field=models.CharField(blank=True, max_length=30, verbose_name='token', null=True), - ), + model_name="counter", + name="token", + field=models.CharField( + blank=True, max_length=30, verbose_name="token", null=True + ), + ) ] diff --git a/counter/migrations/0009_eticket.py b/counter/migrations/0009_eticket.py index b59038b4..6df0ea73 100644 --- a/counter/migrations/0009_eticket.py +++ b/counter/migrations/0009_eticket.py @@ -6,18 +6,37 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0008_counter_token'), - ] + dependencies = [("counter", "0008_counter_token")] operations = [ migrations.CreateModel( - name='Eticket', + name="Eticket", fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), - ('banner', models.ImageField(null=True, upload_to='etickets', blank=True)), - ('secret', models.CharField(unique=True, verbose_name='secret', max_length=64)), - ('product', models.OneToOneField(verbose_name='product', related_name='eticket', to='counter.Product')), + ( + "id", + models.AutoField( + verbose_name="ID", + auto_created=True, + primary_key=True, + serialize=False, + ), + ), + ( + "banner", + models.ImageField(null=True, upload_to="etickets", blank=True), + ), + ( + "secret", + models.CharField(unique=True, verbose_name="secret", max_length=64), + ), + ( + "product", + models.OneToOneField( + verbose_name="product", + related_name="eticket", + to="counter.Product", + ), + ), ], - ), + ) ] diff --git a/counter/migrations/0010_auto_20161003_1900.py b/counter/migrations/0010_auto_20161003_1900.py index 9f1d73c4..e438b05e 100644 --- a/counter/migrations/0010_auto_20161003_1900.py +++ b/counter/migrations/0010_auto_20161003_1900.py @@ -6,19 +6,19 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0009_eticket'), - ] + dependencies = [("counter", "0009_eticket")] operations = [ migrations.AddField( - model_name='eticket', - name='event_date', - field=models.DateField(blank=True, verbose_name='event date', null=True), + model_name="eticket", + name="event_date", + field=models.DateField(blank=True, verbose_name="event date", null=True), ), migrations.AddField( - model_name='eticket', - name='event_title', - field=models.CharField(blank=True, max_length=64, verbose_name='event title', null=True), + model_name="eticket", + name="event_title", + field=models.CharField( + blank=True, max_length=64, verbose_name="event title", null=True + ), ), ] diff --git a/counter/migrations/0011_auto_20161004_2039.py b/counter/migrations/0011_auto_20161004_2039.py index 3ce88403..c70bad63 100644 --- a/counter/migrations/0011_auto_20161004_2039.py +++ b/counter/migrations/0011_auto_20161004_2039.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0010_auto_20161003_1900'), - ] + dependencies = [("counter", "0010_auto_20161003_1900")] operations = [ migrations.AlterField( - model_name='eticket', - name='banner', - field=models.ImageField(null=True, verbose_name='banner', blank=True, upload_to='etickets'), - ), + model_name="eticket", + name="banner", + field=models.ImageField( + null=True, verbose_name="banner", blank=True, upload_to="etickets" + ), + ) ] diff --git a/counter/migrations/0012_auto_20170515_2202.py b/counter/migrations/0012_auto_20170515_2202.py index 8c2ead76..e50d4856 100644 --- a/counter/migrations/0012_auto_20170515_2202.py +++ b/counter/migrations/0012_auto_20170515_2202.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0011_auto_20161004_2039'), - ] + dependencies = [("counter", "0011_auto_20161004_2039")] operations = [ migrations.AlterField( - model_name='permanency', - name='end', - field=models.DateTimeField(db_index=True, verbose_name='end date', null=True), - ), + model_name="permanency", + name="end", + field=models.DateTimeField( + db_index=True, verbose_name="end date", null=True + ), + ) ] diff --git a/counter/migrations/0013_customer_recorded_products.py b/counter/migrations/0013_customer_recorded_products.py index 026b7b33..84f5caf5 100644 --- a/counter/migrations/0013_customer_recorded_products.py +++ b/counter/migrations/0013_customer_recorded_products.py @@ -12,7 +12,9 @@ from counter.models import Customer, Product, Selling, Counter def balance_ecocups(apps, schema_editor): for customer in Customer.objects.all(): customer.recorded_products = 0 - for selling in customer.buyings.filter(product__id__in=[settings.SITH_ECOCUP_CONS, settings.SITH_ECOCUP_DECO]).all(): + for selling in customer.buyings.filter( + product__id__in=[settings.SITH_ECOCUP_CONS, settings.SITH_ECOCUP_DECO] + ).all(): if selling.product.is_record_product: customer.recorded_products += selling.quantity elif selling.product.is_unrecord_product: @@ -20,24 +22,29 @@ def balance_ecocups(apps, schema_editor): if customer.recorded_products < -settings.SITH_ECOCUP_LIMIT: qt = -(customer.recorded_products + settings.SITH_ECOCUP_LIMIT) cons = Product.objects.get(id=settings.SITH_ECOCUP_CONS) - Selling(label=_("Ecocup regularization"), product=cons, unit_price=cons.selling_price, - club=cons.club, counter=Counter.objects.filter(name='Foyer').first(), - quantity=qt, seller=User.objects.get(id=0), customer=customer).save(allow_negative=True) + Selling( + label=_("Ecocup regularization"), + product=cons, + unit_price=cons.selling_price, + club=cons.club, + counter=Counter.objects.filter(name="Foyer").first(), + quantity=qt, + seller=User.objects.get(id=0), + customer=customer, + ).save(allow_negative=True) customer.recorded_products += qt customer.save() class Migration(migrations.Migration): - dependencies = [ - ('counter', '0012_auto_20170515_2202'), - ] + dependencies = [("counter", "0012_auto_20170515_2202")] operations = [ migrations.AddField( - model_name='customer', - name='recorded_products', - field=models.IntegerField(verbose_name='recorded items', default=0), + model_name="customer", + name="recorded_products", + field=models.IntegerField(verbose_name="recorded items", default=0), ), migrations.RunPython(balance_ecocups), ] diff --git a/counter/migrations/0014_auto_20170816_1521.py b/counter/migrations/0014_auto_20170816_1521.py index d7a64ff3..7e13c170 100644 --- a/counter/migrations/0014_auto_20170816_1521.py +++ b/counter/migrations/0014_auto_20170816_1521.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0013_customer_recorded_products'), - ] + dependencies = [("counter", "0013_customer_recorded_products")] operations = [ migrations.AlterField( - model_name='customer', - name='recorded_products', - field=models.IntegerField(default=0, verbose_name='recorded product'), - ), + model_name="customer", + name="recorded_products", + field=models.IntegerField(default=0, verbose_name="recorded product"), + ) ] diff --git a/counter/migrations/0014_auto_20170817_1537.py b/counter/migrations/0014_auto_20170817_1537.py index 8e4e3df5..67acbc13 100644 --- a/counter/migrations/0014_auto_20170817_1537.py +++ b/counter/migrations/0014_auto_20170817_1537.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0013_customer_recorded_products'), - ] + dependencies = [("counter", "0013_customer_recorded_products")] operations = [ migrations.AlterField( - model_name='customer', - name='recorded_products', - field=models.IntegerField(verbose_name='recorded product', default=0), - ), + model_name="customer", + name="recorded_products", + field=models.IntegerField(verbose_name="recorded product", default=0), + ) ] diff --git a/counter/migrations/0015_merge.py b/counter/migrations/0015_merge.py index 23830568..aefcfaca 100644 --- a/counter/migrations/0015_merge.py +++ b/counter/migrations/0015_merge.py @@ -7,9 +7,8 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('counter', '0014_auto_20170817_1537'), - ('counter', '0014_auto_20170816_1521'), + ("counter", "0014_auto_20170817_1537"), + ("counter", "0014_auto_20170816_1521"), ] - operations = [ - ] + operations = [] diff --git a/counter/migrations/0016_producttype_comment.py b/counter/migrations/0016_producttype_comment.py index 26244fe6..295553b2 100644 --- a/counter/migrations/0016_producttype_comment.py +++ b/counter/migrations/0016_producttype_comment.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('counter', '0015_merge'), - ] + dependencies = [("counter", "0015_merge")] operations = [ migrations.AddField( - model_name='producttype', - name='comment', - field=models.TextField(verbose_name='comment', blank=True, null=True), - ), + model_name="producttype", + name="comment", + field=models.TextField(verbose_name="comment", blank=True, null=True), + ) ] diff --git a/counter/models.py b/counter/models.py index 2c2ebf06..9767cfd3 100644 --- a/counter/models.py +++ b/counter/models.py @@ -48,15 +48,16 @@ class Customer(models.Model): This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and is used by other accounting classes as reference to the customer, rather than using User """ + user = models.OneToOneField(User, primary_key=True) - account_id = models.CharField(_('account id'), max_length=10, unique=True) - amount = CurrencyField(_('amount')) - recorded_products = models.IntegerField(_('recorded product'), default=0) + account_id = models.CharField(_("account id"), max_length=10, unique=True) + amount = CurrencyField(_("amount")) + recorded_products = models.IntegerField(_("recorded product"), default=0) class Meta: - verbose_name = _('customer') - verbose_name_plural = _('customers') - ordering = ['account_id', ] + verbose_name = _("customer") + verbose_name_plural = _("customers") + ordering = ["account_id"] def __str__(self): return "%s - %s" % (self.user.username, self.account_id) @@ -70,8 +71,12 @@ class Customer(models.Model): @property def can_buy(self): - return (self.user.subscriptions.last() and - (date.today() - self.user.subscriptions.order_by('subscription_end').last().subscription_end) < timedelta(days=90)) + return self.user.subscriptions.last() and ( + date.today() + - self.user.subscriptions.order_by("subscription_end") + .last() + .subscription_end + ) < timedelta(days=90) def generate_account_id(number): number = str(number) @@ -99,10 +104,10 @@ class Customer(models.Model): self.save() def get_absolute_url(self): - return reverse('core:user_account', kwargs={'user_id': self.user.pk}) + return reverse("core:user_account", kwargs={"user_id": self.user.pk}) def get_full_url(self): - return ''.join(['https://', settings.SITH_URL, self.get_absolute_url()]) + return "".join(["https://", settings.SITH_URL, self.get_absolute_url()]) class ProductType(models.Model): @@ -110,13 +115,14 @@ class ProductType(models.Model): This describes a product type Useful only for categorizing, changes are made at the product level for now """ - name = models.CharField(_('name'), max_length=30) - description = models.TextField(_('description'), null=True, blank=True) - comment = models.TextField(_('comment'), null=True, blank=True) - icon = models.ImageField(upload_to='products', null=True, blank=True) + + name = models.CharField(_("name"), max_length=30) + description = models.TextField(_("description"), null=True, blank=True) + comment = models.TextField(_("comment"), null=True, blank=True) + icon = models.ImageField(upload_to="products", null=True, blank=True) class Meta: - verbose_name = _('product type') + verbose_name = _("product type") def is_owned_by(self, user): """ @@ -130,32 +136,49 @@ class ProductType(models.Model): return self.name def get_absolute_url(self): - return reverse('counter:producttype_list') + return reverse("counter:producttype_list") class Product(models.Model): """ This describes a product, with all its related informations """ - name = models.CharField(_('name'), max_length=64) - description = models.TextField(_('description'), blank=True) - product_type = models.ForeignKey(ProductType, related_name='products', verbose_name=_("product type"), null=True, blank=True, - on_delete=models.SET_NULL) - code = models.CharField(_('code'), max_length=16, blank=True) - purchase_price = CurrencyField(_('purchase price')) - selling_price = CurrencyField(_('selling price')) - special_selling_price = CurrencyField(_('special selling price')) - icon = models.ImageField(upload_to='products', null=True, blank=True, verbose_name=_("icon")) + + name = models.CharField(_("name"), max_length=64) + description = models.TextField(_("description"), blank=True) + product_type = models.ForeignKey( + ProductType, + related_name="products", + verbose_name=_("product type"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + code = models.CharField(_("code"), max_length=16, blank=True) + purchase_price = CurrencyField(_("purchase price")) + selling_price = CurrencyField(_("selling price")) + special_selling_price = CurrencyField(_("special selling price")) + icon = models.ImageField( + upload_to="products", null=True, blank=True, verbose_name=_("icon") + ) club = models.ForeignKey(Club, related_name="products", verbose_name=_("club")) - limit_age = models.IntegerField(_('limit age'), default=0) - tray = models.BooleanField(_('tray price'), default=False) - parent_product = models.ForeignKey('self', related_name='children_products', verbose_name=_("parent product"), null=True, - blank=True, on_delete=models.SET_NULL) - buying_groups = models.ManyToManyField(Group, related_name='products', verbose_name=_("buying groups"), blank=True) + limit_age = models.IntegerField(_("limit age"), default=0) + tray = models.BooleanField(_("tray price"), default=False) + parent_product = models.ForeignKey( + "self", + related_name="children_products", + verbose_name=_("parent product"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + buying_groups = models.ManyToManyField( + Group, related_name="products", verbose_name=_("buying groups"), blank=True + ) archived = models.BooleanField(_("archived"), default=False) class Meta: - verbose_name = _('product') + verbose_name = _("product") @property def is_record_product(self): @@ -169,7 +192,9 @@ class Product(models.Model): """ Method to see if that object can be edited by the given user """ - if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID): + if user.is_in_group( + settings.SITH_GROUP_ACCOUNTING_ADMIN_ID + ) or user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID): return True return False @@ -177,27 +202,39 @@ class Product(models.Model): return "%s (%s)" % (self.name, self.code) def get_absolute_url(self): - return reverse('counter:product_list') + return reverse("counter:product_list") class Counter(models.Model): - name = models.CharField(_('name'), max_length=30) + name = models.CharField(_("name"), max_length=30) club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club")) - products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True) - type = models.CharField(_('counter type'), - max_length=255, - choices=[('BAR', _('Bar')), ('OFFICE', _('Office')), ('EBOUTIC', _('Eboutic'))]) - sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True) - edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True) - view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True) - token = models.CharField(_('token'), max_length=30, null=True, blank=True) + products = models.ManyToManyField( + Product, related_name="counters", verbose_name=_("products"), blank=True + ) + type = models.CharField( + _("counter type"), + max_length=255, + choices=[("BAR", _("Bar")), ("OFFICE", _("Office")), ("EBOUTIC", _("Eboutic"))], + ) + sellers = models.ManyToManyField( + User, verbose_name=_("sellers"), related_name="counters", blank=True + ) + edit_groups = models.ManyToManyField( + Group, related_name="editable_counters", blank=True + ) + view_groups = models.ManyToManyField( + Group, related_name="viewable_counters", blank=True + ) + token = models.CharField(_("token"), max_length=30, null=True, blank=True) class Meta: - verbose_name = _('counter') + verbose_name = _("counter") def __getattribute__(self, name): if name == "edit_groups": - return Group.objects.filter(name=self.club.unix_name + settings.SITH_BOARD_SUFFIX).all() + return Group.objects.filter( + name=self.club.unix_name + settings.SITH_BOARD_SUFFIX + ).all() return object.__getattribute__(self, name) def __str__(self): @@ -205,8 +242,8 @@ class Counter(models.Model): def get_absolute_url(self): if self.type == "EBOUTIC": - return reverse('eboutic:main') - return reverse('counter:details', kwargs={'counter_id': self.id}) + return reverse("eboutic:main") + return reverse("counter:details", kwargs={"counter_id": self.id}) def is_owned_by(self, user): mem = self.club.get_membership_for(user) @@ -217,11 +254,16 @@ class Counter(models.Model): def can_be_viewed_by(self, user): if self.type == "BAR": return True - return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user in self.sellers.all() + return ( + user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) + or user in self.sellers.all() + ) def gen_token(self): """Generate a new token for this counter""" - self.token = ''.join(random.choice(string.ascii_letters + string.digits) for x in range(30)) + self.token = "".join( + random.choice(string.ascii_letters + string.digits) for x in range(30) + ) self.save() def add_barman(self, user): @@ -253,7 +295,9 @@ class Counter(models.Model): pl = Permanency.objects.filter(counter=self, end=None).all() bl = [] for p in pl: - if timezone.now() - p.activity < timedelta(minutes=settings.SITH_BARMAN_TIMEOUT): + if timezone.now() - p.activity < timedelta( + minutes=settings.SITH_BARMAN_TIMEOUT + ): bl.append(p.user) else: p.end = p.activity @@ -281,7 +325,10 @@ class Counter(models.Model): """ Returns True if the counter self is inactive from SITH_COUNTER_MINUTE_INACTIVE's value minutes, else False """ - return self.is_open() and ((timezone.now() - self.permanencies.order_by('-activity').first().activity) > datetime.timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE)) + return self.is_open() and ( + (timezone.now() - self.permanencies.order_by("-activity").first().activity) + > datetime.timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE) + ) def barman_list(self): """ @@ -294,22 +341,33 @@ class Refilling(models.Model): """ Handle the refilling """ + counter = models.ForeignKey(Counter, related_name="refillings", blank=False) - amount = CurrencyField(_('amount')) - operator = models.ForeignKey(User, related_name="refillings_as_operator", blank=False) + amount = CurrencyField(_("amount")) + operator = models.ForeignKey( + User, related_name="refillings_as_operator", blank=False + ) customer = models.ForeignKey(Customer, related_name="refillings", blank=False) - date = models.DateTimeField(_('date')) - payment_method = models.CharField(_('payment method'), max_length=255, - choices=settings.SITH_COUNTER_PAYMENT_METHOD, default='CASH') - bank = models.CharField(_('bank'), max_length=255, - choices=settings.SITH_COUNTER_BANK, default='OTHER') - is_validated = models.BooleanField(_('is validated'), default=False) + date = models.DateTimeField(_("date")) + payment_method = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_COUNTER_PAYMENT_METHOD, + default="CASH", + ) + bank = models.CharField( + _("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER" + ) + is_validated = models.BooleanField(_("is validated"), default=False) class Meta: verbose_name = _("refilling") def __str__(self): - return "Refilling: %.2f for %s" % (self.amount, self.customer.user.get_display_name()) + return "Refilling: %.2f for %s" % ( + self.amount, + self.customer.user.get_display_name(), + ) def is_owned_by(self, user): return user.is_owner(self.counter) and self.payment_method != "CARD" @@ -328,11 +386,19 @@ class Refilling(models.Model): self.customer.save() self.is_validated = True if self.customer.user.preferences.notify_on_refill: - Notification(user=self.customer.user, url=reverse('core:user_account_detail', - kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}), - param=str(self.amount), - type="REFILLING", - ).save() + Notification( + user=self.customer.user, + url=reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.customer.user.id, + "year": self.date.year, + "month": self.date.month, + }, + ), + param=str(self.amount), + type="REFILLING", + ).save() super(Refilling, self).save(*args, **kwargs) @@ -340,25 +406,60 @@ class Selling(models.Model): """ Handle the sellings """ + label = models.CharField(_("label"), max_length=64) - product = models.ForeignKey(Product, related_name="sellings", null=True, blank=True, on_delete=models.SET_NULL) - counter = models.ForeignKey(Counter, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL) - club = models.ForeignKey(Club, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL) - unit_price = CurrencyField(_('unit price')) - quantity = models.IntegerField(_('quantity')) - seller = models.ForeignKey(User, related_name="sellings_as_operator", null=True, blank=False, on_delete=models.SET_NULL) - customer = models.ForeignKey(Customer, related_name="buyings", null=True, blank=False, on_delete=models.SET_NULL) - date = models.DateTimeField(_('date')) - payment_method = models.CharField(_('payment method'), max_length=255, - choices=[('SITH_ACCOUNT', _('Sith account')), ('CARD', _('Credit card'))], default='SITH_ACCOUNT') - is_validated = models.BooleanField(_('is validated'), default=False) + product = models.ForeignKey( + Product, + related_name="sellings", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + counter = models.ForeignKey( + Counter, + related_name="sellings", + null=True, + blank=False, + on_delete=models.SET_NULL, + ) + club = models.ForeignKey( + Club, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL + ) + unit_price = CurrencyField(_("unit price")) + quantity = models.IntegerField(_("quantity")) + seller = models.ForeignKey( + User, + related_name="sellings_as_operator", + null=True, + blank=False, + on_delete=models.SET_NULL, + ) + customer = models.ForeignKey( + Customer, + related_name="buyings", + null=True, + blank=False, + on_delete=models.SET_NULL, + ) + date = models.DateTimeField(_("date")) + payment_method = models.CharField( + _("payment method"), + max_length=255, + choices=[("SITH_ACCOUNT", _("Sith account")), ("CARD", _("Credit card"))], + default="SITH_ACCOUNT", + ) + is_validated = models.BooleanField(_("is validated"), default=False) class Meta: verbose_name = _("selling") def __str__(self): - return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label, - self.quantity * self.unit_price, self.customer.user.get_display_name()) + return "Selling: %d x %s (%f) for %s" % ( + self.quantity, + self.label, + self.quantity * self.unit_price, + self.customer.user.get_display_name(), + ) def is_owned_by(self, user): return user.is_owner(self.counter) and self.payment_method != "CARD" @@ -374,30 +475,25 @@ class Selling(models.Model): def send_mail_customer(self): event = self.product.eticket.event_title or _("Unknown event") - subject = _('Eticket bought for the event %(event)s') % {'event': event} + subject = _("Eticket bought for the event %(event)s") % {"event": event} message_html = _( "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s." ) % { - 'event': event, - 'url': ''.join(( - '', - self.customer.get_full_url(), - '' - )) + "event": event, + "url": "".join( + ( + '', + self.customer.get_full_url(), + "", + ) + ), } message_txt = _( "You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s." - ) % { - 'event': event, - 'url': self.customer.get_full_url(), - } - self.customer.user.email_user( - subject, - message_txt, - html_message=message_html - ) + ) % {"event": event, "url": self.customer.get_full_url()} + self.customer.user.email_user(subject, message_txt, html_message=message_html) def save(self, allow_negative=False, *args, **kwargs): """ @@ -412,34 +508,52 @@ class Selling(models.Model): self.is_validated = True u = User.objects.filter(id=self.customer.user.id).first() if u.was_subscribed: - if self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER: + if ( + self.product + and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER + ): sub = Subscription( member=u, - subscription_type='un-semestre', + subscription_type="un-semestre", payment_method="EBOUTIC", location="EBOUTIC", ) sub.subscription_start = Subscription.compute_start() sub.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration']) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ] + ) sub.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'], - start=sub.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ], + start=sub.subscription_start, + ) sub.save() - elif self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS: + elif ( + self.product + and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS + ): u = User.objects.filter(id=self.customer.user.id).first() sub = Subscription( member=u, - subscription_type='deux-semestres', + subscription_type="deux-semestres", payment_method="EBOUTIC", location="EBOUTIC", ) sub.subscription_start = Subscription.compute_start() sub.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration']) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ] + ) sub.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'], - start=sub.subscription_start) + duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type][ + "duration" + ], + start=sub.subscription_start, + ) sub.save() try: if self.product.eticket: @@ -449,8 +563,14 @@ class Selling(models.Model): if self.customer.user.preferences.notify_on_click: Notification( user=self.customer.user, - url=reverse('core:user_account_detail', - kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}), + url=reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.customer.user.id, + "year": self.date.year, + "month": self.date.month, + }, + ), param="%d x %s" % (self.quantity, self.label), type="SELLING", ).save() @@ -461,29 +581,38 @@ class Permanency(models.Model): """ This class aims at storing a traceability of who was barman where and when """ + user = models.ForeignKey(User, related_name="permanencies", verbose_name=_("user")) - counter = models.ForeignKey(Counter, related_name="permanencies", verbose_name=_("counter")) - start = models.DateTimeField(_('start date')) - end = models.DateTimeField(_('end date'), null=True, db_index=True) - activity = models.DateTimeField(_('last activity date'), auto_now=True) + counter = models.ForeignKey( + Counter, related_name="permanencies", verbose_name=_("counter") + ) + start = models.DateTimeField(_("start date")) + end = models.DateTimeField(_("end date"), null=True, db_index=True) + activity = models.DateTimeField(_("last activity date"), auto_now=True) class Meta: verbose_name = _("permanency") def __str__(self): - return "%s in %s from %s (last activity: %s) to %s" % (self.user, self.counter, - self.start.strftime("%Y-%m-%d %H:%M:%S"), - self.activity.strftime("%Y-%m-%d %H:%M:%S"), - self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", - ) + return "%s in %s from %s (last activity: %s) to %s" % ( + self.user, + self.counter, + self.start.strftime("%Y-%m-%d %H:%M:%S"), + self.activity.strftime("%Y-%m-%d %H:%M:%S"), + self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "", + ) class CashRegisterSummary(models.Model): - user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user")) - counter = models.ForeignKey(Counter, related_name="cash_summaries", verbose_name=_("counter")) - date = models.DateTimeField(_('date')) - comment = models.TextField(_('comment'), null=True, blank=True) - emptied = models.BooleanField(_('emptied'), default=False) + user = models.ForeignKey( + User, related_name="cash_summaries", verbose_name=_("user") + ) + counter = models.ForeignKey( + Counter, related_name="cash_summaries", verbose_name=_("counter") + ) + date = models.DateTimeField(_("date")) + comment = models.TextField(_("comment"), null=True, blank=True) + emptied = models.BooleanField(_("emptied"), default=False) class Meta: verbose_name = _("cash register summary") @@ -492,37 +621,37 @@ class CashRegisterSummary(models.Model): return "At %s by %s - Total: %s €" % (self.counter, self.user, self.get_total()) def __getattribute__(self, name): - if name[:5] == 'check': - checks = self.items.filter(check=True).order_by('value').all() - if name == 'ten_cents': + if name[:5] == "check": + checks = self.items.filter(check=True).order_by("value").all() + if name == "ten_cents": return self.items.filter(value=0.1, check=False).first() - elif name == 'twenty_cents': + elif name == "twenty_cents": return self.items.filter(value=0.2, check=False).first() - elif name == 'fifty_cents': + elif name == "fifty_cents": return self.items.filter(value=0.5, check=False).first() - elif name == 'one_euro': + elif name == "one_euro": return self.items.filter(value=1, check=False).first() - elif name == 'two_euros': + elif name == "two_euros": return self.items.filter(value=2, check=False).first() - elif name == 'five_euros': + elif name == "five_euros": return self.items.filter(value=5, check=False).first() - elif name == 'ten_euros': + elif name == "ten_euros": return self.items.filter(value=10, check=False).first() - elif name == 'twenty_euros': + elif name == "twenty_euros": return self.items.filter(value=20, check=False).first() - elif name == 'fifty_euros': + elif name == "fifty_euros": return self.items.filter(value=50, check=False).first() - elif name == 'hundred_euros': + elif name == "hundred_euros": return self.items.filter(value=100, check=False).first() - elif name == 'check_1': + elif name == "check_1": return checks[0] if 0 < len(checks) else None - elif name == 'check_2': + elif name == "check_2": return checks[1] if 1 < len(checks) else None - elif name == 'check_3': + elif name == "check_3": return checks[2] if 2 < len(checks) else None - elif name == 'check_4': + elif name == "check_4": return checks[3] if 3 < len(checks) else None - elif name == 'check_5': + elif name == "check_5": return checks[4] if 4 < len(checks) else None else: return object.__getattribute__(self, name) @@ -547,14 +676,16 @@ class CashRegisterSummary(models.Model): return super(CashRegisterSummary, self).save(*args, **kwargs) def get_absolute_url(self): - return reverse('counter:cash_summary_list') + return reverse("counter:cash_summary_list") class CashRegisterSummaryItem(models.Model): - cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary")) + cash_summary = models.ForeignKey( + CashRegisterSummary, related_name="items", verbose_name=_("cash summary") + ) value = CurrencyField(_("value")) - quantity = models.IntegerField(_('quantity'), default=0) - check = models.BooleanField(_('check'), default=False) + quantity = models.IntegerField(_("quantity"), default=0) + check = models.BooleanField(_("check"), default=False) class Meta: verbose_name = _("cash register summary item") @@ -564,17 +695,24 @@ class Eticket(models.Model): """ Eticket can be linked to a product an allows PDF generation """ - product = models.OneToOneField(Product, related_name='eticket', verbose_name=_("product")) - banner = models.ImageField(upload_to='etickets', null=True, blank=True, verbose_name=_("banner")) - event_date = models.DateField(_('event date'), null=True, blank=True) - event_title = models.CharField(_('event title'), max_length=64, null=True, blank=True) - secret = models.CharField(_('secret'), max_length=64, unique=True) + + product = models.OneToOneField( + Product, related_name="eticket", verbose_name=_("product") + ) + banner = models.ImageField( + upload_to="etickets", null=True, blank=True, verbose_name=_("banner") + ) + event_date = models.DateField(_("event date"), null=True, blank=True) + event_title = models.CharField( + _("event title"), max_length=64, null=True, blank=True + ) + secret = models.CharField(_("secret"), max_length=64, unique=True) def __str__(self): return "%s" % (self.product.name) def get_absolute_url(self): - return reverse('counter:eticket_list') + return reverse("counter:eticket_list") def save(self, *args, **kwargs): if not self.id: @@ -590,4 +728,7 @@ class Eticket(models.Model): def get_hash(self, string): import hashlib import hmac - return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest() + + return hmac.new( + bytes(self.secret, "utf-8"), bytes(string, "utf-8"), hashlib.sha1 + ).hexdigest() diff --git a/counter/tests.py b/counter/tests.py index 15fcc45b..7e037826 100644 --- a/counter/tests.py +++ b/counter/tests.py @@ -39,46 +39,48 @@ class CounterTest(TestCase): self.mde = Counter.objects.filter(name="MDE").first() def test_full_click(self): - response = self.client.post(reverse("counter:login", kwargs={"counter_id": self.mde.id}), { - "username": self.skia.username, - "password": "plop" - }) + response = self.client.post( + reverse("counter:login", kwargs={"counter_id": self.mde.id}), + {"username": self.skia.username, "password": "plop"}, + ) response = self.client.get( - reverse("counter:details", kwargs={"counter_id": self.mde.id})) + reverse("counter:details", kwargs={"counter_id": self.mde.id}) + ) self.assertTrue( - 'class="link-button">S' Kia' in str(response.content)) + 'class="link-button">S' Kia' in str(response.content) + ) counter_token = re.search( - r'name="counter_token" value="([^"]*)"', str(response.content)).group(1) + r'name="counter_token" value="([^"]*)"', str(response.content) + ).group(1) - response = self.client.post(reverse("counter:details", - kwargs={"counter_id": self.mde.id}), { - "code": "4000k", - "counter_token": counter_token, - }) - location = response.get('location') + response = self.client.post( + reverse("counter:details", kwargs={"counter_id": self.mde.id}), + {"code": "4000k", "counter_token": counter_token}, + ) + location = response.get("location") - response = self.client.get(response.get('location')) - self.assertTrue('>Richard BatsbakRichard BatsbakClient : Richard Batsbak - Nouveau montant : 8.30' in str(response_get.content)) + response_get = self.client.get(response.get("location")) + self.assertTrue( + "

    Client : Richard Batsbak - Nouveau montant : 8.30" + in str(response_get.content) + ) class BarmanConnectionTest(TestCase): @@ -93,40 +95,50 @@ class BarmanConnectionTest(TestCase): self.counter = Counter.objects.filter(id=2).first() def test_barman_granted(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "krophil", - 'password': "plop"}) - response_get = self.client.get(reverse("counter:details", - args=[self.counter.id])) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) + response_get = self.client.get( + reverse("counter:details", args=[self.counter.id]) + ) - self.assertTrue( - '

    Entrez un code client :

    ' in str(response_get.content)) + self.assertTrue("

    Entrez un code client :

    " in str(response_get.content)) def test_counters_list_barmen(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "krophil", - 'password': "plop"}) - response_get = self.client.get(reverse("counter:activity", - args=[self.counter.id])) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) + response_get = self.client.get( + reverse("counter:activity", args=[self.counter.id]) + ) self.assertTrue( - '
  • Kro Phil'
  • ' in str(response_get.content)) + '
  • Kro Phil'
  • ' + in str(response_get.content) + ) def test_barman_denied(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "skia", - 'password': "plop"}) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "skia", "password": "plop"}, + ) response_get = self.client.get( - reverse("counter:details", args=[self.counter.id])) + reverse("counter:details", args=[self.counter.id]) + ) - self.assertTrue('

    Merci de vous identifier

    ' in str(response_get.content)) + self.assertTrue("

    Merci de vous identifier

    " in str(response_get.content)) def test_counters_list_no_barmen(self): - self.client.post(reverse('counter:login', args=[self.counter.id]), - {'username': "krophil", - 'password': "plop"}) + self.client.post( + reverse("counter:login", args=[self.counter.id]), + {"username": "krophil", "password": "plop"}, + ) response_get = self.client.get( - reverse("counter:activity", args=[self.counter.id])) + reverse("counter:activity", args=[self.counter.id]) + ) self.assertFalse( - '
  • S' Kia
  • ' in str(response_get.content)) + '
  • S' Kia
  • ' in str(response_get.content) + ) diff --git a/counter/urls.py b/counter/urls.py index aaade292..9b99b604 100644 --- a/counter/urls.py +++ b/counter/urls.py @@ -27,37 +27,106 @@ from django.conf.urls import url from counter.views import * urlpatterns = [ - url(r'^(?P[0-9]+)$', CounterMain.as_view(), name='details'), - url(r'^(?P[0-9]+)/click/(?P[0-9]+)$', CounterClick.as_view(), name='click'), - url(r'^(?P[0-9]+)/last_ops$', CounterLastOperationsView.as_view(), name='last_ops'), - url(r'^(?P[0-9]+)/cash_summary$', CounterCashSummaryView.as_view(), name='cash_summary'), - url(r'^(?P[0-9]+)/activity$', CounterActivityView.as_view(), name='activity'), - url(r'^(?P[0-9]+)/stats$', CounterStatView.as_view(), name='stats'), - url(r'^(?P[0-9]+)/login$', CounterLogin.as_view(), name='login'), - url(r'^(?P[0-9]+)/logout$', CounterLogout.as_view(), name='logout'), - url(r'^eticket/(?P[0-9]+)/pdf$', EticketPDFView.as_view(), name='eticket_pdf'), - url(r'^admin/(?P[0-9]+)$', CounterEditView.as_view(), name='admin'), - url(r'^admin/(?P[0-9]+)/prop$', CounterEditPropView.as_view(), name='prop_admin'), - url(r'^admin$', CounterListView.as_view(), name='admin_list'), - url(r'^admin/new$', CounterCreateView.as_view(), name='new'), - url(r'^admin/delete/(?P[0-9]+)$', CounterDeleteView.as_view(), name='delete'), - url(r'^admin/invoices_call$', InvoiceCallView.as_view(), name='invoices_call'), - url(r'^admin/cash_summary/list$', CashSummaryListView.as_view(), name='cash_summary_list'), - url(r'^admin/cash_summary/(?P[0-9]+)$', CashSummaryEditView.as_view(), name='cash_summary_edit'), - url(r'^admin/product/list$', ProductListView.as_view(), name='product_list'), - url(r'^admin/product/list_archived$', ProductArchivedListView.as_view(), name='product_list_archived'), - url(r'^admin/product/create$', ProductCreateView.as_view(), name='new_product'), - url(r'^admin/product/(?P[0-9]+)$', ProductEditView.as_view(), name='product_edit'), - url(r'^admin/producttype/list$', ProductTypeListView.as_view(), name='producttype_list'), - url(r'^admin/producttype/create$', ProductTypeCreateView.as_view(), name='new_producttype'), - url(r'^admin/producttype/(?P[0-9]+)$', ProductTypeEditView.as_view(), name='producttype_edit'), - url(r'^admin/eticket/list$', EticketListView.as_view(), name='eticket_list'), - url(r'^admin/eticket/new$', EticketCreateView.as_view(), name='new_eticket'), - url(r'^admin/eticket/(?P[0-9]+)$', EticketEditView.as_view(), name='edit_eticket'), - url(r'^admin/selling/(?P[0-9]+)/delete$', SellingDeleteView.as_view(), name='selling_delete'), - url(r'^admin/refilling/(?P[0-9]+)/delete$', RefillingDeleteView.as_view(), name='refilling_delete'), - url(r'^admin/(?P[0-9]+)/refillings$', CounterRefillingListView.as_view(), name='refilling_list'), - + url(r"^(?P[0-9]+)$", CounterMain.as_view(), name="details"), + url( + r"^(?P[0-9]+)/click/(?P[0-9]+)$", + CounterClick.as_view(), + name="click", + ), + url( + r"^(?P[0-9]+)/last_ops$", + CounterLastOperationsView.as_view(), + name="last_ops", + ), + url( + r"^(?P[0-9]+)/cash_summary$", + CounterCashSummaryView.as_view(), + name="cash_summary", + ), + url( + r"^(?P[0-9]+)/activity$", + CounterActivityView.as_view(), + name="activity", + ), + url(r"^(?P[0-9]+)/stats$", CounterStatView.as_view(), name="stats"), + url(r"^(?P[0-9]+)/login$", CounterLogin.as_view(), name="login"), + url(r"^(?P[0-9]+)/logout$", CounterLogout.as_view(), name="logout"), + url( + r"^eticket/(?P[0-9]+)/pdf$", + EticketPDFView.as_view(), + name="eticket_pdf", + ), + url(r"^admin/(?P[0-9]+)$", CounterEditView.as_view(), name="admin"), + url( + r"^admin/(?P[0-9]+)/prop$", + CounterEditPropView.as_view(), + name="prop_admin", + ), + url(r"^admin$", CounterListView.as_view(), name="admin_list"), + url(r"^admin/new$", CounterCreateView.as_view(), name="new"), + url( + r"^admin/delete/(?P[0-9]+)$", + CounterDeleteView.as_view(), + name="delete", + ), + url(r"^admin/invoices_call$", InvoiceCallView.as_view(), name="invoices_call"), + url( + r"^admin/cash_summary/list$", + CashSummaryListView.as_view(), + name="cash_summary_list", + ), + url( + r"^admin/cash_summary/(?P[0-9]+)$", + CashSummaryEditView.as_view(), + name="cash_summary_edit", + ), + url(r"^admin/product/list$", ProductListView.as_view(), name="product_list"), + url( + r"^admin/product/list_archived$", + ProductArchivedListView.as_view(), + name="product_list_archived", + ), + url(r"^admin/product/create$", ProductCreateView.as_view(), name="new_product"), + url( + r"^admin/product/(?P[0-9]+)$", + ProductEditView.as_view(), + name="product_edit", + ), + url( + r"^admin/producttype/list$", + ProductTypeListView.as_view(), + name="producttype_list", + ), + url( + r"^admin/producttype/create$", + ProductTypeCreateView.as_view(), + name="new_producttype", + ), + url( + r"^admin/producttype/(?P[0-9]+)$", + ProductTypeEditView.as_view(), + name="producttype_edit", + ), + url(r"^admin/eticket/list$", EticketListView.as_view(), name="eticket_list"), + url(r"^admin/eticket/new$", EticketCreateView.as_view(), name="new_eticket"), + url( + r"^admin/eticket/(?P[0-9]+)$", + EticketEditView.as_view(), + name="edit_eticket", + ), + url( + r"^admin/selling/(?P[0-9]+)/delete$", + SellingDeleteView.as_view(), + name="selling_delete", + ), + url( + r"^admin/refilling/(?P[0-9]+)/delete$", + RefillingDeleteView.as_view(), + name="refilling_delete", + ), + url( + r"^admin/(?P[0-9]+)/refillings$", + CounterRefillingListView.as_view(), + name="refilling_list", + ), ] - - diff --git a/counter/views.py b/counter/views.py index 6a39619e..941cbc80 100644 --- a/counter/views.py +++ b/counter/views.py @@ -27,7 +27,13 @@ from django.http import Http404 from django.core.exceptions import PermissionDenied from django.views.generic import ListView, DetailView, RedirectView, TemplateView from django.views.generic.base import View -from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin +from django.views.generic.edit import ( + UpdateView, + CreateView, + DeleteView, + ProcessFormView, + FormMixin, +) from django.forms.models import modelform_factory from django.forms import CheckboxSelectMultiple from django.core.urlresolvers import reverse_lazy, reverse @@ -48,8 +54,18 @@ from core.views import CanViewMixin, TabedViewMixin from core.views.forms import LoginForm, SelectDate, SelectDateTime from core.models import User from subscription.models import Subscription -from counter.models import Counter, Customer, Product, Selling, Refilling, ProductType, \ - CashRegisterSummary, CashRegisterSummaryItem, Eticket, Permanency +from counter.models import ( + Counter, + Customer, + Product, + Selling, + Refilling, + ProductType, + CashRegisterSummary, + CashRegisterSummaryItem, + Eticket, + Permanency, +) from accounting.models import CurrencyField @@ -57,6 +73,7 @@ class CounterAdminMixin(View): """ This view is made to protect counter admin section """ + edit_group = [settings.SITH_GROUP_COUNTER_ADMIN_ID] edit_club = [] @@ -73,8 +90,11 @@ class CounterAdminMixin(View): return False def dispatch(self, request, *args, **kwargs): - if not (request.user.is_root or self._test_group(request.user) - or self._test_club(request.user)): + if not ( + request.user.is_root + or self._test_group(request.user) + or self._test_club(request.user) + ): raise PermissionDenied return super(CounterAdminMixin, self).dispatch(request, *args, **kwargs) @@ -87,134 +107,196 @@ class GetUserForm(forms.Form): The Form implements a nice JS widget allowing the user to type a customer account id, or search the database with some nickname, first name, or last name (TODO) """ + code = forms.CharField(label="Code", max_length=10, required=False) - id = AutoCompleteSelectField('users', required=False, label=_("Select user"), help_text=None) + id = AutoCompleteSelectField( + "users", required=False, label=_("Select user"), help_text=None + ) def as_p(self): - self.fields['code'].widget.attrs['autofocus'] = True + self.fields["code"].widget.attrs["autofocus"] = True return super(GetUserForm, self).as_p() def clean(self): cleaned_data = super(GetUserForm, self).clean() cus = None - if cleaned_data['code'] != "": - cus = Customer.objects.filter(account_id__iexact=cleaned_data['code']).first() - elif cleaned_data['id'] is not None: - cus = Customer.objects.filter(user=cleaned_data['id']).first() - if (cus is None or not cus.can_buy): + if cleaned_data["code"] != "": + cus = Customer.objects.filter( + account_id__iexact=cleaned_data["code"] + ).first() + elif cleaned_data["id"] is not None: + cus = Customer.objects.filter(user=cleaned_data["id"]).first() + if cus is None or not cus.can_buy: raise forms.ValidationError(_("User not found")) - cleaned_data['user_id'] = cus.user.id - cleaned_data['user'] = cus.user + cleaned_data["user_id"] = cus.user.id + cleaned_data["user"] = cus.user return cleaned_data class RefillForm(forms.ModelForm): - error_css_class = 'error' - required_css_class = 'required' - amount = forms.FloatField(min_value=0, widget=forms.NumberInput(attrs={'class': 'focus'})) + error_css_class = "error" + required_css_class = "required" + amount = forms.FloatField( + min_value=0, widget=forms.NumberInput(attrs={"class": "focus"}) + ) class Meta: model = Refilling - fields = ['amount', 'payment_method', 'bank'] + fields = ["amount", "payment_method", "bank"] class CounterTabsMixin(TabedViewMixin): def get_tabs_title(self): - if hasattr(self.object, 'stock_owner'): + if hasattr(self.object, "stock_owner"): return self.object.stock_owner.counter else: return self.object def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse_lazy('counter:details', - kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), - 'slug': 'counter', - 'name': _("Counter"), - }) - if self.object.stock_owner.counter.type if hasattr(self.object, 'stock_owner') else self.object.type == "BAR": - tab_list.append({ - 'url': reverse_lazy('counter:cash_summary', - kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), - 'slug': 'cash_summary', - 'name': _("Cash summary"), - }) - tab_list.append({ - 'url': reverse_lazy('counter:last_ops', - kwargs={'counter_id': self.object.stock_owner.counter.id if hasattr(self.object, 'stock_owner') else self.object.id}), - 'slug': 'last_ops', - 'name': _("Last operations"), - }) + tab_list.append( + { + "url": reverse_lazy( + "counter:details", + kwargs={ + "counter_id": self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + }, + ), + "slug": "counter", + "name": _("Counter"), + } + ) + if ( + self.object.stock_owner.counter.type + if hasattr(self.object, "stock_owner") + else self.object.type == "BAR" + ): + tab_list.append( + { + "url": reverse_lazy( + "counter:cash_summary", + kwargs={ + "counter_id": self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + }, + ), + "slug": "cash_summary", + "name": _("Cash summary"), + } + ) + tab_list.append( + { + "url": reverse_lazy( + "counter:last_ops", + kwargs={ + "counter_id": self.object.stock_owner.counter.id + if hasattr(self.object, "stock_owner") + else self.object.id + }, + ), + "slug": "last_ops", + "name": _("Last operations"), + } + ) try: - tab_list.append({ - 'url': reverse_lazy('stock:take_items', - kwargs={'stock_id': self.object.stock.id if hasattr(self.object, 'stock') else self.object.stock_owner.id}), - 'slug': 'take_items_from_stock', - 'name': _("Take items from stock"), - }) + tab_list.append( + { + "url": reverse_lazy( + "stock:take_items", + kwargs={ + "stock_id": self.object.stock.id + if hasattr(self.object, "stock") + else self.object.stock_owner.id + }, + ), + "slug": "take_items_from_stock", + "name": _("Take items from stock"), + } + ) except: pass # The counter just have no stock return tab_list -class CounterMain(CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin): +class CounterMain( + CounterTabsMixin, CanViewMixin, DetailView, ProcessFormView, FormMixin +): """ The public (barman) view """ + model = Counter - template_name = 'counter/counter_main.jinja' + template_name = "counter/counter_main.jinja" pk_url_kwarg = "counter_id" - form_class = GetUserForm # Form to enter a client code and get the corresponding user id + form_class = ( + GetUserForm + ) # Form to enter a client code and get the corresponding user id current_tab = "counter" def post(self, request, *args, **kwargs): self.object = self.get_object() - if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and - self.request.session['counter_token'] == self.object.token): # Check the token to avoid the bar to be stolen - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, - kwargs={'counter_id': self.object.id}) + '?bad_location') + if self.object.type == "BAR" and not ( + "counter_token" in self.request.session.keys() + and self.request.session["counter_token"] == self.object.token + ): # Check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.object.id}, + ) + + "?bad_location" + ) return super(CounterMain, self).post(request, *args, **kwargs) def get_context_data(self, **kwargs): """ We handle here the login form for the barman """ - if self.request.method == 'POST': + if self.request.method == "POST": self.object = self.get_object() self.object.update_activity() kwargs = super(CounterMain, self).get_context_data(**kwargs) - kwargs['login_form'] = LoginForm() - kwargs['login_form'].fields['username'].widget.attrs['autofocus'] = True - kwargs['login_form'].cleaned_data = {} # add_error fails if there are no cleaned_data + kwargs["login_form"] = LoginForm() + kwargs["login_form"].fields["username"].widget.attrs["autofocus"] = True + kwargs[ + "login_form" + ].cleaned_data = {} # add_error fails if there are no cleaned_data if "credentials" in self.request.GET: - kwargs['login_form'].add_error(None, _("Bad credentials")) + kwargs["login_form"].add_error(None, _("Bad credentials")) if "sellers" in self.request.GET: - kwargs['login_form'].add_error(None, _("User is not barman")) - kwargs['form'] = self.get_form() - kwargs['form'].cleaned_data = {} # same as above + kwargs["login_form"].add_error(None, _("User is not barman")) + kwargs["form"] = self.get_form() + kwargs["form"].cleaned_data = {} # same as above if "bad_location" in self.request.GET: - kwargs['form'].add_error(None, _("Bad location, someone is already logged in somewhere else")) - if self.object.type == 'BAR': - kwargs['barmen'] = self.object.get_barmen_list() + kwargs["form"].add_error( + None, _("Bad location, someone is already logged in somewhere else") + ) + if self.object.type == "BAR": + kwargs["barmen"] = self.object.get_barmen_list() elif self.request.user.is_authenticated(): - kwargs['barmen'] = [self.request.user] - if 'last_basket' in self.request.session.keys(): - kwargs['last_basket'] = self.request.session.pop('last_basket') - kwargs['last_customer'] = self.request.session.pop('last_customer') - kwargs['last_total'] = self.request.session.pop('last_total') - kwargs['new_customer_amount'] = self.request.session.pop('new_customer_amount') + kwargs["barmen"] = [self.request.user] + if "last_basket" in self.request.session.keys(): + kwargs["last_basket"] = self.request.session.pop("last_basket") + kwargs["last_customer"] = self.request.session.pop("last_customer") + kwargs["last_total"] = self.request.session.pop("last_total") + kwargs["new_customer_amount"] = self.request.session.pop( + "new_customer_amount" + ) return kwargs def form_valid(self, form): """ We handle here the redirection, passing the user id of the asked customer """ - self.kwargs['user_id'] = form.cleaned_data['user_id'] + self.kwargs["user_id"] = form.cleaned_data["user_id"] return super(CounterMain, self).form_valid(form) def get_success_url(self): - return reverse_lazy('counter:click', args=self.args, kwargs=self.kwargs) + return reverse_lazy("counter:click", args=self.args, kwargs=self.kwargs) class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): @@ -223,19 +305,25 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): This is a detail view not to have to worry about loading the counter Everything is made by hand in the post method """ + model = Counter - template_name = 'counter/counter_click.jinja' + template_name = "counter/counter_click.jinja" pk_url_kwarg = "counter_id" current_tab = "counter" def dispatch(self, request, *args, **kwargs): - self.customer = get_object_or_404(Customer, user__id=self.kwargs['user_id']) + self.customer = get_object_or_404(Customer, user__id=self.kwargs["user_id"]) obj = self.get_object() if not self.customer.can_buy: raise Http404 if obj.type == "BAR": - if not ('counter_token' in request.session.keys() and - request.session['counter_token'] == obj.token) or len(obj.get_barmen_list()) < 1: + if ( + not ( + "counter_token" in request.session.keys() + and request.session["counter_token"] == obj.token + ) + or len(obj.get_barmen_list()) < 1 + ): raise PermissionDenied else: if not request.user.is_authenticated(): @@ -244,18 +332,18 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def get(self, request, *args, **kwargs): """Simple get view""" - if 'basket' not in request.session.keys(): # Init the basket session entry - request.session['basket'] = {} - request.session['basket_total'] = 0 - request.session['not_enough'] = False # Reset every variable - request.session['too_young'] = False - request.session['not_allowed'] = False - request.session['no_age'] = False + if "basket" not in request.session.keys(): # Init the basket session entry + request.session["basket"] = {} + request.session["basket_total"] = 0 + request.session["not_enough"] = False # Reset every variable + request.session["too_young"] = False + request.session["not_allowed"] = False + request.session["no_age"] = False self.refill_form = None ret = super(CounterClick, self).get(request, *args, **kwargs) - if ((self.object.type != "BAR" and not request.user.is_authenticated()) or - (self.object.type == "BAR" and - len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in + if (self.object.type != "BAR" and not request.user.is_authenticated()) or ( + self.object.type == "BAR" and len(self.object.get_barmen_list()) < 1 + ): # Check that at least one barman is logged in ret = self.cancel(request) # Otherwise, go to main view return ret @@ -263,21 +351,29 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): """ Handle the many possibilities of the post request """ self.object = self.get_object() self.refill_form = None - if ((self.object.type != "BAR" and not request.user.is_authenticated()) or - (self.object.type == "BAR" and - len(self.object.get_barmen_list()) < 1)): # Check that at least one barman is logged in + if (self.object.type != "BAR" and not request.user.is_authenticated()) or ( + self.object.type == "BAR" and len(self.object.get_barmen_list()) < 1 + ): # Check that at least one barman is logged in return self.cancel(request) - if self.object.type == "BAR" and not ('counter_token' in self.request.session.keys() and - self.request.session['counter_token'] == self.object.token): # Also check the token to avoid the bar to be stolen - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, - kwargs={'counter_id': self.object.id}) + '?bad_location') - if 'basket' not in request.session.keys(): - request.session['basket'] = {} - request.session['basket_total'] = 0 - request.session['not_enough'] = False # Reset every variable - request.session['too_young'] = False - request.session['not_allowed'] = False - request.session['no_age'] = False + if self.object.type == "BAR" and not ( + "counter_token" in self.request.session.keys() + and self.request.session["counter_token"] == self.object.token + ): # Also check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.object.id}, + ) + + "?bad_location" + ) + if "basket" not in request.session.keys(): + request.session["basket"] = {} + request.session["basket_total"] = 0 + request.session["not_enough"] = False # Reset every variable + request.session["too_young"] = False + request.session["not_allowed"] = False + request.session["no_age"] = False if self.object.type != "BAR": self.operator = request.user elif self.is_barman_price(): @@ -285,23 +381,25 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): else: self.operator = self.object.get_random_barman() - if 'add_product' in request.POST['action']: + if "add_product" in request.POST["action"]: self.add_product(request) - elif 'del_product' in request.POST['action']: + elif "del_product" in request.POST["action"]: self.del_product(request) - elif 'refill' in request.POST['action']: + elif "refill" in request.POST["action"]: self.refill(request) - elif 'code' in request.POST['action']: + elif "code" in request.POST["action"]: return self.parse_code(request) - elif 'cancel' in request.POST['action']: + elif "cancel" in request.POST["action"]: return self.cancel(request) - elif 'finish' in request.POST['action']: + elif "finish" in request.POST["action"]: return self.finish(request) context = self.get_context_data(object=self.object) return self.render_to_response(context) def is_barman_price(self): - if self.object.type == "BAR" and self.customer.user.id in [s.id for s in self.object.get_barmen_list()]: + if self.object.type == "BAR" and self.customer.user.id in [ + s.id for s in self.object.get_barmen_list() + ]: return True else: return False @@ -319,20 +417,23 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def sum_basket(self, request): total = 0 - for pid, infos in request.session['basket'].items(): - total += infos['price'] * infos['qty'] + for pid, infos in request.session["basket"].items(): + total += infos["price"] * infos["qty"] return total / 100 def get_total_quantity_for_pid(self, request, pid): pid = str(pid) try: - return request.session['basket'][pid]['qty'] + request.session['basket'][pid]['bonus_qty'] + return ( + request.session["basket"][pid]["qty"] + + request.session["basket"][pid]["bonus_qty"] + ) except: return 0 def compute_record_product(self, request, product=None): recorded = 0 - basket = request.session['basket'] + basket = request.session["basket"] if product: if product.is_record_product: @@ -343,14 +444,15 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): for p in basket: bproduct = self.get_product(str(p)) if bproduct.is_record_product: - recorded -= basket[p]['qty'] + recorded -= basket[p]["qty"] elif bproduct.is_unrecord_product: - recorded += basket[p]['qty'] + recorded += basket[p]["qty"] return recorded def is_record_product_ok(self, request, product): return self.customer.can_record_more( - self.compute_record_product(request, product)) + self.compute_record_product(request, product) + ) def add_product(self, request, q=1, p=None): """ @@ -358,7 +460,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): q is the quantity passed as integer p is the product id, passed as an integer """ - pid = p or request.POST['product_id'] + pid = p or request.POST["product_id"] pid = str(pid) price = self.get_price(pid) total = self.sum_basket(request) @@ -371,57 +473,74 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): if self.customer.user.is_in_group(g.name): can_buy = True if not can_buy: - request.session['not_allowed'] = True + request.session["not_allowed"] = True return False bq = 0 # Bonus quantity, for trays - if product.tray: # Handle the tray to adjust the quantity q to add and the bonus quantity bq + if ( + product.tray + ): # Handle the tray to adjust the quantity q to add and the bonus quantity bq total_qty_mod_6 = self.get_total_quantity_for_pid(request, pid) % 6 bq = int((total_qty_mod_6 + q) / 6) # Integer division q -= bq - if self.customer.amount < (total + round(q * float(price), 2)): # Check for enough money - request.session['not_enough'] = True + if self.customer.amount < ( + total + round(q * float(price), 2) + ): # Check for enough money + request.session["not_enough"] = True return False - if product.is_unrecord_product and not self.is_record_product_ok(request, product): - request.session['not_allowed'] = True + if product.is_unrecord_product and not self.is_record_product_ok( + request, product + ): + request.session["not_allowed"] = True return False if product.limit_age >= 18 and not self.customer.user.date_of_birth: - request.session['no_age'] = True + request.session["no_age"] = True return False if product.limit_age >= 18 and self.customer.user.is_banned_alcohol: - request.session['not_allowed'] = True + request.session["not_allowed"] = True return False if self.customer.user.is_banned_counter: - request.session['not_allowed'] = True + request.session["not_allowed"] = True return False - if self.customer.user.date_of_birth and self.customer.user.get_age() < product.limit_age: # Check if affordable - request.session['too_young'] = True + if ( + self.customer.user.date_of_birth + and self.customer.user.get_age() < product.limit_age + ): # Check if affordable + request.session["too_young"] = True return False - if pid in request.session['basket']: # Add if already in basket - request.session['basket'][pid]['qty'] += q - request.session['basket'][pid]['bonus_qty'] += bq + if pid in request.session["basket"]: # Add if already in basket + request.session["basket"][pid]["qty"] += q + request.session["basket"][pid]["bonus_qty"] += bq else: # or create if not - request.session['basket'][pid] = {'qty': q, 'price': int(price * 100), 'bonus_qty': bq} + request.session["basket"][pid] = { + "qty": q, + "price": int(price * 100), + "bonus_qty": bq, + } request.session.modified = True return True def del_product(self, request): """ Delete a product from the basket """ - pid = str(request.POST['product_id']) + pid = str(request.POST["product_id"]) product = self.get_product(pid) - if pid in request.session['basket']: - if product.tray and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) and request.session['basket'][pid]['bonus_qty']: - request.session['basket'][pid]['bonus_qty'] -= 1 + if pid in request.session["basket"]: + if ( + product.tray + and (self.get_total_quantity_for_pid(request, pid) % 6 == 0) + and request.session["basket"][pid]["bonus_qty"] + ): + request.session["basket"][pid]["bonus_qty"] -= 1 else: - request.session['basket'][pid]['qty'] -= 1 - if request.session['basket'][pid]['qty'] <= 0: - del request.session['basket'][pid] + request.session["basket"][pid]["qty"] -= 1 + if request.session["basket"][pid]["qty"] <= 0: + del request.session["basket"][pid] else: - request.session['basket'][pid] = None + request.session["basket"][pid] = None request.session.modified = True def parse_code(self, request): """Parse the string entered by the barman""" - string = str(request.POST['code']).upper() + string = str(request.POST["code"]).upper() if string == _("END"): return self.finish(request) elif string == _("CAN"): @@ -429,8 +548,8 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): regex = re.compile(r"^((?P[0-9]+)X)?(?P[A-Z0-9]+)$") m = regex.match(string) if m is not None: - nb = m.group('nb') - code = m.group('code') + nb = m.group("nb") + code = m.group("code") if nb is None: nb = 1 else: @@ -445,46 +564,66 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def finish(self, request): """ Finish the click session, and validate the basket """ with transaction.atomic(): - request.session['last_basket'] = [] + request.session["last_basket"] = [] if self.sum_basket(request) > self.customer.amount: raise DataError(_("You have not enough money to buy all the basket")) - for pid, infos in request.session['basket'].items(): + for pid, infos in request.session["basket"].items(): # This duplicates code for DB optimization (prevent to load many times the same object) p = Product.objects.filter(pk=pid).first() if self.is_barman_price(): uprice = p.special_selling_price else: uprice = p.selling_price - request.session['last_basket'].append("%d x %s" % (infos['qty'] + infos['bonus_qty'], p.name)) - s = Selling(label=p.name, product=p, club=p.club, counter=self.object, unit_price=uprice, - quantity=infos['qty'], seller=self.operator, customer=self.customer) + request.session["last_basket"].append( + "%d x %s" % (infos["qty"] + infos["bonus_qty"], p.name) + ) + s = Selling( + label=p.name, + product=p, + club=p.club, + counter=self.object, + unit_price=uprice, + quantity=infos["qty"], + seller=self.operator, + customer=self.customer, + ) s.save() - if infos['bonus_qty']: - s = Selling(label=p.name + " (Plateau)", product=p, club=p.club, counter=self.object, unit_price=0, - quantity=infos['bonus_qty'], seller=self.operator, customer=self.customer) + if infos["bonus_qty"]: + s = Selling( + label=p.name + " (Plateau)", + product=p, + club=p.club, + counter=self.object, + unit_price=0, + quantity=infos["bonus_qty"], + seller=self.operator, + customer=self.customer, + ) s.save() self.customer.recorded_products -= self.compute_record_product(request) self.customer.save() - request.session['last_customer'] = self.customer.user.get_display_name() - request.session['last_total'] = "%0.2f" % self.sum_basket(request) - request.session['new_customer_amount'] = str(self.customer.amount) - del request.session['basket'] + request.session["last_customer"] = self.customer.user.get_display_name() + request.session["last_total"] = "%0.2f" % self.sum_basket(request) + request.session["new_customer_amount"] = str(self.customer.amount) + del request.session["basket"] request.session.modified = True - kwargs = { - 'counter_id': self.object.id, - } - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs)) + kwargs = {"counter_id": self.object.id} + return HttpResponseRedirect( + reverse_lazy("counter:details", args=self.args, kwargs=kwargs) + ) def cancel(self, request): """ Cancel the click session """ - kwargs = {'counter_id': self.object.id} - request.session.pop('basket', None) - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, kwargs=kwargs)) + kwargs = {"counter_id": self.object.id} + request.session.pop("basket", None) + return HttpResponseRedirect( + reverse_lazy("counter:details", args=self.args, kwargs=kwargs) + ) def refill(self, request): """Refill the customer's account""" - if self.get_object().type == 'BAR': + if self.get_object().type == "BAR": form = RefillForm(request.POST) if form.is_valid(): form.instance.counter = self.object @@ -499,10 +638,10 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def get_context_data(self, **kwargs): """ Add customer to the context """ kwargs = super(CounterClick, self).get_context_data(**kwargs) - kwargs['customer'] = self.customer - kwargs['basket_total'] = self.sum_basket(self.request) - kwargs['refill_form'] = self.refill_form or RefillForm() - kwargs['categories'] = ProductType.objects.all() + kwargs["customer"] = self.customer + kwargs["basket_total"] = self.sum_basket(self.request) + kwargs["refill_form"] = self.refill_form or RefillForm() + kwargs["categories"] = ProductType.objects.all() return kwargs @@ -512,22 +651,26 @@ class CounterLogin(RedirectView): Logged barmen are stored in the Permanency model """ + permanent = False def post(self, request, *args, **kwargs): """ Register the logged user as barman for this counter """ - self.counter_id = kwargs['counter_id'] - self.counter = Counter.objects.filter(id=kwargs['counter_id']).first() + self.counter_id = kwargs["counter_id"] + self.counter = Counter.objects.filter(id=kwargs["counter_id"]).first() form = LoginForm(request, data=request.POST) self.errors = [] if form.is_valid(): - user = User.objects.filter(username=form.cleaned_data['username']).first() - if user in self.counter.sellers.all() and not user in self.counter.get_barmen_list(): + user = User.objects.filter(username=form.cleaned_data["username"]).first() + if ( + user in self.counter.sellers.all() + and not user in self.counter.get_barmen_list() + ): if len(self.counter.get_barmen_list()) <= 0: self.counter.gen_token() - request.session['counter_token'] = self.counter.token + request.session["counter_token"] = self.counter.token self.counter.add_barman(user) else: self.errors += ["sellers"] @@ -536,7 +679,11 @@ class CounterLogin(RedirectView): return super(CounterLogin, self).post(request, *args, **kwargs) def get_redirect_url(self, *args, **kwargs): - return reverse_lazy('counter:details', args=args, kwargs=kwargs) + "?" + '&'.join(self.errors) + return ( + reverse_lazy("counter:details", args=args, kwargs=kwargs) + + "?" + + "&".join(self.errors) + ) class CounterLogout(RedirectView): @@ -546,13 +693,14 @@ class CounterLogout(RedirectView): """ Unregister the user from the barman """ - self.counter = Counter.objects.filter(id=kwargs['counter_id']).first() - user = User.objects.filter(id=request.POST['user_id']).first() + self.counter = Counter.objects.filter(id=kwargs["counter_id"]).first() + user = User.objects.filter(id=request.POST["user_id"]).first() self.counter.del_barman(user) return super(CounterLogout, self).post(request, *args, **kwargs) def get_redirect_url(self, *args, **kwargs): - return reverse_lazy('counter:details', args=args, kwargs=kwargs) + return reverse_lazy("counter:details", args=args, kwargs=kwargs) + # Counter admin views @@ -560,45 +708,41 @@ class CounterLogout(RedirectView): class CounterAdminTabsMixin(TabedViewMixin): tabs_title = _("Counter administration") list_of_tabs = [ + {"url": reverse_lazy("stock:list"), "slug": "stocks", "name": _("Stocks")}, { - 'url': reverse_lazy('stock:list'), - 'slug': 'stocks', - 'name': _("Stocks"), + "url": reverse_lazy("counter:admin_list"), + "slug": "counters", + "name": _("Counters"), }, { - 'url': reverse_lazy('counter:admin_list'), - 'slug': 'counters', - 'name': _("Counters"), + "url": reverse_lazy("counter:product_list"), + "slug": "products", + "name": _("Products"), }, { - 'url': reverse_lazy('counter:product_list'), - 'slug': 'products', - 'name': _("Products"), + "url": reverse_lazy("counter:product_list_archived"), + "slug": "archive", + "name": _("Archived products"), }, { - 'url': reverse_lazy('counter:product_list_archived'), - 'slug': 'archive', - 'name': _("Archived products"), + "url": reverse_lazy("counter:producttype_list"), + "slug": "product_types", + "name": _("Product types"), }, { - 'url': reverse_lazy('counter:producttype_list'), - 'slug': 'product_types', - 'name': _("Product types"), + "url": reverse_lazy("counter:cash_summary_list"), + "slug": "cash_summary", + "name": _("Cash register summaries"), }, { - 'url': reverse_lazy('counter:cash_summary_list'), - 'slug': 'cash_summary', - 'name': _("Cash register summaries"), + "url": reverse_lazy("counter:invoices_call"), + "slug": "invoices_call", + "name": _("Invoices call"), }, { - 'url': reverse_lazy('counter:invoices_call'), - 'slug': 'invoices_call', - 'name': _("Invoices call"), - }, - { - 'url': reverse_lazy('counter:eticket_list'), - 'slug': 'etickets', - 'name': _("Etickets"), + "url": reverse_lazy("counter:eticket_list"), + "slug": "etickets", + "name": _("Etickets"), }, ] @@ -607,27 +751,30 @@ class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): """ A list view for the admins """ + model = Counter - template_name = 'counter/counter_list.jinja' + template_name = "counter/counter_list.jinja" current_tab = "counters" class CounterEditForm(forms.ModelForm): class Meta: model = Counter - fields = ['sellers', 'products'] - sellers = make_ajax_field(Counter, 'sellers', 'users', help_text="") - products = make_ajax_field(Counter, 'products', 'products', help_text="") + fields = ["sellers", "products"] + + sellers = make_ajax_field(Counter, "sellers", "users", help_text="") + products = make_ajax_field(Counter, "products", "products", help_text="") class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit a counter's main informations (for the counter's manager) """ + model = Counter form_class = CounterEditForm pk_url_kwarg = "counter_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "counters" def dispatch(self, request, *args, **kwargs): @@ -636,17 +783,18 @@ class CounterEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): return super(CounterEditView, self).dispatch(request, *args, **kwargs) def get_success_url(self): - return reverse_lazy('counter:admin', kwargs={'counter_id': self.object.id}) + return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit a counter's main informations (for the counter's admin) """ + model = Counter - form_class = modelform_factory(Counter, fields=['name', 'club', 'type']) + form_class = modelform_factory(Counter, fields=["name", "club", "type"]) pk_url_kwarg = "counter_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "counters" @@ -654,10 +802,14 @@ class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ Create a counter (for the admins) """ + model = Counter - form_class = modelform_factory(Counter, fields=['name', 'club', 'type', 'products'], - widgets={'products': CheckboxSelectMultiple}) - template_name = 'core/create.jinja' + form_class = modelform_factory( + Counter, + fields=["name", "club", "type", "products"], + widgets={"products": CheckboxSelectMultiple}, + ) + template_name = "core/create.jinja" current_tab = "counters" @@ -665,12 +817,14 @@ class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): """ Delete a counter (for the admins) """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('counter:admin_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("counter:admin_list") current_tab = "counters" + # Product management @@ -678,8 +832,9 @@ class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """ A list view for the admins """ + model = ProductType - template_name = 'counter/producttype_list.jinja' + template_name = "counter/producttype_list.jinja" current_tab = "product_types" @@ -687,9 +842,10 @@ class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView """ A create view for the admins """ + model = ProductType - fields = ['name', 'description', 'comment', 'icon'] - template_name = 'core/create.jinja' + fields = ["name", "description", "comment", "icon"] + template_name = "core/create.jinja" current_tab = "products" @@ -697,9 +853,10 @@ class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ An edit view for the admins """ + model = ProductType - template_name = 'core/edit.jinja' - fields = ['name', 'description', 'comment', 'icon'] + template_name = "core/edit.jinja" + fields = ["name", "description", "comment", "icon"] pk_url_kwarg = "type_id" current_tab = "products" @@ -708,10 +865,11 @@ class ProductArchivedListView(CounterAdminTabsMixin, CounterAdminMixin, ListView """ A list view for the admins """ + model = Product - template_name = 'counter/product_list.jinja' + template_name = "counter/product_list.jinja" queryset = Product.objects.filter(archived=True) - ordering = ['name'] + ordering = ["name"] current_tab = "archive" @@ -719,36 +877,68 @@ class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """ A list view for the admins """ + model = Product - template_name = 'counter/product_list.jinja' + template_name = "counter/product_list.jinja" queryset = Product.objects.filter(archived=False) - ordering = ['name'] + ordering = ["name"] current_tab = "products" class ProductEditForm(forms.ModelForm): class Meta: model = Product - fields = ['name', 'description', 'product_type', 'code', 'parent_product', 'buying_groups', 'purchase_price', - 'selling_price', 'special_selling_price', 'icon', 'club', 'limit_age', 'tray', 'archived'] - parent_product = AutoCompleteSelectField('products', show_help_text=False, label=_("Parent product"), required=False) - buying_groups = AutoCompleteSelectMultipleField('groups', show_help_text=False, help_text="", label=_("Buying groups"), required=False) - club = AutoCompleteSelectField('clubs', show_help_text=False) - counters = AutoCompleteSelectMultipleField('counters', show_help_text=False, help_text="", label=_("Counters"), required=False) + fields = [ + "name", + "description", + "product_type", + "code", + "parent_product", + "buying_groups", + "purchase_price", + "selling_price", + "special_selling_price", + "icon", + "club", + "limit_age", + "tray", + "archived", + ] + + parent_product = AutoCompleteSelectField( + "products", show_help_text=False, label=_("Parent product"), required=False + ) + buying_groups = AutoCompleteSelectMultipleField( + "groups", + show_help_text=False, + help_text="", + label=_("Buying groups"), + required=False, + ) + club = AutoCompleteSelectField("clubs", show_help_text=False) + counters = AutoCompleteSelectMultipleField( + "counters", + show_help_text=False, + help_text="", + label=_("Counters"), + required=False, + ) def __init__(self, *args, **kwargs): super(ProductEditForm, self).__init__(*args, **kwargs) if self.instance.id: - self.fields['counters'].initial = [str(c.id) for c in self.instance.counters.all()] + self.fields["counters"].initial = [ + str(c.id) for c in self.instance.counters.all() + ] def save(self, *args, **kwargs): ret = super(ProductEditForm, self).save(*args, **kwargs) - if self.fields['counters'].initial: - for cid in self.fields['counters'].initial: + if self.fields["counters"].initial: + for cid in self.fields["counters"].initial: c = Counter.objects.filter(id=int(cid)).first() c.products.remove(self.instance) c.save() - for cid in self.cleaned_data['counters']: + for cid in self.cleaned_data["counters"]: c = Counter.objects.filter(id=int(cid)).first() c.products.add(self.instance) c.save() @@ -759,9 +949,10 @@ class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ A create view for the admins """ + model = Product form_class = ProductEditForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" current_tab = "products" @@ -769,10 +960,11 @@ class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ An edit view for the admins """ + model = Product form_class = ProductEditForm pk_url_kwarg = "product_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "products" @@ -780,23 +972,33 @@ class RefillingDeleteView(DeleteView): """ Delete a refilling (for the admins) """ + model = Refilling pk_url_kwarg = "refilling_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): """ We have here a very particular right handling, we can't inherit from CanEditPropMixin """ self.object = self.get_object() - if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and - 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id}) + if ( + timezone.now() - self.object.date + <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + self.success_url = reverse( + "counter:details", kwargs={"counter_id": self.object.counter.id} + ) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): - self.success_url = reverse('core:user_account', kwargs={'user_id': self.object.customer.user.id}) + self.success_url = reverse( + "core:user_account", kwargs={"user_id": self.object.customer.user.id} + ) return super(RefillingDeleteView, self).dispatch(request, *args, **kwargs) raise PermissionDenied @@ -805,26 +1007,37 @@ class SellingDeleteView(DeleteView): """ Delete a selling (for the admins) """ + model = Selling pk_url_kwarg = "selling_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): """ We have here a very particular right handling, we can't inherit from CanEditPropMixin """ self.object = self.get_object() - if (timezone.now() - self.object.date <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) and - 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - self.success_url = reverse('counter:details', kwargs={'counter_id': self.object.counter.id}) + if ( + timezone.now() - self.object.date + <= timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + self.success_url = reverse( + "counter:details", kwargs={"counter_id": self.object.counter.id} + ) return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): - self.success_url = reverse('core:user_account', kwargs={'user_id': self.object.customer.user.id}) + self.success_url = reverse( + "core:user_account", kwargs={"user_id": self.object.customer.user.id} + ) return super(SellingDeleteView, self).dispatch(request, *args, **kwargs) raise PermissionDenied + # Cash register summaries @@ -832,6 +1045,7 @@ class CashRegisterSummaryForm(forms.Form): """ Provide the cash summary form """ + ten_cents = forms.IntegerField(label=_("10 cents"), required=False, min_value=0) twenty_cents = forms.IntegerField(label=_("20 cents"), required=False, min_value=0) fifty_cents = forms.IntegerField(label=_("50 cents"), required=False, min_value=0) @@ -841,46 +1055,108 @@ class CashRegisterSummaryForm(forms.Form): ten_euros = forms.IntegerField(label=_("10 euros"), required=False, min_value=0) twenty_euros = forms.IntegerField(label=_("20 euros"), required=False, min_value=0) fifty_euros = forms.IntegerField(label=_("50 euros"), required=False, min_value=0) - hundred_euros = forms.IntegerField(label=_("100 euros"), required=False, min_value=0) - check_1_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_1_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_2_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_2_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_3_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_3_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_4_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_4_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) - check_5_value = forms.DecimalField(label=_("Check amount"), required=False, min_value=0) - check_5_quantity = forms.IntegerField(label=_("Check quantity"), required=False, min_value=0) + hundred_euros = forms.IntegerField( + label=_("100 euros"), required=False, min_value=0 + ) + check_1_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_1_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_2_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_2_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_3_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_3_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_4_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_4_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) + check_5_value = forms.DecimalField( + label=_("Check amount"), required=False, min_value=0 + ) + check_5_quantity = forms.IntegerField( + label=_("Check quantity"), required=False, min_value=0 + ) comment = forms.CharField(label=_("Comment"), required=False) emptied = forms.BooleanField(label=_("Emptied"), required=False) def __init__(self, *args, **kwargs): - instance = kwargs.pop('instance', None) + instance = kwargs.pop("instance", None) super(CashRegisterSummaryForm, self).__init__(*args, **kwargs) if instance: - self.fields['ten_cents'].initial = instance.ten_cents.quantity if instance.ten_cents else 0 - self.fields['twenty_cents'].initial = instance.twenty_cents.quantity if instance.twenty_cents else 0 - self.fields['fifty_cents'].initial = instance.fifty_cents.quantity if instance.fifty_cents else 0 - self.fields['one_euro'].initial = instance.one_euro.quantity if instance.one_euro else 0 - self.fields['two_euros'].initial = instance.two_euros.quantity if instance.two_euros else 0 - self.fields['five_euros'].initial = instance.five_euros.quantity if instance.five_euros else 0 - self.fields['ten_euros'].initial = instance.ten_euros.quantity if instance.ten_euros else 0 - self.fields['twenty_euros'].initial = instance.twenty_euros.quantity if instance.twenty_euros else 0 - self.fields['fifty_euros'].initial = instance.fifty_euros.quantity if instance.fifty_euros else 0 - self.fields['hundred_euros'].initial = instance.hundred_euros.quantity if instance.hundred_euros else 0 - self.fields['check_1_quantity'].initial = instance.check_1.quantity if instance.check_1 else 0 - self.fields['check_2_quantity'].initial = instance.check_2.quantity if instance.check_2 else 0 - self.fields['check_3_quantity'].initial = instance.check_3.quantity if instance.check_3 else 0 - self.fields['check_4_quantity'].initial = instance.check_4.quantity if instance.check_4 else 0 - self.fields['check_5_quantity'].initial = instance.check_5.quantity if instance.check_5 else 0 - self.fields['check_1_value'].initial = instance.check_1.value if instance.check_1 else 0 - self.fields['check_2_value'].initial = instance.check_2.value if instance.check_2 else 0 - self.fields['check_3_value'].initial = instance.check_3.value if instance.check_3 else 0 - self.fields['check_4_value'].initial = instance.check_4.value if instance.check_4 else 0 - self.fields['check_5_value'].initial = instance.check_5.value if instance.check_5 else 0 - self.fields['comment'].initial = instance.comment - self.fields['emptied'].initial = instance.emptied + self.fields["ten_cents"].initial = ( + instance.ten_cents.quantity if instance.ten_cents else 0 + ) + self.fields["twenty_cents"].initial = ( + instance.twenty_cents.quantity if instance.twenty_cents else 0 + ) + self.fields["fifty_cents"].initial = ( + instance.fifty_cents.quantity if instance.fifty_cents else 0 + ) + self.fields["one_euro"].initial = ( + instance.one_euro.quantity if instance.one_euro else 0 + ) + self.fields["two_euros"].initial = ( + instance.two_euros.quantity if instance.two_euros else 0 + ) + self.fields["five_euros"].initial = ( + instance.five_euros.quantity if instance.five_euros else 0 + ) + self.fields["ten_euros"].initial = ( + instance.ten_euros.quantity if instance.ten_euros else 0 + ) + self.fields["twenty_euros"].initial = ( + instance.twenty_euros.quantity if instance.twenty_euros else 0 + ) + self.fields["fifty_euros"].initial = ( + instance.fifty_euros.quantity if instance.fifty_euros else 0 + ) + self.fields["hundred_euros"].initial = ( + instance.hundred_euros.quantity if instance.hundred_euros else 0 + ) + self.fields["check_1_quantity"].initial = ( + instance.check_1.quantity if instance.check_1 else 0 + ) + self.fields["check_2_quantity"].initial = ( + instance.check_2.quantity if instance.check_2 else 0 + ) + self.fields["check_3_quantity"].initial = ( + instance.check_3.quantity if instance.check_3 else 0 + ) + self.fields["check_4_quantity"].initial = ( + instance.check_4.quantity if instance.check_4 else 0 + ) + self.fields["check_5_quantity"].initial = ( + instance.check_5.quantity if instance.check_5 else 0 + ) + self.fields["check_1_value"].initial = ( + instance.check_1.value if instance.check_1 else 0 + ) + self.fields["check_2_value"].initial = ( + instance.check_2.value if instance.check_2 else 0 + ) + self.fields["check_3_value"].initial = ( + instance.check_3.value if instance.check_3 else 0 + ) + self.fields["check_4_value"].initial = ( + instance.check_4.value if instance.check_4 else 0 + ) + self.fields["check_5_value"].initial = ( + instance.check_5.value if instance.check_5 else 0 + ) + self.fields["comment"].initial = instance.comment + self.fields["emptied"].initial = instance.emptied self.instance = instance else: self.instance = None @@ -888,50 +1164,89 @@ class CashRegisterSummaryForm(forms.Form): def save(self, counter=None): cd = self.cleaned_data summary = self.instance or CashRegisterSummary( - counter=counter, - user=counter.get_random_barman(), + counter=counter, user=counter.get_random_barman() ) - summary.comment = cd['comment'] - summary.emptied = cd['emptied'] + summary.comment = cd["comment"] + summary.emptied = cd["emptied"] summary.save() summary.items.all().delete() # Cash - if cd['ten_cents']: - CashRegisterSummaryItem(cash_summary=summary, value=0.1, quantity=cd['ten_cents']).save() - if cd['twenty_cents']: - CashRegisterSummaryItem(cash_summary=summary, value=0.2, quantity=cd['twenty_cents']).save() - if cd['fifty_cents']: - CashRegisterSummaryItem(cash_summary=summary, value=0.5, quantity=cd['fifty_cents']).save() - if cd['one_euro']: - CashRegisterSummaryItem(cash_summary=summary, value=1, quantity=cd['one_euro']).save() - if cd['two_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=2, quantity=cd['two_euros']).save() - if cd['five_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=5, quantity=cd['five_euros']).save() - if cd['ten_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=10, quantity=cd['ten_euros']).save() - if cd['twenty_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=20, quantity=cd['twenty_euros']).save() - if cd['fifty_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=50, quantity=cd['fifty_euros']).save() - if cd['hundred_euros']: - CashRegisterSummaryItem(cash_summary=summary, value=100, quantity=cd['hundred_euros']).save() + if cd["ten_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.1, quantity=cd["ten_cents"] + ).save() + if cd["twenty_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.2, quantity=cd["twenty_cents"] + ).save() + if cd["fifty_cents"]: + CashRegisterSummaryItem( + cash_summary=summary, value=0.5, quantity=cd["fifty_cents"] + ).save() + if cd["one_euro"]: + CashRegisterSummaryItem( + cash_summary=summary, value=1, quantity=cd["one_euro"] + ).save() + if cd["two_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=2, quantity=cd["two_euros"] + ).save() + if cd["five_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=5, quantity=cd["five_euros"] + ).save() + if cd["ten_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=10, quantity=cd["ten_euros"] + ).save() + if cd["twenty_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=20, quantity=cd["twenty_euros"] + ).save() + if cd["fifty_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=50, quantity=cd["fifty_euros"] + ).save() + if cd["hundred_euros"]: + CashRegisterSummaryItem( + cash_summary=summary, value=100, quantity=cd["hundred_euros"] + ).save() # Checks - if cd['check_1_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_1_value'], - quantity=cd['check_1_quantity'], check=True).save() - if cd['check_2_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_2_value'], - quantity=cd['check_2_quantity'], check=True).save() - if cd['check_3_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_3_value'], - quantity=cd['check_3_quantity'], check=True).save() - if cd['check_4_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_4_value'], - quantity=cd['check_4_quantity'], check=True).save() - if cd['check_5_quantity']: - CashRegisterSummaryItem(cash_summary=summary, value=cd['check_5_value'], - quantity=cd['check_5_quantity'], check=True).save() + if cd["check_1_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_1_value"], + quantity=cd["check_1_quantity"], + check=True, + ).save() + if cd["check_2_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_2_value"], + quantity=cd["check_2_quantity"], + check=True, + ).save() + if cd["check_3_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_3_value"], + quantity=cd["check_3_quantity"], + check=True, + ).save() + if cd["check_4_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_4_value"], + quantity=cd["check_4_quantity"], + check=True, + ).save() + if cd["check_5_quantity"]: + CashRegisterSummaryItem( + cash_summary=summary, + value=cd["check_5_value"], + quantity=cd["check_5_quantity"], + check=True, + ).save() if summary.items.count() < 1: summary.delete() @@ -940,9 +1255,10 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): """ Provide the last operations to allow barmen to delete them """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/last_ops.jinja' + template_name = "counter/last_ops.jinja" current_tab = "last_ops" def dispatch(self, request, *args, **kwargs): @@ -950,18 +1266,34 @@ class CounterLastOperationsView(CounterTabsMixin, CanViewMixin, DetailView): We have here again a very particular right handling """ self.object = self.get_object() - if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - return super(CounterLastOperationsView, self).dispatch(request, *args, **kwargs) - return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location') + if ( + self.object.get_barmen_list() + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + return super(CounterLastOperationsView, self).dispatch( + request, *args, **kwargs + ) + return HttpResponseRedirect( + reverse("counter:details", kwargs={"counter_id": self.object.id}) + + "?bad_location" + ) def get_context_data(self, **kwargs): """Add form to the context """ kwargs = super(CounterLastOperationsView, self).get_context_data(**kwargs) - threshold = timezone.now() - timedelta(minutes=settings.SITH_LAST_OPERATIONS_LIMIT) - kwargs['last_refillings'] = self.object.refillings.filter(date__gte=threshold).order_by('-id')[:20] - kwargs['last_sellings'] = self.object.sellings.filter(date__gte=threshold).order_by('-id')[:20] + threshold = timezone.now() - timedelta( + minutes=settings.SITH_LAST_OPERATIONS_LIMIT + ) + kwargs["last_refillings"] = self.object.refillings.filter( + date__gte=threshold + ).order_by("-id")[:20] + kwargs["last_sellings"] = self.object.sellings.filter( + date__gte=threshold + ).order_by("-id")[:20] return kwargs @@ -969,9 +1301,10 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): """ Provide the cash summary form """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/cash_register_summary.jinja' + template_name = "counter/cash_register_summary.jinja" current_tab = "cash_summary" def dispatch(self, request, *args, **kwargs): @@ -979,11 +1312,21 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): We have here again a very particular right handling """ self.object = self.get_object() - if (self.object.get_barmen_list() and 'counter_token' in request.session.keys() and - request.session['counter_token'] and # check if not null for counters that have no token set - Counter.objects.filter(token=request.session['counter_token']).exists()): - return super(CounterCashSummaryView, self).dispatch(request, *args, **kwargs) - return HttpResponseRedirect(reverse('counter:details', kwargs={'counter_id': self.object.id}) + '?bad_location') + if ( + self.object.get_barmen_list() + and "counter_token" in request.session.keys() + and request.session["counter_token"] + and Counter.objects.filter( # check if not null for counters that have no token set + token=request.session["counter_token"] + ).exists() + ): + return super(CounterCashSummaryView, self).dispatch( + request, *args, **kwargs + ) + return HttpResponseRedirect( + reverse("counter:details", kwargs={"counter_id": self.object.id}) + + "?bad_location" + ) def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -999,12 +1342,12 @@ class CounterCashSummaryView(CounterTabsMixin, CanViewMixin, DetailView): return super(CounterCashSummaryView, self).get(request, *args, **kwargs) def get_success_url(self): - return reverse_lazy('counter:details', kwargs={'counter_id': self.object.id}) + return reverse_lazy("counter:details", kwargs={"counter_id": self.object.id}) def get_context_data(self, **kwargs): """ Add form to the context """ kwargs = super(CounterCashSummaryView, self).get_context_data(**kwargs) - kwargs['form'] = self.form + kwargs["form"] = self.form return kwargs @@ -1012,159 +1355,262 @@ class CounterActivityView(DetailView): """ Show the bar activity """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/activity.jinja' + template_name = "counter/activity.jinja" class CounterStatView(DetailView, CounterAdminMixin): """ Show the bar stats """ + model = Counter pk_url_kwarg = "counter_id" - template_name = 'counter/stats.jinja' + template_name = "counter/stats.jinja" def get_context_data(self, **kwargs): """ Add stats to the context """ from django.db.models import Sum, Case, When, F, DecimalField - kwargs = super(CounterStatView, self).get_context_data(**kwargs) - kwargs['Customer'] = Customer - kwargs['User'] = User - semester_start = Subscription.compute_start(d=date.today(), duration=3) - kwargs['total_sellings'] = Selling.objects.filter(date__gte=semester_start, - counter=self.object).aggregate(total_sellings=Sum(F('quantity') * F('unit_price'), - output_field=CurrencyField()))['total_sellings'] - kwargs['top'] = Selling.objects.values('customer__user').annotate( - selling_sum=Sum( - Case(When(counter=self.object, - date__gte=semester_start, - unit_price__gt=0, - then=F('unit_price') * F('quantity')), - output_field=CurrencyField() - ) - ) - ).exclude(selling_sum=None).order_by('-selling_sum').all()[:100] - kwargs['top_barman'] = Permanency.objects.values('user').annotate( - perm_sum=Sum( - Case(When(counter=self.object, - end__gt=datetime(year=1999, month=1, day=1), - then=F('end') - F('start')), - output_field=models.DateTimeField() - ) - ) - ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] - kwargs['top_barman_semester'] = Permanency.objects.values('user').annotate( - perm_sum=Sum( - Case(When(counter=self.object, - start__gt=semester_start, - end__gt=datetime(year=1999, month=1, day=1), - then=F('end') - F('start')), - output_field=models.DateTimeField() - ) - ) - ).exclude(perm_sum=None).order_by('-perm_sum').all()[:100] - kwargs['sith_date'] = settings.SITH_START_DATE[0] - kwargs['semester_start'] = semester_start + kwargs = super(CounterStatView, self).get_context_data(**kwargs) + kwargs["Customer"] = Customer + kwargs["User"] = User + semester_start = Subscription.compute_start(d=date.today(), duration=3) + kwargs["total_sellings"] = Selling.objects.filter( + date__gte=semester_start, counter=self.object + ).aggregate( + total_sellings=Sum( + F("quantity") * F("unit_price"), output_field=CurrencyField() + ) + )[ + "total_sellings" + ] + kwargs["top"] = ( + Selling.objects.values("customer__user") + .annotate( + selling_sum=Sum( + Case( + When( + counter=self.object, + date__gte=semester_start, + unit_price__gt=0, + then=F("unit_price") * F("quantity"), + ), + output_field=CurrencyField(), + ) + ) + ) + .exclude(selling_sum=None) + .order_by("-selling_sum") + .all()[:100] + ) + kwargs["top_barman"] = ( + Permanency.objects.values("user") + .annotate( + perm_sum=Sum( + Case( + When( + counter=self.object, + end__gt=datetime(year=1999, month=1, day=1), + then=F("end") - F("start"), + ), + output_field=models.DateTimeField(), + ) + ) + ) + .exclude(perm_sum=None) + .order_by("-perm_sum") + .all()[:100] + ) + kwargs["top_barman_semester"] = ( + Permanency.objects.values("user") + .annotate( + perm_sum=Sum( + Case( + When( + counter=self.object, + start__gt=semester_start, + end__gt=datetime(year=1999, month=1, day=1), + then=F("end") - F("start"), + ), + output_field=models.DateTimeField(), + ) + ) + ) + .exclude(perm_sum=None) + .order_by("-perm_sum") + .all()[:100] + ) + + kwargs["sith_date"] = settings.SITH_START_DATE[0] + kwargs["semester_start"] = semester_start return kwargs def dispatch(self, request, *args, **kwargs): try: return super(CounterStatView, self).dispatch(request, *args, **kwargs) except: - if (request.user.is_root + if ( + request.user.is_root or request.user.is_board_member - or self.object.is_owned_by(request.user)): + or self.object.is_owned_by(request.user) + ): return super(CanEditMixin, self).dispatch(request, *args, **kwargs) raise PermissionDenied class CashSummaryEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """Edit cash summaries""" + model = CashRegisterSummary - template_name = 'counter/cash_register_summary.jinja' + template_name = "counter/cash_register_summary.jinja" context_object_name = "cashsummary" pk_url_kwarg = "cashsummary_id" form_class = CashRegisterSummaryForm current_tab = "cash_summary" def get_success_url(self): - return reverse('counter:cash_summary_list') + return reverse("counter:cash_summary_list") class CashSummaryFormBase(forms.Form): - begin_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Begin date"), required=False, widget=SelectDateTime) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), required=False, widget=SelectDateTime) + begin_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Begin date"), + required=False, + widget=SelectDateTime, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + required=False, + widget=SelectDateTime, + ) class CashSummaryListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """Display a list of cash summaries""" + model = CashRegisterSummary - template_name = 'counter/cash_summary_list.jinja' + template_name = "counter/cash_summary_list.jinja" context_object_name = "cashsummary_list" current_tab = "cash_summary" - queryset = CashRegisterSummary.objects.all().order_by('-date') + queryset = CashRegisterSummary.objects.all().order_by("-date") paginate_by = settings.SITH_COUNTER_CASH_SUMMARY_LENGTH def get_context_data(self, **kwargs): """ Add sums to the context """ kwargs = super(CashSummaryListView, self).get_context_data(**kwargs) form = CashSummaryFormBase(self.request.GET) - kwargs['form'] = form - kwargs['summaries_sums'] = {} - kwargs['refilling_sums'] = {} + kwargs["form"] = form + kwargs["summaries_sums"] = {} + kwargs["refilling_sums"] = {} for c in Counter.objects.filter(type="BAR").all(): refillings = Refilling.objects.filter(counter=c) cashredistersummaries = CashRegisterSummary.objects.filter(counter=c) - if form.is_valid() and form.cleaned_data['begin_date']: - refillings = refillings.filter(date__gte=form.cleaned_data['begin_date']) - cashredistersummaries = cashredistersummaries.filter(date__gte=form.cleaned_data['begin_date']) + if form.is_valid() and form.cleaned_data["begin_date"]: + refillings = refillings.filter( + date__gte=form.cleaned_data["begin_date"] + ) + cashredistersummaries = cashredistersummaries.filter( + date__gte=form.cleaned_data["begin_date"] + ) else: - last_summary = CashRegisterSummary.objects.filter(counter=c, emptied=True).order_by('-date').first() + last_summary = ( + CashRegisterSummary.objects.filter(counter=c, emptied=True) + .order_by("-date") + .first() + ) if last_summary: refillings = refillings.filter(date__gt=last_summary.date) - cashredistersummaries = cashredistersummaries.filter(date__gt=last_summary.date) + cashredistersummaries = cashredistersummaries.filter( + date__gt=last_summary.date + ) else: - refillings = refillings.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) # My birth date should be old enough - cashredistersummaries = cashredistersummaries.filter(date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC)) - if form.is_valid() and form.cleaned_data['end_date']: - refillings = refillings.filter(date__lte=form.cleaned_data['end_date']) - cashredistersummaries = cashredistersummaries.filter(date__lte=form.cleaned_data['end_date']) - kwargs['summaries_sums'][c.name] = sum([s.get_total() for s in cashredistersummaries.all()]) - kwargs['refilling_sums'][c.name] = sum([s.amount for s in refillings.all()]) + refillings = refillings.filter( + date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC) + ) # My birth date should be old enough + cashredistersummaries = cashredistersummaries.filter( + date__gte=datetime(year=1994, month=5, day=17, tzinfo=pytz.UTC) + ) + if form.is_valid() and form.cleaned_data["end_date"]: + refillings = refillings.filter(date__lte=form.cleaned_data["end_date"]) + cashredistersummaries = cashredistersummaries.filter( + date__lte=form.cleaned_data["end_date"] + ) + kwargs["summaries_sums"][c.name] = sum( + [s.get_total() for s in cashredistersummaries.all()] + ) + kwargs["refilling_sums"][c.name] = sum([s.amount for s in refillings.all()]) return kwargs class InvoiceCallView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): - template_name = 'counter/invoices_call.jinja' - current_tab = 'invoices_call' + template_name = "counter/invoices_call.jinja" + current_tab = "invoices_call" def get_context_data(self, **kwargs): """ Add sums to the context """ kwargs = super(InvoiceCallView, self).get_context_data(**kwargs) - kwargs['months'] = Selling.objects.datetimes('date', 'month', order='DESC') + kwargs["months"] = Selling.objects.datetimes("date", "month", order="DESC") start_date = None end_date = None try: - start_date = datetime.strptime(self.request.GET['month'], '%Y-%m') + start_date = datetime.strptime(self.request.GET["month"], "%Y-%m") except: - start_date = datetime(year=timezone.now().year, month=(timezone.now().month + 10) % 12 + 1, day=1) + start_date = datetime( + year=timezone.now().year, + month=(timezone.now().month + 10) % 12 + 1, + day=1, + ) start_date = start_date.replace(tzinfo=pytz.UTC) - end_date = (start_date + timedelta(days=32)).replace(day=1, hour=0, minute=0, microsecond=0) + end_date = (start_date + timedelta(days=32)).replace( + day=1, hour=0, minute=0, microsecond=0 + ) from django.db.models import Sum, Case, When, F, DecimalField - kwargs['sum_cb'] = sum([r.amount for r in Refilling.objects.filter(payment_method='CARD', is_validated=True, - date__gte=start_date, date__lte=end_date)]) - kwargs['sum_cb'] += sum([s.quantity * s.unit_price for s in Selling.objects.filter(payment_method='CARD', is_validated=True, - date__gte=start_date, date__lte=end_date)]) - kwargs['start_date'] = start_date - kwargs['sums'] = Selling.objects.values('club__name').annotate(selling_sum=Sum( - Case(When(date__gte=start_date, - date__lt=end_date, - then=F('unit_price') * F('quantity')), - output_field=CurrencyField() - ) - )).exclude(selling_sum=None).order_by('-selling_sum') + + kwargs["sum_cb"] = sum( + [ + r.amount + for r in Refilling.objects.filter( + payment_method="CARD", + is_validated=True, + date__gte=start_date, + date__lte=end_date, + ) + ] + ) + kwargs["sum_cb"] += sum( + [ + s.quantity * s.unit_price + for s in Selling.objects.filter( + payment_method="CARD", + is_validated=True, + date__gte=start_date, + date__lte=end_date, + ) + ] + ) + kwargs["start_date"] = start_date + kwargs["sums"] = ( + Selling.objects.values("club__name") + .annotate( + selling_sum=Sum( + Case( + When( + date__gte=start_date, + date__lt=end_date, + then=F("unit_price") * F("quantity"), + ), + output_field=CurrencyField(), + ) + ) + ) + .exclude(selling_sum=None) + .order_by("-selling_sum") + ) return kwargs @@ -1172,28 +1618,31 @@ class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """ A list view for the admins """ + model = Eticket - template_name = 'counter/eticket_list.jinja' - ordering = ['id'] + template_name = "counter/eticket_list.jinja" + ordering = ["id"] current_tab = "etickets" class EticketForm(forms.ModelForm): class Meta: model = Eticket - fields = ['product', 'banner', 'event_title', 'event_date'] - widgets = { - 'event_date': SelectDate, - } - product = AutoCompleteSelectField('products', show_help_text=False, label=_("Product"), required=True) + fields = ["product", "banner", "event_title", "event_date"] + widgets = {"event_date": SelectDate} + + product = AutoCompleteSelectField( + "products", show_help_text=False, label=_("Product"), required=True + ) class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """ Create an eticket """ + model = Eticket - template_name = 'core/create.jinja' + template_name = "core/create.jinja" form_class = EticketForm current_tab = "etickets" @@ -1202,8 +1651,9 @@ class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """ Edit an eticket """ + model = Eticket - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" form_class = EticketForm pk_url_kwarg = "eticket_id" current_tab = "etickets" @@ -1213,6 +1663,7 @@ class EticketPDFView(CanViewMixin, DetailView): """ Display the PDF of an eticket """ + model = Selling pk_url_kwarg = "selling_id" @@ -1223,13 +1674,19 @@ class EticketPDFView(CanViewMixin, DetailView): from reportlab.graphics.shapes import Drawing from reportlab.graphics.barcode.qr import QrCodeWidget from reportlab.graphics import renderPDF + self.object = self.get_object() eticket = self.object.product.eticket user = self.object.customer.user - code = "%s %s %s %s" % (self.object.customer.user.id, self.object.product.id, self.object.id, self.object.quantity) + code = "%s %s %s %s" % ( + self.object.customer.user.id, + self.object.product.id, + self.object.id, + self.object.quantity, + ) code += " " + eticket.get_hash(code)[:8].upper() - response = HttpResponse(content_type='application/pdf') - response['Content-Disposition'] = 'filename="eticket.pdf"' + response = HttpResponse(content_type="application/pdf") + response["Content-Disposition"] = 'filename="eticket.pdf"' p = canvas.Canvas(response) p.setTitle("Eticket") im = ImageReader("core/static/core/img/eticket.jpg") @@ -1257,15 +1714,22 @@ class EticketPDFView(CanViewMixin, DetailView): p.drawCentredString(10.5 * cm, 23.6 * cm, eticket.event_title) if eticket.event_date: p.setFont("Helvetica-Bold", 16) - p.drawCentredString(10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y")) # FIXME with a locale + p.drawCentredString( + 10.5 * cm, 22.6 * cm, eticket.event_date.strftime("%d %b %Y") + ) # FIXME with a locale p.setFont("Helvetica-Bold", 14) - p.drawCentredString(10.5 * cm, 15 * cm, "%s : %d %s" % (user.get_display_name(), self.object.quantity, str(_("people(s)")))) + p.drawCentredString( + 10.5 * cm, + 15 * cm, + "%s : %d %s" + % (user.get_display_name(), self.object.quantity, str(_("people(s)"))), + ) p.setFont("Courier-Bold", 14) qrcode = QrCodeWidget(code) bounds = qrcode.getBounds() width = bounds[2] - bounds[0] height = bounds[3] - bounds[1] - d = Drawing(260, 260, transform=[260. / width, 0, 0, 260. / height, 0, 0]) + d = Drawing(260, 260, transform=[260.0 / width, 0, 0, 260.0 / height, 0, 0]) d.add(qrcode) renderPDF.draw(d, p, 10.5 * cm - 130, 6.1 * cm) p.drawCentredString(10.5 * cm, 6 * cm, code) @@ -1286,17 +1750,18 @@ class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListVie """ List of refillings on a counter """ + model = Refilling - template_name = 'counter/refilling_list.jinja' + template_name = "counter/refilling_list.jinja" current_tab = "counters" paginate_by = 30 def dispatch(self, request, *args, **kwargs): - self.counter = get_object_or_404(Counter, pk=kwargs['counter_id']) + self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"]) self.queryset = Refilling.objects.filter(counter__id=self.counter.id) return super(CounterRefillingListView, self).dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(CounterRefillingListView, self).get_context_data(**kwargs) - kwargs['counter'] = self.counter + kwargs["counter"] = self.counter return kwargs diff --git a/eboutic/__init__.py b/eboutic/__init__.py index 0a9419f8..0ace29c4 100644 --- a/eboutic/__init__.py +++ b/eboutic/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/eboutic/migrations/0001_initial.py b/eboutic/migrations/0001_initial.py index 820f5344..2eadf88c 100644 --- a/eboutic/migrations/0001_initial.py +++ b/eboutic/migrations/0001_initial.py @@ -8,56 +8,127 @@ from django.conf import settings class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] + dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] operations = [ migrations.CreateModel( - name='Basket', + name="Basket", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='baskets')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "user", + models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="baskets", + ), + ), ], ), migrations.CreateModel( - name='BasketItem', + name="BasketItem", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('product_id', models.IntegerField(verbose_name='product id')), - ('product_name', models.CharField(max_length=255, verbose_name='product name')), - ('type_id', models.IntegerField(verbose_name='product type id')), - ('product_unit_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='unit price')), - ('quantity', models.IntegerField(verbose_name='quantity')), - ('basket', models.ForeignKey(verbose_name='basket', to='eboutic.Basket', related_name='items')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("product_id", models.IntegerField(verbose_name="product id")), + ( + "product_name", + models.CharField(max_length=255, verbose_name="product name"), + ), + ("type_id", models.IntegerField(verbose_name="product type id")), + ( + "product_unit_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="unit price" + ), + ), + ("quantity", models.IntegerField(verbose_name="quantity")), + ( + "basket", + models.ForeignKey( + verbose_name="basket", to="eboutic.Basket", related_name="items" + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, ), migrations.CreateModel( - name='Invoice', + name="Invoice", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), - ('validated', models.BooleanField(verbose_name='validated', default=False)), - ('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='invoices')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date", auto_now=True)), + ( + "validated", + models.BooleanField(verbose_name="validated", default=False), + ), + ( + "user", + models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="invoices", + ), + ), ], ), migrations.CreateModel( - name='InvoiceItem', + name="InvoiceItem", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('product_id', models.IntegerField(verbose_name='product id')), - ('product_name', models.CharField(max_length=255, verbose_name='product name')), - ('type_id', models.IntegerField(verbose_name='product type id')), - ('product_unit_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='unit price')), - ('quantity', models.IntegerField(verbose_name='quantity')), - ('invoice', models.ForeignKey(verbose_name='invoice', to='eboutic.Invoice', related_name='items')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("product_id", models.IntegerField(verbose_name="product id")), + ( + "product_name", + models.CharField(max_length=255, verbose_name="product name"), + ), + ("type_id", models.IntegerField(verbose_name="product type id")), + ( + "product_unit_price", + accounting.models.CurrencyField( + decimal_places=2, max_digits=12, verbose_name="unit price" + ), + ), + ("quantity", models.IntegerField(verbose_name="quantity")), + ( + "invoice", + models.ForeignKey( + verbose_name="invoice", + to="eboutic.Invoice", + related_name="items", + ), + ), ], - options={ - 'abstract': False, - }, + options={"abstract": False}, ), ] diff --git a/eboutic/models.py b/eboutic/models.py index 3a61da9d..0a157afa 100644 --- a/eboutic/models.py +++ b/eboutic/models.py @@ -35,14 +35,23 @@ class Basket(models.Model): """ Basket is built when the user connects to an eboutic page """ - user = models.ForeignKey(User, related_name='baskets', verbose_name=_('user'), blank=False) - date = models.DateTimeField(_('date'), auto_now=True) + + user = models.ForeignKey( + User, related_name="baskets", verbose_name=_("user"), blank=False + ) + date = models.DateTimeField(_("date"), auto_now=True) def add_product(self, p, q=1): item = self.items.filter(product_id=p.id).first() if item is None: - BasketItem(basket=self, product_id=p.id, product_name=p.name, type_id=p.product_type.id, - quantity=q, product_unit_price=p.selling_price).save() + BasketItem( + basket=self, + product_id=p.id, + product_name=p.name, + type_id=p.product_type.id, + quantity=q, + product_unit_price=p.selling_price, + ).save() else: item.quantity += q item.save() @@ -69,8 +78,11 @@ class Invoice(models.Model): """ Invoices are generated once the payment has been validated """ - user = models.ForeignKey(User, related_name='invoices', verbose_name=_('user'), blank=False) - date = models.DateTimeField(_('date'), auto_now=True) + + user = models.ForeignKey( + User, related_name="invoices", verbose_name=_("user"), blank=False + ) + date = models.DateTimeField(_("date"), auto_now=True) validated = models.BooleanField(_("validated"), default=False) def __str__(self): @@ -86,9 +98,14 @@ class Invoice(models.Model): if self.validated: raise DataError(_("Invoice already validated")) from counter.models import Customer + if not Customer.objects.filter(user=self.user).exists(): number = Customer.objects.count() + 1 - Customer(user=self.user, account_id=Customer.generate_account_id(number), amount=0).save() + Customer( + user=self.user, + account_id=Customer.generate_account_id(number), + amount=0, + ).save() eboutic = Counter.objects.filter(type="EBOUTIC").first() for i in self.items.all(): if i.type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING: @@ -123,22 +140,28 @@ class Invoice(models.Model): class AbstractBaseItem(models.Model): - product_id = models.IntegerField(_('product id')) - product_name = models.CharField(_('product name'), max_length=255) - type_id = models.IntegerField(_('product type id')) - product_unit_price = CurrencyField(_('unit price')) - quantity = models.IntegerField(_('quantity')) + product_id = models.IntegerField(_("product id")) + product_name = models.CharField(_("product name"), max_length=255) + type_id = models.IntegerField(_("product type id")) + product_unit_price = CurrencyField(_("unit price")) + quantity = models.IntegerField(_("quantity")) class Meta: abstract = True def __str__(self): - return "Item: %s (%s) x%d" % (self.product_name, self.product_unit_price, self.quantity) + return "Item: %s (%s) x%d" % ( + self.product_name, + self.product_unit_price, + self.quantity, + ) class BasketItem(AbstractBaseItem): - basket = models.ForeignKey(Basket, related_name='items', verbose_name=_('basket')) + basket = models.ForeignKey(Basket, related_name="items", verbose_name=_("basket")) class InvoiceItem(AbstractBaseItem): - invoice = models.ForeignKey(Invoice, related_name='items', verbose_name=_('invoice')) + invoice = models.ForeignKey( + Invoice, related_name="items", verbose_name=_("invoice") + ) diff --git a/eboutic/tests.py b/eboutic/tests.py index a2dc4875..0a727950 100644 --- a/eboutic/tests.py +++ b/eboutic/tests.py @@ -62,112 +62,193 @@ class EbouticTest(TestCase): sig = crypto.sign(privkey, query, "sha1") b64sig = base64.b64encode(sig).decode("ascii") - url = reverse("eboutic:etransation_autoanswer") + "?%s&Sig=%s" % (query, urllib.parse.quote_plus(b64sig)) + url = reverse("eboutic:etransation_autoanswer") + "?%s&Sig=%s" % ( + query, + urllib.parse.quote_plus(b64sig), + ) response = self.client.get(url) self.assertTrue(response.status_code == 200) self.assertTrue(response.content.decode("utf-8") == "") return response def test_buy_simple_product_with_sith_account(self): - self.client.login(username='subscriber', password='plop') - Refilling(amount=10, counter=self.eboutic, operator=self.skia, customer=self.subscriber.customer).save() - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.barbar.id}) - self.assertTrue("\\n" - " \\n" - "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content)) + self.client.login(username="subscriber", password="plop") + Refilling( + amount=10, + counter=self.eboutic, + operator=self.skia, + customer=self.subscriber.customer, + ).save() + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.barbar.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Barbar\\n 1\\n" - " 1.70 \\xe2\\x82\\xac\\n " in str(response.content)) - response = self.client.post(reverse("eboutic:pay_with_sith"), { - "action": "pay_with_sith_account" - }) - self.assertTrue("Le paiement a \\xc3\\xa9t\\xc3\\xa9 effectu\\xc3\\xa9\\n" in str(response.content)) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (8.30 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("Eboutic\\n Subscribed User\\n" - " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" - " Compte utilisateur" in str(response.content)) + self.assertTrue( + "\\n Barbar\\n 1\\n" + " 1.70 \\xe2\\x82\\xac\\n " + in str(response.content) + ) + response = self.client.post( + reverse("eboutic:pay_with_sith"), {"action": "pay_with_sith_account"} + ) + self.assertTrue( + "Le paiement a \\xc3\\xa9t\\xc3\\xa9 effectu\\xc3\\xa9\\n" + in str(response.content) + ) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (8.30 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + 'Eboutic\\n Subscribed User\\n' + " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" + " Compte utilisateur" in str(response.content) + ) def test_buy_simple_product_with_credit_card(self): - self.client.login(username='subscriber', password='plop') - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.barbar.id}) - self.assertTrue("\\n" - " \\n" - "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content)) + self.client.login(username="subscriber", password="plop") + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.barbar.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Barbar: 1.70 \\xe2\\x82\\xac" in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Barbar\\n 1\\n" - " 1.70 \\xe2\\x82\\xac\\n " in str(response.content)) + self.assertTrue( + "\\n Barbar\\n 1\\n" + " 1.70 \\xe2\\x82\\xac\\n " + in str(response.content) + ) response = self.generate_bank_valid_answer_from_page_content(response.content) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("Eboutic\\n Subscribed User\\n" - " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" - " Carte bancaire" in str(response.content)) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (0.00 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + 'Eboutic\\n Subscribed User\\n' + " Barbar\\n 1\\n 1.70 \\xe2\\x82\\xac\\n" + " Carte bancaire" in str(response.content) + ) def test_buy_refill_product_with_credit_card(self): - self.client.login(username='subscriber', password='plop') - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.refill.id}) - self.assertTrue("\\n" - " \\n" - "\\n Rechargement 15 \\xe2\\x82\\xac: 15.00 \\xe2\\x82\\xac" in str(response.content)) + self.client.login(username="subscriber", password="plop") + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.refill.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Rechargement 15 \\xe2\\x82\\xac: 15.00 \\xe2\\x82\\xac" + in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Rechargement 15 \\xe2\\x82\\xac\\n 1\\n" - " 15.00 \\xe2\\x82\\xac\\n " in str(response.content)) + self.assertTrue( + "\\n Rechargement 15 \\xe2\\x82\\xac\\n 1\\n" + " 15.00 \\xe2\\x82\\xac\\n " + in str(response.content) + ) response = self.generate_bank_valid_answer_from_page_content(response.content) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (15.00 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("\\n
      \\n \\n " - "
    • 1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac
    • \\n" - " \\n
    \\n \\n" - " 15.00 \\xe2\\x82\\xac" in str(response.content)) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (15.00 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + "\\n
      \\n \\n " + "
    • 1 x Rechargement 15 \\xe2\\x82\\xac - 15.00 \\xe2\\x82\\xac
    • \\n" + " \\n
    \\n \\n" + " 15.00 \\xe2\\x82\\xac" in str(response.content) + ) def test_buy_subscribe_product_with_credit_card(self): - self.client.login(username='old_subscriber', password='plop') - response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})) + self.client.login(username="old_subscriber", password="plop") + response = self.client.get( + reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}) + ) self.assertTrue("Non cotisant" in str(response.content)) - response = self.client.post(reverse("eboutic:main"), { - "action": "add_product", - "product_id": self.cotis.id}) - self.assertTrue("\\n" - " \\n" - "\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac" in str(response.content)) + response = self.client.post( + reverse("eboutic:main"), + {"action": "add_product", "product_id": self.cotis.id}, + ) + self.assertTrue( + '\\n' + ' \\n' + "\\n Cotis 1 semestre: 15.00 \\xe2\\x82\\xac" + in str(response.content) + ) response = self.client.post(reverse("eboutic:command")) - self.assertTrue("\\n Cotis 1 semestre\\n 1\\n" - " 15.00 \\xe2\\x82\\xac\\n " in str(response.content)) + self.assertTrue( + "\\n Cotis 1 semestre\\n 1\\n" + " 15.00 \\xe2\\x82\\xac\\n " + in str(response.content) + ) response = self.generate_bank_valid_answer_from_page_content(response.content) - response = self.client.get(reverse("core:user_account_detail", kwargs={ - "user_id": self.old_subscriber.id, - "year": datetime.now().year, - "month": datetime.now().month, - })) - self.assertTrue("class=\"selected_tab\">Compte (0.00 \\xe2\\x82\\xac)" in str(response.content)) - self.assertTrue("\\n
      \\n \\n " - "
    • 1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac
    • \\n" - " \\n
    \\n \\n" - " 15.00 \\xe2\\x82\\xac" in str(response.content)) - response = self.client.get(reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id})) + response = self.client.get( + reverse( + "core:user_account_detail", + kwargs={ + "user_id": self.old_subscriber.id, + "year": datetime.now().year, + "month": datetime.now().month, + }, + ) + ) + self.assertTrue( + 'class="selected_tab">Compte (0.00 \\xe2\\x82\\xac)' + in str(response.content) + ) + self.assertTrue( + "\\n
      \\n \\n " + "
    • 1 x Cotis 1 semestre - 15.00 \\xe2\\x82\\xac
    • \\n" + " \\n
    \\n \\n" + " 15.00 \\xe2\\x82\\xac" in str(response.content) + ) + response = self.client.get( + reverse("core:user_profile", kwargs={"user_id": self.old_subscriber.id}) + ) self.assertTrue("Cotisant jusqu\\'au" in str(response.content)) diff --git a/eboutic/tests/test.py b/eboutic/tests/test.py index 38be7646..83a14ad9 100755 --- a/eboutic/tests/test.py +++ b/eboutic/tests/test.py @@ -32,7 +32,3 @@ try: print("Verify OK") except: print("Verify failed") - - - - diff --git a/eboutic/urls.py b/eboutic/urls.py index d4ddb486..4ee79f53 100644 --- a/eboutic/urls.py +++ b/eboutic/urls.py @@ -28,8 +28,12 @@ from eboutic.views import * urlpatterns = [ # Subscription views - url(r'^$', EbouticMain.as_view(), name='main'), - url(r'^command$', EbouticCommand.as_view(), name='command'), - url(r'^pay$', EbouticPayWithSith.as_view(), name='pay_with_sith'), - url(r'^et_autoanswer$', EtransactionAutoAnswer.as_view(), name='etransation_autoanswer'), + url(r"^$", EbouticMain.as_view(), name="main"), + url(r"^command$", EbouticCommand.as_view(), name="command"), + url(r"^pay$", EbouticPayWithSith.as_view(), name="pay_with_sith"), + url( + r"^et_autoanswer$", + EtransactionAutoAnswer.as_view(), + name="etransation_autoanswer", + ), ] diff --git a/eboutic/views.py b/eboutic/views.py index 7d841d26..56017237 100644 --- a/eboutic/views.py +++ b/eboutic/views.py @@ -41,44 +41,50 @@ from eboutic.models import Basket, Invoice, InvoiceItem class EbouticMain(TemplateView): - template_name = 'eboutic/eboutic_main.jinja' + template_name = "eboutic/eboutic_main.jinja" def make_basket(self, request): - if 'basket_id' not in request.session.keys(): # Init the basket session entry + if "basket_id" not in request.session.keys(): # Init the basket session entry self.basket = Basket(user=request.user) self.basket.save() else: - self.basket = Basket.objects.filter(id=request.session['basket_id']).first() + self.basket = Basket.objects.filter(id=request.session["basket_id"]).first() if self.basket is None: self.basket = Basket(user=request.user) self.basket.save() - request.session['basket_id'] = self.basket.id + request.session["basket_id"] = self.basket.id request.session.modified = True def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) self.object = Counter.objects.filter(type="EBOUTIC").first() self.make_basket(request) return super(EbouticMain, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) self.object = Counter.objects.filter(type="EBOUTIC").first() self.make_basket(request) - if 'add_product' in request.POST['action']: + if "add_product" in request.POST["action"]: self.add_product(request) - elif 'del_product' in request.POST['action']: + elif "del_product" in request.POST["action"]: self.del_product(request) return self.render_to_response(self.get_context_data(**kwargs)) def add_product(self, request): """ Add a product to the basket """ try: - p = self.object.products.filter(id=int(request.POST['product_id'])).first() + p = self.object.products.filter(id=int(request.POST["product_id"])).first() if not p.buying_groups.exists(): self.basket.add_product(p) for g in p.buying_groups.all(): @@ -91,79 +97,122 @@ class EbouticMain(TemplateView): def del_product(self, request): """ Delete a product from the basket """ try: - p = self.object.products.filter(id=int(request.POST['product_id'])).first() + p = self.object.products.filter(id=int(request.POST["product_id"])).first() self.basket.del_product(p) except: pass def get_context_data(self, **kwargs): kwargs = super(EbouticMain, self).get_context_data(**kwargs) - kwargs['basket'] = self.basket - kwargs['eboutic'] = Counter.objects.filter(type="EBOUTIC").first() - kwargs['categories'] = ProductType.objects.all() + kwargs["basket"] = self.basket + kwargs["eboutic"] = Counter.objects.filter(type="EBOUTIC").first() + kwargs["categories"] = ProductType.objects.all() if not self.request.user.was_subscribed: - kwargs['categories'] = kwargs['categories'].exclude(id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION) + kwargs["categories"] = kwargs["categories"].exclude( + id=settings.SITH_PRODUCTTYPE_SUBSCRIPTION + ) return kwargs class EbouticCommand(TemplateView): - template_name = 'eboutic/eboutic_makecommand.jinja' + template_name = "eboutic/eboutic_makecommand.jinja" def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) def post(self, request, *args, **kwargs): if not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('core:login', args=self.args, kwargs=kwargs) + "?next=" + - request.path) - if 'basket_id' not in request.session.keys(): - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) - self.basket = Basket.objects.filter(id=request.session['basket_id']).first() + return HttpResponseRedirect( + reverse_lazy("core:login", args=self.args, kwargs=kwargs) + + "?next=" + + request.path + ) + if "basket_id" not in request.session.keys(): + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) + self.basket = Basket.objects.filter(id=request.session["basket_id"]).first() if self.basket is None: - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) else: - kwargs['basket'] = self.basket + kwargs["basket"] = self.basket return self.render_to_response(self.get_context_data(**kwargs)) def get_context_data(self, **kwargs): kwargs = super(EbouticCommand, self).get_context_data(**kwargs) - kwargs['et_request'] = OrderedDict() - kwargs['et_request']['PBX_SITE'] = settings.SITH_EBOUTIC_PBX_SITE - kwargs['et_request']['PBX_RANG'] = settings.SITH_EBOUTIC_PBX_RANG - kwargs['et_request']['PBX_IDENTIFIANT'] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT - kwargs['et_request']['PBX_TOTAL'] = int(self.basket.get_total() * 100) - kwargs['et_request']['PBX_DEVISE'] = 978 # This is Euro. ET support only this value anyway - kwargs['et_request']['PBX_CMD'] = self.basket.id - kwargs['et_request']['PBX_PORTEUR'] = self.basket.user.email - kwargs['et_request']['PBX_RETOUR'] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" - kwargs['et_request']['PBX_HASH'] = "SHA512" - kwargs['et_request']['PBX_TYPEPAIEMENT'] = "CARTE" - kwargs['et_request']['PBX_TYPECARTE'] = "CB" - kwargs['et_request']['PBX_TIME'] = str(datetime.now().replace(microsecond=0).isoformat('T')) - kwargs['et_request']['PBX_HMAC'] = hmac.new(settings.SITH_EBOUTIC_HMAC_KEY, - bytes("&".join(["%s=%s" % (k, v) for k, v in kwargs['et_request'].items()]), 'utf-8'), - "sha512").hexdigest().upper() + kwargs["et_request"] = OrderedDict() + kwargs["et_request"]["PBX_SITE"] = settings.SITH_EBOUTIC_PBX_SITE + kwargs["et_request"]["PBX_RANG"] = settings.SITH_EBOUTIC_PBX_RANG + kwargs["et_request"]["PBX_IDENTIFIANT"] = settings.SITH_EBOUTIC_PBX_IDENTIFIANT + kwargs["et_request"]["PBX_TOTAL"] = int(self.basket.get_total() * 100) + kwargs["et_request"][ + "PBX_DEVISE" + ] = 978 # This is Euro. ET support only this value anyway + kwargs["et_request"]["PBX_CMD"] = self.basket.id + kwargs["et_request"]["PBX_PORTEUR"] = self.basket.user.email + kwargs["et_request"]["PBX_RETOUR"] = "Amount:M;BasketID:R;Auto:A;Error:E;Sig:K" + kwargs["et_request"]["PBX_HASH"] = "SHA512" + kwargs["et_request"]["PBX_TYPEPAIEMENT"] = "CARTE" + kwargs["et_request"]["PBX_TYPECARTE"] = "CB" + kwargs["et_request"]["PBX_TIME"] = str( + datetime.now().replace(microsecond=0).isoformat("T") + ) + kwargs["et_request"]["PBX_HMAC"] = ( + hmac.new( + settings.SITH_EBOUTIC_HMAC_KEY, + bytes( + "&".join( + ["%s=%s" % (k, v) for k, v in kwargs["et_request"].items()] + ), + "utf-8", + ), + "sha512", + ) + .hexdigest() + .upper() + ) return kwargs class EbouticPayWithSith(TemplateView): - template_name = 'eboutic/eboutic_payment_result.jinja' + template_name = "eboutic/eboutic_payment_result.jinja" def post(self, request, *args, **kwargs): try: with transaction.atomic(): - if 'basket_id' not in request.session.keys() or not request.user.is_authenticated(): - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) - b = Basket.objects.filter(id=request.session['basket_id']).first() - if b is None or b.items.filter(type_id=settings.SITH_COUNTER_PRODUCTTYPE_REFILLING).exists(): - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) + if ( + "basket_id" not in request.session.keys() + or not request.user.is_authenticated() + ): + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) + b = Basket.objects.filter(id=request.session["basket_id"]).first() + if ( + b is None + or b.items.filter( + type_id=settings.SITH_COUNTER_PRODUCTTYPE_REFILLING + ).exists() + ): + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) c = Customer.objects.filter(user__id=b.user.id).first() if c is None: - return HttpResponseRedirect(reverse_lazy('eboutic:main', args=self.args, kwargs=kwargs)) - kwargs['not_enough'] = True + return HttpResponseRedirect( + reverse_lazy("eboutic:main", args=self.args, kwargs=kwargs) + ) + kwargs["not_enough"] = True if c.amount < b.get_total(): raise DataError(_("You do not have enough money to buy the basket")) else: @@ -182,33 +231,44 @@ class EbouticPayWithSith(TemplateView): payment_method="SITH_ACCOUNT", ).save() b.delete() - kwargs['not_enough'] = False - request.session.pop('basket_id', None) + kwargs["not_enough"] = False + request.session.pop("basket_id", None) except DataError as e: - kwargs['not_enough'] = True + kwargs["not_enough"] = True return self.render_to_response(self.get_context_data(**kwargs)) class EtransactionAutoAnswer(View): def get(self, request, *args, **kwargs): - if (not 'Amount' in request.GET.keys() or - not 'BasketID' in request.GET.keys() or - not 'Auto' in request.GET.keys() or - not 'Error' in request.GET.keys() or - not 'Sig' in request.GET.keys()): + if ( + not "Amount" in request.GET.keys() + or not "BasketID" in request.GET.keys() + or not "Auto" in request.GET.keys() + or not "Error" in request.GET.keys() + or not "Sig" in request.GET.keys() + ): return HttpResponse("Bad arguments", status=400) key = crypto.load_publickey(crypto.FILETYPE_PEM, settings.SITH_EBOUTIC_PUB_KEY) cert = crypto.X509() cert.set_pubkey(key) - sig = base64.b64decode(request.GET['Sig']) + sig = base64.b64decode(request.GET["Sig"]) try: - crypto.verify(cert, sig, '&'.join(request.META['QUERY_STRING'].split('&')[:-1]), "sha1") + crypto.verify( + cert, + sig, + "&".join(request.META["QUERY_STRING"].split("&")[:-1]), + "sha1", + ) except: return HttpResponse("Bad signature", status=400) - if request.GET['Error'] == "00000": + if request.GET["Error"] == "00000": try: with transaction.atomic(): - b = Basket.objects.select_for_update().filter(id=request.GET['BasketID']).first() + b = ( + Basket.objects.select_for_update() + .filter(id=request.GET["BasketID"]) + .first() + ) if b is None: raise SuspiciousOperation("Basket does not exists") i = Invoice() @@ -216,12 +276,20 @@ class EtransactionAutoAnswer(View): i.payment_method = "CARD" i.save() for it in b.items.all(): - InvoiceItem(invoice=i, product_id=it.product_id, product_name=it.product_name, type_id=it.type_id, - product_unit_price=it.product_unit_price, quantity=it.quantity).save() + InvoiceItem( + invoice=i, + product_id=it.product_id, + product_name=it.product_name, + type_id=it.type_id, + product_unit_price=it.product_unit_price, + quantity=it.quantity, + ).save() i.validate() b.delete() except Exception as e: return HttpResponse("Payment failed with error: " + repr(e), status=400) return HttpResponse() else: - return HttpResponse("Payment failed with error: " + request.GET['Error'], status=400) + return HttpResponse( + "Payment failed with error: " + request.GET["Error"], status=400 + ) diff --git a/election/migrations/0001_initial.py b/election/migrations/0001_initial.py index 706d0aa9..00e15cfa 100644 --- a/election/migrations/0001_initial.py +++ b/election/migrations/0001_initial.py @@ -8,74 +8,208 @@ from django.conf import settings class Migration(migrations.Migration): dependencies = [ - ('core', '0018_auto_20161224_0211'), + ("core", "0018_auto_20161224_0211"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Candidature', + name="Candidature", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('program', models.TextField(null=True, verbose_name='description', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ( + "program", + models.TextField(null=True, verbose_name="description", blank=True), + ), ], ), migrations.CreateModel( - name='Election', + name="Election", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('description', models.TextField(null=True, verbose_name='description', blank=True)), - ('start_candidature', models.DateTimeField(verbose_name='start candidature')), - ('end_candidature', models.DateTimeField(verbose_name='end candidature')), - ('start_date', models.DateTimeField(verbose_name='start date')), - ('end_date', models.DateTimeField(verbose_name='end date')), - ('candidature_groups', models.ManyToManyField(related_name='candidate_elections', verbose_name='candidature groups', blank=True, to='core.Group')), - ('edit_groups', models.ManyToManyField(related_name='editable_elections', verbose_name='edit groups', blank=True, to='core.Group')), - ('view_groups', models.ManyToManyField(related_name='viewable_elections', verbose_name='view groups', blank=True, to='core.Group')), - ('vote_groups', models.ManyToManyField(related_name='votable_elections', verbose_name='vote groups', blank=True, to='core.Group')), - ('voters', models.ManyToManyField(related_name='voted_elections', verbose_name='voters', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "description", + models.TextField(null=True, verbose_name="description", blank=True), + ), + ( + "start_candidature", + models.DateTimeField(verbose_name="start candidature"), + ), + ( + "end_candidature", + models.DateTimeField(verbose_name="end candidature"), + ), + ("start_date", models.DateTimeField(verbose_name="start date")), + ("end_date", models.DateTimeField(verbose_name="end date")), + ( + "candidature_groups", + models.ManyToManyField( + related_name="candidate_elections", + verbose_name="candidature groups", + blank=True, + to="core.Group", + ), + ), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_elections", + verbose_name="edit groups", + blank=True, + to="core.Group", + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_elections", + verbose_name="view groups", + blank=True, + to="core.Group", + ), + ), + ( + "vote_groups", + models.ManyToManyField( + related_name="votable_elections", + verbose_name="vote groups", + blank=True, + to="core.Group", + ), + ), + ( + "voters", + models.ManyToManyField( + related_name="voted_elections", + verbose_name="voters", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='ElectionList', + name="ElectionList", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('election', models.ForeignKey(verbose_name='election', to='election.Election', related_name='election_lists')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "election", + models.ForeignKey( + verbose_name="election", + to="election.Election", + related_name="election_lists", + ), + ), ], ), migrations.CreateModel( - name='Role', + name="Role", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('description', models.TextField(null=True, verbose_name='description', blank=True)), - ('max_choice', models.IntegerField(verbose_name='max choice', default=1)), - ('election', models.ForeignKey(verbose_name='election', to='election.Election', related_name='roles')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ("title", models.CharField(max_length=255, verbose_name="title")), + ( + "description", + models.TextField(null=True, verbose_name="description", blank=True), + ), + ( + "max_choice", + models.IntegerField(verbose_name="max choice", default=1), + ), + ( + "election", + models.ForeignKey( + verbose_name="election", + to="election.Election", + related_name="roles", + ), + ), ], ), migrations.CreateModel( - name='Vote', + name="Vote", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), - ('candidature', models.ManyToManyField(related_name='votes', verbose_name='candidature', to='election.Candidature')), - ('role', models.ForeignKey(verbose_name='role', to='election.Role', related_name='votes')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + serialize=False, + auto_created=True, + ), + ), + ( + "candidature", + models.ManyToManyField( + related_name="votes", + verbose_name="candidature", + to="election.Candidature", + ), + ), + ( + "role", + models.ForeignKey( + verbose_name="role", to="election.Role", related_name="votes" + ), + ), ], ), migrations.AddField( - model_name='candidature', - name='election_list', - field=models.ForeignKey(verbose_name='election list', to='election.ElectionList', related_name='candidatures'), + model_name="candidature", + name="election_list", + field=models.ForeignKey( + verbose_name="election list", + to="election.ElectionList", + related_name="candidatures", + ), ), migrations.AddField( - model_name='candidature', - name='role', - field=models.ForeignKey(verbose_name='role', to='election.Role', related_name='candidatures'), + model_name="candidature", + name="role", + field=models.ForeignKey( + verbose_name="role", to="election.Role", related_name="candidatures" + ), ), migrations.AddField( - model_name='candidature', - name='user', - field=models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL, related_name='candidates', blank=True), + model_name="candidature", + name="user", + field=models.ForeignKey( + verbose_name="user", + to=settings.AUTH_USER_MODEL, + related_name="candidates", + blank=True, + ), ), ] diff --git a/election/migrations/0002_election_archived.py b/election/migrations/0002_election_archived.py index fda74ef0..c113cf46 100644 --- a/election/migrations/0002_election_archived.py +++ b/election/migrations/0002_election_archived.py @@ -6,14 +6,12 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('election', '0001_initial'), - ] + dependencies = [("election", "0001_initial")] operations = [ migrations.AddField( - model_name='election', - name='archived', - field=models.BooleanField(verbose_name='archived', default=False), - ), + model_name="election", + name="archived", + field=models.BooleanField(verbose_name="archived", default=False), + ) ] diff --git a/election/migrations/0003_auto_20171202_1819.py b/election/migrations/0003_auto_20171202_1819.py index ef7868d6..e918bc06 100644 --- a/election/migrations/0003_auto_20171202_1819.py +++ b/election/migrations/0003_auto_20171202_1819.py @@ -6,18 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('election', '0002_election_archived'), - ] + dependencies = [("election", "0002_election_archived")] operations = [ - migrations.AlterModelOptions( - name='role', - options={'ordering': ('order',)}, - ), + migrations.AlterModelOptions(name="role", options={"ordering": ("order",)}), migrations.AddField( - model_name='role', - name='order', + model_name="role", + name="order", field=models.PositiveIntegerField(editable=False, default=0, db_index=True), preserve_default=False, ), diff --git a/election/models.py b/election/models.py index 6d31ed0a..5211aad6 100644 --- a/election/models.py +++ b/election/models.py @@ -10,30 +10,45 @@ class Election(models.Model): """ This class allows to create a new election """ - title = models.CharField(_('title'), max_length=255) - description = models.TextField(_('description'), null=True, blank=True) - start_candidature = models.DateTimeField(_('start candidature'), blank=False) - end_candidature = models.DateTimeField(_('end candidature'), blank=False) - start_date = models.DateTimeField(_('start date'), blank=False) - end_date = models.DateTimeField(_('end date'), blank=False) + + title = models.CharField(_("title"), max_length=255) + description = models.TextField(_("description"), null=True, blank=True) + start_candidature = models.DateTimeField(_("start candidature"), blank=False) + end_candidature = models.DateTimeField(_("end candidature"), blank=False) + start_date = models.DateTimeField(_("start date"), blank=False) + end_date = models.DateTimeField(_("end date"), blank=False) edit_groups = models.ManyToManyField( - Group, related_name="editable_elections", - verbose_name=_("edit groups"), blank=True) + Group, + related_name="editable_elections", + verbose_name=_("edit groups"), + blank=True, + ) view_groups = models.ManyToManyField( - Group, related_name="viewable_elections", - verbose_name=_("view groups"), blank=True) + Group, + related_name="viewable_elections", + verbose_name=_("view groups"), + blank=True, + ) vote_groups = models.ManyToManyField( - Group, related_name="votable_elections", - verbose_name=_("vote groups"), blank=True) + Group, + related_name="votable_elections", + verbose_name=_("vote groups"), + blank=True, + ) candidature_groups = models.ManyToManyField( - Group, related_name="candidate_elections", - verbose_name=_("candidature groups"), blank=True) + Group, + related_name="candidate_elections", + verbose_name=_("candidature groups"), + blank=True, + ) - voters = models.ManyToManyField(User, verbose_name=('voters'), related_name='voted_elections') + voters = models.ManyToManyField( + User, verbose_name=("voters"), related_name="voted_elections" + ) archived = models.BooleanField(_("archived"), default=False) def __str__(self): @@ -94,10 +109,13 @@ class Role(OrderedModel): """ This class allows to create a new role avaliable for a candidature """ - election = models.ForeignKey(Election, related_name='roles', verbose_name=_("election")) - title = models.CharField(_('title'), max_length=255) - description = models.TextField(_('description'), null=True, blank=True) - max_choice = models.IntegerField(_('max choice'), default=1) + + election = models.ForeignKey( + Election, related_name="roles", verbose_name=_("election") + ) + title = models.CharField(_("title"), max_length=255) + description = models.TextField(_("description"), null=True, blank=True) + max_choice = models.IntegerField(_("max choice"), default=1) def results(self, total_vote): results = {} @@ -105,18 +123,21 @@ class Role(OrderedModel): non_blank = 0 for candidature in self.candidatures.all(): cand_results = {} - cand_results['vote'] = self.votes.filter(candidature=candidature).count() + cand_results["vote"] = self.votes.filter(candidature=candidature).count() if total_vote == 0: - cand_results['percent'] = 0 + cand_results["percent"] = 0 else: - cand_results['percent'] = cand_results['vote'] * 100 / total_vote - non_blank += cand_results['vote'] + cand_results["percent"] = cand_results["vote"] * 100 / total_vote + non_blank += cand_results["vote"] results[candidature.user.username] = cand_results - results['total vote'] = total_vote + results["total vote"] = total_vote if total_vote == 0: - results['blank vote'] = {'vote': 0, 'percent': 0} + results["blank vote"] = {"vote": 0, "percent": 0} else: - results['blank vote'] = {'vote': total_vote - non_blank, 'percent': (total_vote - non_blank) * 100 / total_vote} + results["blank vote"] = { + "vote": total_vote - non_blank, + "percent": (total_vote - non_blank) * 100 / total_vote, + } return results @property @@ -131,8 +152,11 @@ class ElectionList(models.Model): """ To allow per list vote """ - title = models.CharField(_('title'), max_length=255) - election = models.ForeignKey(Election, related_name='election_lists', verbose_name=_("election")) + + title = models.CharField(_("title"), max_length=255) + election = models.ForeignKey( + Election, related_name="election_lists", verbose_name=_("election") + ) def can_be_edited_by(self, user): return user.can_edit(self.election) @@ -150,10 +174,15 @@ class Candidature(models.Model): """ This class is a component of responsability """ - role = models.ForeignKey(Role, related_name='candidatures', verbose_name=_("role")) - user = models.ForeignKey(User, verbose_name=_('user'), related_name='candidates', blank=True) - program = models.TextField(_('description'), null=True, blank=True) - election_list = models.ForeignKey(ElectionList, related_name='candidatures', verbose_name=_('election list')) + + role = models.ForeignKey(Role, related_name="candidatures", verbose_name=_("role")) + user = models.ForeignKey( + User, verbose_name=_("user"), related_name="candidates", blank=True + ) + program = models.TextField(_("description"), null=True, blank=True) + election_list = models.ForeignKey( + ElectionList, related_name="candidatures", verbose_name=_("election list") + ) def delete(self): for vote in self.votes.all(): @@ -171,8 +200,11 @@ class Vote(models.Model): """ This class allows to vote for candidates """ - role = models.ForeignKey(Role, related_name='votes', verbose_name=_("role")) - candidature = models.ManyToManyField(Candidature, related_name='votes', verbose_name=_("candidature")) + + role = models.ForeignKey(Role, related_name="votes", verbose_name=_("role")) + candidature = models.ManyToManyField( + Candidature, related_name="votes", verbose_name=_("candidature") + ) def __str__(self): return "Vote" diff --git a/election/tests.py b/election/tests.py index 61ad1226..1675d224 100644 --- a/election/tests.py +++ b/election/tests.py @@ -13,13 +13,11 @@ class MainElection(TestCase): self.election = Election.objects.all().first() self.public_group = Group.objects.get(id=settings.SITH_GROUP_PUBLIC_ID) - self.subscriber_group = Group.objects.get( - name=settings.SITH_MAIN_MEMBERS_GROUP) - self.ae_board_group = Group.objects.get( - name=settings.SITH_MAIN_BOARD_GROUP) - self.sli = User.objects.get(username='sli') - self.subscriber = User.objects.get(username='subscriber') - self.public = User.objects.get(username='public') + self.subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) + self.ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) + self.sli = User.objects.get(username="sli") + self.subscriber = User.objects.get(username="subscriber") + self.public = User.objects.get(username="public") class ElectionDetailTest(MainElection): @@ -27,11 +25,13 @@ class ElectionDetailTest(MainElection): self.election.view_groups.remove(self.public_group) self.election.view_groups.add(self.subscriber_group) self.election.save() - self.client.login(username=self.public.username, password='plop') - response_get = self.client.get(reverse('election:detail', - args=str(self.election.id))) - response_post = self.client.get(reverse('election:detail', - args=str(self.election.id))) + self.client.login(username=self.public.username, password="plop") + response_get = self.client.get( + reverse("election:detail", args=str(self.election.id)) + ) + response_post = self.client.get( + reverse("election:detail", args=str(self.election.id)) + ) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) self.election.view_groups.remove(self.subscriber_group) @@ -39,22 +39,26 @@ class ElectionDetailTest(MainElection): self.election.save() def test_permisson_granted(self): - self.client.login(username=self.public.username, password='plop') - response_get = self.client.get(reverse('election:detail', - args=str(self.election.id))) - response_post = self.client.post(reverse('election:detail', - args=str(self.election.id))) + self.client.login(username=self.public.username, password="plop") + response_get = self.client.get( + reverse("election:detail", args=str(self.election.id)) + ) + response_post = self.client.post( + reverse("election:detail", args=str(self.election.id)) + ) self.assertFalse(response_get.status_code == 403) self.assertFalse(response_post.status_code == 403) - self.assertTrue('La roue tourne' in str(response_get.content)) + self.assertTrue("La roue tourne" in str(response_get.content)) class ElectionUpdateView(MainElection): def test_permission_denied(self): - self.client.login(username=self.subscriber.username, password='plop') - response_get = self.client.get(reverse('election:update', - args=str(self.election.id))) - response_post = self.client.post(reverse('election:update', - args=str(self.election.id))) + self.client.login(username=self.subscriber.username, password="plop") + response_get = self.client.get( + reverse("election:update", args=str(self.election.id)) + ) + response_post = self.client.post( + reverse("election:update", args=str(self.election.id)) + ) self.assertTrue(response_get.status_code == 403) self.assertTrue(response_post.status_code == 403) diff --git a/election/urls.py b/election/urls.py index 2962d1ee..c7bde487 100644 --- a/election/urls.py +++ b/election/urls.py @@ -3,19 +3,53 @@ from django.conf.urls import url from election.views import * urlpatterns = [ - url(r'^$', ElectionsListView.as_view(), name='list'), - url(r'^archived$', ElectionListArchivedView.as_view(), name='list_archived'), - url(r'^add$', ElectionCreateView.as_view(), name='create'), - url(r'^(?P[0-9]+)/edit$', ElectionUpdateView.as_view(), name='update'), - url(r'^(?P[0-9]+)/delete$', ElectionDeleteView.as_view(), name='delete'), - url(r'^(?P[0-9]+)/list/add$', ElectionListCreateView.as_view(), name='create_list'), - url(r'^(?P[0-9]+)/list/delete$', ElectionListDeleteView.as_view(), name='delete_list'), - url(r'^(?P[0-9]+)/role/create$', RoleCreateView.as_view(), name='create_role'), - url(r'^(?P[0-9]+)/role/edit$', RoleUpdateView.as_view(), name='update_role'), - url(r'^(?P[0-9]+)/role/delete$', RoleDeleteView.as_view(), name='delete_role'), - url(r'^(?P[0-9]+)/candidate/add$', CandidatureCreateView.as_view(), name='candidate'), - url(r'^(?P[0-9]+)/candidate/edit$', CandidatureUpdateView.as_view(), name='update_candidate'), - url(r'^(?P[0-9]+)/candidate/delete$', CandidatureDeleteView.as_view(), name='delete_candidate'), - url(r'^(?P[0-9]+)/vote$', VoteFormView.as_view(), name='vote'), - url(r'^(?P[0-9]+)/detail$', ElectionDetailView.as_view(), name='detail'), + url(r"^$", ElectionsListView.as_view(), name="list"), + url(r"^archived$", ElectionListArchivedView.as_view(), name="list_archived"), + url(r"^add$", ElectionCreateView.as_view(), name="create"), + url(r"^(?P[0-9]+)/edit$", ElectionUpdateView.as_view(), name="update"), + url( + r"^(?P[0-9]+)/delete$", ElectionDeleteView.as_view(), name="delete" + ), + url( + r"^(?P[0-9]+)/list/add$", + ElectionListCreateView.as_view(), + name="create_list", + ), + url( + r"^(?P[0-9]+)/list/delete$", + ElectionListDeleteView.as_view(), + name="delete_list", + ), + url( + r"^(?P[0-9]+)/role/create$", + RoleCreateView.as_view(), + name="create_role", + ), + url( + r"^(?P[0-9]+)/role/edit$", RoleUpdateView.as_view(), name="update_role" + ), + url( + r"^(?P[0-9]+)/role/delete$", + RoleDeleteView.as_view(), + name="delete_role", + ), + url( + r"^(?P[0-9]+)/candidate/add$", + CandidatureCreateView.as_view(), + name="candidate", + ), + url( + r"^(?P[0-9]+)/candidate/edit$", + CandidatureUpdateView.as_view(), + name="update_candidate", + ), + url( + r"^(?P[0-9]+)/candidate/delete$", + CandidatureDeleteView.as_view(), + name="delete_candidate", + ), + url(r"^(?P[0-9]+)/vote$", VoteFormView.as_view(), name="vote"), + url( + r"^(?P[0-9]+)/detail$", ElectionDetailView.as_view(), name="detail" + ), ] diff --git a/election/views.py b/election/views.py index b562a1cb..77d00ac3 100644 --- a/election/views.py +++ b/election/views.py @@ -21,19 +21,30 @@ from ajax_select import make_ajax_field # Custom form field + class LimitedCheckboxField(forms.ModelMultipleChoiceField): """ Used to replace ModelMultipleChoiceField but with automatic backend verification """ - def __init__(self, queryset, max_choice, required=True, widget=None, - label=None, initial=None, help_text='', *args, **kwargs): + def __init__( + self, + queryset, + max_choice, + required=True, + widget=None, + label=None, + initial=None, + help_text="", + *args, + **kwargs + ): self.max_choice = max_choice widget = forms.CheckboxSelectMultiple() - super(LimitedCheckboxField, - self).__init__(queryset, None, required, widget, - label, initial, help_text, *args, **kwargs) + super(LimitedCheckboxField, self).__init__( + queryset, None, required, widget, label, initial, help_text, *args, **kwargs + ) def clean(self, value): qs = super(LimitedCheckboxField, self).clean(value) @@ -42,7 +53,9 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField): def validate(self, qs): if qs.count() > self.max_choice: - raise forms.ValidationError(_("You have selected too much candidates."), code='invalid') + raise forms.ValidationError( + _("You have selected too much candidates."), code="invalid" + ) # Forms @@ -50,24 +63,29 @@ class LimitedCheckboxField(forms.ModelMultipleChoiceField): class CandidateForm(forms.ModelForm): """ Form to candidate """ + class Meta: model = Candidature - fields = ['user', 'role', 'program', 'election_list'] - widgets = { - 'program': forms.Textarea - } + fields = ["user", "role", "program", "election_list"] + widgets = {"program": forms.Textarea} - user = AutoCompleteSelectField('users', label=_('User to candidate'), help_text=None, required=True) + user = AutoCompleteSelectField( + "users", label=_("User to candidate"), help_text=None, required=True + ) def __init__(self, *args, **kwargs): - election_id = kwargs.pop('election_id', None) - can_edit = kwargs.pop('can_edit', False) + election_id = kwargs.pop("election_id", None) + can_edit = kwargs.pop("can_edit", False) super(CandidateForm, self).__init__(*args, **kwargs) if election_id: - self.fields['role'].queryset = Role.objects.filter(election__id=election_id).all() - self.fields['election_list'].queryset = ElectionList.objects.filter(election__id=election_id).all() + self.fields["role"].queryset = Role.objects.filter( + election__id=election_id + ).all() + self.fields["election_list"].queryset = ElectionList.objects.filter( + election__id=election_id + ).all() if not can_edit: - self.fields['user'].widget = forms.HiddenInput() + self.fields["user"].widget = forms.HiddenInput() class VoteForm(forms.Form): @@ -77,67 +95,112 @@ class VoteForm(forms.Form): for role in election.roles.all(): cand = role.candidatures if role.max_choice > 1: - self.fields[role.title] = LimitedCheckboxField(cand, role.max_choice, required=False) + self.fields[role.title] = LimitedCheckboxField( + cand, role.max_choice, required=False + ) else: - self.fields[role.title] = forms.ModelChoiceField(cand, required=False, - widget=forms.RadioSelect(), - empty_label=_("Blank vote")) + self.fields[role.title] = forms.ModelChoiceField( + cand, + required=False, + widget=forms.RadioSelect(), + empty_label=_("Blank vote"), + ) class RoleForm(forms.ModelForm): """ Form for creating a role """ + class Meta: model = Role - fields = ['title', 'election', 'description', 'max_choice'] + fields = ["title", "election", "description", "max_choice"] def __init__(self, *args, **kwargs): - election_id = kwargs.pop('election_id', None) + election_id = kwargs.pop("election_id", None) super(RoleForm, self).__init__(*args, **kwargs) if election_id: - self.fields['election'].queryset = Election.objects.filter(id=election_id).all() + self.fields["election"].queryset = Election.objects.filter( + id=election_id + ).all() def clean(self): cleaned_data = super(RoleForm, self).clean() - title = cleaned_data.get('title') - election = cleaned_data.get('election') + title = cleaned_data.get("title") + election = cleaned_data.get("election") if Role.objects.filter(title=title, election=election).exists(): - raise forms.ValidationError(_("This role already exists for this election"), code='invalid') + raise forms.ValidationError( + _("This role already exists for this election"), code="invalid" + ) class ElectionListForm(forms.ModelForm): class Meta: model = ElectionList - fields = ('title', 'election') + fields = ("title", "election") def __init__(self, *args, **kwargs): - election_id = kwargs.pop('election_id', None) + election_id = kwargs.pop("election_id", None) super(ElectionListForm, self).__init__(*args, **kwargs) if election_id: - self.fields['election'].queryset = Election.objects.filter(id=election_id).all() + self.fields["election"].queryset = Election.objects.filter( + id=election_id + ).all() class ElectionForm(forms.ModelForm): class Meta: model = Election - fields = ['title', 'description', 'archived', - 'start_candidature', 'end_candidature', - 'start_date', 'end_date', - 'edit_groups', 'view_groups', - 'vote_groups', 'candidature_groups'] + fields = [ + "title", + "description", + "archived", + "start_candidature", + "end_candidature", + "start_date", + "end_date", + "edit_groups", + "view_groups", + "vote_groups", + "candidature_groups", + ] - edit_groups = make_ajax_field(Election, 'edit_groups', 'groups', help_text="", label=_("edit groups")) - view_groups = make_ajax_field(Election, 'view_groups', 'groups', help_text="", label=_("view groups")) - vote_groups = make_ajax_field(Election, 'vote_groups', 'groups', help_text="", label=_("vote groups")) - candidature_groups = make_ajax_field(Election, 'candidature_groups', 'groups', help_text="", label=_("candidature groups")) + edit_groups = make_ajax_field( + Election, "edit_groups", "groups", help_text="", label=_("edit groups") + ) + view_groups = make_ajax_field( + Election, "view_groups", "groups", help_text="", label=_("view groups") + ) + vote_groups = make_ajax_field( + Election, "vote_groups", "groups", help_text="", label=_("vote groups") + ) + candidature_groups = make_ajax_field( + Election, + "candidature_groups", + "groups", + help_text="", + label=_("candidature groups"), + ) - start_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start date"), - widget=SelectDateTime, required=True) - end_date = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End date"), - widget=SelectDateTime, required=True) - start_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("Start candidature"), - widget=SelectDateTime, required=True) - end_candidature = forms.DateTimeField(['%Y-%m-%d %H:%M:%S'], label=_("End candidature"), - widget=SelectDateTime, required=True) + start_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=True, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], label=_("End date"), widget=SelectDateTime, required=True + ) + start_candidature = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start candidature"), + widget=SelectDateTime, + required=True, + ) + end_candidature = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End candidature"), + widget=SelectDateTime, + required=True, + ) # Display elections @@ -147,42 +210,52 @@ class ElectionsListView(CanViewMixin, ListView): """ A list of all non archived elections visible """ + model = Election ordering = ["-id"] paginate_by = 10 - template_name = 'election/election_list.jinja' + template_name = "election/election_list.jinja" def get_queryset(self): - return super(ElectionsListView, self).get_queryset().filter(archived=False).all() + return ( + super(ElectionsListView, self).get_queryset().filter(archived=False).all() + ) class ElectionListArchivedView(CanViewMixin, ListView): """ A list of all archived elections visible """ + model = Election ordering = ["-id"] paginate_by = 10 - template_name = 'election/election_list.jinja' + template_name = "election/election_list.jinja" def get_queryset(self): - return super(ElectionListArchivedView, self).get_queryset().filter(archived=True).all() + return ( + super(ElectionListArchivedView, self) + .get_queryset() + .filter(archived=True) + .all() + ) class ElectionDetailView(CanViewMixin, DetailView): """ Details an election responsability by responsability """ + model = Election - template_name = 'election/election_detail.jinja' + template_name = "election/election_detail.jinja" pk_url_kwarg = "election_id" def get(self, request, *arg, **kwargs): r = super(ElectionDetailView, self).get(request, *arg, **kwargs) election = self.get_object() if request.user.can_edit(election) and election.is_vote_editable: - action = request.GET.get('action', None) - role = request.GET.get('role', None) + action = request.GET.get("action", None) + role = request.GET.get("role", None) if action and role and Role.objects.filter(id=role).exists(): if action == "up": Role.objects.get(id=role).up() @@ -192,28 +265,32 @@ class ElectionDetailView(CanViewMixin, DetailView): Role.objects.get(id=role).bottom() elif action == "top": Role.objects.get(id=role).top() - return redirect(reverse_lazy('election:detail', kwargs={'election_id': election.id})) + return redirect( + reverse_lazy("election:detail", kwargs={"election_id": election.id}) + ) return r def get_context_data(self, **kwargs): """ Add additionnal data to the template """ kwargs = super(ElectionDetailView, self).get_context_data(**kwargs) - kwargs['election_form'] = VoteForm(self.object, self.request.user) - kwargs['election_results'] = self.object.results + kwargs["election_form"] = VoteForm(self.object, self.request.user) + kwargs["election_results"] = self.object.results return kwargs # Form view + class VoteFormView(CanCreateMixin, FormView): """ Alows users to vote """ + form_class = VoteForm - template_name = 'election/election_detail.jinja' + template_name = "election/election_detail.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) return super(VoteFormView, self).dispatch(request, *arg, **kwargs) def vote(self, election_data): @@ -235,8 +312,8 @@ class VoteFormView(CanCreateMixin, FormView): def get_form_kwargs(self): kwargs = super(VoteFormView, self).get_form_kwargs() - kwargs['election'] = self.election - kwargs['user'] = self.request.user + kwargs["election"] = self.election + kwargs["user"] = self.request.user return kwargs def form_valid(self, form): @@ -252,41 +329,43 @@ class VoteFormView(CanCreateMixin, FormView): return res def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) def get_context_data(self, **kwargs): """ Add additionnal data to the template """ kwargs = super(VoteFormView, self).get_context_data(**kwargs) - kwargs['object'] = self.election - kwargs['election'] = self.election - kwargs['election_form'] = self.get_form() + kwargs["object"] = self.election + kwargs["election"] = self.election + kwargs["election_form"] = self.get_form() return kwargs # Create views + class CandidatureCreateView(CanCreateMixin, CreateView): """ View dedicated to a cundidature creation """ + form_class = CandidateForm model = Candidature - template_name = 'election/candidate_form.jinja' + template_name = "election/candidate_form.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) return super(CandidatureCreateView, self).dispatch(request, *arg, **kwargs) def get_initial(self): init = {} self.can_edit = self.request.user.can_edit(self.election) - init['user'] = self.request.user.id + init["user"] = self.request.user.id return init def get_form_kwargs(self): kwargs = super(CandidatureCreateView, self).get_form_kwargs() - kwargs['election_id'] = self.election.id - kwargs['can_edit'] = self.can_edit + kwargs["election_id"] = self.election.id + kwargs["can_edit"] = self.can_edit return kwargs def form_valid(self, form): @@ -295,23 +374,25 @@ class CandidatureCreateView(CanCreateMixin, CreateView): """ obj = form.instance obj.election = Election.objects.get(id=self.election.id) - if(obj.election.can_candidate(obj.user)) and (obj.user == self.request.user or self.can_edit): + if (obj.election.can_candidate(obj.user)) and ( + obj.user == self.request.user or self.can_edit + ): return super(CreateView, self).form_valid(form) raise PermissionDenied def get_context_data(self, **kwargs): kwargs = super(CandidatureCreateView, self).get_context_data(**kwargs) - kwargs['election'] = self.election + kwargs["election"] = self.election return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) class ElectionCreateView(CanCreateMixin, CreateView): model = Election form_class = ElectionForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *args, **kwargs): if not request.user.is_subscribed: @@ -326,23 +407,23 @@ class ElectionCreateView(CanCreateMixin, CreateView): return super(CreateView, self).form_valid(form) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.object.id}) class RoleCreateView(CanCreateMixin, CreateView): model = Role form_class = RoleForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) if not self.election.is_vote_editable: raise PermissionDenied return super(RoleCreateView, self).dispatch(request, *arg, **kwargs) def get_initial(self): init = {} - init['election'] = self.election + init["election"] = self.election return init def form_valid(self, form): @@ -358,32 +439,34 @@ class RoleCreateView(CanCreateMixin, CreateView): def get_form_kwargs(self): kwargs = super(RoleCreateView, self).get_form_kwargs() - kwargs['election_id'] = self.election.id + kwargs["election_id"] = self.election.id return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.election.id} + ) class ElectionListCreateView(CanCreateMixin, CreateView): model = ElectionList form_class = ElectionListForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def dispatch(self, request, *arg, **kwargs): - self.election = get_object_or_404(Election, pk=kwargs['election_id']) + self.election = get_object_or_404(Election, pk=kwargs["election_id"]) if not self.election.is_vote_editable: raise PermissionDenied return super(ElectionListCreateView, self).dispatch(request, *arg, **kwargs) def get_initial(self): init = {} - init['election'] = self.election + init["election"] = self.election return init def get_form_kwargs(self): kwargs = super(ElectionListCreateView, self).get_form_kwargs() - kwargs['election_id'] = self.election.id + kwargs["election_id"] = self.election.id return kwargs def form_valid(self, form): @@ -401,7 +484,10 @@ class ElectionListCreateView(CanCreateMixin, CreateView): raise PermissionDenied def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.election.id} + ) + # Update view @@ -409,38 +495,42 @@ class ElectionListCreateView(CanCreateMixin, CreateView): class ElectionUpdateView(CanEditMixin, UpdateView): model = Election form_class = ElectionForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'election_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "election_id" def get_initial(self): init = {} try: - init['start_date'] = self.object.start_date.strftime('%Y-%m-%d %H:%M:%S') + init["start_date"] = self.object.start_date.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass try: - init['end_date'] = self.object.end_date.strftime('%Y-%m-%d %H:%M:%S') + init["end_date"] = self.object.end_date.strftime("%Y-%m-%d %H:%M:%S") except Exception: pass try: - init['start_candidature'] = self.object.start_candidature.strftime('%Y-%m-%d %H:%M:%S') + init["start_candidature"] = self.object.start_candidature.strftime( + "%Y-%m-%d %H:%M:%S" + ) except Exception: pass try: - init['end_candidature'] = self.object.end_candidature.strftime('%Y-%m-%d %H:%M:%S') + init["end_candidature"] = self.object.end_candidature.strftime( + "%Y-%m-%d %H:%M:%S" + ) except Exception: pass return init def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.object.id}) class CandidatureUpdateView(CanEditMixin, UpdateView): model = Candidature form_class = CandidateForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'candidature_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "candidature_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -449,7 +539,7 @@ class CandidatureUpdateView(CanEditMixin, UpdateView): return super(CandidatureUpdateView, self).dispatch(request, *arg, **kwargs) def remove_fields(self): - self.form.fields.pop('role', None) + self.form.fields.pop("role", None) def get(self, request, *args, **kwargs): self.form = self.get_form() @@ -459,24 +549,30 @@ class CandidatureUpdateView(CanEditMixin, UpdateView): def post(self, request, *args, **kwargs): self.form = self.get_form() self.remove_fields() - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): return super(CandidatureUpdateView, self).form_valid(self.form) return self.form_invalid(self.form) def get_form_kwargs(self): kwargs = super(CandidatureUpdateView, self).get_form_kwargs() - kwargs['election_id'] = self.object.role.election.id + kwargs["election_id"] = self.object.role.election.id return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.role.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.role.election.id} + ) class RoleUpdateView(CanEditMixin, UpdateView): model = Role form_class = RoleForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'role_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "role_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -485,7 +581,7 @@ class RoleUpdateView(CanEditMixin, UpdateView): return super(RoleUpdateView, self).dispatch(request, *arg, **kwargs) def remove_fields(self): - self.form.fields.pop('election', None) + self.form.fields.pop("election", None) def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -497,25 +593,32 @@ class RoleUpdateView(CanEditMixin, UpdateView): self.object = self.get_object() self.form = self.get_form() self.remove_fields() - if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): + if ( + request.user.is_authenticated() + and request.user.can_edit(self.object) + and self.form.is_valid() + ): return super(RoleUpdateView, self).form_valid(self.form) return self.form_invalid(self.form) def get_form_kwargs(self): kwargs = super(RoleUpdateView, self).get_form_kwargs() - kwargs['election_id'] = self.object.election.id + kwargs["election_id"] = self.object.election.id return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.object.election.id}) + return reverse_lazy( + "election:detail", kwargs={"election_id": self.object.election.id} + ) + # Delete Views class ElectionDeleteView(DeleteView): model = Election - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'election_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "election_id" def dispatch(self, request, *args, **kwargs): if request.user.is_root: @@ -523,13 +626,13 @@ class ElectionDeleteView(DeleteView): raise PermissionDenied def get_success_url(self, **kwargs): - return reverse_lazy('election:list') + return reverse_lazy("election:list") class CandidatureDeleteView(CanEditMixin, DeleteView): model = Candidature - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'candidature_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "candidature_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -539,13 +642,13 @@ class CandidatureDeleteView(CanEditMixin, DeleteView): return super(CandidatureDeleteView, self).dispatch(request, *arg, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) class RoleDeleteView(CanEditMixin, DeleteView): model = Role - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'role_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "role_id" def dispatch(self, request, *arg, **kwargs): self.object = self.get_object() @@ -555,13 +658,13 @@ class RoleDeleteView(CanEditMixin, DeleteView): return super(RoleDeleteView, self).dispatch(request, *arg, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) class ElectionListDeleteView(CanEditMixin, DeleteView): model = ElectionList - template_name = 'core/delete_confirm.jinja' - pk_url_kwarg = 'list_id' + template_name = "core/delete_confirm.jinja" + pk_url_kwarg = "list_id" def dispatch(self, request, *args, **kwargs): self.object = self.get_object() @@ -571,4 +674,4 @@ class ElectionListDeleteView(CanEditMixin, DeleteView): return super(ElectionListDeleteView, self).dispatch(request, *args, **kwargs) def get_success_url(self, **kwargs): - return reverse_lazy('election:detail', kwargs={'election_id': self.election.id}) + return reverse_lazy("election:detail", kwargs={"election_id": self.election.id}) diff --git a/forum/__init__.py b/forum/__init__.py index 0a9419f8..0ace29c4 100644 --- a/forum/__init__.py +++ b/forum/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/forum/admin.py b/forum/admin.py index 99462dfe..330243e8 100644 --- a/forum/admin.py +++ b/forum/admin.py @@ -45,4 +45,3 @@ admin.site.register(Forum, ForumAdmin) admin.site.register(ForumTopic, ForumTopicAdmin) admin.site.register(ForumMessage, ForumMessageAdmin) admin.site.register(ForumUserInfo) - diff --git a/forum/migrations/0001_initial.py b/forum/migrations/0001_initial.py index 5e5a499c..63aa4dd8 100644 --- a/forum/migrations/0001_initial.py +++ b/forum/migrations/0001_initial.py @@ -12,71 +12,211 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('club', '0006_auto_20161229_0040'), - ('core', '0019_preferences_receive_weekmail'), + ("club", "0006_auto_20161229_0040"), + ("core", "0019_preferences_receive_weekmail"), ] operations = [ migrations.CreateModel( - name='Forum', + name="Forum", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('description', models.CharField(max_length=256, verbose_name='description', default='')), - ('is_category', models.BooleanField(verbose_name='is a category', default=False)), - ('edit_groups', models.ManyToManyField(related_name='editable_forums', to='core.Group', blank=True, default=[4])), - ('owner_club', models.ForeignKey(to='club.Club', verbose_name='owner club', related_name='owned_forums', default=1)), - ('parent', models.ForeignKey(to='forum.Forum', null=True, related_name='children', blank=True)), - ('view_groups', models.ManyToManyField(related_name='viewable_forums', to='core.Group', blank=True, default=[2])), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "description", + models.CharField( + max_length=256, verbose_name="description", default="" + ), + ), + ( + "is_category", + models.BooleanField(verbose_name="is a category", default=False), + ), + ( + "edit_groups", + models.ManyToManyField( + related_name="editable_forums", + to="core.Group", + blank=True, + default=[4], + ), + ), + ( + "owner_club", + models.ForeignKey( + to="club.Club", + verbose_name="owner club", + related_name="owned_forums", + default=1, + ), + ), + ( + "parent", + models.ForeignKey( + to="forum.Forum", null=True, related_name="children", blank=True + ), + ), + ( + "view_groups", + models.ManyToManyField( + related_name="viewable_forums", + to="core.Group", + blank=True, + default=[2], + ), + ), ], ), migrations.CreateModel( - name='ForumMessage', + name="ForumMessage", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('title', models.CharField(max_length=64, blank=True, verbose_name='title', default='')), - ('message', models.TextField(verbose_name='message', default='')), - ('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), - ('author', models.ForeignKey(related_name='forum_messages', to=settings.AUTH_USER_MODEL)), - ('readers', models.ManyToManyField(to=settings.AUTH_USER_MODEL, verbose_name='readers', related_name='read_messages')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "title", + models.CharField( + max_length=64, blank=True, verbose_name="title", default="" + ), + ), + ("message", models.TextField(verbose_name="message", default="")), + ( + "date", + models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), + ), + ( + "author", + models.ForeignKey( + related_name="forum_messages", to=settings.AUTH_USER_MODEL + ), + ), + ( + "readers", + models.ManyToManyField( + to=settings.AUTH_USER_MODEL, + verbose_name="readers", + related_name="read_messages", + ), + ), ], - options={ - 'ordering': ['id'], - }, + options={"ordering": ["id"]}, ), migrations.CreateModel( - name='ForumMessageMeta', + name="ForumMessageMeta", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('date', models.DateTimeField(verbose_name='date', default=django.utils.timezone.now)), - ('action', models.CharField(max_length=16, choices=[('EDIT', 'Message edited by'), ('DELETE', 'Message deleted by'), ('UNDELETE', 'Message undeleted by')], verbose_name='action')), - ('message', models.ForeignKey(related_name='metas', to='forum.ForumMessage')), - ('user', models.ForeignKey(related_name='forum_message_metas', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "date", + models.DateTimeField( + verbose_name="date", default=django.utils.timezone.now + ), + ), + ( + "action", + models.CharField( + max_length=16, + choices=[ + ("EDIT", "Message edited by"), + ("DELETE", "Message deleted by"), + ("UNDELETE", "Message undeleted by"), + ], + verbose_name="action", + ), + ), + ( + "message", + models.ForeignKey(related_name="metas", to="forum.ForumMessage"), + ), + ( + "user", + models.ForeignKey( + related_name="forum_message_metas", to=settings.AUTH_USER_MODEL + ), + ), ], ), migrations.CreateModel( - name='ForumTopic', + name="ForumTopic", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('description', models.CharField(max_length=256, verbose_name='description', default='')), - ('author', models.ForeignKey(related_name='forum_topics', to=settings.AUTH_USER_MODEL)), - ('forum', models.ForeignKey(related_name='topics', to='forum.Forum')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "description", + models.CharField( + max_length=256, verbose_name="description", default="" + ), + ), + ( + "author", + models.ForeignKey( + related_name="forum_topics", to=settings.AUTH_USER_MODEL + ), + ), + ("forum", models.ForeignKey(related_name="topics", to="forum.Forum")), ], - options={ - 'ordering': ['-id'], - }, + options={"ordering": ["-id"]}, ), migrations.CreateModel( - name='ForumUserInfo', + name="ForumUserInfo", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('last_read_date', models.DateTimeField(verbose_name='last read date', default=datetime.datetime(1999, 1, 1, 0, 0, tzinfo=utc))), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, related_name='_forum_infos')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "last_read_date", + models.DateTimeField( + verbose_name="last read date", + default=datetime.datetime(1999, 1, 1, 0, 0, tzinfo=utc), + ), + ), + ( + "user", + models.OneToOneField( + to=settings.AUTH_USER_MODEL, related_name="_forum_infos" + ), + ), ], ), migrations.AddField( - model_name='forummessage', - name='topic', - field=models.ForeignKey(related_name='messages', to='forum.ForumTopic'), + model_name="forummessage", + name="topic", + field=models.ForeignKey(related_name="messages", to="forum.ForumTopic"), ), ] diff --git a/forum/migrations/0002_auto_20170312_1753.py b/forum/migrations/0002_auto_20170312_1753.py index fdd0a431..a0807c47 100644 --- a/forum/migrations/0002_auto_20170312_1753.py +++ b/forum/migrations/0002_auto_20170312_1753.py @@ -6,23 +6,25 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('forum', '0001_initial'), - ] + dependencies = [("forum", "0001_initial")] operations = [ - migrations.AlterModelOptions( - name='forum', - options={'ordering': ['number']}, - ), + migrations.AlterModelOptions(name="forum", options={"ordering": ["number"]}), migrations.AddField( - model_name='forum', - name='number', - field=models.IntegerField(verbose_name='number to choose a specific forum ordering', default=1), + model_name="forum", + name="number", + field=models.IntegerField( + verbose_name="number to choose a specific forum ordering", default=1 + ), ), migrations.AlterField( - model_name='forum', - name='edit_groups', - field=models.ManyToManyField(related_name='editable_forums', blank=True, to='core.Group', default=[331]), + model_name="forum", + name="edit_groups", + field=models.ManyToManyField( + related_name="editable_forums", + blank=True, + to="core.Group", + default=[331], + ), ), ] diff --git a/forum/migrations/0003_auto_20170510_1754.py b/forum/migrations/0003_auto_20170510_1754.py index 6b2c26f1..db175969 100644 --- a/forum/migrations/0003_auto_20170510_1754.py +++ b/forum/migrations/0003_auto_20170510_1754.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('forum', '0002_auto_20170312_1753'), - ] + dependencies = [("forum", "0002_auto_20170312_1753")] operations = [ migrations.AlterField( - model_name='forum', - name='edit_groups', - field=models.ManyToManyField(blank=True, default=[4], related_name='editable_forums', to='core.Group'), - ), + model_name="forum", + name="edit_groups", + field=models.ManyToManyField( + blank=True, default=[4], related_name="editable_forums", to="core.Group" + ), + ) ] diff --git a/forum/migrations/0004_auto_20170531_1949.py b/forum/migrations/0004_auto_20170531_1949.py index 0ae64560..d6c4a660 100644 --- a/forum/migrations/0004_auto_20170531_1949.py +++ b/forum/migrations/0004_auto_20170531_1949.py @@ -7,57 +7,67 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('forum', '0003_auto_20170510_1754'), - ] + dependencies = [("forum", "0003_auto_20170510_1754")] operations = [ migrations.AlterModelOptions( - name='forummessage', - options={'ordering': ['-date']}, + name="forummessage", options={"ordering": ["-date"]} ), migrations.AlterModelOptions( - name='forumtopic', - options={'ordering': ['-_last_message__date']}, + name="forumtopic", options={"ordering": ["-_last_message__date"]} ), migrations.AddField( - model_name='forum', - name='_last_message', - field=models.ForeignKey(verbose_name='the last message', to='forum.ForumMessage', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='forums_where_its_last'), + model_name="forum", + name="_last_message", + field=models.ForeignKey( + verbose_name="the last message", + to="forum.ForumMessage", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="forums_where_its_last", + ), ), migrations.AddField( - model_name='forum', - name='_topic_number', - field=models.IntegerField(default=0, verbose_name='number of topics'), + model_name="forum", + name="_topic_number", + field=models.IntegerField(default=0, verbose_name="number of topics"), ), migrations.AddField( - model_name='forummessage', - name='_deleted', - field=models.BooleanField(default=False, verbose_name='is deleted'), + model_name="forummessage", + name="_deleted", + field=models.BooleanField(default=False, verbose_name="is deleted"), ), migrations.AddField( - model_name='forumtopic', - name='_last_message', - field=models.ForeignKey(verbose_name='the last message', to='forum.ForumMessage', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+'), + model_name="forumtopic", + name="_last_message", + field=models.ForeignKey( + verbose_name="the last message", + to="forum.ForumMessage", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="+", + ), ), migrations.AddField( - model_name='forumtopic', - name='_message_number', - field=models.IntegerField(default=0, verbose_name='number of messages'), + model_name="forumtopic", + name="_message_number", + field=models.IntegerField(default=0, verbose_name="number of messages"), ), migrations.AddField( - model_name='forumtopic', - name='_title', - field=models.CharField(max_length=64, blank=True, verbose_name='title'), + model_name="forumtopic", + name="_title", + field=models.CharField(max_length=64, blank=True, verbose_name="title"), ), migrations.AlterField( - model_name='forum', - name='description', - field=models.CharField(max_length=512, default='', verbose_name='description'), + model_name="forum", + name="description", + field=models.CharField( + max_length=512, default="", verbose_name="description" + ), ), migrations.AlterField( - model_name='forum', - name='id', + model_name="forum", + name="id", field=models.AutoField(primary_key=True, serialize=False, db_index=True), ), ] diff --git a/forum/migrations/0005_forumtopic_subscribed_users.py b/forum/migrations/0005_forumtopic_subscribed_users.py index f82f3836..9e6e83c7 100644 --- a/forum/migrations/0005_forumtopic_subscribed_users.py +++ b/forum/migrations/0005_forumtopic_subscribed_users.py @@ -9,13 +9,17 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('forum', '0004_auto_20170531_1949'), + ("forum", "0004_auto_20170531_1949"), ] operations = [ migrations.AddField( - model_name='forumtopic', - name='subscribed_users', - field=models.ManyToManyField(verbose_name='subscribed users', related_name='favorite_topics', to=settings.AUTH_USER_MODEL), - ), + model_name="forumtopic", + name="subscribed_users", + field=models.ManyToManyField( + verbose_name="subscribed users", + related_name="favorite_topics", + to=settings.AUTH_USER_MODEL, + ), + ) ] diff --git a/forum/migrations/0006_auto_20180426_2013.py b/forum/migrations/0006_auto_20180426_2013.py index 92c53d9f..08ceed56 100644 --- a/forum/migrations/0006_auto_20180426_2013.py +++ b/forum/migrations/0006_auto_20180426_2013.py @@ -7,19 +7,27 @@ import forum.models class Migration(migrations.Migration): - dependencies = [ - ('forum', '0005_forumtopic_subscribed_users'), - ] + dependencies = [("forum", "0005_forumtopic_subscribed_users")] operations = [ migrations.AlterField( - model_name='forum', - name='edit_groups', - field=models.ManyToManyField(blank=True, default=forum.models.Forum.get_default_edit_group, related_name='editable_forums', to='core.Group'), + model_name="forum", + name="edit_groups", + field=models.ManyToManyField( + blank=True, + default=forum.models.Forum.get_default_edit_group, + related_name="editable_forums", + to="core.Group", + ), ), migrations.AlterField( - model_name='forum', - name='view_groups', - field=models.ManyToManyField(blank=True, default=forum.models.Forum.get_default_view_group, related_name='viewable_forums', to='core.Group'), + model_name="forum", + name="view_groups", + field=models.ManyToManyField( + blank=True, + default=forum.models.Forum.get_default_view_group, + related_name="viewable_forums", + to="core.Group", + ), ), ] diff --git a/forum/models.py b/forum/models.py index 7f04779c..523b1169 100644 --- a/forum/models.py +++ b/forum/models.py @@ -38,7 +38,6 @@ from core.models import User, Group from club.models import Club - class Forum(models.Model): """ The Forum class, made as a tree to allow nice tidy organization @@ -47,27 +46,51 @@ class Forum(models.Model): edit_groups allows to put any group as a forum admin view_groups allows some groups to view a forum """ + # Those functions prevent generating migration upon settings changes - def get_default_edit_group(): return [settings.SITH_GROUP_OLD_SUBSCRIBERS_ID] - def get_default_view_group(): return [settings.SITH_GROUP_PUBLIC_ID] + def get_default_edit_group(): + return [settings.SITH_GROUP_OLD_SUBSCRIBERS_ID] + + def get_default_view_group(): + return [settings.SITH_GROUP_PUBLIC_ID] + id = models.AutoField(primary_key=True, db_index=True) - name = models.CharField(_('name'), max_length=64) - description = models.CharField(_('description'), max_length=512, default="") - is_category = models.BooleanField(_('is a category'), default=False) - parent = models.ForeignKey('Forum', related_name='children', null=True, blank=True) - owner_club = models.ForeignKey(Club, related_name="owned_forums", verbose_name=_("owner club"), - default=settings.SITH_MAIN_CLUB_ID) - edit_groups = models.ManyToManyField(Group, related_name="editable_forums", blank=True, - default=get_default_edit_group) - view_groups = models.ManyToManyField(Group, related_name="viewable_forums", blank=True, - default=get_default_view_group) - number = models.IntegerField(_("number to choose a specific forum ordering"), default=1) - _last_message = models.ForeignKey('ForumMessage', related_name="forums_where_its_last", - verbose_name=_("the last message"), null=True, on_delete=models.SET_NULL) + name = models.CharField(_("name"), max_length=64) + description = models.CharField(_("description"), max_length=512, default="") + is_category = models.BooleanField(_("is a category"), default=False) + parent = models.ForeignKey("Forum", related_name="children", null=True, blank=True) + owner_club = models.ForeignKey( + Club, + related_name="owned_forums", + verbose_name=_("owner club"), + default=settings.SITH_MAIN_CLUB_ID, + ) + edit_groups = models.ManyToManyField( + Group, + related_name="editable_forums", + blank=True, + default=get_default_edit_group, + ) + view_groups = models.ManyToManyField( + Group, + related_name="viewable_forums", + blank=True, + default=get_default_view_group, + ) + number = models.IntegerField( + _("number to choose a specific forum ordering"), default=1 + ) + _last_message = models.ForeignKey( + "ForumMessage", + related_name="forums_where_its_last", + verbose_name=_("the last message"), + null=True, + on_delete=models.SET_NULL, + ) _topic_number = models.IntegerField(_("number of topics"), default=0) class Meta: - ordering = ['number'] + ordering = ["number"] def clean(self): self.check_loop() @@ -87,8 +110,18 @@ class Forum(models.Model): self.parent.set_topic_number() def set_last_message(self): - topic = ForumTopic.objects.filter(forum__id=self.id).exclude(_last_message=None).order_by('-_last_message__id').first() - forum = Forum.objects.filter(parent__id=self.id).exclude(_last_message=None).order_by('-_last_message__id').first() + topic = ( + ForumTopic.objects.filter(forum__id=self.id) + .exclude(_last_message=None) + .order_by("-_last_message__id") + .first() + ) + forum = ( + Forum.objects.filter(parent__id=self.id) + .exclude(_last_message=None) + .order_by("-_last_message__id") + .first() + ) if topic and forum: if topic._last_message_id < forum._last_message_id: self._last_message_id = forum._last_message_id @@ -117,8 +150,8 @@ class Forum(models.Model): self.save() _club_memberships = {} # This cache is particularly efficient: - # divided by 3 the number of requests on the main forum page - # after the first load + # divided by 3 the number of requests on the main forum page + # after the first load def is_owned_by(self, user): if user.is_in_group(settings.SITH_GROUP_FORUM_ADMIN_ID): return True @@ -141,7 +174,7 @@ class Forum(models.Model): cur = self while cur.parent is not None: if cur in objs: - raise ValidationError(_('You can not make loops in forums')) + raise ValidationError(_("You can not make loops in forums")) objs.append(cur) cur = cur.parent @@ -149,10 +182,14 @@ class Forum(models.Model): return "%s" % (self.name) def get_full_name(self): - return '/'.join(chain.from_iterable([[parent.name for parent in self.get_parent_list()], [self.name]])) + return "/".join( + chain.from_iterable( + [[parent.name for parent in self.get_parent_list()], [self.name]] + ) + ) def get_absolute_url(self): - return reverse('forum:view_forum', kwargs={'forum_id': self.id}) + return reverse("forum:view_forum", kwargs={"forum_id": self.id}) @cached_property def parent_list(self): @@ -189,17 +226,24 @@ class Forum(models.Model): class ForumTopic(models.Model): - forum = models.ForeignKey(Forum, related_name='topics') - author = models.ForeignKey(User, related_name='forum_topics') - description = models.CharField(_('description'), max_length=256, default="") - subscribed_users = models.ManyToManyField(User, related_name='favorite_topics', verbose_name=_("subscribed users")) - _last_message = models.ForeignKey('ForumMessage', related_name="+", verbose_name=_("the last message"), - null=True, on_delete=models.SET_NULL) - _title = models.CharField(_('title'), max_length=64, blank=True) + forum = models.ForeignKey(Forum, related_name="topics") + author = models.ForeignKey(User, related_name="forum_topics") + description = models.CharField(_("description"), max_length=256, default="") + subscribed_users = models.ManyToManyField( + User, related_name="favorite_topics", verbose_name=_("subscribed users") + ) + _last_message = models.ForeignKey( + "ForumMessage", + related_name="+", + verbose_name=_("the last message"), + null=True, + on_delete=models.SET_NULL, + ) + _title = models.CharField(_("title"), max_length=64, blank=True) _message_number = models.IntegerField(_("number of messages"), default=0) class Meta: - ordering = ['-_last_message__date'] + ordering = ["-_last_message__date"] def save(self, *args, **kwargs): super(ForumTopic, self).save(*args, **kwargs) @@ -219,11 +263,16 @@ class ForumTopic(models.Model): return "%s" % (self.title) def get_absolute_url(self): - return reverse('forum:view_topic', kwargs={'topic_id': self.id}) + return reverse("forum:view_topic", kwargs={"topic_id": self.id}) def get_first_unread_message(self, user): try: - msg = self.messages.exclude(readers=user).filter(date__gte=user.forum_infos.last_read_date).order_by('id').first() + msg = ( + self.messages.exclude(readers=user) + .filter(date__gte=user.forum_infos.last_read_date) + .order_by("id") + .first() + ) return msg except: return None @@ -241,16 +290,19 @@ class ForumMessage(models.Model): """ "A ForumMessage object represents a message in the forum" -- Cpt. Obvious """ - topic = models.ForeignKey(ForumTopic, related_name='messages') - author = models.ForeignKey(User, related_name='forum_messages') + + topic = models.ForeignKey(ForumTopic, related_name="messages") + author = models.ForeignKey(User, related_name="forum_messages") title = models.CharField(_("title"), default="", max_length=64, blank=True) message = models.TextField(_("message"), default="") - date = models.DateTimeField(_('date'), default=timezone.now) - readers = models.ManyToManyField(User, related_name="read_messages", verbose_name=_("readers")) - _deleted = models.BooleanField(_('is deleted'), default=False) + date = models.DateTimeField(_("date"), default=timezone.now) + readers = models.ManyToManyField( + User, related_name="read_messages", verbose_name=_("readers") + ) + _deleted = models.BooleanField(_("is deleted"), default=False) class Meta: - ordering = ['-date'] + ordering = ["-date"] def __str__(self): return "%s (%s) - %s" % (self.id, self.author, self.title) @@ -266,32 +318,46 @@ class ForumMessage(models.Model): self.topic.save() def is_first_in_topic(self): - return bool(self.id == self.topic.messages.order_by('date').first().id) + return bool(self.id == self.topic.messages.order_by("date").first().id) def is_last_in_topic(self): - return bool(self.id == self.topic.messages.order_by('date').last().id) + return bool(self.id == self.topic.messages.order_by("date").last().id) def is_owned_by(self, user): # Anyone can create a topic: it's better to - # check the rights at the forum level, since it's more controlled + # check the rights at the forum level, since it's more controlled return self.topic.forum.is_owned_by(user) or user.id == self.author.id def can_be_edited_by(self, user): return user.can_edit(self.topic.forum) def can_be_viewed_by(self, user): - return not self._deleted # No need to check the real rights since it's already done by the Topic view + return ( + not self._deleted + ) # No need to check the real rights since it's already done by the Topic view def can_be_moderated_by(self, user): return self.topic.forum.is_owned_by(user) or user.id == self.author.id def get_absolute_url(self): - return reverse('forum:view_message', kwargs={'message_id': self.id}) + return reverse("forum:view_message", kwargs={"message_id": self.id}) def get_url(self): - return self.topic.get_absolute_url() + "?page=" + str(self.get_page()) + "#msg_" + str(self.id) + return ( + self.topic.get_absolute_url() + + "?page=" + + str(self.get_page()) + + "#msg_" + + str(self.id) + ) def get_page(self): - return int(self.topic.messages.filter(id__lt=self.id).count() / settings.SITH_FORUM_PAGE_LENGTH) + 1 + return ( + int( + self.topic.messages.filter(id__lt=self.id).count() + / settings.SITH_FORUM_PAGE_LENGTH + ) + + 1 + ) def mark_as_read(self, user): try: # Need the try/except because of AnonymousUser @@ -301,26 +367,28 @@ class ForumMessage(models.Model): pass def is_read(self, user): - return (self.date < user.forum_infos.last_read_date) or (user in self.readers.all()) + return (self.date < user.forum_infos.last_read_date) or ( + user in self.readers.all() + ) def is_deleted(self): - meta = self.metas.exclude(action="EDIT").order_by('-date').first() + meta = self.metas.exclude(action="EDIT").order_by("-date").first() if meta: return meta.action == "DELETE" return False MESSAGE_META_ACTIONS = [ - ('EDIT', _("Message edited by")), - ('DELETE', _("Message deleted by")), - ('UNDELETE', _("Message undeleted by")), + ("EDIT", _("Message edited by")), + ("DELETE", _("Message deleted by")), + ("UNDELETE", _("Message undeleted by")), ] class ForumMessageMeta(models.Model): user = models.ForeignKey(User, related_name="forum_message_metas") message = models.ForeignKey(ForumMessage, related_name="metas") - date = models.DateTimeField(_('date'), default=timezone.now) + date = models.DateTimeField(_("date"), default=timezone.now) action = models.CharField(_("action"), choices=MESSAGE_META_ACTIONS, max_length=16) def save(self, *args, **kwargs): @@ -335,9 +403,14 @@ class ForumUserInfo(models.Model): However, this can be extended with lot of user preferences dedicated to a user, such as the favourite topics, the signature, and so on... """ + user = models.OneToOneField(User, related_name="_forum_infos") - last_read_date = models.DateTimeField(_('last read date'), default=datetime(year=settings.SITH_SCHOOL_START_YEAR, - month=1, day=1, tzinfo=pytz.UTC)) + last_read_date = models.DateTimeField( + _("last read date"), + default=datetime( + year=settings.SITH_SCHOOL_START_YEAR, month=1, day=1, tzinfo=pytz.UTC + ), + ) def __str__(self): return str(self.user) diff --git a/forum/urls.py b/forum/urls.py index 26558cf9..27ff5718 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -27,22 +27,59 @@ from django.conf.urls import url from forum.views import * urlpatterns = [ - url(r'^$', ForumMainView.as_view(), name='main'), - url(r'^new_forum$', ForumCreateView.as_view(), name='new_forum'), - url(r'^mark_all_as_read$', ForumMarkAllAsRead.as_view(), name='mark_all_as_read'), - url(r'^last_unread$', ForumLastUnread.as_view(), name='last_unread'), - url(r'^favorite_topics$', ForumFavoriteTopics.as_view(), name='favorite_topics'), - url(r'^(?P[0-9]+)$', ForumDetailView.as_view(), name='view_forum'), - url(r'^(?P[0-9]+)/edit$', ForumEditView.as_view(), name='edit_forum'), - url(r'^(?P[0-9]+)/delete$', ForumDeleteView.as_view(), name='delete_forum'), - url(r'^(?P[0-9]+)/new_topic$', ForumTopicCreateView.as_view(), name='new_topic'), - url(r'^topic/(?P[0-9]+)$', ForumTopicDetailView.as_view(), name='view_topic'), - url(r'^topic/(?P[0-9]+)/edit$', ForumTopicEditView.as_view(), name='edit_topic'), - url(r'^topic/(?P[0-9]+)/new_message$', ForumMessageCreateView.as_view(), name='new_message'), - url(r'^topic/(?P[0-9]+)/toggle_subscribe$', ForumTopicSubscribeView.as_view(), name='toggle_subscribe_topic'), - url(r'^message/(?P[0-9]+)$', ForumMessageView.as_view(), name='view_message'), - url(r'^message/(?P[0-9]+)/edit$', ForumMessageEditView.as_view(), name='edit_message'), - url(r'^message/(?P[0-9]+)/delete$', ForumMessageDeleteView.as_view(), name='delete_message'), - url(r'^message/(?P[0-9]+)/undelete$', ForumMessageUndeleteView.as_view(), name='undelete_message'), + url(r"^$", ForumMainView.as_view(), name="main"), + url(r"^new_forum$", ForumCreateView.as_view(), name="new_forum"), + url(r"^mark_all_as_read$", ForumMarkAllAsRead.as_view(), name="mark_all_as_read"), + url(r"^last_unread$", ForumLastUnread.as_view(), name="last_unread"), + url(r"^favorite_topics$", ForumFavoriteTopics.as_view(), name="favorite_topics"), + url(r"^(?P[0-9]+)$", ForumDetailView.as_view(), name="view_forum"), + url(r"^(?P[0-9]+)/edit$", ForumEditView.as_view(), name="edit_forum"), + url( + r"^(?P[0-9]+)/delete$", ForumDeleteView.as_view(), name="delete_forum" + ), + url( + r"^(?P[0-9]+)/new_topic$", + ForumTopicCreateView.as_view(), + name="new_topic", + ), + url( + r"^topic/(?P[0-9]+)$", + ForumTopicDetailView.as_view(), + name="view_topic", + ), + url( + r"^topic/(?P[0-9]+)/edit$", + ForumTopicEditView.as_view(), + name="edit_topic", + ), + url( + r"^topic/(?P[0-9]+)/new_message$", + ForumMessageCreateView.as_view(), + name="new_message", + ), + url( + r"^topic/(?P[0-9]+)/toggle_subscribe$", + ForumTopicSubscribeView.as_view(), + name="toggle_subscribe_topic", + ), + url( + r"^message/(?P[0-9]+)$", + ForumMessageView.as_view(), + name="view_message", + ), + url( + r"^message/(?P[0-9]+)/edit$", + ForumMessageEditView.as_view(), + name="edit_message", + ), + url( + r"^message/(?P[0-9]+)/delete$", + ForumMessageDeleteView.as_view(), + name="delete_message", + ), + url( + r"^message/(?P[0-9]+)/undelete$", + ForumMessageUndeleteView.as_view(), + name="undelete_message", + ), ] - diff --git a/forum/views.py b/forum/views.py index 73f8423c..946d563c 100644 --- a/forum/views.py +++ b/forum/views.py @@ -42,13 +42,15 @@ from forum.models import Forum, ForumMessage, ForumTopic, ForumMessageMeta class ForumMainView(ListView): - queryset = Forum.objects.filter(parent=None).prefetch_related("children___last_message__author", "children___last_message__topic") + queryset = Forum.objects.filter(parent=None).prefetch_related( + "children___last_message__author", "children___last_message__topic" + ) template_name = "forum/main.jinja" class ForumMarkAllAsRead(RedirectView): permanent = False - url = reverse_lazy('forum:last_unread') + url = reverse_lazy("forum:last_unread") def get(self, request, *args, **kwargs): try: @@ -78,11 +80,15 @@ class ForumLastUnread(ListView): paginate_by = settings.SITH_FORUM_PAGE_LENGTH / 2 def get_queryset(self): - topic_list = self.model.objects.filter(_last_message__date__gt=self.request.user.forum_infos.last_read_date)\ - .exclude(_last_message__readers=self.request.user)\ - .order_by('-_last_message__date')\ - .select_related('_last_message__author', 'author')\ - .prefetch_related('forum__edit_groups') + topic_list = ( + self.model.objects.filter( + _last_message__date__gt=self.request.user.forum_infos.last_read_date + ) + .exclude(_last_message__readers=self.request.user) + .order_by("-_last_message__date") + .select_related("_last_message__author", "author") + .prefetch_related("forum__edit_groups") + ) return topic_list @@ -94,9 +100,18 @@ class ForumNameField(forms.ModelChoiceField): class ForumForm(forms.ModelForm): class Meta: model = Forum - fields = ['name', 'parent', 'number', 'owner_club', 'is_category', 'edit_groups', 'view_groups'] - edit_groups = make_ajax_field(Forum, 'edit_groups', 'groups', help_text="") - view_groups = make_ajax_field(Forum, 'view_groups', 'groups', help_text="") + fields = [ + "name", + "parent", + "number", + "owner_club", + "is_category", + "edit_groups", + "view_groups", + ] + + edit_groups = make_ajax_field(Forum, "edit_groups", "groups", help_text="") + view_groups = make_ajax_field(Forum, "view_groups", "groups", help_text="") parent = ForumNameField(Forum.objects.all()) @@ -108,18 +123,20 @@ class ForumCreateView(CanCreateMixin, CreateView): def get_initial(self): init = super(ForumCreateView, self).get_initial() try: - parent = Forum.objects.filter(id=self.request.GET['parent']).first() - init['parent'] = parent - init['owner_club'] = parent.owner_club - init['edit_groups'] = parent.edit_groups.all() - init['view_groups'] = parent.view_groups.all() + parent = Forum.objects.filter(id=self.request.GET["parent"]).first() + init["parent"] = parent + init["owner_club"] = parent.owner_club + init["edit_groups"] = parent.edit_groups.all() + init["view_groups"] = parent.view_groups.all() except: pass return init class ForumEditForm(ForumForm): - recursive = forms.BooleanField(label=_("Apply rights and club owner recursively"), required=False) + recursive = forms.BooleanField( + label=_("Apply rights and club owner recursively"), required=False + ) class ForumEditView(CanEditPropMixin, UpdateView): @@ -127,11 +144,11 @@ class ForumEditView(CanEditPropMixin, UpdateView): pk_url_kwarg = "forum_id" form_class = ForumEditForm template_name = "core/edit.jinja" - success_url = reverse_lazy('forum:main') + success_url = reverse_lazy("forum:main") def form_valid(self, form): ret = super(ForumEditView, self).form_valid(form) - if form.cleaned_data['recursive']: + if form.cleaned_data["recursive"]: self.object.apply_rights_recursively() return ret @@ -140,7 +157,7 @@ class ForumDeleteView(CanEditPropMixin, DeleteView): model = Forum pk_url_kwarg = "forum_id" template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy('forum:main') + success_url = reverse_lazy("forum:main") class ForumDetailView(CanViewMixin, DetailView): @@ -150,12 +167,13 @@ class ForumDetailView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): kwargs = super(ForumDetailView, self).get_context_data(**kwargs) - qs = self.object.topics.order_by('-_last_message__date')\ - .select_related('_last_message__author', 'author')\ + qs = ( + self.object.topics.order_by("-_last_message__date") + .select_related("_last_message__author", "author") .prefetch_related("forum__edit_groups") - paginator = Paginator(qs, - settings.SITH_FORUM_PAGE_LENGTH) - page = self.request.GET.get('topic_page') + ) + paginator = Paginator(qs, settings.SITH_FORUM_PAGE_LENGTH) + page = self.request.GET.get("topic_page") try: kwargs["topics"] = paginator.page(page) except PageNotAnInteger: @@ -168,10 +186,9 @@ class ForumDetailView(CanViewMixin, DetailView): class TopicForm(forms.ModelForm): class Meta: model = ForumMessage - fields = ['title', 'message'] - widgets = { - 'message': MarkdownInput, - } + fields = ["title", "message"] + widgets = {"message": MarkdownInput} + title = forms.CharField(required=True, label=_("Title")) @@ -181,13 +198,17 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): template_name = "forum/reply.jinja" def dispatch(self, request, *args, **kwargs): - self.forum = get_object_or_404(Forum, id=self.kwargs['forum_id'], is_category=False) + self.forum = get_object_or_404( + Forum, id=self.kwargs["forum_id"], is_category=False + ) if not request.user.can_view(self.forum): raise PermissionDenied return super(ForumTopicCreateView, self).dispatch(request, *args, **kwargs) def form_valid(self, form): - topic = ForumTopic(_title=form.instance.title, author=self.request.user, forum=self.forum) + topic = ForumTopic( + _title=form.instance.title, author=self.request.user, forum=self.forum + ) topic.save() form.instance.topic = topic form.instance.author = self.request.user @@ -196,10 +217,11 @@ class ForumTopicCreateView(CanCreateMixin, CreateView): class ForumTopicEditView(CanEditMixin, UpdateView): model = ForumTopic - fields = ['forum'] + fields = ["forum"] pk_url_kwarg = "topic_id" template_name = "core/edit.jinja" + class ForumTopicSubscribeView(CanViewMixin, SingleObjectMixin, RedirectView): model = ForumTopic pk_url_kwarg = "topic_id" @@ -222,19 +244,22 @@ class ForumTopicDetailView(CanViewMixin, DetailView): pk_url_kwarg = "topic_id" template_name = "forum/topic.jinja" context_object_name = "topic" - queryset = ForumTopic.objects.select_related('forum__parent') + queryset = ForumTopic.objects.select_related("forum__parent") def get_context_data(self, **kwargs): kwargs = super(ForumTopicDetailView, self).get_context_data(**kwargs) try: msg = self.object.get_first_unread_message(self.request.user) - kwargs['first_unread_message_id'] = msg.id + kwargs["first_unread_message_id"] = msg.id except: - kwargs['first_unread_message_id'] = float("inf") - paginator = Paginator(self.object.messages.select_related('author__avatar_pict') - .prefetch_related('topic__forum__edit_groups', 'readers').order_by('date'), - settings.SITH_FORUM_PAGE_LENGTH) - page = self.request.GET.get('page') + kwargs["first_unread_message_id"] = float("inf") + paginator = Paginator( + self.object.messages.select_related("author__avatar_pict") + .prefetch_related("topic__forum__edit_groups", "readers") + .order_by("date"), + settings.SITH_FORUM_PAGE_LENGTH, + ) + page = self.request.GET.get("page") try: kwargs["msgs"] = paginator.page(page) except PageNotAnInteger: @@ -256,17 +281,23 @@ class ForumMessageView(SingleObjectMixin, RedirectView): class ForumMessageEditView(CanEditMixin, UpdateView): model = ForumMessage - form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message', ], widgets={'message': MarkdownInput}) + form_class = forms.modelform_factory( + model=ForumMessage, + fields=["title", "message"], + widgets={"message": MarkdownInput}, + ) template_name = "forum/reply.jinja" pk_url_kwarg = "message_id" def form_valid(self, form): - ForumMessageMeta(message=self.object, user=self.request.user, action="EDIT").save() + ForumMessageMeta( + message=self.object, user=self.request.user, action="EDIT" + ).save() return super(ForumMessageEditView, self).form_valid(form) def get_context_data(self, **kwargs): kwargs = super(ForumMessageEditView, self).get_context_data(**kwargs) - kwargs['topic'] = self.object.topic + kwargs["topic"] = self.object.topic return kwargs @@ -278,7 +309,9 @@ class ForumMessageDeleteView(SingleObjectMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() if self.object.can_be_moderated_by(self.request.user): - ForumMessageMeta(message=self.object, user=self.request.user, action="DELETE").save() + ForumMessageMeta( + message=self.object, user=self.request.user, action="DELETE" + ).save() return self.object.get_absolute_url() @@ -290,17 +323,23 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): self.object = self.get_object() if self.object.can_be_moderated_by(self.request.user): - ForumMessageMeta(message=self.object, user=self.request.user, action="UNDELETE").save() + ForumMessageMeta( + message=self.object, user=self.request.user, action="UNDELETE" + ).save() return self.object.get_absolute_url() class ForumMessageCreateView(CanCreateMixin, CreateView): model = ForumMessage - form_class = forms.modelform_factory(model=ForumMessage, fields=['title', 'message', ], widgets={'message': MarkdownInput}) + form_class = forms.modelform_factory( + model=ForumMessage, + fields=["title", "message"], + widgets={"message": MarkdownInput}, + ) template_name = "forum/reply.jinja" def dispatch(self, request, *args, **kwargs): - self.topic = get_object_or_404(ForumTopic, id=self.kwargs['topic_id']) + self.topic = get_object_or_404(ForumTopic, id=self.kwargs["topic_id"]) if not request.user.can_view(self.topic): raise PermissionDenied return super(ForumMessageCreateView, self).dispatch(request, *args, **kwargs) @@ -308,12 +347,18 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): def get_initial(self): init = super(ForumMessageCreateView, self).get_initial() try: - message = ForumMessage.objects.select_related('author').filter(id=self.request.GET['quote_id']).first() - init['message'] = "> ##### %s\n" % (_("%(author)s said") % {'author': message.author.get_short_name()}) - init['message'] += "\n".join([ - "> " + line for line in message.message.split('\n') - ]) - init['message'] += "\n\n" + message = ( + ForumMessage.objects.select_related("author") + .filter(id=self.request.GET["quote_id"]) + .first() + ) + init["message"] = "> ##### %s\n" % ( + _("%(author)s said") % {"author": message.author.get_short_name()} + ) + init["message"] += "\n".join( + ["> " + line for line in message.message.split("\n")] + ) + init["message"] += "\n\n" except Exception as e: print(repr(e)) return init @@ -325,5 +370,5 @@ class ForumMessageCreateView(CanCreateMixin, CreateView): def get_context_data(self, **kwargs): kwargs = super(ForumMessageCreateView, self).get_context_data(**kwargs) - kwargs['topic'] = self.topic + kwargs["topic"] = self.topic return kwargs diff --git a/launderette/__init__.py b/launderette/__init__.py index 0a9419f8..0ace29c4 100644 --- a/launderette/__init__.py +++ b/launderette/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/launderette/migrations/0001_initial.py b/launderette/migrations/0001_initial.py index dc441a4b..c437d29d 100644 --- a/launderette/migrations/0001_initial.py +++ b/launderette/migrations/0001_initial.py @@ -6,76 +6,168 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0001_initial'), - ('counter', '0001_initial'), - ] + dependencies = [("subscription", "0001_initial"), ("counter", "0001_initial")] operations = [ migrations.CreateModel( - name='Launderette', + name="Launderette", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('counter', models.OneToOneField(related_name='launderette', verbose_name='counter', to='counter.Counter')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "counter", + models.OneToOneField( + related_name="launderette", + verbose_name="counter", + to="counter.Counter", + ), + ), ], - options={ - 'verbose_name': 'Launderette', - }, + options={"verbose_name": "Launderette"}, ), migrations.CreateModel( - name='Machine', + name="Machine", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=30, verbose_name='name')), - ('type', models.CharField(choices=[('WASHING', 'Washing'), ('DRYING', 'Drying')], max_length=10, verbose_name='type')), - ('is_working', models.BooleanField(verbose_name='is working', default=True)), - ('launderette', models.ForeignKey(verbose_name='launderette', to='launderette.Launderette', related_name='machines')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=30, verbose_name="name")), + ( + "type", + models.CharField( + choices=[("WASHING", "Washing"), ("DRYING", "Drying")], + max_length=10, + verbose_name="type", + ), + ), + ( + "is_working", + models.BooleanField(verbose_name="is working", default=True), + ), + ( + "launderette", + models.ForeignKey( + verbose_name="launderette", + to="launderette.Launderette", + related_name="machines", + ), + ), ], - options={ - 'verbose_name': 'Machine', - }, + options={"verbose_name": "Machine"}, ), migrations.CreateModel( - name='Slot', + name="Slot", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('start_date', models.DateTimeField(verbose_name='start date')), - ('type', models.CharField(choices=[('WASHING', 'Washing'), ('DRYING', 'Drying')], max_length=10, verbose_name='type')), - ('machine', models.ForeignKey(verbose_name='machine', to='launderette.Machine', related_name='slots')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("start_date", models.DateTimeField(verbose_name="start date")), + ( + "type", + models.CharField( + choices=[("WASHING", "Washing"), ("DRYING", "Drying")], + max_length=10, + verbose_name="type", + ), + ), + ( + "machine", + models.ForeignKey( + verbose_name="machine", + to="launderette.Machine", + related_name="slots", + ), + ), ], - options={ - 'verbose_name': 'Slot', - 'ordering': ['start_date'], - }, + options={"verbose_name": "Slot", "ordering": ["start_date"]}, ), migrations.CreateModel( - name='Token', + name="Token", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('name', models.CharField(max_length=5, verbose_name='name')), - ('type', models.CharField(choices=[('WASHING', 'Washing'), ('DRYING', 'Drying')], max_length=10, verbose_name='type')), - ('borrow_date', models.DateTimeField(null=True, verbose_name='borrow date', blank=True)), - ('launderette', models.ForeignKey(verbose_name='launderette', to='launderette.Launderette', related_name='tokens')), - ('user', models.ForeignKey(null=True, related_name='tokens', verbose_name='user', to='core.User', blank=True)), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ("name", models.CharField(max_length=5, verbose_name="name")), + ( + "type", + models.CharField( + choices=[("WASHING", "Washing"), ("DRYING", "Drying")], + max_length=10, + verbose_name="type", + ), + ), + ( + "borrow_date", + models.DateTimeField( + null=True, verbose_name="borrow date", blank=True + ), + ), + ( + "launderette", + models.ForeignKey( + verbose_name="launderette", + to="launderette.Launderette", + related_name="tokens", + ), + ), + ( + "user", + models.ForeignKey( + null=True, + related_name="tokens", + verbose_name="user", + to="core.User", + blank=True, + ), + ), ], - options={ - 'verbose_name': 'Token', - 'ordering': ['type', 'name'], - }, + options={"verbose_name": "Token", "ordering": ["type", "name"]}, ), migrations.AddField( - model_name='slot', - name='token', - field=models.ForeignKey(null=True, related_name='slots', verbose_name='token', to='launderette.Token', blank=True), + model_name="slot", + name="token", + field=models.ForeignKey( + null=True, + related_name="slots", + verbose_name="token", + to="launderette.Token", + blank=True, + ), ), migrations.AddField( - model_name='slot', - name='user', - field=models.ForeignKey(verbose_name='user', to='core.User', related_name='slots'), + model_name="slot", + name="user", + field=models.ForeignKey( + verbose_name="user", to="core.User", related_name="slots" + ), ), migrations.AlterUniqueTogether( - name='token', - unique_together=set([('name', 'launderette', 'type')]), + name="token", unique_together=set([("name", "launderette", "type")]) ), ] diff --git a/launderette/models.py b/launderette/models.py index 9528db70..0c92c8d7 100644 --- a/launderette/models.py +++ b/launderette/models.py @@ -35,24 +35,30 @@ from club.models import Club class Launderette(models.Model): - name = models.CharField(_('name'), max_length=30) - counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='launderette') + name = models.CharField(_("name"), max_length=30) + counter = models.OneToOneField( + Counter, verbose_name=_("counter"), related_name="launderette" + ) class Meta: - verbose_name = _('Launderette') + verbose_name = _("Launderette") def is_owned_by(self, user): """ Method to see if that object can be edited by the given user """ - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 9: return True return False def can_be_edited_by(self, user): - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 2: return True @@ -65,7 +71,7 @@ class Launderette(models.Model): return self.name def get_absolute_url(self): - return reverse('launderette:launderette_list') + return reverse("launderette:launderette_list") def get_machine_list(self): return Machine.objects.filter(launderette_id=self.id) @@ -81,19 +87,25 @@ class Launderette(models.Model): class Machine(models.Model): - name = models.CharField(_('name'), max_length=30) - launderette = models.ForeignKey(Launderette, related_name='machines', verbose_name=_('launderette')) - type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) - is_working = models.BooleanField(_('is working'), default=True) + name = models.CharField(_("name"), max_length=30) + launderette = models.ForeignKey( + Launderette, related_name="machines", verbose_name=_("launderette") + ) + type = models.CharField( + _("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES + ) + is_working = models.BooleanField(_("is working"), default=True) class Meta: - verbose_name = _('Machine') + verbose_name = _("Machine") def is_owned_by(self, user): """ Method to see if that object can be edited by the given user """ - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 9: return True @@ -103,20 +115,29 @@ class Machine(models.Model): return "%s %s" % (self._meta.verbose_name, self.name) def get_absolute_url(self): - return reverse('launderette:launderette_admin', kwargs={"launderette_id": self.launderette.id}) + return reverse( + "launderette:launderette_admin", + kwargs={"launderette_id": self.launderette.id}, + ) class Token(models.Model): - name = models.CharField(_('name'), max_length=5) - launderette = models.ForeignKey(Launderette, related_name='tokens', verbose_name=_('launderette')) - type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) - borrow_date = models.DateTimeField(_('borrow date'), null=True, blank=True) - user = models.ForeignKey(User, related_name='tokens', verbose_name=_('user'), null=True, blank=True) + name = models.CharField(_("name"), max_length=5) + launderette = models.ForeignKey( + Launderette, related_name="tokens", verbose_name=_("launderette") + ) + type = models.CharField( + _("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES + ) + borrow_date = models.DateTimeField(_("borrow date"), null=True, blank=True) + user = models.ForeignKey( + User, related_name="tokens", verbose_name=_("user"), null=True, blank=True + ) class Meta: - verbose_name = _('Token') - unique_together = ('name', 'launderette', 'type') - ordering = ['type', 'name'] + verbose_name = _("Token") + unique_together = ("name", "launderette", "type") + ordering = ["type", "name"] def save(self, *args, **kwargs): if self.name == "": @@ -128,14 +149,25 @@ class Token(models.Model): """ Method to see if that object can be edited by the given user """ - launderette_club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() + launderette_club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() m = launderette_club.get_membership_for(user) if m and m.role >= 9: return True return False def __str__(self): - return self.__class__._meta.verbose_name + " " + self.get_type_display() + " #" + self.name + " (" + self.launderette.name + ")" + return ( + self.__class__._meta.verbose_name + + " " + + self.get_type_display() + + " #" + + self.name + + " (" + + self.launderette.name + + ")" + ) def is_avaliable(self): if not self.borrow_date and not self.user: @@ -145,19 +177,30 @@ class Token(models.Model): class Slot(models.Model): - start_date = models.DateTimeField(_('start date')) - type = models.CharField(_('type'), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES) - machine = models.ForeignKey(Machine, related_name='slots', verbose_name=_('machine')) - token = models.ForeignKey(Token, related_name='slots', verbose_name=_('token'), blank=True, null=True) - user = models.ForeignKey(User, related_name='slots', verbose_name=_('user')) + start_date = models.DateTimeField(_("start date")) + type = models.CharField( + _("type"), max_length=10, choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES + ) + machine = models.ForeignKey( + Machine, related_name="slots", verbose_name=_("machine") + ) + token = models.ForeignKey( + Token, related_name="slots", verbose_name=_("token"), blank=True, null=True + ) + user = models.ForeignKey(User, related_name="slots", verbose_name=_("user")) class Meta: - verbose_name = _('Slot') - ordering = ['start_date'] + verbose_name = _("Slot") + ordering = ["start_date"] def is_owned_by(self, user): return user == self.user def __str__(self): - return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % (self.user, self.start_date, self.get_type_display(), - self.machine.name, self.token) + return "User: %s - Date: %s - Type: %s - Machine: %s - Token: %s" % ( + self.user, + self.start_date, + self.get_type_display(), + self.machine.name, + self.token, + ) diff --git a/launderette/urls.py b/launderette/urls.py index 844f1385..35baabb1 100644 --- a/launderette/urls.py +++ b/launderette/urls.py @@ -28,20 +28,49 @@ from launderette.views import * urlpatterns = [ # views - url(r'^$', LaunderetteMainView.as_view(), name='launderette_main'), - url(r'^slot/(?P[0-9]+)/delete$', SlotDeleteView.as_view(), name='delete_slot'), - url(r'^book$', LaunderetteBookMainView.as_view(), name='book_main'), - url(r'^book/(?P[0-9]+)$', LaunderetteBookView.as_view(), name='book_slot'), - url(r'^(?P[0-9]+)/click$', LaunderetteMainClickView.as_view(), name='main_click'), - url(r'^(?P[0-9]+)/click/(?P[0-9]+)$', LaunderetteClickView.as_view(), name='click'), - url(r'^admin$', LaunderetteListView.as_view(), name='launderette_list'), - url(r'^admin/(?P[0-9]+)$', LaunderetteAdminView.as_view(), name='launderette_admin'), - url(r'^admin/(?P[0-9]+)/edit$', LaunderetteEditView.as_view(), name='launderette_edit'), - url(r'^admin/new$', LaunderetteCreateView.as_view(), name='launderette_new'), - url(r'^admin/machine/new$', MachineCreateView.as_view(), name='machine_new'), - url(r'^admin/machine/(?P[0-9]+)/edit$', MachineEditView.as_view(), name='machine_edit'), - url(r'^admin/machine/(?P[0-9]+)/delete$', MachineDeleteView.as_view(), name='machine_delete'), + url(r"^$", LaunderetteMainView.as_view(), name="launderette_main"), + url( + r"^slot/(?P[0-9]+)/delete$", + SlotDeleteView.as_view(), + name="delete_slot", + ), + url(r"^book$", LaunderetteBookMainView.as_view(), name="book_main"), + url( + r"^book/(?P[0-9]+)$", + LaunderetteBookView.as_view(), + name="book_slot", + ), + url( + r"^(?P[0-9]+)/click$", + LaunderetteMainClickView.as_view(), + name="main_click", + ), + url( + r"^(?P[0-9]+)/click/(?P[0-9]+)$", + LaunderetteClickView.as_view(), + name="click", + ), + url(r"^admin$", LaunderetteListView.as_view(), name="launderette_list"), + url( + r"^admin/(?P[0-9]+)$", + LaunderetteAdminView.as_view(), + name="launderette_admin", + ), + url( + r"^admin/(?P[0-9]+)/edit$", + LaunderetteEditView.as_view(), + name="launderette_edit", + ), + url(r"^admin/new$", LaunderetteCreateView.as_view(), name="launderette_new"), + url(r"^admin/machine/new$", MachineCreateView.as_view(), name="machine_new"), + url( + r"^admin/machine/(?P[0-9]+)/edit$", + MachineEditView.as_view(), + name="machine_edit", + ), + url( + r"^admin/machine/(?P[0-9]+)/delete$", + MachineDeleteView.as_view(), + name="machine_delete", + ), ] - - - diff --git a/launderette/views.py b/launderette/views.py index da7e6219..0b920ac1 100644 --- a/launderette/views.py +++ b/launderette/views.py @@ -48,26 +48,29 @@ from counter.views import GetUserForm class LaunderetteMainView(TemplateView): """Main presentation view""" - template_name = 'launderette/launderette_main.jinja' + + template_name = "launderette/launderette_main.jinja" def get_context_data(self, **kwargs): """ Add page to the context """ kwargs = super(LaunderetteMainView, self).get_context_data(**kwargs) - kwargs['page'] = Page.objects.filter(name='launderette').first() + kwargs["page"] = Page.objects.filter(name="launderette").first() return kwargs class LaunderetteBookMainView(CanViewMixin, ListView): """Choose which launderette to book""" + model = Launderette - template_name = 'launderette/launderette_book_choose.jinja' + template_name = "launderette/launderette_book_choose.jinja" class LaunderetteBookView(CanViewMixin, DetailView): """Display the launderette schedule""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'launderette/launderette_book.jinja' + template_name = "launderette/launderette_book.jinja" def get(self, request, *args, **kwargs): self.slot_type = "BOTH" @@ -79,23 +82,46 @@ class LaunderetteBookView(CanViewMixin, DetailView): self.machines = {} with transaction.atomic(): self.object = self.get_object() - if 'slot_type' in request.POST.keys(): - self.slot_type = request.POST['slot_type'] - if 'slot' in request.POST.keys() and request.user.is_authenticated(): + if "slot_type" in request.POST.keys(): + self.slot_type = request.POST["slot_type"] + if "slot" in request.POST.keys() and request.user.is_authenticated(): self.subscriber = request.user if self.subscriber.is_subscribed: - self.date = dateparse.parse_datetime(request.POST['slot']).replace(tzinfo=pytz.UTC) + self.date = dateparse.parse_datetime(request.POST["slot"]).replace( + tzinfo=pytz.UTC + ) if self.slot_type == "WASHING": if self.check_slot(self.slot_type): - Slot(user=self.subscriber, start_date=self.date, machine=self.machines[self.slot_type], type=self.slot_type).save() + Slot( + user=self.subscriber, + start_date=self.date, + machine=self.machines[self.slot_type], + type=self.slot_type, + ).save() elif self.slot_type == "DRYING": if self.check_slot(self.slot_type): - Slot(user=self.subscriber, start_date=self.date, machine=self.machines[self.slot_type], type=self.slot_type).save() + Slot( + user=self.subscriber, + start_date=self.date, + machine=self.machines[self.slot_type], + type=self.slot_type, + ).save() else: - if self.check_slot("WASHING") and self.check_slot("DRYING", self.date + timedelta(hours=1)): - Slot(user=self.subscriber, start_date=self.date, machine=self.machines["WASHING"], type="WASHING").save() - Slot(user=self.subscriber, start_date=self.date + timedelta(hours=1), - machine=self.machines["DRYING"], type="DRYING").save() + if self.check_slot("WASHING") and self.check_slot( + "DRYING", self.date + timedelta(hours=1) + ): + Slot( + user=self.subscriber, + start_date=self.date, + machine=self.machines["WASHING"], + type="WASHING", + ).save() + Slot( + user=self.subscriber, + start_date=self.date + timedelta(hours=1), + machine=self.machines["DRYING"], + type="DRYING", + ).save() return super(LaunderetteBookView, self).get(request, *args, **kwargs) def check_slot(self, type, date=None): @@ -118,31 +144,42 @@ class LaunderetteBookView(CanViewMixin, DetailView): def get_context_data(self, **kwargs): """ Add page to the context """ kwargs = super(LaunderetteBookView, self).get_context_data(**kwargs) - kwargs['planning'] = OrderedDict() - kwargs['slot_type'] = self.slot_type - start_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.UTC) - for date in LaunderetteBookView.date_iterator(start_date, start_date + timedelta(days=6), timedelta(days=1)): - kwargs['planning'][date] = [] - for h in LaunderetteBookView.date_iterator(date, date + timedelta(days=1), timedelta(hours=1)): + kwargs["planning"] = OrderedDict() + kwargs["slot_type"] = self.slot_type + start_date = datetime.now().replace( + hour=0, minute=0, second=0, microsecond=0, tzinfo=pytz.UTC + ) + for date in LaunderetteBookView.date_iterator( + start_date, start_date + timedelta(days=6), timedelta(days=1) + ): + kwargs["planning"][date] = [] + for h in LaunderetteBookView.date_iterator( + date, date + timedelta(days=1), timedelta(hours=1) + ): free = False - if self.slot_type == "BOTH" and self.check_slot("WASHING", h) and self.check_slot("DRYING", h + timedelta(hours=1)): + if ( + self.slot_type == "BOTH" + and self.check_slot("WASHING", h) + and self.check_slot("DRYING", h + timedelta(hours=1)) + ): free = True elif self.slot_type == "WASHING" and self.check_slot("WASHING", h): free = True elif self.slot_type == "DRYING" and self.check_slot("DRYING", h): free = True if free and datetime.now().replace(tzinfo=pytz.UTC) < h: - kwargs['planning'][date].append(h) + kwargs["planning"][date].append(h) else: - kwargs['planning'][date].append(None) + kwargs["planning"][date].append(None) return kwargs class SlotDeleteView(CanEditPropMixin, DeleteView): """Delete a slot""" + model = Slot pk_url_kwarg = "slot_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" def get_success_url(self): return self.request.user.get_absolute_url() @@ -150,76 +187,108 @@ class SlotDeleteView(CanEditPropMixin, DeleteView): # For admins + class LaunderetteListView(CanEditPropMixin, ListView): """Choose which launderette to administer""" + model = Launderette - template_name = 'launderette/launderette_list.jinja' + template_name = "launderette/launderette_list.jinja" class LaunderetteEditView(CanEditPropMixin, UpdateView): """Edit a launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - fields = ['name'] - template_name = 'core/edit.jinja' + fields = ["name"] + template_name = "core/edit.jinja" class LaunderetteCreateView(CanCreateMixin, CreateView): """Create a new launderette""" + model = Launderette - fields = ['name'] - template_name = 'core/create.jinja' + fields = ["name"] + template_name = "core/create.jinja" def form_valid(self, form): - club = Club.objects.filter(unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name']).first() - c = Counter(name=form.instance.name, club=club, type='OFFICE') + club = Club.objects.filter( + unix_name=settings.SITH_LAUNDERETTE_MANAGER["unix_name"] + ).first() + c = Counter(name=form.instance.name, club=club, type="OFFICE") c.save() form.instance.counter = c return super(LaunderetteCreateView, self).form_valid(form) class ManageTokenForm(forms.Form): - action = forms.ChoiceField(choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))], initial="BACK", - label=_("Action"), widget=forms.RadioSelect) - token_type = forms.ChoiceField(choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES, label=_("Type"), initial="WASHING", - widget=forms.RadioSelect) - tokens = forms.CharField(max_length=512, widget=forms.widgets.Textarea, label=_("Tokens, separated by spaces")) + action = forms.ChoiceField( + choices=[("BACK", _("Back")), ("ADD", _("Add")), ("DEL", _("Delete"))], + initial="BACK", + label=_("Action"), + widget=forms.RadioSelect, + ) + token_type = forms.ChoiceField( + choices=settings.SITH_LAUNDERETTE_MACHINE_TYPES, + label=_("Type"), + initial="WASHING", + widget=forms.RadioSelect, + ) + tokens = forms.CharField( + max_length=512, + widget=forms.widgets.Textarea, + label=_("Tokens, separated by spaces"), + ) def process(self, launderette): cleaned_data = self.cleaned_data - token_list = cleaned_data['tokens'].strip(" \n\r").split(" ") - token_type = cleaned_data['token_type'] + token_list = cleaned_data["tokens"].strip(" \n\r").split(" ") + token_type = cleaned_data["token_type"] self.data = {} - if cleaned_data['action'] == "BACK": + if cleaned_data["action"] == "BACK": for t in token_list: try: - tok = Token.objects.filter(launderette=launderette, type=token_type, name=t).first() + tok = Token.objects.filter( + launderette=launderette, type=token_type, name=t + ).first() tok.borrow_date = None tok.user = None tok.save() except: - self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t}) - elif cleaned_data['action'] == "ADD": + self.add_error( + None, + _("Token %(token_name)s does not exists") % {"token_name": t}, + ) + elif cleaned_data["action"] == "ADD": for t in token_list: try: Token(launderette=launderette, type=token_type, name=t).save() except DataError as e: self.add_error(None, e) except: - self.add_error(None, _("Token %(token_name)s already exists") % {'token_name': t}) - elif cleaned_data['action'] == "DEL": + self.add_error( + None, + _("Token %(token_name)s already exists") % {"token_name": t}, + ) + elif cleaned_data["action"] == "DEL": for t in token_list: try: - Token.objects.filter(launderette=launderette, type=token_type, name=t).delete() + Token.objects.filter( + launderette=launderette, type=token_type, name=t + ).delete() except: - self.add_error(None, _("Token %(token_name)s does not exists") % {'token_name': t}) + self.add_error( + None, + _("Token %(token_name)s does not exists") % {"token_name": t}, + ) class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): """The admin page of the launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'launderette/launderette_admin.jinja' + template_name = "launderette/launderette_admin.jinja" form_class = ManageTokenForm def get(self, request, *args, **kwargs): @@ -252,17 +321,19 @@ class LaunderetteAdminView(CanEditPropMixin, BaseFormView, DetailView): """ kwargs = super(LaunderetteAdminView, self).get_context_data(**kwargs) if self.request.method == "GET": - kwargs['form'] = self.get_form() + kwargs["form"] = self.get_form() return kwargs def get_success_url(self): - return reverse_lazy('launderette:launderette_admin', args=self.args, kwargs=self.kwargs) + return reverse_lazy( + "launderette:launderette_admin", args=self.args, kwargs=self.kwargs + ) class GetLaunderetteUserForm(GetUserForm): def clean(self): cleaned_data = super(GetLaunderetteUserForm, self).clean() - sub = cleaned_data['user'] + sub = cleaned_data["user"] if sub.slots.all().count() <= 0: raise forms.ValidationError(_("User has booked no slot")) return cleaned_data @@ -270,10 +341,13 @@ class GetLaunderetteUserForm(GetUserForm): class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): """The click page of the launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'counter/counter_main.jinja' - form_class = GetLaunderetteUserForm # Form to enter a client code and get the corresponding user id + template_name = "counter/counter_main.jinja" + form_class = ( + GetLaunderetteUserForm + ) # Form to enter a client code and get the corresponding user id def get(self, request, *args, **kwargs): self.object = self.get_object() @@ -287,7 +361,7 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): """ We handle here the redirection, passing the user id of the asked customer """ - self.kwargs['user_id'] = form.cleaned_data['user_id'] + self.kwargs["user_id"] = form.cleaned_data["user_id"] return super(LaunderetteMainClickView, self).form_valid(form) def get_context_data(self, **kwargs): @@ -295,18 +369,20 @@ class LaunderetteMainClickView(CanEditMixin, BaseFormView, DetailView): We handle here the login form for the barman """ kwargs = super(LaunderetteMainClickView, self).get_context_data(**kwargs) - kwargs['counter'] = self.object.counter - kwargs['form'] = self.get_form() - kwargs['barmen'] = [self.request.user] - if 'last_basket' in self.request.session.keys(): - kwargs['last_basket'] = self.request.session.pop('last_basket', None) - kwargs['last_customer'] = self.request.session.pop('last_customer', None) - kwargs['last_total'] = self.request.session.pop('last_total', None) - kwargs['new_customer_amount'] = self.request.session.pop('new_customer_amount', None) + kwargs["counter"] = self.object.counter + kwargs["form"] = self.get_form() + kwargs["barmen"] = [self.request.user] + if "last_basket" in self.request.session.keys(): + kwargs["last_basket"] = self.request.session.pop("last_basket", None) + kwargs["last_customer"] = self.request.session.pop("last_customer", None) + kwargs["last_total"] = self.request.session.pop("last_total", None) + kwargs["new_customer_amount"] = self.request.session.pop( + "new_customer_amount", None + ) return kwargs def get_success_url(self): - return reverse_lazy('launderette:click', args=self.args, kwargs=self.kwargs) + return reverse_lazy("launderette:click", args=self.args, kwargs=self.kwargs) class ClickTokenForm(forms.BaseForm): @@ -317,8 +393,8 @@ class ClickTokenForm(forms.BaseForm): counter = Counter.objects.filter(id=self.counter_id).first() subscriber = customer.user self.last_basket = { - 'last_basket': [], - 'last_customer': customer.user.get_display_name(), + "last_basket": [], + "last_customer": customer.user.get_display_name(), } total = 0 for k, t in self.cleaned_data.items(): @@ -331,50 +407,77 @@ class ClickTokenForm(forms.BaseForm): t.borrow_date = datetime.now().replace(tzinfo=pytz.UTC) t.save() price = settings.SITH_LAUNDERETTE_PRICES[t.type] - s = Selling(label="Jeton " + t.get_type_display() + " N°" + t.name, club=counter.club, product=None, counter=counter, unit_price=price, - quantity=1, seller=operator, customer=customer) + s = Selling( + label="Jeton " + t.get_type_display() + " N°" + t.name, + club=counter.club, + product=None, + counter=counter, + unit_price=price, + quantity=1, + seller=operator, + customer=customer, + ) s.save() total += price - self.last_basket['last_basket'].append("Jeton " + t.get_type_display() + " N°" + t.name) - self.last_basket['new_customer_amount'] = str(customer.amount) - self.last_basket['last_total'] = str(total) + self.last_basket["last_basket"].append( + "Jeton " + t.get_type_display() + " N°" + t.name + ) + self.last_basket["new_customer_amount"] = str(customer.amount) + self.last_basket["last_total"] = str(total) return self.cleaned_data class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): """The click page of the launderette""" + model = Launderette pk_url_kwarg = "launderette_id" - template_name = 'launderette/launderette_click.jinja' + template_name = "launderette/launderette_click.jinja" def get_form_class(self): fields = OrderedDict() kwargs = {} + def clean_field_factory(field_name, slot): def clean_field(self2): t_name = str(self2.data[field_name]) if t_name != "": - t = Token.objects.filter(name=str(self2.data[field_name]), type=slot.type, launderette=self.object, - user=None).first() + t = Token.objects.filter( + name=str(self2.data[field_name]), + type=slot.type, + launderette=self.object, + user=None, + ).first() if t is None: raise forms.ValidationError(_("Token not found")) return t + return clean_field - for s in self.subscriber.slots.filter(token=None, start_date__gte=timezone.now().replace(tzinfo=None)).all(): + + for s in self.subscriber.slots.filter( + token=None, start_date__gte=timezone.now().replace(tzinfo=None) + ).all(): field_name = "slot-%s" % (str(s.id)) - fields[field_name] = forms.CharField(max_length=5, required=False, - label="%s - %s" % (s.get_type_display(), defaultfilters.date(s.start_date, "j N Y H:i"))) + fields[field_name] = forms.CharField( + max_length=5, + required=False, + label="%s - %s" + % ( + s.get_type_display(), + defaultfilters.date(s.start_date, "j N Y H:i"), + ), + ) # XXX l10n settings.DATETIME_FORMAT didn't work here :/ kwargs["clean_" + field_name] = clean_field_factory(field_name, s) - kwargs['subscriber_id'] = self.subscriber.id - kwargs['counter_id'] = self.object.counter.id - kwargs['operator_id'] = self.operator.id - kwargs['base_fields'] = fields - return type('ClickForm', (ClickTokenForm,), kwargs) + kwargs["subscriber_id"] = self.subscriber.id + kwargs["counter_id"] = self.object.counter.id + kwargs["operator_id"] = self.operator.id + kwargs["base_fields"] = fields + return type("ClickForm", (ClickTokenForm,), kwargs) def get(self, request, *args, **kwargs): """Simple get view""" - self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first() + self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first() self.subscriber = self.customer.user self.operator = request.user return super(LaunderetteClickView, self).get(request, *args, **kwargs) @@ -382,7 +485,7 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): def post(self, request, *args, **kwargs): """ Handle the many possibilities of the post request """ self.object = self.get_object() - self.customer = Customer.objects.filter(user__id=self.kwargs['user_id']).first() + self.customer = Customer.objects.filter(user__id=self.kwargs["user_id"]).first() self.subscriber = self.customer.user self.operator = request.user return super(LaunderetteClickView, self).post(request, *args, **kwargs) @@ -399,43 +502,50 @@ class LaunderetteClickView(CanEditMixin, DetailView, BaseFormView): We handle here the login form for the barman """ kwargs = super(LaunderetteClickView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['counter'] = self.object.counter - kwargs['customer'] = self.customer + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["counter"] = self.object.counter + kwargs["customer"] = self.customer return kwargs def get_success_url(self): - self.kwargs.pop('user_id', None) - return reverse_lazy('launderette:main_click', args=self.args, kwargs=self.kwargs) + self.kwargs.pop("user_id", None) + return reverse_lazy( + "launderette:main_click", args=self.args, kwargs=self.kwargs + ) class MachineEditView(CanEditPropMixin, UpdateView): """Edit a machine""" + model = Machine pk_url_kwarg = "machine_id" - fields = ['name', 'launderette', 'type', 'is_working'] - template_name = 'core/edit.jinja' + fields = ["name", "launderette", "type", "is_working"] + template_name = "core/edit.jinja" class MachineDeleteView(CanEditPropMixin, DeleteView): """Edit a machine""" + model = Machine pk_url_kwarg = "machine_id" - template_name = 'core/delete_confirm.jinja' - success_url = reverse_lazy('launderette:launderette_list') + template_name = "core/delete_confirm.jinja" + success_url = reverse_lazy("launderette:launderette_list") class MachineCreateView(CanCreateMixin, CreateView): """Create a new machine""" + model = Machine - fields = ['name', 'launderette', 'type'] - template_name = 'core/create.jinja' + fields = ["name", "launderette", "type"] + template_name = "core/create.jinja" def get_initial(self): ret = super(MachineCreateView, self).get_initial() - if 'launderette' in self.request.GET.keys(): - obj = Launderette.objects.filter(id=int(self.request.GET['launderette'])).first() + if "launderette" in self.request.GET.keys(): + obj = Launderette.objects.filter( + id=int(self.request.GET["launderette"]) + ).first() if obj is not None: - ret['launderette'] = obj.id + ret["launderette"] = obj.id return ret diff --git a/matmat/urls.py b/matmat/urls.py index 24557952..1959553d 100644 --- a/matmat/urls.py +++ b/matmat/urls.py @@ -27,8 +27,8 @@ from django.conf.urls import url from matmat.views import * urlpatterns = [ - url(r'^$', SearchNormalFormView.as_view(), name="search"), - url(r'^reverse$', SearchReverseFormView.as_view(), name="search_reverse"), - url(r'^quick$', SearchQuickFormView.as_view(), name="search_quick"), - url(r'^clear$', SearchClearFormView.as_view(), name="search_clear"), + url(r"^$", SearchNormalFormView.as_view(), name="search"), + url(r"^reverse$", SearchReverseFormView.as_view(), name="search_reverse"), + url(r"^quick$", SearchQuickFormView.as_view(), name="search_quick"), + url(r"^clear$", SearchClearFormView.as_view(), name="search_clear"), ] diff --git a/matmat/views.py b/matmat/views.py index 83961796..e1a541d0 100644 --- a/matmat/views.py +++ b/matmat/views.py @@ -54,28 +54,29 @@ class SearchForm(forms.ModelForm): class Meta: model = User fields = [ - 'first_name', - 'last_name', - 'nick_name', - 'role', - 'department', - 'semester', - 'promo', - 'date_of_birth', - 'phone', + "first_name", + "last_name", + "nick_name", + "role", + "department", + "semester", + "promo", + "date_of_birth", + "phone", ] widgets = { - 'date_of_birth': SelectDate, - 'phone': PhoneNumberInternationalFallbackWidget, + "date_of_birth": SelectDate, + "phone": PhoneNumberInternationalFallbackWidget, } - sex = forms.ChoiceField([ - ("MAN", _("Man")), - ("WOMAN", _("Woman")), - ("INDIFFERENT", _("Indifferent")) - ], widget=forms.RadioSelect, initial="INDIFFERENT", label=_('Sex')) + sex = forms.ChoiceField( + [("MAN", _("Man")), ("WOMAN", _("Woman")), ("INDIFFERENT", _("Indifferent"))], + widget=forms.RadioSelect, + initial="INDIFFERENT", + label=_("Sex"), + ) - quick = forms.CharField(label=_('Last/First name or nickname'), max_length=255) + quick = forms.CharField(label=_("Last/First name or nickname"), max_length=255) def __init__(self, *args, **kwargs): super(SearchForm, self).__init__(*args, **kwargs) @@ -86,10 +87,11 @@ class SearchForm(forms.ModelForm): def cleaned_data_json(self): data = self.cleaned_data for key in data.keys(): - if key in ('date_of_birth', 'phone') and data[key] is not None: + if key in ("date_of_birth", "phone") and data[key] is not None: data[key] = str(data[key]) return data + # Views @@ -97,16 +99,16 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): model = User ordering = ["-id"] paginate_by = 12 - template_name = 'matmat/search_form.jinja' + template_name = "matmat/search_form.jinja" def dispatch(self, request, *args, **kwargs): - self.form_class = kwargs['form'] - self.search_type = kwargs['search_type'] + self.form_class = kwargs["form"] + self.search_type = kwargs["search_type"] self.session = request.session - self.last_search = self.session.get('matmat_search_result', str([])) + self.last_search = self.session.get("matmat_search_result", str([])) self.last_search = literal_eval(self.last_search) - if 'valid_form' in kwargs.keys(): - self.valid_form = kwargs['valid_form'] + if "valid_form" in kwargs.keys(): + self.valid_form = kwargs["valid_form"] else: self.valid_form = None @@ -124,18 +126,18 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): def get_context_data(self, **kwargs): self.object = None kwargs = super(SearchFormListView, self).get_context_data(**kwargs) - kwargs['form'] = self.form_class - kwargs['result_exists'] = self.result_exists + kwargs["form"] = self.form_class + kwargs["result_exists"] = self.result_exists return kwargs def get_queryset(self): q = self.init_query if self.valid_form is not None: if self.search_type == SearchType.REVERSE: - q = q.filter(phone=self.valid_form['phone']).all() + q = q.filter(phone=self.valid_form["phone"]).all() elif self.search_type == SearchType.QUICK: - if self.valid_form['quick'].strip(): - q = search_user(self.valid_form['quick']) + if self.valid_form["quick"].strip(): + q = search_user(self.valid_form["quick"]) else: q = [] if not self.can_see_hidden and len(q) > 0: @@ -143,7 +145,9 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): else: search_dict = {} for key, value in self.valid_form.items(): - if key not in ('phone', 'quick') and not (value == '' or value is None or value == 'INDIFFERENT'): + if key not in ("phone", "quick") and not ( + value == "" or value is None or value == "INDIFFERENT" + ): search_dict[key + "__icontains"] = value q = q.filter(**search_dict).all() else: @@ -155,7 +159,7 @@ class SearchFormListView(FormerSubscriberMixin, SingleObjectMixin, ListView): self.last_search = [] for user in q: self.last_search.append(user.id) - self.session['matmat_search_result'] = str(self.last_search) + self.session["matmat_search_result"] = str(self.last_search) return q @@ -163,13 +167,14 @@ class SearchFormView(FormerSubscriberMixin, FormView): """ Allows users to search inside the user list """ + form_class = SearchForm def dispatch(self, request, *args, **kwargs): self.session = request.session self.init_query = User.objects - kwargs['form'] = self.get_form() - kwargs['search_type'] = self.search_type + kwargs["form"] = self.get_form() + kwargs["search_type"] = self.search_type return super(SearchFormView, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): @@ -180,14 +185,14 @@ class SearchFormView(FormerSubscriberMixin, FormView): form = self.get_form() view = SearchFormListView.as_view() if form.is_valid(): - kwargs['valid_form'] = form.clean() - request.session['matmat_search_form'] = form.cleaned_data_json + kwargs["valid_form"] = form.clean() + request.session["matmat_search_form"] = form.cleaned_data_json return view(request, *args, **kwargs) def get_initial(self): - init = self.session.get('matmat_search_form', {}) + init = self.session.get("matmat_search_form", {}) if not init: - init['department'] = '' + init["department"] = "" return init @@ -210,8 +215,8 @@ class SearchClearFormView(FormerSubscriberMixin, View): def dispatch(self, request, *args, **kwargs): super(SearchClearFormView, self).dispatch(request, *args, **kwargs) - if 'matmat_search_form' in request.session.keys(): - request.session.pop('matmat_search_form') - if 'matmat_search_result' in request.session.keys(): - request.session.pop('matmat_search_result') - return HttpResponseRedirect(reverse('matmat:search')) + if "matmat_search_form" in request.session.keys(): + request.session.pop("matmat_search_form") + if "matmat_search_result" in request.session.keys(): + request.session.pop("matmat_search_result") + return HttpResponseRedirect(reverse("matmat:search")) diff --git a/migrate.py b/migrate.py index cf424b51..8abe62e8 100644 --- a/migrate.py +++ b/migrate.py @@ -33,7 +33,7 @@ from pytz import timezone from os import listdir os.environ["DJANGO_SETTINGS_MODULE"] = "sith.settings" -os.environ['DJANGO_COLORS'] = 'nocolor' +os.environ["DJANGO_COLORS"] = "nocolor" django.setup() from django.db import IntegrityError @@ -47,67 +47,93 @@ from django.core.files import File from core.models import User, SithFile from core.utils import doku_to_markdown, bbcode_to_markdown from club.models import Club, Membership, Mailing, MailingSubscription -from counter.models import Customer, Counter, Selling, Refilling, Product, ProductType, Permanency, Eticket +from counter.models import ( + Customer, + Counter, + Selling, + Refilling, + Product, + ProductType, + Permanency, + Eticket, +) from subscription.models import Subscription from eboutic.models import Invoice, InvoiceItem -from accounting.models import BankAccount, ClubAccount, GeneralJournal, Operation, AccountingType, Company, SimplifiedAccountingType, Label +from accounting.models import ( + BankAccount, + ClubAccount, + GeneralJournal, + Operation, + AccountingType, + Company, + SimplifiedAccountingType, + Label, +) from sas.models import Album, Picture, PeoplePictureRelation -from forum.models import Forum, ForumTopic, ForumMessage, ForumMessageMeta, ForumUserInfo +from forum.models import ( + Forum, + ForumTopic, + ForumMessage, + ForumMessageMeta, + ForumUserInfo, +) db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) start = datetime.datetime.now() + def reset_index(*args): sqlcmd = StringIO() call_command("sqlsequencereset", *args, stdout=sqlcmd) cursor = connection.cursor() cursor.execute(sqlcmd.getvalue()) + def to_unicode(s): if s: - return bytes(s, 'cp1252', errors="replace").decode('utf-8', errors='replace') + return bytes(s, "cp1252", errors="replace").decode("utf-8", errors="replace") return "" def migrate_core(): def migrate_users(): - SEX = {'1': 'MAN', '2': 'WOMAN', None: 'MAN'} + SEX = {"1": "MAN", "2": "WOMAN", None: "MAN"} TSHIRT = { - None: '-', - '': '-', - 'NULL': '-', - 'XS': 'XS', - 'S': 'S', - 'M': 'M', - 'L': 'L', - 'XL': 'XL', - 'XXL': 'XXL', - 'XXXL': 'XXXL', - } + None: "-", + "": "-", + "NULL": "-", + "XS": "XS", + "S": "S", + "M": "M", + "L": "L", + "XL": "XL", + "XXL": "XXL", + "XXXL": "XXXL", + } ROLE = { - 'doc': 'DOCTOR', - 'etu': 'STUDENT', - 'anc': 'FORMER STUDENT', - 'ens': 'TEACHER', - 'adm': 'ADMINISTRATIVE', - 'srv': 'SERVICE', - 'per': 'AGENT', - None: '', - } + "doc": "DOCTOR", + "etu": "STUDENT", + "anc": "FORMER STUDENT", + "ens": "TEACHER", + "adm": "ADMINISTRATIVE", + "srv": "SERVICE", + "per": "AGENT", + None: "", + } DEPARTMENTS = { - 'tc': 'TC', - 'gi': 'GI', - 'gesc': 'GESC', - 'na': 'NA', - 'mc': 'MC', - 'imap': 'IMAP', - 'huma': 'HUMA', - 'edim': 'EDIM', - 'ee': 'EE', - 'imsi': 'IMSI', - 'truc': 'NA', - None: 'NA', - } + "tc": "TC", + "gi": "GI", + "gesc": "GESC", + "na": "NA", + "mc": "MC", + "imap": "IMAP", + "huma": "HUMA", + "edim": "EDIM", + "ee": "EE", + "imsi": "IMSI", + "truc": "NA", + None: "NA", + } def get_random_free_email(): email = "no_email_%s@git.an" % random.randrange(4000, 40000) @@ -116,7 +142,8 @@ def migrate_core(): return email c = db.cursor(MySQLdb.cursors.SSDictCursor) - c.execute(""" + c.execute( + """ SELECT * FROM utilisateurs utl LEFT JOIN utl_etu ue @@ -128,36 +155,49 @@ def migrate_core(): LEFT JOIN loc_ville ville ON utl.id_ville = ville.id_ville -- WHERE utl.id_utilisateur = 9360 - """) + """ + ) User.objects.filter(id__gt=0).delete() print("Users deleted") for u in c: try: new = User( - id=u['id_utilisateur'], - last_name=to_unicode(u['nom_utl']) or "Bou", - first_name=to_unicode(u['prenom_utl']) or "Bi", - email=u['email_utl'], - second_email=u['email_utbm'] or "", - date_of_birth=u['date_naissance_utl'], - last_update=u['date_maj_utl'], - nick_name=to_unicode(u['surnom_utbm']), - sex=SEX[u['sexe_utl']], - tshirt_size=TSHIRT[u['taille_tshirt_utl']], - role=ROLE[u['role_utbm']], - department=DEPARTMENTS[u['departement_utbm']], - dpt_option=to_unicode(u['filiere_utbm']), - semester=u['semestre_utbm'] or 0, - quote=to_unicode(u['citation']), - school=to_unicode(u['nom_ecole_etudiant']), - promo=u['promo_utbm'] or 0, - forum_signature=to_unicode(u['signature_utl']), - address=(to_unicode(u['addresse_utl']) + ", " + to_unicode(u['cpostal_ville']) + " " + to_unicode(u['nom_ville'])), - parent_address=(to_unicode(u['adresse_parents']) + ", " + to_unicode(u['cpostal_parents']) + " " + to_unicode(u['ville_parents'])), - phone=u['tel_portable_utl'] or "", - parent_phone=u['tel_parents'] or "", - is_subscriber_viewable=bool(u['publique_utl']), + id=u["id_utilisateur"], + last_name=to_unicode(u["nom_utl"]) or "Bou", + first_name=to_unicode(u["prenom_utl"]) or "Bi", + email=u["email_utl"], + second_email=u["email_utbm"] or "", + date_of_birth=u["date_naissance_utl"], + last_update=u["date_maj_utl"], + nick_name=to_unicode(u["surnom_utbm"]), + sex=SEX[u["sexe_utl"]], + tshirt_size=TSHIRT[u["taille_tshirt_utl"]], + role=ROLE[u["role_utbm"]], + department=DEPARTMENTS[u["departement_utbm"]], + dpt_option=to_unicode(u["filiere_utbm"]), + semester=u["semestre_utbm"] or 0, + quote=to_unicode(u["citation"]), + school=to_unicode(u["nom_ecole_etudiant"]), + promo=u["promo_utbm"] or 0, + forum_signature=to_unicode(u["signature_utl"]), + address=( + to_unicode(u["addresse_utl"]) + + ", " + + to_unicode(u["cpostal_ville"]) + + " " + + to_unicode(u["nom_ville"]) + ), + parent_address=( + to_unicode(u["adresse_parents"]) + + ", " + + to_unicode(u["cpostal_parents"]) + + " " + + to_unicode(u["ville_parents"]) + ), + phone=u["tel_portable_utl"] or "", + parent_phone=u["tel_parents"] or "", + is_subscriber_viewable=bool(u["publique_utl"]), ) new.generate_username() new.set_password(str(random.randrange(1000000, 10000000))) @@ -168,12 +208,12 @@ def migrate_core(): new.save() print("New email generated") else: - print("FAIL for user %s: %s" % (u['id_utilisateur'], repr(e))) + print("FAIL for user %s: %s" % (u["id_utilisateur"], repr(e))) except Exception as e: - print("FAIL for user %s: %s" % (u['id_utilisateur'], repr(e))) + print("FAIL for user %s: %s" % (u["id_utilisateur"], repr(e))) c.close() print("Users migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_profile_pict(): PROFILE_ROOT = "/data/matmatronch/" @@ -182,16 +222,23 @@ def migrate_core(): profile.children.all().delete() print("Profiles pictures deleted") for filename in listdir(PROFILE_ROOT): - if filename.split('.')[-2] != "mini": + if filename.split(".")[-2] != "mini": try: - uid = filename.split('.')[0].split('-')[0] + uid = filename.split(".")[0].split("-")[0] user = User.objects.filter(id=int(uid)).first() if user: - f = File(open(PROFILE_ROOT + '/' + filename, 'rb')) - f.name = f.name.split('/')[-1] - t = filename.split('.')[1] - new_file = SithFile(parent=profile, name=filename, - file=f, owner=user, is_folder=False, mime_type="image/jpeg", size=f.size) + f = File(open(PROFILE_ROOT + "/" + filename, "rb")) + f.name = f.name.split("/")[-1] + t = filename.split(".")[1] + new_file = SithFile( + parent=profile, + name=filename, + file=f, + owner=user, + is_folder=False, + mime_type="image/jpeg", + size=f.size, + ) if t == "identity": new_file.save() user.profile_pict = new_file @@ -207,7 +254,7 @@ def migrate_core(): except Exception as e: print(repr(e)) print("Profile pictures migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) migrate_users() migrate_profile_pict() @@ -216,13 +263,15 @@ def migrate_core(): def migrate_club(): def migrate_clubs(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM asso asso WHERE nom_unix_asso <> "ae" AND nom_unix_asso <> "bdf" AND nom_unix_asso <> "laverie" - """) + """ + ) # club = cur.fetchone() # for k,v in club.items(): # print("%40s | %40s" % (k, v)) @@ -230,94 +279,89 @@ def migrate_club(): for c in cur: try: new = Club( - id=c['id_asso'], - name=to_unicode(c['nom_asso']), - unix_name=to_unicode(c['nom_unix_asso']), - address=to_unicode(c['adresse_postale']), - ) + id=c["id_asso"], + name=to_unicode(c["nom_asso"]), + unix_name=to_unicode(c["nom_unix_asso"]), + address=to_unicode(c["adresse_postale"]), + ) new.save() except Exception as e: - print("FAIL for club %s: %s" % (c['nom_unix_asso'], repr(e))) - cur.execute(""" + print("FAIL for club %s: %s" % (c["nom_unix_asso"], repr(e))) + cur.execute( + """ SELECT * FROM asso - """) + """ + ) for c in cur: - club = Club.objects.filter(id=c['id_asso']).first() - parent = Club.objects.filter(id=c['id_asso_parent']).first() + club = Club.objects.filter(id=c["id_asso"]).first() + parent = Club.objects.filter(id=c["id_asso_parent"]).first() club.parent = parent club.save() cur.close() print("Clubs migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_club_memberships(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM asso_membre - """) + """ + ) Membership.objects.all().delete() print("Memberships deleted") for m in cur: try: - club = Club.objects.filter(id=m['id_asso']).first() - user = User.objects.filter(id=m['id_utilisateur']).first() + club = Club.objects.filter(id=m["id_asso"]).first() + user = User.objects.filter(id=m["id_utilisateur"]).first() if club and user: new = Membership( - id=Membership.objects.count()+1, - club=club, - user=user, - start_date=m['date_debut'], - end_date=m['date_fin'], - role=m['role'], - description=to_unicode(m['desc_role']), - ) + id=Membership.objects.count() + 1, + club=club, + user=user, + start_date=m["date_debut"], + end_date=m["date_fin"], + role=m["role"], + description=to_unicode(m["desc_role"]), + ) new.save() except Exception as e: - print("FAIL for club membership %s: %s" % (m['id_asso'], repr(e))) + print("FAIL for club membership %s: %s" % (m["id_asso"], repr(e))) cur.close() print("Clubs memberships migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) # migrate_clubs() migrate_club_memberships() + def migrate_subscriptions(): - LOCATION = { - 5: "SEVENANS", - 6: "BELFORT", - 9: "MONTBELIARD", - None: "SEVENANS", - } + LOCATION = {5: "SEVENANS", 6: "BELFORT", 9: "MONTBELIARD", None: "SEVENANS"} TYPE = { - 0: 'un-semestre', - 1: 'deux-semestres', - 2: 'cursus-tronc-commun', - 3: 'cursus-branche', - 4: 'membre-honoraire', - 5: 'assidu', - 6: 'amicale/doceo', - 7: 'reseau-ut', - 8: 'crous', - 9: 'sbarro/esta', - 10: 'cursus-alternant', - None: 'un-semestre', - } - PAYMENT = { - 1: "CHECK", - 2: "CARD", - 3: "CASH", - 4: "OTHER", - 5: "EBOUTIC", - 0: "OTHER", - } + 0: "un-semestre", + 1: "deux-semestres", + 2: "cursus-tronc-commun", + 3: "cursus-branche", + 4: "membre-honoraire", + 5: "assidu", + 6: "amicale/doceo", + 7: "reseau-ut", + 8: "crous", + 9: "sbarro/esta", + 10: "cursus-alternant", + None: "un-semestre", + } + PAYMENT = {1: "CHECK", 2: "CARD", 3: "CASH", 4: "OTHER", 5: "EBOUTIC", 0: "OTHER"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM ae_cotisations - """) + """ + ) Subscription.objects.all().delete() print("Subscriptions deleted") @@ -325,70 +369,78 @@ def migrate_subscriptions(): print("Customers deleted") for r in cur: try: - user = User.objects.filter(id=r['id_utilisateur']).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() if user: new = Subscription( - id=r['id_cotisation'], - member=user, - subscription_start=r['date_cotis'], - subscription_end=r['date_fin_cotis'], - subscription_type=TYPE[r['type_cotis']], - payment_method=PAYMENT[r['mode_paiement_cotis']], - location=LOCATION[r['id_comptoir']], - ) + id=r["id_cotisation"], + member=user, + subscription_start=r["date_cotis"], + subscription_end=r["date_fin_cotis"], + subscription_type=TYPE[r["type_cotis"]], + payment_method=PAYMENT[r["mode_paiement_cotis"]], + location=LOCATION[r["id_comptoir"]], + ) new.save() except Exception as e: - print("FAIL for subscription %s: %s" % (r['id_cotisation'], repr(e))) + print("FAIL for subscription %s: %s" % (r["id_cotisation"], repr(e))) cur.close() print("Subscriptions migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) + def migrate_counter(): def update_customer_account(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM ae_carte carte JOIN ae_cotisations cotis ON carte.id_cotisation = cotis.id_cotisation - """) + """ + ) for r in cur: try: - user = Customer.objects.filter(user_id=r['id_utilisateur']).first() + user = Customer.objects.filter(user_id=r["id_utilisateur"]).first() if user: - user.account_id = str(r['id_carte_ae']) + r['cle_carteae'].lower() + user.account_id = str(r["id_carte_ae"]) + r["cle_carteae"].lower() user.save() except Exception as e: - print("FAIL to update customer account for %s: %s" % (r['id_cotisation'], repr(e))) + print( + "FAIL to update customer account for %s: %s" + % (r["id_cotisation"], repr(e)) + ) cur.close() print("Customer accounts migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_counters(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_comptoir - """) + """ + ) Counter.objects.all().delete() for r in cur: try: - club = Club.objects.filter(id=r['id_assocpt']).first() + club = Club.objects.filter(id=r["id_assocpt"]).first() new = Counter( - id=r['id_comptoir'], - name=to_unicode(r['nom_cpt']), - club=club, - type="OFFICE", - ) + id=r["id_comptoir"], + name=to_unicode(r["nom_cpt"]), + club=club, + type="OFFICE", + ) new.save() except Exception as e: - print("FAIL to migrate counter %s: %s" % (r['id_comptoir'], repr(e))) + print("FAIL to migrate counter %s: %s" % (r["id_comptoir"], repr(e))) cur.close() eboutic = Counter.objects.filter(id=3).first() eboutic.type = "EBOUTIC" eboutic.save() print("Counters migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def reset_customer_amount(): Refilling.objects.all().delete() @@ -401,29 +453,27 @@ def migrate_counter(): def migrate_refillings(): BANK = { - 0: "OTHER", - 1: "SOCIETE-GENERALE", - 2: "BANQUE-POPULAIRE", - 3: "BNP", - 4: "CAISSE-EPARGNE", - 5: "CIC", - 6: "CREDIT-AGRICOLE", - 7: "CREDIT-MUTUEL", - 8: "CREDIT-LYONNAIS", - 9: "LA-POSTE", - 100: "OTHER", - None: "OTHER", - } - PAYMENT = { - 2: "CARD", - 1: "CASH", - 0: "CHECK", - } + 0: "OTHER", + 1: "SOCIETE-GENERALE", + 2: "BANQUE-POPULAIRE", + 3: "BNP", + 4: "CAISSE-EPARGNE", + 5: "CIC", + 6: "CREDIT-AGRICOLE", + 7: "CREDIT-MUTUEL", + 8: "CREDIT-LYONNAIS", + 9: "LA-POSTE", + 100: "OTHER", + None: "OTHER", + } + PAYMENT = {2: "CARD", 1: "CASH", 0: "CHECK"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_rechargements - """) + """ + ) root_cust = Customer.objects.filter(user__id=0).first() mde = Counter.objects.filter(id=1).first() Refilling.objects.all().delete() @@ -431,137 +481,159 @@ def migrate_counter(): fail = 100 for r in cur: try: - cust = Customer.objects.filter(user__id=r['id_utilisateur']).first() - user = User.objects.filter(id=r['id_utilisateur']).first() + cust = Customer.objects.filter(user__id=r["id_utilisateur"]).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() if not cust: if not user: cust = root_cust else: - cust = Customer(user=user, amount=0, account_id=Customer.generate_account_id(fail)) + cust = Customer( + user=user, + amount=0, + account_id=Customer.generate_account_id(fail), + ) cust.save() fail += 1 - op = User.objects.filter(id=r['id_utilisateur_operateur']).first() - counter = Counter.objects.filter(id=r['id_comptoir']).first() + op = User.objects.filter(id=r["id_utilisateur_operateur"]).first() + counter = Counter.objects.filter(id=r["id_comptoir"]).first() new = Refilling( - id=r['id_rechargement'], - counter=counter or mde, - customer=cust or root_cust, - operator=op or root_cust.user, - amount=r['montant_rech']/100, - payment_method=PAYMENT[r['type_paiement_rech']], - bank=BANK[r['banque_rech']], - date=r['date_rech'].replace(tzinfo=timezone('Europe/Paris')), - ) + id=r["id_rechargement"], + counter=counter or mde, + customer=cust or root_cust, + operator=op or root_cust.user, + amount=r["montant_rech"] / 100, + payment_method=PAYMENT[r["type_paiement_rech"]], + bank=BANK[r["banque_rech"]], + date=r["date_rech"].replace(tzinfo=timezone("Europe/Paris")), + ) new.save() except Exception as e: - print("FAIL to migrate refilling %s for %s: %s" % (r['id_rechargement'], r['id_utilisateur'], repr(e))) + print( + "FAIL to migrate refilling %s for %s: %s" + % (r["id_rechargement"], r["id_utilisateur"], repr(e)) + ) cur.close() print("Refillings migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_typeproducts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_type_produit - """) + """ + ) ProductType.objects.all().delete() print("Product types deleted") for r in cur: try: new = ProductType( - id=r['id_typeprod'], - name=to_unicode(r['nom_typeprod']), - description=to_unicode(r['description_typeprod']), - ) + id=r["id_typeprod"], + name=to_unicode(r["nom_typeprod"]), + description=to_unicode(r["description_typeprod"]), + ) new.save() except Exception as e: - print("FAIL to migrate product type %s: %s" % (r['nom_typeprod'], repr(e))) + print( + "FAIL to migrate product type %s: %s" % (r["nom_typeprod"], repr(e)) + ) cur.close() print("Product types migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_products(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_produits - """) + """ + ) Product.objects.all().delete() print("Product deleted") for r in cur: try: - type = ProductType.objects.filter(id=r['id_typeprod']).first() - club = Club.objects.filter(id=r['id_assocpt']).first() + type = ProductType.objects.filter(id=r["id_typeprod"]).first() + club = Club.objects.filter(id=r["id_assocpt"]).first() new = Product( - id=r['id_produit'], - product_type=type, - name=to_unicode(r['nom_prod']), - description=to_unicode(r['description_prod']), - code=to_unicode(r['cbarre_prod']), - purchase_price=r['prix_achat_prod']/100, - selling_price=r['prix_vente_prod']/100, - special_selling_price=r['prix_vente_barman_prod']/100, - club=club, - limit_age=r['mineur'] or 0, - tray=bool(r['plateau']), - ) + id=r["id_produit"], + product_type=type, + name=to_unicode(r["nom_prod"]), + description=to_unicode(r["description_prod"]), + code=to_unicode(r["cbarre_prod"]), + purchase_price=r["prix_achat_prod"] / 100, + selling_price=r["prix_vente_prod"] / 100, + special_selling_price=r["prix_vente_barman_prod"] / 100, + club=club, + limit_age=r["mineur"] or 0, + tray=bool(r["plateau"]), + ) new.save() except Exception as e: - print("FAIL to migrate product %s: %s" % (r['nom_prod'], repr(e))) + print("FAIL to migrate product %s: %s" % (r["nom_prod"], repr(e))) cur.close() print("Product migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_product_pict(): FILE_ROOT = "/data/files/" cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_produits WHERE id_file IS NOT NULL - """) + """ + ) for r in cur: try: - prod = Product.objects.filter(id=r['id_produit']).first() + prod = Product.objects.filter(id=r["id_produit"]).first() if prod: - f = File(open(FILE_ROOT + '/' + str(r['id_file']) + ".1", 'rb')) + f = File(open(FILE_ROOT + "/" + str(r["id_file"]) + ".1", "rb")) f.name = prod.name prod.icon = f prod.save() except Exception as e: print(repr(e)) print("Product pictures migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_products_to_counter(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_mise_en_vente - """) + """ + ) for r in cur: try: - product = Product.objects.filter(id=r['id_produit']).first() - counter = Counter.objects.filter(id=r['id_comptoir']).first() + product = Product.objects.filter(id=r["id_produit"]).first() + counter = Counter.objects.filter(id=r["id_comptoir"]).first() counter.products.add(product) counter.save() except Exception as e: - print("FAIL to set product %s in counter %s: %s" % (product, counter, repr(e))) + print( + "FAIL to set product %s in counter %s: %s" + % (product, counter, repr(e)) + ) cur.close() print("Product in counters migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_invoices(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_vendu ven LEFT JOIN cpt_debitfacture fac ON ven.id_facture = fac.id_facture WHERE fac.mode_paiement = 'SG' - """) + """ + ) Invoice.objects.all().delete() print("Invoices deleted") Refilling.objects.filter(payment_method="CARD").delete() @@ -571,21 +643,29 @@ def migrate_counter(): root = User.objects.filter(id=0).first() for r in cur: try: - product = Product.objects.filter(id=r['id_produit']).first() - user = User.objects.filter(id=r['id_utilisateur_client']).first() - i = Invoice.objects.filter(id=r['id_facture']).first() or Invoice(id=r['id_facture']) + product = Product.objects.filter(id=r["id_produit"]).first() + user = User.objects.filter(id=r["id_utilisateur_client"]).first() + i = Invoice.objects.filter(id=r["id_facture"]).first() or Invoice( + id=r["id_facture"] + ) i.user = user or root for f in i._meta.local_fields: if f.name == "date": f.auto_now = False - i.date = r['date_facture'].replace(tzinfo=timezone('Europe/Paris')) + i.date = r["date_facture"].replace(tzinfo=timezone("Europe/Paris")) i.save() - InvoiceItem(invoice=i, product_id=product.id, product_name=product.name, type_id=product.product_type.id, - product_unit_price=r['prix_unit']/100, quantity=r['quantite']).save() + InvoiceItem( + invoice=i, + product_id=product.id, + product_name=product.name, + type_id=product.product_type.id, + product_unit_price=r["prix_unit"] / 100, + quantity=r["quantite"], + ).save() except ValidationError as e: print(repr(e) + " for %s (%s)" % (customer, customer.user.id)) except Exception as e: - print("FAIL to migrate invoice %s: %s" % (r['id_facture'], repr(e))) + print("FAIL to migrate invoice %s: %s" % (r["id_facture"], repr(e))) cur.close() for i in Invoice.objects.all(): for f in i._meta.local_fields: @@ -593,17 +673,19 @@ def migrate_counter(): f.auto_now = False i.validate() print("Invoices migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_sellings(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_vendu ven LEFT JOIN cpt_debitfacture fac ON ven.id_facture = fac.id_facture WHERE fac.mode_paiement = 'AE' - """) + """ + ) Selling.objects.filter(payment_method="SITH_ACCOUNT").delete() print("Sith account selling deleted") for c in Customer.objects.all(): @@ -616,57 +698,62 @@ def migrate_counter(): beer = Product.objects.filter(id=1).first() for r in cur: try: - product = Product.objects.filter(id=r['id_produit']).first() or beer - club = Club.objects.filter(id=r['id_assocpt']).first() or ae - counter = Counter.objects.filter(id=r['id_comptoir']).first() or mde - op = User.objects.filter(id=r['id_utilisateur']).first() or root - customer = Customer.objects.filter(user__id=r['id_utilisateur_client']).first() or root.customer + product = Product.objects.filter(id=r["id_produit"]).first() or beer + club = Club.objects.filter(id=r["id_assocpt"]).first() or ae + counter = Counter.objects.filter(id=r["id_comptoir"]).first() or mde + op = User.objects.filter(id=r["id_utilisateur"]).first() or root + customer = ( + Customer.objects.filter(user__id=r["id_utilisateur_client"]).first() + or root.customer + ) new = Selling( - label=product.name or "Produit inexistant", - counter=counter, - club=club, - product=product, - seller=op, - customer=customer, - unit_price=r['prix_unit']/100, - quantity=r['quantite'], - payment_method="SITH_ACCOUNT", - date=r['date_facture'].replace(tzinfo=timezone('Europe/Paris')), - ) + label=product.name or "Produit inexistant", + counter=counter, + club=club, + product=product, + seller=op, + customer=customer, + unit_price=r["prix_unit"] / 100, + quantity=r["quantite"], + payment_method="SITH_ACCOUNT", + date=r["date_facture"].replace(tzinfo=timezone("Europe/Paris")), + ) new.save() except ValidationError as e: print(repr(e) + " for %s (%s)" % (customer, customer.user.id)) except Exception as e: - print("FAIL to migrate selling %s: %s" % (r['id_facture'], repr(e))) + print("FAIL to migrate selling %s: %s" % (r["id_facture"], repr(e))) cur.close() print("Sellings migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_permanencies(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_tracking - """) + """ + ) Permanency.objects.all().delete() print("Permanencies deleted") for r in cur: try: - counter = Counter.objects.filter(id=r['id_comptoir']).first() - user = User.objects.filter(id=r['id_utilisateur']).first() + counter = Counter.objects.filter(id=r["id_comptoir"]).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() new = Permanency( - user=user, - counter=counter, - start=r['logged_time'].replace(tzinfo=timezone('Europe/Paris')), - activity=r['logged_time'].replace(tzinfo=timezone('Europe/Paris')), - end=r['closed_time'].replace(tzinfo=timezone('Europe/Paris')), - ) + user=user, + counter=counter, + start=r["logged_time"].replace(tzinfo=timezone("Europe/Paris")), + activity=r["logged_time"].replace(tzinfo=timezone("Europe/Paris")), + end=r["closed_time"].replace(tzinfo=timezone("Europe/Paris")), + ) new.save() except Exception as e: print("FAIL to migrate permanency: %s" % (repr(e))) cur.close() print("Permanencies migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) update_customer_account() migrate_counters() @@ -680,241 +767,252 @@ def migrate_counter(): migrate_refillings() migrate_sellings() + def check_accounts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM utilisateurs - """) + """ + ) mde = Counter.objects.filter(id=1).first() - ae = Club.objects.filter(unix_name='ae').first() + ae = Club.objects.filter(unix_name="ae").first() root = User.objects.filter(id=0).first() for r in cur: - if r['montant_compte'] and r['montant_compte'] > 0: + if r["montant_compte"] and r["montant_compte"] > 0: try: - cust = Customer.objects.filter(user__id=r['id_utilisateur']).first() - if int(cust.amount * 100) != r['montant_compte']: - print("Adding %s to %s's account" % (float(cust.amount) - (r['montant_compte']/100), cust.user)) + cust = Customer.objects.filter(user__id=r["id_utilisateur"]).first() + if int(cust.amount * 100) != r["montant_compte"]: + print( + "Adding %s to %s's account" + % (float(cust.amount) - (r["montant_compte"] / 100), cust.user) + ) new = Selling( - label="Ajustement migration base de donnée", - counter=mde, - club=ae, - product=None, - seller=root, - customer=cust, - unit_price=float(cust.amount) - (r['montant_compte']/100.), - quantity=1, - payment_method="SITH_ACCOUNT", - ) + label="Ajustement migration base de donnée", + counter=mde, + club=ae, + product=None, + seller=root, + customer=cust, + unit_price=float(cust.amount) - (r["montant_compte"] / 100.0), + quantity=1, + payment_method="SITH_ACCOUNT", + ) new.save() except Exception as e: print("FAIL to adjust user account: %s" % (repr(e))) + + ### Accounting + def migrate_accounting(): def migrate_companies(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM entreprise - """) + """ + ) Company.objects.all().delete() print("Company deleted") for r in cur: try: new = Company( - id=r['id_ent'], - name=to_unicode(r['nom_entreprise']), - street=to_unicode(r['rue_entreprise']), - city=to_unicode(r['ville_entreprise']), - postcode=to_unicode(r['cpostal_entreprise']), - country=to_unicode(r['pays_entreprise']), - phone=to_unicode(r['telephone_entreprise']), - email=to_unicode(r['email_entreprise']), - website=to_unicode(r['siteweb_entreprise']), - ) + id=r["id_ent"], + name=to_unicode(r["nom_entreprise"]), + street=to_unicode(r["rue_entreprise"]), + city=to_unicode(r["ville_entreprise"]), + postcode=to_unicode(r["cpostal_entreprise"]), + country=to_unicode(r["pays_entreprise"]), + phone=to_unicode(r["telephone_entreprise"]), + email=to_unicode(r["email_entreprise"]), + website=to_unicode(r["siteweb_entreprise"]), + ) new.save() except Exception as e: print("FAIL to migrate company: %s" % (repr(e))) cur.close() print("Companies migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_bank_accounts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_cpbancaire - """) + """ + ) BankAccount.objects.all().delete() print("Bank accounts deleted") - ae = Club.objects.filter(unix_name='ae').first() + ae = Club.objects.filter(unix_name="ae").first() for r in cur: try: new = BankAccount( - id=r['id_cptbc'], - club=ae, - name=to_unicode(r['nom_cptbc']), - ) + id=r["id_cptbc"], club=ae, name=to_unicode(r["nom_cptbc"]) + ) new.save() except Exception as e: print("FAIL to migrate bank account: %s" % (repr(e))) cur.close() print("Bank accounts migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_club_accounts(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_cpasso - """) + """ + ) ClubAccount.objects.all().delete() print("Club accounts deleted") ae = Club.objects.filter(id=1).first() for r in cur: try: - club = Club.objects.filter(id=r['id_asso']).first() or ae - bank_acc = BankAccount.objects.filter(id=r['id_cptbc']).first() + club = Club.objects.filter(id=r["id_asso"]).first() or ae + bank_acc = BankAccount.objects.filter(id=r["id_cptbc"]).first() new = ClubAccount( - id=r['id_cptasso'], - club=club, - name=club.name[:30], - bank_account=bank_acc, - ) + id=r["id_cptasso"], + club=club, + name=club.name[:30], + bank_account=bank_acc, + ) new.save() except Exception as e: print("FAIL to migrate club account: %s" % (repr(e))) cur.close() print("Club accounts migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_journals(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_classeur - """) + """ + ) GeneralJournal.objects.all().delete() print("General journals deleted") for r in cur: try: - club_acc = ClubAccount.objects.filter(id=r['id_cptasso']).first() + club_acc = ClubAccount.objects.filter(id=r["id_cptasso"]).first() new = GeneralJournal( - id=r['id_classeur'], - club_account=club_acc, - name=to_unicode(r['nom_classeur']), - start_date=r['date_debut_classeur'], - end_date=r['date_fin_classeur'], - closed=bool(r['ferme']), - ) + id=r["id_classeur"], + club_account=club_acc, + name=to_unicode(r["nom_classeur"]), + start_date=r["date_debut_classeur"], + end_date=r["date_fin_classeur"], + closed=bool(r["ferme"]), + ) new.save() except Exception as e: print("FAIL to migrate general journal: %s" % (repr(e))) cur.close() print("General journals migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_accounting_types(): - MOVEMENT = { - -1: "DEBIT", - 0: "NEUTRAL", - 1: "CREDIT", - } + MOVEMENT = {-1: "DEBIT", 0: "NEUTRAL", 1: "CREDIT"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_op_plcptl - """) + """ + ) AccountingType.objects.all().delete() print("Accounting types deleted") for r in cur: try: new = AccountingType( - id=r['id_opstd'], - code=str(r['code_plan']), - label=to_unicode(r['libelle_plan']).capitalize(), - movement_type=MOVEMENT[r['type_mouvement']], - ) + id=r["id_opstd"], + code=str(r["code_plan"]), + label=to_unicode(r["libelle_plan"]).capitalize(), + movement_type=MOVEMENT[r["type_mouvement"]], + ) new.save() except Exception as e: print("FAIL to migrate accounting type: %s" % (repr(e))) cur.close() print("Accounting types migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_simpleaccounting_types(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_op_clb WHERE id_asso IS NULL - """) + """ + ) SimplifiedAccountingType.objects.all().delete() print("Simple accounting types deleted") for r in cur: try: - at = AccountingType.objects.filter(id=r['id_opstd']).first() + at = AccountingType.objects.filter(id=r["id_opstd"]).first() new = SimplifiedAccountingType( - id=r['id_opclb'], - label=to_unicode(r['libelle_opclb']).capitalize(), - accounting_type=at, - ) + id=r["id_opclb"], + label=to_unicode(r["libelle_opclb"]).capitalize(), + accounting_type=at, + ) new.save() except Exception as e: print("FAIL to migrate simple type: %s" % (repr(e))) cur.close() print("Simple accounting types migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_labels(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_libelle WHERE id_asso IS NOT NULL - """) + """ + ) Label.objects.all().delete() print("Labels deleted") for r in cur: try: - club_accounts = ClubAccount.objects.filter(club__id=r['id_asso']).all() + club_accounts = ClubAccount.objects.filter(club__id=r["id_asso"]).all() for ca in club_accounts: - new = Label( - club_account=ca, - name=to_unicode(r['nom_libelle']), - ) + new = Label(club_account=ca, name=to_unicode(r["nom_libelle"])) new.save() except Exception as e: print("FAIL to migrate label: %s" % (repr(e))) cur.close() print("Labels migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_operations(): MODE = { - 1: "CHECK", - 2: "CASH", - 3: "TRANSFERT", - 4: "CARD", - 0: "CASH", - None: "CASH", - } - MOVEMENT_TYPE = { - -1: "DEBIT", - 0: "NEUTRAL", - 1: "CREDIT", - None: "NEUTRAL", - } + 1: "CHECK", + 2: "CASH", + 3: "TRANSFERT", + 4: "CARD", + 0: "CASH", + None: "CASH", + } + MOVEMENT_TYPE = {-1: "DEBIT", 0: "NEUTRAL", 1: "CREDIT", None: "NEUTRAL"} cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_operation op LEFT JOIN cpta_op_clb clb ON op.id_opclb = clb.id_opclb LEFT JOIN cpta_libelle lab ON op.id_libelle = lab.id_libelle - """) + """ + ) Operation.objects.all().delete() print("Operation deleted") for r in cur: @@ -922,44 +1020,63 @@ def migrate_accounting(): simple_type = None accounting_type = None label = None - if r['id_opclb']: - simple_type = SimplifiedAccountingType.objects.filter(id=r['id_opclb']).first() - if r['id_opstd']: - accounting_type = AccountingType.objects.filter(id=r['id_opstd']).first() + if r["id_opclb"]: + simple_type = SimplifiedAccountingType.objects.filter( + id=r["id_opclb"] + ).first() + if r["id_opstd"]: + accounting_type = AccountingType.objects.filter( + id=r["id_opstd"] + ).first() if not accounting_type and simple_type: accounting_type = simple_type.accounting_type if not accounting_type: - accounting_type = AccountingType.objects.filter(movement_type=MOVEMENT_TYPE[r['type_mouvement']]).first() - journal = GeneralJournal.objects.filter(id=r['id_classeur']).first() - if r['id_libelle']: - label = journal.club_account.labels.filter(name=to_unicode(r['nom_libelle'])).first() + accounting_type = AccountingType.objects.filter( + movement_type=MOVEMENT_TYPE[r["type_mouvement"]] + ).first() + journal = GeneralJournal.objects.filter(id=r["id_classeur"]).first() + if r["id_libelle"]: + label = journal.club_account.labels.filter( + name=to_unicode(r["nom_libelle"]) + ).first() + def get_target_type(): - if r['id_utilisateur']: + if r["id_utilisateur"]: return "USER" - if r['id_asso']: + if r["id_asso"]: return "CLUB" - if r['id_ent']: + if r["id_ent"]: return "COMPANY" - if r['id_classeur']: + if r["id_classeur"]: return "ACCOUNT" + def get_target_id(): - return int(r['id_utilisateur'] or r['id_asso'] or r['id_ent'] or r['id_classeur']) or None - new = Operation( - id=r['id_op'], - journal=journal, - amount=r['montant_op']/100, - date=r['date_op'] or journal.end_date, - remark=to_unicode(r['commentaire_op']), - mode=MODE[r['mode_op']], - cheque_number=str(r['num_cheque_op']), - done=bool(r['op_effctue']), - simpleaccounting_type=simple_type, - accounting_type=accounting_type, - target_type=get_target_type(), - target_id=get_target_id(), - target_label="-", - label=label, + return ( + int( + r["id_utilisateur"] + or r["id_asso"] + or r["id_ent"] + or r["id_classeur"] ) + or None + ) + + new = Operation( + id=r["id_op"], + journal=journal, + amount=r["montant_op"] / 100, + date=r["date_op"] or journal.end_date, + remark=to_unicode(r["commentaire_op"]), + mode=MODE[r["mode_op"]], + cheque_number=str(r["num_cheque_op"]), + done=bool(r["op_effctue"]), + simpleaccounting_type=simple_type, + accounting_type=accounting_type, + target_type=get_target_type(), + target_id=get_target_id(), + target_label="-", + label=label, + ) try: new.clean() except: @@ -970,19 +1087,21 @@ def migrate_accounting(): print("FAIL to migrate operation: %s" % (repr(e))) cur.close() print("Operations migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def make_operation_links(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpta_operation - """) + """ + ) for r in cur: - if r['id_op_liee']: + if r["id_op_liee"]: try: - op1 = Operation.objects.filter(id=r['id_op']).first() - op2 = Operation.objects.filter(id=r['id_op_liee']).first() + op1 = Operation.objects.filter(id=r["id_op"]).first() + op2 = Operation.objects.filter(id=r["id_op_liee"]).first() op1.linked_operation = op2 op1.save() op2.linked_operation = op1 @@ -991,7 +1110,7 @@ def migrate_accounting(): print("FAIL to link operations: %s" % (repr(e))) cur.close() print("Operations links migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) migrate_companies() migrate_accounting_types() @@ -1003,54 +1122,58 @@ def migrate_accounting(): migrate_operations() make_operation_links() + def migrate_godfathers(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM parrains - """) + """ + ) for r in cur: try: - father = User.objects.filter(id=r['id_utilisateur']).first() - child = User.objects.filter(id=r['id_utilisateur_fillot']).first() + father = User.objects.filter(id=r["id_utilisateur"]).first() + child = User.objects.filter(id=r["id_utilisateur_fillot"]).first() father.godchildren.add(child) father.save() except Exception as e: print("FAIL to migrate godfathering: %s" % (repr(e))) cur.close() print("Godfathers migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) + def migrate_etickets(): FILE_ROOT = "/data/files/" cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM cpt_etickets - """) + """ + ) Eticket.objects.all().delete() print("Etickets deleted") for r in cur: try: - p = Product.objects.filter(id=r['id_produit']).first() + p = Product.objects.filter(id=r["id_produit"]).first() try: - f = File(open(FILE_ROOT + '/' + str(r['banner']) + ".1", 'rb')) + f = File(open(FILE_ROOT + "/" + str(r["banner"]) + ".1", "rb")) except: f = None e = Eticket( - product=p, - secret=to_unicode(r['secret']), - banner=f, - event_title=p.name, - ) + product=p, secret=to_unicode(r["secret"]), banner=f, event_title=p.name + ) e.save() - e.secret=to_unicode(r['secret']) + e.secret = to_unicode(r["secret"]) e.save() except Exception as e: print("FAIL to migrate eticket: %s" % (repr(e))) cur.close() print("Etickets migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) + def migrate_sas(): album_link = {} @@ -1058,34 +1181,44 @@ def migrate_sas(): FILE_ROOT = "/data/sas/" SithFile.objects.filter(id__gte=18892).delete() print("Album/Pictures deleted") - reset_index('core', 'sas') + reset_index("core", "sas") cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_cat_photos - """) + """ + ) root = User.objects.filter(username="root").first() for r in cur: try: - a = Album(name=to_unicode(r['nom_catph']), owner=root, is_moderated=True, parent=None) + a = Album( + name=to_unicode(r["nom_catph"]), + owner=root, + is_moderated=True, + parent=None, + ) a.save() - album_link[str(r['id_catph'])] = a.id + album_link[str(r["id_catph"])] = a.id except Exception as e: print("FAIL to migrate Album: %s" % (repr(e))) print("Album moved, need to make the tree") - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_cat_photos - """) + """ + ) for r in cur: try: - p = Album.objects.filter(id=album_link[str(r['id_catph_parent'])]).first() - a = Album.objects.filter(id=album_link[str(r['id_catph'])]).first() + p = Album.objects.filter(id=album_link[str(r["id_catph_parent"])]).first() + a = Album.objects.filter(id=album_link[str(r["id_catph"])]).first() a.parent = p a.save() - except: pass + except: + pass print("Album migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) with open("albums.link", "w") as f: f.write(str(album_link)) cur.close() @@ -1093,41 +1226,46 @@ def migrate_sas(): chunk = 0 while not finished: cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_photos ORDER BY 'id_photo' LIMIT %s, 1000 - """, (chunk*1000, )) + """, + (chunk * 1000,), + ) has_result = False for r in cur: try: - user = User.objects.filter(id=r['id_utilisateur']).first() or root - parent = Album.objects.filter(id=album_link[str(r['id_catph'])]).first() + user = User.objects.filter(id=r["id_utilisateur"]).first() or root + parent = Album.objects.filter(id=album_link[str(r["id_catph"])]).first() file_name = FILE_ROOT - if r['date_prise_vue']: - file_name += r['date_prise_vue'].strftime("%Y/%m/%d") + if r["date_prise_vue"]: + file_name += r["date_prise_vue"].strftime("%Y/%m/%d") else: - file_name += '/'.join(["1970", "01", "01"]) - file_name += "/" + str(r['id_photo']) + ".jpg" + file_name += "/".join(["1970", "01", "01"]) + file_name += "/" + str(r["id_photo"]) + ".jpg" file = File(open(file_name, "rb")) - file.name = str(r['id_photo']) + ".jpg" + file.name = str(r["id_photo"]) + ".jpg" p = Picture( - name=str(r['id_photo']) + ".jpg", - owner=user, - is_moderated=True, - is_folder=False, - mime_type="image/jpeg", - parent=parent, - file=file, - ) - if r['date_prise_vue']: - p.date = r['date_prise_vue'].replace(tzinfo=timezone('Europe/Paris')) + name=str(r["id_photo"]) + ".jpg", + owner=user, + is_moderated=True, + is_folder=False, + mime_type="image/jpeg", + parent=parent, + file=file, + ) + if r["date_prise_vue"]: + p.date = r["date_prise_vue"].replace( + tzinfo=timezone("Europe/Paris") + ) else: - p.date = r['date_ajout_ph'].replace(tzinfo=timezone('Europe/Paris')) + p.date = r["date_ajout_ph"].replace(tzinfo=timezone("Europe/Paris")) for f in p._meta.local_fields: if f.name == "date": f.auto_now = False @@ -1135,202 +1273,228 @@ def migrate_sas(): p.save() db2 = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) cur2 = db2.cursor(MySQLdb.cursors.SSDictCursor) - cur2.execute(""" + cur2.execute( + """ SELECT * FROM sas_personnes_photos WHERE id_photo = %s - """, (r['id_photo'], )) + """, + (r["id_photo"],), + ) for r2 in cur2: try: - u = User.objects.filter(id=r2['id_utilisateur']).first() + u = User.objects.filter(id=r2["id_utilisateur"]).first() if u: PeoplePictureRelation(user=u, picture=p).save() except: - print("Fail to associate user %d to picture %d" % (r2['id_utilisateur'], p.id)) + print( + "Fail to associate user %d to picture %d" + % (r2["id_utilisateur"], p.id) + ) has_result = True except Exception as e: print("FAIL to migrate Picture: %s" % (repr(e))) cur.close() print("Chunk %d migrated at %s" % (chunk, str(datetime.datetime.now()))) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) chunk += 1 finished = not has_result print("SAS migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) # try: # f = File(open(FILE_ROOT + '/' + str(r['banner']) + ".1", 'rb')) # except: # f = None + def reset_sas_moderators(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute(""" + cur.execute( + """ SELECT * FROM sas_photos WHERE id_utilisateur_moderateur IS NOT NULL - """) + """ + ) for r in cur: try: - name = str(r['id_photo']) + '.jpg' + name = str(r["id_photo"]) + ".jpg" pict = SithFile.objects.filter(name__icontains=name, is_in_sas=True).first() - user = User.objects.filter(id=r['id_utilisateur_moderateur']).first() + user = User.objects.filter(id=r["id_utilisateur_moderateur"]).first() if pict and user: pict.moderator = user pict.save() else: - print("No pict %s (%s) or user %s (%s)" %(pict, name, user, r['id_utilisateur_moderateur'])) + print( + "No pict %s (%s) or user %s (%s)" + % (pict, name, user, r["id_utilisateur_moderateur"]) + ) except Exception as e: print(repr(e)) + def migrate_forum(): print("Migrating forum") + def migrate_forums(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up forums") Forum.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_forum WHERE id_forum <> 1 - """) + """ + ) print(" Migrating forums") for r in cur: try: # parent = Forum.objects.filter(id=r['id_forum_parent']).first() - club = Club.objects.filter(id=r['id_asso']).first() + club = Club.objects.filter(id=r["id_asso"]).first() ae = Club.objects.filter(id=settings.SITH_MAIN_CLUB_ID).first() forum = Forum( - id=r['id_forum'], - name=to_unicode(r['titre_forum']), - description=to_unicode(r['description_forum'])[:511], - is_category=bool(r['categorie_forum']), + id=r["id_forum"], + name=to_unicode(r["titre_forum"]), + description=to_unicode(r["description_forum"])[:511], + is_category=bool(r["categorie_forum"]), # parent=parent, owner_club=club or ae, - number=r['ordre_forum'], - ) + number=r["ordre_forum"], + ) forum.save() except Exception as e: print(" FAIL to migrate forum: %s" % (repr(e))) - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_forum WHERE id_forum_parent <> 1 - """) + """ + ) for r in cur: - parent = Forum.objects.filter(id=r['id_forum_parent']).first() - forum = Forum.objects.filter(id=r['id_forum']).first() + parent = Forum.objects.filter(id=r["id_forum_parent"]).first() + forum = Forum.objects.filter(id=r["id_forum"]).first() forum.parent = parent forum.save() cur.close() print(" Forums migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) def migrate_topics(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up topics") ForumTopic.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_sujet - """) + """ + ) print(" Migrating topics") for r in cur: try: - parent = Forum.objects.filter(id=r['id_forum']).first() + parent = Forum.objects.filter(id=r["id_forum"]).first() saloon = Forum.objects.filter(id=3).first() - author = User.objects.filter(id=r['id_utilisateur']).first() + author = User.objects.filter(id=r["id_utilisateur"]).first() root = User.objects.filter(id=0).first() topic = ForumTopic( - id=r['id_sujet'], + id=r["id_sujet"], author=author or root, forum=parent or saloon, - _title=to_unicode(r['titre_sujet'])[:64], - description=to_unicode(r['soustitre_sujet']), - ) + _title=to_unicode(r["titre_sujet"])[:64], + description=to_unicode(r["soustitre_sujet"]), + ) topic.save() except Exception as e: print(" FAIL to migrate topic: %s" % (repr(e))) cur.close() print(" Topics migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) def migrate_messages(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up messages") ForumMessage.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_message - """) + """ + ) print(" Migrating messages") for r in cur: try: - topic = ForumTopic.objects.filter(id=r['id_sujet']).first() - author = User.objects.filter(id=r['id_utilisateur']).first() + topic = ForumTopic.objects.filter(id=r["id_sujet"]).first() + author = User.objects.filter(id=r["id_utilisateur"]).first() root = User.objects.filter(id=0).first() msg = ForumMessage( - id=r['id_message'], + id=r["id_message"], topic=topic, author=author or root, - title=to_unicode(r['titre_message'])[:63], - date=r['date_message'].replace(tzinfo=timezone('Europe/Paris')), - ) + title=to_unicode(r["titre_message"])[:63], + date=r["date_message"].replace(tzinfo=timezone("Europe/Paris")), + ) try: - if r['syntaxengine_message'] == "doku": - msg.message = doku_to_markdown(to_unicode(r['contenu_message'])) + if r["syntaxengine_message"] == "doku": + msg.message = doku_to_markdown(to_unicode(r["contenu_message"])) else: - msg.message = bbcode_to_markdown(to_unicode(r['contenu_message'])) + msg.message = bbcode_to_markdown( + to_unicode(r["contenu_message"]) + ) except: - msg.message = to_unicode(r['contenu_message']) + msg.message = to_unicode(r["contenu_message"]) msg.save() except Exception as e: print(" FAIL to migrate message: %s" % (repr(e))) cur.close() print(" Messages migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) def migrate_message_infos(): cur = db.cursor(MySQLdb.cursors.SSDictCursor) print(" Cleaning up message meta") ForumMessageMeta.objects.all().delete() - cur.execute(""" + cur.execute( + """ SELECT * FROM frm_modere_info - """) + """ + ) print(" Migrating message meta") ACTIONS = { - "EDIT": "EDIT", - "AUTOEDIT": "EDIT", - "UNDELETE": "UNDELETE", - "DELETE": "DELETE", - "DELETEFIRST": "DELETE", - "AUTODELETE": "DELETE", - } + "EDIT": "EDIT", + "AUTOEDIT": "EDIT", + "UNDELETE": "UNDELETE", + "DELETE": "DELETE", + "DELETEFIRST": "DELETE", + "AUTODELETE": "DELETE", + } for r in cur: try: - msg = ForumMessage.objects.filter(id=r['id_message']).first() - author = User.objects.filter(id=r['id_utilisateur']).first() + msg = ForumMessage.objects.filter(id=r["id_message"]).first() + author = User.objects.filter(id=r["id_utilisateur"]).first() root = User.objects.filter(id=0).first() meta = ForumMessageMeta( message=msg, user=author or root, - date=r['modere_date'].replace(tzinfo=timezone('Europe/Paris')), - action=ACTIONS[r['modere_action']], - ) + date=r["modere_date"].replace(tzinfo=timezone("Europe/Paris")), + action=ACTIONS[r["modere_action"]], + ) meta.save() except Exception as e: print(" FAIL to migrate message meta: %s" % (repr(e))) cur.close() print(" Messages meta migrated at %s" % datetime.datetime.now()) - print(" Running time: %s" % (datetime.datetime.now()-start)) + print(" Running time: %s" % (datetime.datetime.now() - start)) migrate_forums() migrate_topics() migrate_messages() migrate_message_infos() print("Forum migrated at %s" % datetime.datetime.now()) - print("Running time: %s" % (datetime.datetime.now()-start)) + print("Running time: %s" % (datetime.datetime.now() - start)) def migrate_mailings(): @@ -1342,51 +1506,66 @@ def migrate_mailings(): print("Migrating old mailing database") - cur.execute(""" + cur.execute( + """ SELECT * FROM mailing - """) + """ + ) moderator = User.objects.get(id=0) for mailing in cur: - club = Club.objects.filter(id=mailing['id_asso_parent']) + club = Club.objects.filter(id=mailing["id_asso_parent"]) if club.exists(): print(mailing) club = club.first() - if mailing['nom']: - mailing['nom'] = '.' + mailing['nom'] - Mailing(id=mailing['id_mailing'], club=club, email=to_unicode(club.unix_name + mailing['nom']), - moderator=moderator, is_moderated=(mailing['is_valid'] > 0)).save() + if mailing["nom"]: + mailing["nom"] = "." + mailing["nom"] + Mailing( + id=mailing["id_mailing"], + club=club, + email=to_unicode(club.unix_name + mailing["nom"]), + moderator=moderator, + is_moderated=(mailing["is_valid"] > 0), + ).save() print("-------------------") - cur.execute(""" + cur.execute( + """ SELECT * FROM mailing_membres - """) + """ + ) for mailing_sub in cur: - mailing = Mailing.objects.filter(id=mailing_sub['id_mailing']) + mailing = Mailing.objects.filter(id=mailing_sub["id_mailing"]) if mailing.exists(): print(mailing_sub) mailing = mailing.first() - if mailing_sub['id_user'] and User.objects.filter(id=mailing_sub['id_user']).exists(): - user = User.objects.get(id=mailing_sub['id_user']) + if ( + mailing_sub["id_user"] + and User.objects.filter(id=mailing_sub["id_user"]).exists() + ): + user = User.objects.get(id=mailing_sub["id_user"]) MailingSubscription(mailing=mailing, user=user, email=user.email).save() - elif mailing_sub['email']: - MailingSubscription(mailing=mailing, email=to_unicode(mailing_sub['email'])).save() + elif mailing_sub["email"]: + MailingSubscription( + mailing=mailing, email=to_unicode(mailing_sub["email"]) + ).save() def migrate_club_again(): - cur = db.cursor(MySQLdb.cursors.SSDictCursor) - cur.execute("SELECT * FROM asso") + cur = db.cursor(MySQLdb.cursors.SSDictCursor) + cur.execute("SELECT * FROM asso") - print("Migrating club is_active") + print("Migrating club is_active") - for club in cur: - try: - c = Club.objects.get(unix_name=club['nom_unix_asso']) - c.is_active = club['hidden'] == 0 - c.save() - except: pass + for club in cur: + try: + c = Club.objects.get(unix_name=club["nom_unix_asso"]) + c.is_active = club["hidden"] == 0 + c.save() + except: + pass def main(): diff --git a/rootplace/__init__.py b/rootplace/__init__.py index 0a9419f8..0ace29c4 100644 --- a/rootplace/__init__.py +++ b/rootplace/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/rootplace/urls.py b/rootplace/urls.py index 6ab433e3..b94b4030 100644 --- a/rootplace/urls.py +++ b/rootplace/urls.py @@ -26,6 +26,4 @@ from django.conf.urls import url from rootplace.views import * -urlpatterns = [ - url(r'^merge$', MergeUsersView.as_view(), name='merge'), -] +urlpatterns = [url(r"^merge$", MergeUsersView.as_view(), name="merge")] diff --git a/rootplace/views.py b/rootplace/views.py index a03ab3ae..ed9c2773 100644 --- a/rootplace/views.py +++ b/rootplace/views.py @@ -59,7 +59,7 @@ def merge_users(u1, u2): u1.godfathers.add(u) u1.save() for i in u2.invoices.all(): - for f in i._meta.local_fields: # I have sadly not found anything better :/ + for f in i._meta.local_fields: # I have sadly not found anything better :/ if f.name == "date": f.auto_now = False u1.invoices.add(i) @@ -88,8 +88,12 @@ def merge_users(u1, u2): class MergeForm(forms.Form): - user1 = AutoCompleteSelectField('users', label=_("User that will be kept"), help_text=None, required=True) - user2 = AutoCompleteSelectField('users', label=_("User that will be deleted"), help_text=None, required=True) + user1 = AutoCompleteSelectField( + "users", label=_("User that will be kept"), help_text=None, required=True + ) + user2 = AutoCompleteSelectField( + "users", label=_("User that will be deleted"), help_text=None, required=True + ) class MergeUsersView(FormView): @@ -103,8 +107,10 @@ class MergeUsersView(FormView): raise PermissionDenied def form_valid(self, form): - self.final_user = merge_users(form.cleaned_data['user1'], form.cleaned_data['user2']) + self.final_user = merge_users( + form.cleaned_data["user1"], form.cleaned_data["user2"] + ) return super(MergeUsersView, self).form_valid(form) def get_success_url(self): - return reverse('core:user_profile', kwargs={'user_id': self.final_user.id}) + return reverse("core:user_profile", kwargs={"user_id": self.final_user.id}) diff --git a/sas/__init__.py b/sas/__init__.py index 0a9419f8..0ace29c4 100644 --- a/sas/__init__.py +++ b/sas/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/sas/migrations/0001_initial.py b/sas/migrations/0001_initial.py index 573a7274..a089648b 100644 --- a/sas/migrations/0001_initial.py +++ b/sas/migrations/0001_initial.py @@ -6,27 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('core', '0006_auto_20161108_1703'), - ] + dependencies = [("core", "0006_auto_20161108_1703")] operations = [ migrations.CreateModel( - name='Album', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.sithfile',), + name="Album", fields=[], options={"proxy": True}, bases=("core.sithfile",) ), migrations.CreateModel( - name='Picture', - fields=[ - ], - options={ - 'proxy': True, - }, - bases=('core.sithfile',), + name="Picture", fields=[], options={"proxy": True}, bases=("core.sithfile",) ), ] diff --git a/sas/migrations/0002_auto_20161119_1241.py b/sas/migrations/0002_auto_20161119_1241.py index ff51d24c..ed759ebc 100644 --- a/sas/migrations/0002_auto_20161119_1241.py +++ b/sas/migrations/0002_auto_20161119_1241.py @@ -9,20 +9,39 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('sas', '0001_initial'), + ("sas", "0001_initial"), ] operations = [ migrations.CreateModel( - name='PeoplePictureRelation', + name="PeoplePictureRelation", fields=[ - ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), - ('picture', models.ForeignKey(related_name='people', to='sas.Picture', verbose_name='picture')), - ('user', models.ForeignKey(related_name='pictures', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ( + "id", + models.AutoField( + primary_key=True, + verbose_name="ID", + auto_created=True, + serialize=False, + ), + ), + ( + "picture", + models.ForeignKey( + related_name="people", to="sas.Picture", verbose_name="picture" + ), + ), + ( + "user", + models.ForeignKey( + related_name="pictures", + to=settings.AUTH_USER_MODEL, + verbose_name="user", + ), + ), ], ), migrations.AlterUniqueTogether( - name='peoplepicturerelation', - unique_together=set([('user', 'picture')]), + name="peoplepicturerelation", unique_together=set([("user", "picture")]) ), ] diff --git a/sas/models.py b/sas/models.py index dafe9ece..a92d41c6 100644 --- a/sas/models.py +++ b/sas/models.py @@ -38,14 +38,20 @@ from core.utils import resize_image, exif_auto_rotate class SASPictureManager(models.Manager): def get_queryset(self): - return super(SASPictureManager, self).get_queryset().filter(is_in_sas=True, - is_folder=False) + return ( + super(SASPictureManager, self) + .get_queryset() + .filter(is_in_sas=True, is_folder=False) + ) class SASAlbumManager(models.Manager): def get_queryset(self): - return super(SASAlbumManager, self).get_queryset().filter(is_in_sas=True, - is_folder=True) + return ( + super(SASAlbumManager, self) + .get_queryset() + .filter(is_in_sas=True, is_folder=True) + ) class Picture(SithFile): @@ -56,7 +62,9 @@ class Picture(SithFile): @property def is_vertical(self): - with open(os.path.join(settings.MEDIA_ROOT, self.file.name).encode('utf-8'), 'rb') as f: + with open( + os.path.join(settings.MEDIA_ROOT, self.file.name).encode("utf-8"), "rb" + ) as f: im = Image.open(BytesIO(f.read())) (w, h) = im.size return (w / h) < 1 @@ -64,24 +72,27 @@ class Picture(SithFile): def can_be_edited_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) # or user.can_edit(file) + return user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ) # or user.can_edit(file) def can_be_viewed_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return self.can_be_edited_by(user) or (self.is_in_sas and self.is_moderated and - user.was_subscribed) # or user.can_view(file) + return self.can_be_edited_by(user) or ( + self.is_in_sas and self.is_moderated and user.was_subscribed + ) # or user.can_view(file) def get_download_url(self): - return reverse('sas:download', kwargs={'picture_id': self.id}) + return reverse("sas:download", kwargs={"picture_id": self.id}) def get_download_compressed_url(self): - return reverse('sas:download_compressed', kwargs={'picture_id': self.id}) + return reverse("sas:download_compressed", kwargs={"picture_id": self.id}) def get_download_thumb_url(self): - return reverse('sas:download_thumb', kwargs={'picture_id': self.id}) + return reverse("sas:download_thumb", kwargs={"picture_id": self.id}) def get_absolute_url(self): - return reverse('sas:picture', kwargs={'picture_id': self.id}) + return reverse("sas:picture", kwargs={"picture_id": self.id}) def generate_thumbnails(self, overwrite=False): im = Image.open(BytesIO(self.file.read())) @@ -89,9 +100,9 @@ class Picture(SithFile): im = exif_auto_rotate(im) except: pass - file = resize_image(im, max(im.size), self.mime_type.split('/')[-1]) - thumb = resize_image(im, 200, self.mime_type.split('/')[-1]) - compressed = resize_image(im, 1200, self.mime_type.split('/')[-1]) + file = resize_image(im, max(im.size), self.mime_type.split("/")[-1]) + thumb = resize_image(im, 200, self.mime_type.split("/")[-1]) + compressed = resize_image(im, 1200, self.mime_type.split("/")[-1]) if overwrite: self.file.delete() self.thumbnail.delete() @@ -105,28 +116,60 @@ class Picture(SithFile): self.save() def rotate(self, degree): - for attr in ['file', 'compressed', 'thumbnail']: + for attr in ["file", "compressed", "thumbnail"]: name = self.__getattribute__(attr).name - with open(os.path.join(settings.MEDIA_ROOT, name).encode('utf-8'), 'r+b') as file: + with open( + os.path.join(settings.MEDIA_ROOT, name).encode("utf-8"), "r+b" + ) as file: if file: im = Image.open(BytesIO(file.read())) file.seek(0) im = im.rotate(degree, expand=True) - im.save(fp=file, format=self.mime_type.split('/')[-1].upper(), quality=90, optimize=True, progressive=True) + im.save( + fp=file, + format=self.mime_type.split("/")[-1].upper(), + quality=90, + optimize=True, + progressive=True, + ) def get_next(self): if self.is_moderated: - return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, - id__gt=self.id).order_by('id').first() + return ( + self.parent.children.filter( + is_moderated=True, + asked_for_removal=False, + is_folder=False, + id__gt=self.id, + ) + .order_by("id") + .first() + ) else: - return Picture.objects.filter(id__gt=self.id, is_moderated=False).order_by('id').first() + return ( + Picture.objects.filter(id__gt=self.id, is_moderated=False) + .order_by("id") + .first() + ) def get_previous(self): if self.is_moderated: - return self.parent.children.filter(is_moderated=True, asked_for_removal=False, is_folder=False, - id__lt=self.id).order_by('id').last() + return ( + self.parent.children.filter( + is_moderated=True, + asked_for_removal=False, + is_folder=False, + id__lt=self.id, + ) + .order_by("id") + .last() + ) else: - return Picture.objects.filter(id__lt=self.id, is_moderated=False).order_by('-id').first() + return ( + Picture.objects.filter(id__lt=self.id, is_moderated=False) + .order_by("-id") + .first() + ) class Album(SithFile): @@ -145,25 +188,34 @@ class Album(SithFile): def can_be_edited_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID) # or user.can_edit(file) + return user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ) # or user.can_edit(file) def can_be_viewed_by(self, user): # file = SithFile.objects.filter(id=self.id).first() - return self.can_be_edited_by(user) or (self.is_in_sas and self.is_moderated and - user.was_subscribed) # or user.can_view(file) + return self.can_be_edited_by(user) or ( + self.is_in_sas and self.is_moderated and user.was_subscribed + ) # or user.can_view(file) def get_absolute_url(self): - return reverse('sas:album', kwargs={'album_id': self.id}) + return reverse("sas:album", kwargs={"album_id": self.id}) def get_download_url(self): - return reverse('sas:album_preview', kwargs={'album_id': self.id}) + return reverse("sas:album_preview", kwargs={"album_id": self.id}) def generate_thumbnail(self): - p = self.children_pictures.order_by('?').first() or self.children_albums.exclude(file=None).exclude(file="").order_by('?').first() + p = ( + self.children_pictures.order_by("?").first() + or self.children_albums.exclude(file=None) + .exclude(file="") + .order_by("?") + .first() + ) if p and p.file: im = Image.open(BytesIO(p.file.read())) self.file = resize_image(im, 200, "jpeg") - self.file.name = self.name + '/thumb.jpg' + self.file.name = self.name + "/thumb.jpg" self.save() @@ -182,11 +234,20 @@ class PeoplePictureRelation(models.Model): The PeoplePictureRelation class makes the connection between User and Picture """ - user = models.ForeignKey(User, verbose_name=_('user'), related_name="pictures", null=False, blank=False) - picture = models.ForeignKey(Picture, verbose_name=_('picture'), related_name="people", null=False, blank=False) + + user = models.ForeignKey( + User, verbose_name=_("user"), related_name="pictures", null=False, blank=False + ) + picture = models.ForeignKey( + Picture, + verbose_name=_("picture"), + related_name="people", + null=False, + blank=False, + ) class Meta: - unique_together = ['user', 'picture'] + unique_together = ["user", "picture"] def __str__(self): return self.user.get_display_name() + " - " + str(self.picture) diff --git a/sas/urls.py b/sas/urls.py index e4804292..7b53c0ec 100644 --- a/sas/urls.py +++ b/sas/urls.py @@ -27,17 +27,35 @@ from django.conf.urls import url from sas.views import * urlpatterns = [ - url(r'^$', SASMainView.as_view(), name='main'), - url(r'^moderation$', ModerationView.as_view(), name='moderation'), - url(r'^album/(?P[0-9]+)$', AlbumView.as_view(), name='album'), - url(r'^album/(?P[0-9]+)/upload$', AlbumUploadView.as_view(), name='album_upload'), - url(r'^album/(?P[0-9]+)/edit$', AlbumEditView.as_view(), name='album_edit'), - url(r'^album/(?P[0-9]+)/preview$', send_album, name='album_preview'), - url(r'^picture/(?P[0-9]+)$', PictureView.as_view(), name='picture'), - url(r'^picture/(?P[0-9]+)/edit$', PictureEditView.as_view(), name='picture_edit'), - url(r'^picture/(?P[0-9]+)/download$', send_pict, name='download'), - url(r'^picture/(?P[0-9]+)/download/compressed$', send_compressed, name='download_compressed'), - url(r'^picture/(?P[0-9]+)/download/thumb$', send_thumb, name='download_thumb'), + url(r"^$", SASMainView.as_view(), name="main"), + url(r"^moderation$", ModerationView.as_view(), name="moderation"), + url(r"^album/(?P[0-9]+)$", AlbumView.as_view(), name="album"), + url( + r"^album/(?P[0-9]+)/upload$", + AlbumUploadView.as_view(), + name="album_upload", + ), + url( + r"^album/(?P[0-9]+)/edit$", AlbumEditView.as_view(), name="album_edit" + ), + url(r"^album/(?P[0-9]+)/preview$", send_album, name="album_preview"), + url(r"^picture/(?P[0-9]+)$", PictureView.as_view(), name="picture"), + url( + r"^picture/(?P[0-9]+)/edit$", + PictureEditView.as_view(), + name="picture_edit", + ), + url(r"^picture/(?P[0-9]+)/download$", send_pict, name="download"), + url( + r"^picture/(?P[0-9]+)/download/compressed$", + send_compressed, + name="download_compressed", + ), + url( + r"^picture/(?P[0-9]+)/download/thumb$", + send_thumb, + name="download_thumb", + ), # url(r'^album/new$', AlbumCreateView.as_view(), name='album_new'), # url(r'^(?P[0-9]+)/$', ClubView.as_view(), name='club_view'), ] diff --git a/sas/views.py b/sas/views.py index e6d20e10..859b4a7f 100644 --- a/sas/views.py +++ b/sas/views.py @@ -44,22 +44,43 @@ from sas.models import Picture, Album, PeoplePictureRelation class SASForm(forms.Form): - album_name = forms.CharField(label=_("Add a new album"), max_length=30, required=False) - images = forms.ImageField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Upload images"), - required=False) + album_name = forms.CharField( + label=_("Add a new album"), max_length=30, required=False + ) + images = forms.ImageField( + widget=forms.ClearableFileInput(attrs={"multiple": True}), + label=_("Upload images"), + required=False, + ) def process(self, parent, owner, files, automodere=False): try: - if self.cleaned_data['album_name'] != "": - album = Album(parent=parent, name=self.cleaned_data['album_name'], owner=owner, is_moderated=automodere) + if self.cleaned_data["album_name"] != "": + album = Album( + parent=parent, + name=self.cleaned_data["album_name"], + owner=owner, + is_moderated=automodere, + ) album.clean() album.save() except Exception as e: - self.add_error(None, _("Error creating album %(album)s: %(msg)s") % - {'album': self.cleaned_data['album_name'], 'msg': repr(e)}) + self.add_error( + None, + _("Error creating album %(album)s: %(msg)s") + % {"album": self.cleaned_data["album_name"], "msg": repr(e)}, + ) for f in files: - new_file = Picture(parent=parent, name=f.name, file=f, owner=owner, mime_type=f.content_type, size=f._size, - is_folder=False, is_moderated=automodere) + new_file = Picture( + parent=parent, + name=f.name, + file=f, + owner=owner, + mime_type=f.content_type, + size=f._size, + is_folder=False, + is_moderated=automodere, + ) if automodere: new_file.moderator = owner try: @@ -67,30 +88,41 @@ class SASForm(forms.Form): new_file.generate_thumbnails() new_file.save() except Exception as e: - self.add_error(None, _("Error uploading file %(file_name)s: %(msg)s") % {'file_name': f, 'msg': repr(e)}) + self.add_error( + None, + _("Error uploading file %(file_name)s: %(msg)s") + % {"file_name": f, "msg": repr(e)}, + ) class RelationForm(forms.ModelForm): class Meta: model = PeoplePictureRelation - fields = ['picture'] - widgets = {'picture': forms.HiddenInput} - users = AutoCompleteSelectMultipleField('users', show_help_text=False, help_text="", label=_("Add user"), required=False) + fields = ["picture"] + widgets = {"picture": forms.HiddenInput} + + users = AutoCompleteSelectMultipleField( + "users", show_help_text=False, help_text="", label=_("Add user"), required=False + ) class SASMainView(FormView): form_class = SASForm template_name = "sas/main.jinja" - success_url = reverse_lazy('sas:main') + success_url = reverse_lazy("sas:main") def post(self, request, *args, **kwargs): self.form = self.get_form() parent = SithFile.objects.filter(id=settings.SITH_SAS_ROOT_DIR_ID).first() - files = request.FILES.getlist('images') + files = request.FILES.getlist("images") root = User.objects.filter(username="root").first() - if request.user.is_authenticated() and request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): + if request.user.is_authenticated() and request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ): if self.form.is_valid(): - self.form.process(parent=parent, owner=root, files=files, automodere=True) + self.form.process( + parent=parent, owner=root, files=files, automodere=True + ) if self.form.is_valid(): return super(SASMainView, self).form_valid(self.form) else: @@ -99,8 +131,10 @@ class SASMainView(FormView): def get_context_data(self, **kwargs): kwargs = super(SASMainView, self).get_context_data(**kwargs) - kwargs['categories'] = Album.objects.filter(parent__id=settings.SITH_SAS_ROOT_DIR_ID).order_by('id') - kwargs['latest'] = Album.objects.filter(is_moderated=True).order_by('-id')[:5] + kwargs["categories"] = Album.objects.filter( + parent__id=settings.SITH_SAS_ROOT_DIR_ID + ).order_by("id") + kwargs["latest"] = Album.objects.filter(is_moderated=True).order_by("-id")[:5] return kwargs @@ -111,23 +145,27 @@ class PictureView(CanViewMixin, DetailView, FormMixin): template_name = "sas/picture.jinja" def get_initial(self): - return {'picture': self.object} + return {"picture": self.object} def get(self, request, *args, **kwargs): self.object = self.get_object() self.form = self.get_form() - if 'rotate_right' in request.GET.keys(): + if "rotate_right" in request.GET.keys(): self.object.rotate(270) - if 'rotate_left' in request.GET.keys(): + if "rotate_left" in request.GET.keys(): self.object.rotate(90) - if 'remove_user' in request.GET.keys(): + if "remove_user" in request.GET.keys(): try: - user = User.objects.filter(id=int(request.GET['remove_user'])).first() - if user.id == request.user.id or request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): - PeoplePictureRelation.objects.filter(user=user, picture=self.object).delete() + user = User.objects.filter(id=int(request.GET["remove_user"])).first() + if user.id == request.user.id or request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ): + PeoplePictureRelation.objects.filter( + user=user, picture=self.object + ).delete() except: pass - if 'ask_removal' in request.GET.keys(): + if "ask_removal" in request.GET.keys(): self.object.is_moderated = False self.object.asked_for_removal = True self.object.save() @@ -139,12 +177,19 @@ class PictureView(CanViewMixin, DetailView, FormMixin): self.form = self.get_form() if request.user.is_authenticated() and request.user.was_subscribed: if self.form.is_valid(): - for uid in self.form.cleaned_data['users']: + for uid in self.form.cleaned_data["users"]: u = User.objects.filter(id=uid).first() - PeoplePictureRelation(user=u, - picture=self.form.cleaned_data['picture']).save() - if not u.notifications.filter(type="NEW_PICTURES", viewed=False).exists(): - Notification(user=u, url=reverse("core:user_pictures", kwargs={'user_id': u.id}), type="NEW_PICTURES").save() + PeoplePictureRelation( + user=u, picture=self.form.cleaned_data["picture"] + ).save() + if not u.notifications.filter( + type="NEW_PICTURES", viewed=False + ).exists(): + Notification( + user=u, + url=reverse("core:user_pictures", kwargs={"user_id": u.id}), + type="NEW_PICTURES", + ).save() return super(PictureView, self).form_valid(self.form) else: self.form.add_error(None, _("You do not have the permission to do that")) @@ -152,16 +197,17 @@ class PictureView(CanViewMixin, DetailView, FormMixin): def get_context_data(self, **kwargs): kwargs = super(PictureView, self).get_context_data(**kwargs) - kwargs['form'] = self.form + kwargs["form"] = self.form return kwargs def get_success_url(self): - return reverse('sas:picture', kwargs={'picture_id': self.object.id}) + return reverse("sas:picture", kwargs={"picture_id": self.object.id}) def send_album(request, album_id): return send_file(request, album_id, Album) + def send_pict(request, picture_id): return send_file(request, picture_id, Picture) @@ -185,11 +231,17 @@ class AlbumUploadView(CanViewMixin, DetailView, FormMixin): self.object.generate_thumbnail() self.form = self.get_form() parent = SithFile.objects.filter(id=self.object.id).first() - files = request.FILES.getlist('images') + files = request.FILES.getlist("images") if request.user.is_authenticated() and request.user.is_subscribed: if self.form.is_valid(): - self.form.process(parent=parent, owner=request.user, files=files, - automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) + self.form.process( + parent=parent, + owner=request.user, + files=files, + automodere=request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ), + ) if self.form.is_valid(): return HttpResponse(str(self.form.errors), status=200) return HttpResponse(str(self.form.errors), status=500) @@ -203,8 +255,8 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): def get(self, request, *args, **kwargs): self.form = self.get_form() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] return super(AlbumView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -212,16 +264,22 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): if not self.object.file: self.object.generate_thumbnail() self.form = self.get_form() - if 'clipboard' not in request.session.keys(): - request.session['clipboard'] = [] + if "clipboard" not in request.session.keys(): + request.session["clipboard"] = [] if request.user.can_edit(self.object): # Handle the copy-paste functions FileView.handle_clipboard(request, self.object) parent = SithFile.objects.filter(id=self.object.id).first() - files = request.FILES.getlist('images') + files = request.FILES.getlist("images") if request.user.is_authenticated() and request.user.is_subscribed: if self.form.is_valid(): - self.form.process(parent=parent, owner=request.user, files=files, - automodere=request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID)) + self.form.process( + parent=parent, + owner=request.user, + files=files, + automodere=request.user.is_in_group( + settings.SITH_GROUP_SAS_ADMIN_ID + ), + ) if self.form.is_valid(): return super(AlbumView, self).form_valid(self.form) else: @@ -229,14 +287,17 @@ class AlbumView(CanViewMixin, DetailView, FormMixin): return self.form_invalid(self.form) def get_success_url(self): - return reverse('sas:album', kwargs={'album_id': self.object.id}) + return reverse("sas:album", kwargs={"album_id": self.object.id}) def get_context_data(self, **kwargs): kwargs = super(AlbumView, self).get_context_data(**kwargs) - kwargs['form'] = self.form - kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) + kwargs["form"] = self.form + kwargs["clipboard"] = SithFile.objects.filter( + id__in=self.request.session["clipboard"] + ) return kwargs + # Admin views @@ -251,12 +312,12 @@ class ModerationView(TemplateView): def post(self, request, *args, **kwargs): if request.user.is_in_group(settings.SITH_GROUP_SAS_ADMIN_ID): try: - a = Album.objects.filter(id=request.POST['album_id']).first() - if 'moderate' in request.POST.keys(): + a = Album.objects.filter(id=request.POST["album_id"]).first() + if "moderate" in request.POST.keys(): a.moderator = request.user a.is_moderated = True a.save() - elif 'delete' in request.POST.keys(): + elif "delete" in request.POST.keys(): a.delete() except: pass @@ -264,45 +325,52 @@ class ModerationView(TemplateView): def get_context_data(self, **kwargs): kwargs = super(ModerationView, self).get_context_data(**kwargs) - kwargs['albums_to_moderate'] = Album.objects.filter(is_moderated=False, is_in_sas=True, - is_folder=True).order_by('id') - kwargs['pictures'] = Picture.objects.filter(is_moderated=False, is_in_sas=True, is_folder=False) - kwargs['albums'] = Album.objects.filter(id__in=kwargs['pictures'].values('parent').distinct('parent')) + kwargs["albums_to_moderate"] = Album.objects.filter( + is_moderated=False, is_in_sas=True, is_folder=True + ).order_by("id") + kwargs["pictures"] = Picture.objects.filter( + is_moderated=False, is_in_sas=True, is_folder=False + ) + kwargs["albums"] = Album.objects.filter( + id__in=kwargs["pictures"].values("parent").distinct("parent") + ) return kwargs class PictureEditForm(forms.ModelForm): class Meta: model = Picture - fields = ['name', 'parent'] - parent = make_ajax_field(Picture, 'parent', 'files', help_text="") + fields = ["name", "parent"] + + parent = make_ajax_field(Picture, "parent", "files", help_text="") class AlbumEditForm(forms.ModelForm): class Meta: model = Album - fields = ['name', 'date', 'file', 'parent', 'edit_groups'] + fields = ["name", "date", "file", "parent", "edit_groups"] + date = forms.DateField(label=_("Date"), widget=SelectDate, required=True) - parent = make_ajax_field(Album, 'parent', 'files', help_text="") - edit_groups = make_ajax_field(Album, 'edit_groups', 'groups', help_text="") + parent = make_ajax_field(Album, "parent", "files", help_text="") + edit_groups = make_ajax_field(Album, "edit_groups", "groups", help_text="") recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) class PictureEditView(CanEditMixin, UpdateView): model = Picture form_class = PictureEditForm - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" pk_url_kwarg = "picture_id" class AlbumEditView(CanEditMixin, UpdateView): model = Album form_class = AlbumEditForm - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" pk_url_kwarg = "album_id" def form_valid(self, form): ret = super(AlbumEditView, self).form_valid(form) - if form.cleaned_data['recursive']: + if form.cleaned_data["recursive"]: self.object.apply_rights_recursively(True) return ret diff --git a/sith/__init__.py b/sith/__init__.py index 0a9419f8..0ace29c4 100644 --- a/sith/__init__.py +++ b/sith/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/sith/settings.py b/sith/settings.py index e40bc343..af52ebcb 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -43,19 +43,19 @@ from django.utils.translation import ugettext_lazy as _ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -os.environ['HTTPS'] = "off" +os.environ["HTTPS"] = "off" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = '(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2' +SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False -INTERNAL_IPS = ['127.0.0.1'] +INTERNAL_IPS = ["127.0.0.1"] -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] # Application definition @@ -63,50 +63,50 @@ ALLOWED_HOSTS = ['*'] SITE_ID = 4000 INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django_jinja', - 'rest_framework', - 'ajax_select', - 'haystack', - 'captcha', - 'core', - 'club', - 'subscription', - 'accounting', - 'counter', - 'eboutic', - 'launderette', - 'api', - 'rootplace', - 'sas', - 'com', - 'election', - 'forum', - 'stock', - 'trombi', - 'matmat', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "django_jinja", + "rest_framework", + "ajax_select", + "haystack", + "captcha", + "core", + "club", + "subscription", + "accounting", + "counter", + "eboutic", + "launderette", + "api", + "rootplace", + "sas", + "com", + "election", + "forum", + "stock", + "trombi", + "matmat", ) MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', - 'core.middleware.AuthenticationMiddleware', + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.auth.middleware.SessionAuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django.middleware.security.SecurityMiddleware", + "core.middleware.AuthenticationMiddleware", ) -ROOT_URLCONF = 'sith.urls' +ROOT_URLCONF = "sith.urls" TEMPLATES = [ { @@ -163,44 +163,44 @@ TEMPLATES = [ "autoescape": True, "auto_reload": True, "translation_engine": "django.utils.translation", - } + }, }, { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ] }, }, ] HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine', - 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), - }, + "default": { + "ENGINE": "haystack.backends.whoosh_backend.WhooshEngine", + "PATH": os.path.join(os.path.dirname(__file__), "whoosh_index"), + } } -HAYSTACK_SIGNAL_PROCESSOR = 'core.search_indexes.UserOnlySignalProcessor' +HAYSTACK_SIGNAL_PROCESSOR = "core.search_indexes.UserOnlySignalProcessor" SASS_PRECISION = 8 -WSGI_APPLICATION = 'sith.wsgi.application' +WSGI_APPLICATION = "sith.wsgi.application" # Database # https://docs.djangoproject.com/en/1.8/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } @@ -208,14 +208,11 @@ DATABASES = { # Internationalization # https://docs.djangoproject.com/en/1.8/topics/i18n/ -LANGUAGE_CODE = 'fr-FR' +LANGUAGE_CODE = "fr-FR" -LANGUAGES = [ - ('en', _('English')), - ('fr', _('French')), -] +LANGUAGES = [("en", _("English")), ("fr", _("French"))] -TIME_ZONE = 'Europe/Paris' +TIME_ZONE = "Europe/Paris" USE_I18N = True @@ -223,9 +220,7 @@ USE_L10N = True USE_TZ = True -LOCALE_PATHS = ( - os.path.join(BASE_DIR, "locale"), -) +LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) PHONENUMBER_DEFAULT_REGION = "FR" @@ -233,33 +228,33 @@ PHONENUMBER_DEFAULT_REGION = "FR" EXTERNAL_RES = True # Medias -MEDIA_ROOT = './data/' -MEDIA_URL = '/data/' +MEDIA_ROOT = "./data/" +MEDIA_URL = "/data/" # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ -STATIC_URL = '/static/' -STATIC_ROOT = './static/' +STATIC_URL = "/static/" +STATIC_ROOT = "./static/" # Static files finders which allow to see static folder in all apps STATICFILES_FINDERS = [ - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - 'core.scss.finder.ScssFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "core.scss.finder.ScssFinder", ] # Auth configuration -AUTH_USER_MODEL = 'core.User' -AUTH_ANONYMOUS_MODEL = 'core.models.AnonymousUser' -LOGIN_URL = '/login' -LOGOUT_URL = '/logout' -LOGIN_REDIRECT_URL = '/' +AUTH_USER_MODEL = "core.User" +AUTH_ANONYMOUS_MODEL = "core.models.AnonymousUser" +LOGIN_URL = "/login" +LOGOUT_URL = "/logout" +LOGIN_REDIRECT_URL = "/" DEFAULT_FROM_EMAIL = "bibou@git.an" SITH_COM_EMAIL = "bibou_com@git.an" # Email -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_HOST = "localhost" EMAIL_PORT = 25 @@ -267,12 +262,12 @@ EMAIL_PORT = 25 IS_OLD_MYSQL_PRESENT = False OLD_MYSQL_INFOS = { - 'host': 'ae-db', - 'user': "my_user", - 'passwd': "password", - 'db': "ae2-db", - 'charset': 'utf8', - 'use_unicode': True, + "host": "ae-db", + "user": "my_user", + "passwd": "password", + "db": "ae2-db", + "charset": "utf8", + "use_unicode": True, } @@ -281,25 +276,27 @@ SITH_NAME = "Sith website" SITH_TWITTER = "@ae_utbm" # AE configuration -SITH_MAIN_CLUB_ID = 1 # TODO: keep only that first setting, with the ID, and do the same for the other clubs +SITH_MAIN_CLUB_ID = ( + 1 +) # TODO: keep only that first setting, with the ID, and do the same for the other clubs SITH_MAIN_CLUB = { - 'name': "AE", - 'unix_name': "ae", - 'address': "6 Boulevard Anatole France, 90000 Belfort" + "name": "AE", + "unix_name": "ae", + "address": "6 Boulevard Anatole France, 90000 Belfort", } # Bar managers SITH_BAR_MANAGER = { - 'name': "BdF", - 'unix_name': "bdf", - 'address': "6 Boulevard Anatole France, 90000 Belfort" + "name": "BdF", + "unix_name": "bdf", + "address": "6 Boulevard Anatole France, 90000 Belfort", } # Launderette managers SITH_LAUNDERETTE_MANAGER = { - 'name': "Laverie", - 'unix_name': "laverie", - 'address': "6 Boulevard Anatole France, 90000 Belfort" + "name": "Laverie", + "unix_name": "laverie", + "address": "6 Boulevard Anatole France, 90000 Belfort", } # Main root for club pages @@ -343,9 +340,9 @@ SITH_SAS_ROOT_DIR_ID = 4 SITH_BOARD_SUFFIX = "-bureau" SITH_MEMBER_SUFFIX = "-membres" -SITH_MAIN_BOARD_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_BOARD_SUFFIX -SITH_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB['unix_name'] + SITH_MEMBER_SUFFIX -SITH_BAR_MANAGER_BOARD_GROUP = SITH_BAR_MANAGER['unix_name'] + SITH_BOARD_SUFFIX +SITH_MAIN_BOARD_GROUP = SITH_MAIN_CLUB["unix_name"] + SITH_BOARD_SUFFIX +SITH_MAIN_MEMBERS_GROUP = SITH_MAIN_CLUB["unix_name"] + SITH_MEMBER_SUFFIX +SITH_BAR_MANAGER_BOARD_GROUP = SITH_BAR_MANAGER["unix_name"] + SITH_BOARD_SUFFIX SITH_PROFILE_DEPARTMENTS = [ ("TC", _("TC")), @@ -364,50 +361,46 @@ SITH_PROFILE_DEPARTMENTS = [ ] SITH_ACCOUNTING_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CASH', _('Cash')), - ('TRANSFERT', _('Transfert')), - ('CARD', _('Credit card')), + ("CHECK", _("Check")), + ("CASH", _("Cash")), + ("TRANSFERT", _("Transfert")), + ("CARD", _("Credit card")), ] SITH_SUBSCRIPTION_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CARD', _('Credit card')), - ('CASH', _('Cash')), - ('EBOUTIC', _('Eboutic')), - ('OTHER', _('Other')), + ("CHECK", _("Check")), + ("CARD", _("Credit card")), + ("CASH", _("Cash")), + ("EBOUTIC", _("Eboutic")), + ("OTHER", _("Other")), ] SITH_SUBSCRIPTION_LOCATIONS = [ - ('BELFORT', _('Belfort')), - ('SEVENANS', _('Sevenans')), - ('MONTBELIARD', _('Montbéliard')), - ('EBOUTIC', _('Eboutic')), + ("BELFORT", _("Belfort")), + ("SEVENANS", _("Sevenans")), + ("MONTBELIARD", _("Montbéliard")), + ("EBOUTIC", _("Eboutic")), ] -SITH_COUNTER_BARS = [ - (1, "MDE"), - (2, "Foyer"), - (35, "La Gommette"), -] +SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] SITH_COUNTER_PAYMENT_METHOD = [ - ('CHECK', _('Check')), - ('CASH', _('Cash')), - ('CARD', _('Credit card')), + ("CHECK", _("Check")), + ("CASH", _("Cash")), + ("CARD", _("Credit card")), ] SITH_COUNTER_BANK = [ - ('OTHER', 'Autre'), - ('SOCIETE-GENERALE', 'Société générale'), - ('BANQUE-POPULAIRE', 'Banque populaire'), - ('BNP', 'BNP'), - ('CAISSE-EPARGNE', 'Caisse d\'épargne'), - ('CIC', 'CIC'), - ('CREDIT-AGRICOLE', 'Crédit Agricole'), - ('CREDIT-MUTUEL', 'Credit Mutuel'), - ('CREDIT-LYONNAIS', 'Credit Lyonnais'), - ('LA-POSTE', 'La Poste'), + ("OTHER", "Autre"), + ("SOCIETE-GENERALE", "Société générale"), + ("BANQUE-POPULAIRE", "Banque populaire"), + ("BNP", "BNP"), + ("CAISSE-EPARGNE", "Caisse d'épargne"), + ("CIC", "CIC"), + ("CREDIT-AGRICOLE", "Crédit Agricole"), + ("CREDIT-MUTUEL", "Credit Mutuel"), + ("CREDIT-LYONNAIS", "Credit Lyonnais"), + ("LA-POSTE", "La Poste"), ] SITH_ECOCUP_CONS = 1152 @@ -428,9 +421,7 @@ SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1 SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2 SITH_PRODUCTTYPE_SUBSCRIPTION = 2 -SITH_CAN_CREATE_SUBSCRIPTIONS = [ - 1, -] +SITH_CAN_CREATE_SUBSCRIPTIONS = [1] # Number of weeks before the end of a subscription when the subscriber can resubscribe SITH_SUBSCRIPTION_END = 10 @@ -438,113 +429,61 @@ SITH_SUBSCRIPTION_END = 10 # Subscription durations are in semestres # Be careful, modifying this parameter will need a migration to be applied SITH_SUBSCRIPTIONS = { - 'un-semestre': { - 'name': _('One semester'), - 'price': 15, - 'duration': 1, + "un-semestre": {"name": _("One semester"), "price": 15, "duration": 1}, + "deux-semestres": {"name": _("Two semesters"), "price": 28, "duration": 2}, + "cursus-tronc-commun": { + "name": _("Common core cursus"), + "price": 45, + "duration": 4, }, - 'deux-semestres': { - 'name': _('Two semesters'), - 'price': 28, - 'duration': 2, + "cursus-branche": {"name": _("Branch cursus"), "price": 45, "duration": 6}, + "cursus-alternant": {"name": _("Alternating cursus"), "price": 30, "duration": 6}, + "membre-honoraire": {"name": _("Honorary member"), "price": 0, "duration": 666}, + "assidu": {"name": _("Assidu member"), "price": 0, "duration": 2}, + "amicale/doceo": {"name": _("Amicale/DOCEO member"), "price": 0, "duration": 2}, + "reseau-ut": {"name": _("UT network member"), "price": 0, "duration": 1}, + "crous": {"name": _("CROUS member"), "price": 0, "duration": 2}, + "sbarro/esta": {"name": _("Sbarro/ESTA member"), "price": 15, "duration": 2}, + "un-semestre-welcome": { + "name": _("One semester Welcome Week"), + "price": 0, + "duration": 1, }, - 'cursus-tronc-commun': { - 'name': _('Common core cursus'), - 'price': 45, - 'duration': 4, + "deux-mois-essai": {"name": _("Two months for free"), "price": 0, "duration": 0.33}, + "benevoles-euroks": {"name": _("Eurok's volunteer"), "price": 5, "duration": 0.1}, + "six-semaines-essai": { + "name": _("Six weeks for free"), + "price": 0, + "duration": 0.23, }, - 'cursus-branche': { - 'name': _('Branch cursus'), - 'price': 45, - 'duration': 6, - }, - 'cursus-alternant': { - 'name': _('Alternating cursus'), - 'price': 30, - 'duration': 6, - }, - 'membre-honoraire': { - 'name': _('Honorary member'), - 'price': 0, - 'duration': 666, - }, - 'assidu': { - 'name': _('Assidu member'), - 'price': 0, - 'duration': 2, - }, - 'amicale/doceo': { - 'name': _('Amicale/DOCEO member'), - 'price': 0, - 'duration': 2, - }, - 'reseau-ut': { - 'name': _('UT network member'), - 'price': 0, - 'duration': 1, - }, - 'crous': { - 'name': _('CROUS member'), - 'price': 0, - 'duration': 2, - }, - 'sbarro/esta': { - 'name': _('Sbarro/ESTA member'), - 'price': 15, - 'duration': 2, - }, - 'un-semestre-welcome': { - 'name': _('One semester Welcome Week'), - 'price': 0, - 'duration': 1, - }, - 'deux-mois-essai': { - 'name': _('Two months for free'), - 'price': 0, - 'duration': 0.33, - }, - 'benevoles-euroks': { - 'name': _('Eurok\'s volunteer'), - 'price': 5, - 'duration': 0.1 - }, - 'six-semaines-essai': { - 'name': _('Six weeks for free'), - 'price': 0, - 'duration': 0.23, - }, - 'un-jour': { - 'name': _('One day'), - 'price': 0, - 'duration': 0.00555333, - } + "un-jour": {"name": _("One day"), "price": 0, "duration": 0.00555333} # To be completed.... } SITH_CLUB_ROLES = {} SITH_CLUB_ROLES_ID = { - 'President': 10, - 'Vice-President': 9, - 'Treasurer': 7, - 'Communication supervisor': 5, - 'Secretary': 4, - 'IT supervisor': 3, - 'Board member': 2, - 'Active member': 1, - 'Curious': 0, + "President": 10, + "Vice-President": 9, + "Treasurer": 7, + "Communication supervisor": 5, + "Secretary": 4, + "IT supervisor": 3, + "Board member": 2, + "Active member": 1, + "Curious": 0, } SITH_CLUB_ROLES = { - 10: _('President'), - 9: _('Vice-President'), - 7: _('Treasurer'), - 5: _('Communication supervisor'), - 4: _('Secretary'), - 3: _('IT supervisor'), - 2: _('Board member'), - 1: _('Active member'), - 0: _('Curious'), + 10: _("President"), + 9: _("Vice-President"), + 7: _("Treasurer"), + 5: _("Communication supervisor"), + 4: _("Secretary"), + 3: _("IT supervisor"), + 2: _("Board member"), + 1: _("Active member"), + 0: _("Curious"), } # This corresponds to the maximum role a user can freely subscribe to @@ -562,85 +501,85 @@ SITH_COUNTER_MINUTE_INACTIVE = 10 # ET variables SITH_EBOUTIC_CB_ENABLED = True -SITH_EBOUTIC_ET_URL = "https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" +SITH_EBOUTIC_ET_URL = ( + "https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi" +) SITH_EBOUTIC_PBX_SITE = "4000666" SITH_EBOUTIC_PBX_RANG = "42" SITH_EBOUTIC_PBX_IDENTIFIANT = "123456789" -SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF") +SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify( + "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" +) SITH_EBOUTIC_PUB_KEY = "" -with open('./sith/et_keys/pubkey.pem') as f: +with open("./sith/et_keys/pubkey.pem") as f: SITH_EBOUTIC_PUB_KEY = f.read() # Launderette variables -SITH_LAUNDERETTE_MACHINE_TYPES = [('WASHING', _('Washing')), ('DRYING', _('Drying'))] -SITH_LAUNDERETTE_PRICES = { - 'WASHING': 1.0, - 'DRYING': 0.75, -} +SITH_LAUNDERETTE_MACHINE_TYPES = [("WASHING", _("Washing")), ("DRYING", _("Drying"))] +SITH_LAUNDERETTE_PRICES = {"WASHING": 1.0, "DRYING": 0.75} SITH_NOTIFICATIONS = [ - ('POSTER_MODERATION', _("A new poster needs to be moderated")), - ('MAILING_MODERATION', _("A new mailing list needs to be moderated")), - ('NEWS_MODERATION', _("There are %s fresh news to be moderated")), - ('FILE_MODERATION', _("New files to be moderated")), - ('SAS_MODERATION', _("There are %s pictures to be moderated in the SAS")), - ('NEW_PICTURES', _("You've been identified on some pictures")), - ('REFILLING', _("You just refilled of %s €")), - ('SELLING', _("You just bought %s")), - ('GENERIC', _("You have a notification")), + ("POSTER_MODERATION", _("A new poster needs to be moderated")), + ("MAILING_MODERATION", _("A new mailing list needs to be moderated")), + ("NEWS_MODERATION", _("There are %s fresh news to be moderated")), + ("FILE_MODERATION", _("New files to be moderated")), + ("SAS_MODERATION", _("There are %s pictures to be moderated in the SAS")), + ("NEW_PICTURES", _("You've been identified on some pictures")), + ("REFILLING", _("You just refilled of %s €")), + ("SELLING", _("You just bought %s")), + ("GENERIC", _("You have a notification")), ] # The keys are the notification names as found in SITH_NOTIFICATIONS, and the # values are the callback function to update the notifs. # The callback must take the notif object as first and single argument. SITH_PERMANENT_NOTIFICATIONS = { - 'NEWS_MODERATION': 'com.models.news_notification_callback', - 'SAS_MODERATION': 'sas.models.sas_notification_callback', + "NEWS_MODERATION": "com.models.news_notification_callback", + "SAS_MODERATION": "sas.models.sas_notification_callback", } SITH_QUICK_NOTIF = { - 'qn_success': _("Success!"), - 'qn_fail': _("Fail!"), - 'qn_weekmail_new_article': _("You successfully posted an article in the Weekmail"), - 'qn_weekmail_article_edit': _("You successfully edited an article in the Weekmail"), - 'qn_weekmail_send_success': _("You successfully sent the Weekmail"), + "qn_success": _("Success!"), + "qn_fail": _("Fail!"), + "qn_weekmail_new_article": _("You successfully posted an article in the Weekmail"), + "qn_weekmail_article_edit": _("You successfully edited an article in the Weekmail"), + "qn_weekmail_send_success": _("You successfully sent the Weekmail"), } # Mailing related settings -SITH_MAILING_DOMAIN = 'utbm.fr' -SITH_MAILING_FETCH_KEY = 'IloveMails' +SITH_MAILING_DOMAIN = "utbm.fr" +SITH_MAILING_FETCH_KEY = "IloveMails" -SITH_GIFT_LIST = [ - ('AE Tee-shirt', _("AE tee-shirt")) -] +SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))] try: from .settings_custom import * + print("Custom settings imported", file=sys.stderr) except: print("Custom settings failed", file=sys.stderr) if DEBUG: INSTALLED_APPS += ("debug_toolbar",) - MIDDLEWARE_CLASSES = ('debug_toolbar.middleware.DebugToolbarMiddleware',) + MIDDLEWARE_CLASSES + MIDDLEWARE_CLASSES = ( + "debug_toolbar.middleware.DebugToolbarMiddleware", + ) + MIDDLEWARE_CLASSES DEBUG_TOOLBAR_PANELS = [ - 'debug_toolbar.panels.versions.VersionsPanel', - 'debug_toolbar.panels.timer.TimerPanel', - 'debug_toolbar.panels.settings.SettingsPanel', - 'debug_toolbar.panels.headers.HeadersPanel', - 'debug_toolbar.panels.request.RequestPanel', - 'debug_toolbar.panels.sql.SQLPanel', - 'debug_toolbar.panels.staticfiles.StaticFilesPanel', - 'sith.toolbar_debug.TemplatesPanel', - 'debug_toolbar.panels.cache.CachePanel', - 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logging.LoggingPanel', - 'debug_toolbar.panels.redirects.RedirectsPanel', - ] - SASS_INCLUDE_FOLDERS = [ - 'core/static/', + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "sith.toolbar_debug.TemplatesPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", ] + SASS_INCLUDE_FOLDERS = ["core/static/"] -if 'test' in sys.argv: +if "test" in sys.argv: CAPTCHA_TEST_MODE = True diff --git a/sith/toolbar_debug.py b/sith/toolbar_debug.py index 9fb29ef3..03b531d5 100644 --- a/sith/toolbar_debug.py +++ b/sith/toolbar_debug.py @@ -27,7 +27,7 @@ from debug_toolbar.panels.templates import TemplatesPanel as BaseTemplatesPanel class TemplatesPanel(BaseTemplatesPanel): def generate_stats(self, *args): - template = self.templates[0]['template'] - if not hasattr(template, 'engine') and hasattr(template, 'backend'): + template = self.templates[0]["template"] + if not hasattr(template, "engine") and hasattr(template, "backend"): template.engine = template.backend return super().generate_stats(*args) diff --git a/sith/urls.py b/sith/urls.py index af443b9c..9b85e64e 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -44,41 +44,55 @@ from django.conf import settings from django.views.i18n import javascript_catalog from ajax_select import urls as ajax_select_urls -js_info_dict = { - 'packages': ('sith',), -} +js_info_dict = {"packages": ("sith",)} handler403 = "core.views.forbidden" handler404 = "core.views.not_found" urlpatterns = [ - url(r'^', include('core.urls', namespace="core", app_name="core")), - url(r'^rootplace/', include('rootplace.urls', namespace="rootplace", app_name="rootplace")), - url(r'^subscription/', include('subscription.urls', namespace="subscription", app_name="subscription")), - url(r'^com/', include('com.urls', namespace="com", app_name="com")), - url(r'^club/', include('club.urls', namespace="club", app_name="club")), - url(r'^counter/', include('counter.urls', namespace="counter", app_name="counter")), - url(r'^stock/', include('stock.urls', namespace="stock", app_name="stock")), - url(r'^accounting/', include('accounting.urls', namespace="accounting", app_name="accounting")), - url(r'^eboutic/', include('eboutic.urls', namespace="eboutic", app_name="eboutic")), - url(r'^launderette/', include('launderette.urls', namespace="launderette", app_name="launderette")), - url(r'^sas/', include('sas.urls', namespace="sas", app_name="sas")), - url(r'^api/v1/', include('api.urls', namespace="api", app_name="api")), - url(r'^election/', include('election.urls', namespace="election", app_name="election")), - url(r'^forum/', include('forum.urls', namespace="forum", app_name="forum")), - url(r'^trombi/', include('trombi.urls', namespace="trombi", app_name="trombi")), - url(r'^matmatronch/', include('matmat.urls', namespace="matmat", app_name="matmat")), - url(r'^admin/', include(admin.site.urls)), - url(r'^ajax_select/', include(ajax_select_urls)), - url(r'^i18n/', include('django.conf.urls.i18n')), - url(r'^jsi18n/$', javascript_catalog, js_info_dict, name='javascript-catalog'), - url(r'^captcha/', include('captcha.urls')), + url(r"^", include("core.urls", namespace="core", app_name="core")), + url( + r"^rootplace/", + include("rootplace.urls", namespace="rootplace", app_name="rootplace"), + ), + url( + r"^subscription/", + include("subscription.urls", namespace="subscription", app_name="subscription"), + ), + url(r"^com/", include("com.urls", namespace="com", app_name="com")), + url(r"^club/", include("club.urls", namespace="club", app_name="club")), + url(r"^counter/", include("counter.urls", namespace="counter", app_name="counter")), + url(r"^stock/", include("stock.urls", namespace="stock", app_name="stock")), + url( + r"^accounting/", + include("accounting.urls", namespace="accounting", app_name="accounting"), + ), + url(r"^eboutic/", include("eboutic.urls", namespace="eboutic", app_name="eboutic")), + url( + r"^launderette/", + include("launderette.urls", namespace="launderette", app_name="launderette"), + ), + url(r"^sas/", include("sas.urls", namespace="sas", app_name="sas")), + url(r"^api/v1/", include("api.urls", namespace="api", app_name="api")), + url( + r"^election/", + include("election.urls", namespace="election", app_name="election"), + ), + url(r"^forum/", include("forum.urls", namespace="forum", app_name="forum")), + url(r"^trombi/", include("trombi.urls", namespace="trombi", app_name="trombi")), + url( + r"^matmatronch/", include("matmat.urls", namespace="matmat", app_name="matmat") + ), + url(r"^admin/", include(admin.site.urls)), + url(r"^ajax_select/", include(ajax_select_urls)), + url(r"^i18n/", include("django.conf.urls.i18n")), + url(r"^jsi18n/$", javascript_catalog, js_info_dict, name="javascript-catalog"), + url(r"^captcha/", include("captcha.urls")), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) import debug_toolbar - urlpatterns += [ - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + + urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))] diff --git a/sith/wsgi.py b/sith/wsgi.py index be0024b7..b440975f 100644 --- a/sith/wsgi.py +++ b/sith/wsgi.py @@ -36,6 +36,6 @@ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sith.settings") -os.environ['HTTPS'] = "on" +os.environ["HTTPS"] = "on" application = get_wsgi_application() diff --git a/stock/__init__.py b/stock/__init__.py index 6871e12c..6a83e48e 100644 --- a/stock/__init__.py +++ b/stock/__init__.py @@ -22,4 +22,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/stock/admin.py b/stock/admin.py index 6f55f86e..46567a3b 100644 --- a/stock/admin.py +++ b/stock/admin.py @@ -31,4 +31,4 @@ from stock.models import Stock, StockItem, ShoppingList, ShoppingListItem admin.site.register(Stock) admin.site.register(StockItem) admin.site.register(ShoppingList) -admin.site.register(ShoppingListItem) \ No newline at end of file +admin.site.register(ShoppingListItem) diff --git a/stock/migrations/0001_initial.py b/stock/migrations/0001_initial.py index 9aa4170e..686d7e65 100644 --- a/stock/migrations/0001_initial.py +++ b/stock/migrations/0001_initial.py @@ -7,64 +7,170 @@ import django.db.models.deletion class Migration(migrations.Migration): - dependencies = [ - ('counter', '0011_auto_20161004_2039'), - ] + dependencies = [("counter", "0011_auto_20161004_2039")] operations = [ migrations.CreateModel( - name='ShoppingList', + name="ShoppingList", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('date', models.DateTimeField(verbose_name='date')), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('todo', models.BooleanField(verbose_name='todo')), - ('comment', models.TextField(verbose_name='comment', blank=True, null=True)), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("date", models.DateTimeField(verbose_name="date")), + ("name", models.CharField(max_length=64, verbose_name="name")), + ("todo", models.BooleanField(verbose_name="todo")), + ( + "comment", + models.TextField(verbose_name="comment", blank=True, null=True), + ), ], ), migrations.CreateModel( - name='ShoppingListItem', + name="ShoppingListItem", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('tobuy_quantity', models.IntegerField(verbose_name='quantity to buy', help_text='quantity to buy during the next shopping session', default=6)), - ('bought_quantity', models.IntegerField(verbose_name='quantity bought', help_text='quantity bought during the last shopping session', default=0)), - ('shopping_lists', models.ManyToManyField(verbose_name='shopping lists', related_name='shopping_items_to_buy', to='stock.ShoppingList')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "tobuy_quantity", + models.IntegerField( + verbose_name="quantity to buy", + help_text="quantity to buy during the next shopping session", + default=6, + ), + ), + ( + "bought_quantity", + models.IntegerField( + verbose_name="quantity bought", + help_text="quantity bought during the last shopping session", + default=0, + ), + ), + ( + "shopping_lists", + models.ManyToManyField( + verbose_name="shopping lists", + related_name="shopping_items_to_buy", + to="stock.ShoppingList", + ), + ), ], ), migrations.CreateModel( - name='Stock', + name="Stock", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('counter', models.OneToOneField(verbose_name='counter', related_name='stock', to='counter.Counter')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "counter", + models.OneToOneField( + verbose_name="counter", + related_name="stock", + to="counter.Counter", + ), + ), ], ), migrations.CreateModel( - name='StockItem', + name="StockItem", fields=[ - ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), - ('name', models.CharField(max_length=64, verbose_name='name')), - ('unit_quantity', models.IntegerField(verbose_name='unit quantity', help_text='number of element in one box', default=0)), - ('effective_quantity', models.IntegerField(verbose_name='effective quantity', help_text='number of box', default=0)), - ('minimal_quantity', models.IntegerField(verbose_name='minimal quantity', help_text='if the effective quantity is less than the minimal, item is added to the shopping list', default=1)), - ('stock_owner', models.ForeignKey(related_name='items', to='stock.Stock')), - ('type', models.ForeignKey(blank=True, null=True, verbose_name='type', related_name='stock_items', on_delete=django.db.models.deletion.SET_NULL, to='counter.ProductType')), + ( + "id", + models.AutoField( + verbose_name="ID", + primary_key=True, + serialize=False, + auto_created=True, + ), + ), + ("name", models.CharField(max_length=64, verbose_name="name")), + ( + "unit_quantity", + models.IntegerField( + verbose_name="unit quantity", + help_text="number of element in one box", + default=0, + ), + ), + ( + "effective_quantity", + models.IntegerField( + verbose_name="effective quantity", + help_text="number of box", + default=0, + ), + ), + ( + "minimal_quantity", + models.IntegerField( + verbose_name="minimal quantity", + help_text="if the effective quantity is less than the minimal, item is added to the shopping list", + default=1, + ), + ), + ( + "stock_owner", + models.ForeignKey(related_name="items", to="stock.Stock"), + ), + ( + "type", + models.ForeignKey( + blank=True, + null=True, + verbose_name="type", + related_name="stock_items", + on_delete=django.db.models.deletion.SET_NULL, + to="counter.ProductType", + ), + ), ], ), migrations.AddField( - model_name='shoppinglistitem', - name='stockitem_owner', - field=models.ForeignKey(null=True, related_name='shopping_item', to='stock.StockItem'), + model_name="shoppinglistitem", + name="stockitem_owner", + field=models.ForeignKey( + null=True, related_name="shopping_item", to="stock.StockItem" + ), ), migrations.AddField( - model_name='shoppinglistitem', - name='type', - field=models.ForeignKey(blank=True, null=True, verbose_name='type', related_name='shoppinglist_items', on_delete=django.db.models.deletion.SET_NULL, to='counter.ProductType'), + model_name="shoppinglistitem", + name="type", + field=models.ForeignKey( + blank=True, + null=True, + verbose_name="type", + related_name="shoppinglist_items", + on_delete=django.db.models.deletion.SET_NULL, + to="counter.ProductType", + ), ), migrations.AddField( - model_name='shoppinglist', - name='stock_owner', - field=models.ForeignKey(null=True, related_name='shopping_lists', to='stock.Stock'), + model_name="shoppinglist", + name="stock_owner", + field=models.ForeignKey( + null=True, related_name="shopping_lists", to="stock.Stock" + ), ), ] diff --git a/stock/models.py b/stock/models.py index b2e0c161..381278b2 100644 --- a/stock/models.py +++ b/stock/models.py @@ -31,59 +31,82 @@ from django.conf import settings from counter.models import Counter, ProductType + class Stock(models.Model): """ The Stock class, this one is used to know how many products are left for a specific counter """ - name = models.CharField(_('name'), max_length=64) - counter = models.OneToOneField(Counter, verbose_name=_('counter'), related_name='stock') + + name = models.CharField(_("name"), max_length=64) + counter = models.OneToOneField( + Counter, verbose_name=_("counter"), related_name="stock" + ) def __str__(self): return "%s (%s)" % (self.name, self.counter) def get_absolute_url(self): - return reverse('stock:list') + return reverse("stock:list") def can_be_viewed_by(self, user): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) + class StockItem(models.Model): """ The StockItem class, element of the stock """ - name = models.CharField(_('name'), max_length=64) - unit_quantity = models.IntegerField(_('unit quantity'), default=0, help_text=_('number of element in one box')) - effective_quantity = models.IntegerField(_('effective quantity'), default=0, help_text=_('number of box')) - minimal_quantity = models.IntegerField(_('minimal quantity'), default=1, - help_text=_('if the effective quantity is less than the minimal, item is added to the shopping list')) - type = models.ForeignKey(ProductType, related_name="stock_items", verbose_name=_("type"), null=True, blank=True, - on_delete=models.SET_NULL) + + name = models.CharField(_("name"), max_length=64) + unit_quantity = models.IntegerField( + _("unit quantity"), default=0, help_text=_("number of element in one box") + ) + effective_quantity = models.IntegerField( + _("effective quantity"), default=0, help_text=_("number of box") + ) + minimal_quantity = models.IntegerField( + _("minimal quantity"), + default=1, + help_text=_( + "if the effective quantity is less than the minimal, item is added to the shopping list" + ), + ) + type = models.ForeignKey( + ProductType, + related_name="stock_items", + verbose_name=_("type"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) stock_owner = models.ForeignKey(Stock, related_name="items") def __str__(self): return "%s" % (self.name) def get_absolute_url(self): - return reverse('stock:items_list', kwargs={'stock_id':self.stock_owner.id}) + return reverse("stock:items_list", kwargs={"stock_id": self.stock_owner.id}) def can_be_viewed_by(self, user): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) + class ShoppingList(models.Model): """ The ShoppingList class, used to make an history of the shopping lists """ - date = models.DateTimeField(_('date')) - name = models.CharField(_('name'), max_length=64) - todo = models.BooleanField(_('todo')) - comment = models.TextField(_('comment'), null=True, blank=True) + + date = models.DateTimeField(_("date")) + name = models.CharField(_("name"), max_length=64) + todo = models.BooleanField(_("todo")) + comment = models.TextField(_("comment"), null=True, blank=True) stock_owner = models.ForeignKey(Stock, null=True, related_name="shopping_lists") def __str__(self): return "%s (%s)" % (self.name, self.date) def get_absolute_url(self): - return reverse('stock:shoppinglist_list') + return reverse("stock:shoppinglist_list") def can_be_viewed_by(self, user): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) @@ -92,13 +115,34 @@ class ShoppingList(models.Model): class ShoppingListItem(models.Model): """ """ - shopping_lists = models.ManyToManyField(ShoppingList, verbose_name=_("shopping lists"), related_name="shopping_items_to_buy") - stockitem_owner = models.ForeignKey(StockItem, related_name="shopping_item", null=True) - name = models.CharField(_('name'), max_length=64) - type = models.ForeignKey(ProductType, related_name="shoppinglist_items", verbose_name=_("type"), null=True, blank=True, - on_delete=models.SET_NULL) - tobuy_quantity = models.IntegerField(_('quantity to buy'), default=6, help_text=_("quantity to buy during the next shopping session")) - bought_quantity = models.IntegerField(_('quantity bought'), default=0, help_text=_("quantity bought during the last shopping session")) + + shopping_lists = models.ManyToManyField( + ShoppingList, + verbose_name=_("shopping lists"), + related_name="shopping_items_to_buy", + ) + stockitem_owner = models.ForeignKey( + StockItem, related_name="shopping_item", null=True + ) + name = models.CharField(_("name"), max_length=64) + type = models.ForeignKey( + ProductType, + related_name="shoppinglist_items", + verbose_name=_("type"), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + tobuy_quantity = models.IntegerField( + _("quantity to buy"), + default=6, + help_text=_("quantity to buy during the next shopping session"), + ) + bought_quantity = models.IntegerField( + _("quantity bought"), + default=0, + help_text=_("quantity bought during the last shopping session"), + ) def __str__(self): return "%s - %s" % (self.name, self.shopping_lists.first()) @@ -107,5 +151,4 @@ class ShoppingListItem(models.Model): return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID) def get_absolute_url(self): - return reverse('stock:shoppinglist_list') - + return reverse("stock:shoppinglist_list") diff --git a/stock/urls.py b/stock/urls.py index ea0ffb3c..ac85d147 100644 --- a/stock/urls.py +++ b/stock/urls.py @@ -28,28 +28,61 @@ from django.conf.urls import include, url from stock.views import * urlpatterns = [ - #Stock urls - url(r'^new/counter/(?P[0-9]+)$', StockCreateView.as_view(), name='new'), - url(r'^edit/(?P[0-9]+)$', StockEditView.as_view(), name='edit'), - url(r'^list$', StockListView.as_view(), name='list'), - - # StockItem urls - url(r'^(?P[0-9]+)$', StockItemList.as_view(), name='items_list'), - url(r'^(?P[0-9]+)/stock_item/new_item$', StockItemCreateView.as_view(), name='new_item'), - url(r'^stock_item/(?P[0-9]+)/edit$', StockItemEditView.as_view(), name='edit_item'), - url(r'^(?P[0-9]+)/stock_item/take_items$', StockTakeItemsBaseFormView.as_view(), name='take_items'), - - # ShoppingList urls - url(r'^(?P[0-9]+)/shopping_list/list$', StockShoppingListView.as_view(), name='shoppinglist_list'), - url(r'^(?P[0-9]+)/shopping_list/create$', StockItemQuantityBaseFormView.as_view(), name='shoppinglist_create'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/items$', StockShoppingListItemListView.as_view(), - name='shoppinglist_items'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/delete$', StockShoppingListDeleteView.as_view(), - name='shoppinglist_delete'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_done$', StockShopppingListSetDone.as_view(), - name='shoppinglist_set_done'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_todo$', StockShopppingListSetTodo.as_view(), - name='shoppinglist_set_todo'), - url(r'^(?P[0-9]+)/shopping_list/(?P[0-9]+)/update_stock$', StockUpdateAfterShopppingBaseFormView.as_view(), - name='update_after_shopping'), - ] + # Stock urls + url(r"^new/counter/(?P[0-9]+)$", StockCreateView.as_view(), name="new"), + url(r"^edit/(?P[0-9]+)$", StockEditView.as_view(), name="edit"), + url(r"^list$", StockListView.as_view(), name="list"), + # StockItem urls + url(r"^(?P[0-9]+)$", StockItemList.as_view(), name="items_list"), + url( + r"^(?P[0-9]+)/stock_item/new_item$", + StockItemCreateView.as_view(), + name="new_item", + ), + url( + r"^stock_item/(?P[0-9]+)/edit$", + StockItemEditView.as_view(), + name="edit_item", + ), + url( + r"^(?P[0-9]+)/stock_item/take_items$", + StockTakeItemsBaseFormView.as_view(), + name="take_items", + ), + # ShoppingList urls + url( + r"^(?P[0-9]+)/shopping_list/list$", + StockShoppingListView.as_view(), + name="shoppinglist_list", + ), + url( + r"^(?P[0-9]+)/shopping_list/create$", + StockItemQuantityBaseFormView.as_view(), + name="shoppinglist_create", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/items$", + StockShoppingListItemListView.as_view(), + name="shoppinglist_items", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/delete$", + StockShoppingListDeleteView.as_view(), + name="shoppinglist_delete", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_done$", + StockShopppingListSetDone.as_view(), + name="shoppinglist_set_done", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/set_todo$", + StockShopppingListSetTodo.as_view(), + name="shoppinglist_set_todo", + ), + url( + r"^(?P[0-9]+)/shopping_list/(?P[0-9]+)/update_stock$", + StockUpdateAfterShopppingBaseFormView.as_view(), + name="update_after_shopping", + ), +] diff --git a/stock/views.py b/stock/views.py index 5ce349d3..6a911164 100644 --- a/stock/views.py +++ b/stock/views.py @@ -29,7 +29,14 @@ from datetime import datetime, timedelta from django.utils import timezone from django.shortcuts import render, get_object_or_404 from django.views.generic import ListView, DetailView, RedirectView, TemplateView -from django.views.generic.edit import UpdateView, CreateView, DeleteView, ProcessFormView, FormMixin, BaseFormView +from django.views.generic.edit import ( + UpdateView, + CreateView, + DeleteView, + ProcessFormView, + FormMixin, + BaseFormView, +) from django.utils.translation import ugettext_lazy as _ from django import forms from django.http import HttpResponseRedirect, HttpResponse @@ -37,7 +44,13 @@ from django.forms.models import modelform_factory from django.core.urlresolvers import reverse_lazy, reverse from django.db import transaction, DataError -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, TabedViewMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + CanCreateMixin, + TabedViewMixin, +) from counter.views import CounterAdminTabsMixin, CounterTabsMixin from counter.models import Counter, ProductType from stock.models import Stock, StockItem, ShoppingList, ShoppingListItem @@ -47,23 +60,26 @@ class StockItemList(CounterAdminTabsMixin, CanCreateMixin, ListView): """ The stockitems list view for the counter owner """ + model = Stock - template_name = 'stock/stock_item_list.jinja' + template_name = "stock/stock_item_list.jinja" pk_url_kwarg = "stock_id" current_tab = "stocks" def get_context_data(self): ret = super(StockItemList, self).get_context_data() - if 'stock_id' in self.kwargs.keys(): - ret['stock'] = Stock.objects.filter(id=self.kwargs['stock_id']).first(); + if "stock_id" in self.kwargs.keys(): + ret["stock"] = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return ret + class StockListView(CounterAdminTabsMixin, CanViewMixin, ListView): """ A list view for the admins """ + model = Stock - template_name = 'stock/stock_list.jinja' + template_name = "stock/stock_list.jinja" current_tab = "stocks" @@ -71,9 +87,10 @@ class StockEditForm(forms.ModelForm): """ A form to change stock's characteristics """ + class Meta: model = Stock - fields = ['name', 'counter'] + fields = ["name", "counter"] def __init__(self, *args, **kwargs): super(StockEditForm, self).__init__(*args, **kwargs) @@ -86,10 +103,11 @@ class StockEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView): """ An edit view for the stock """ + model = Stock - form_class = modelform_factory(Stock, fields=['name', 'counter']) + form_class = modelform_factory(Stock, fields=["name", "counter"]) pk_url_kwarg = "stock_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "stocks" @@ -97,10 +115,21 @@ class StockItemEditView(CounterAdminTabsMixin, CanEditPropMixin, UpdateView): """ An edit view for a stock item """ + model = StockItem - form_class = modelform_factory(StockItem, fields=['name', 'unit_quantity', 'effective_quantity', 'minimal_quantity', 'type', 'stock_owner']) + form_class = modelform_factory( + StockItem, + fields=[ + "name", + "unit_quantity", + "effective_quantity", + "minimal_quantity", + "type", + "stock_owner", + ], + ) pk_url_kwarg = "item_id" - template_name = 'core/edit.jinja' + template_name = "core/edit.jinja" current_tab = "stocks" @@ -108,43 +137,59 @@ class StockCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView): """ A create view for a new Stock """ + model = Stock - form_class = modelform_factory(Stock, fields=['name', 'counter']) - template_name = 'core/create.jinja' + form_class = modelform_factory(Stock, fields=["name", "counter"]) + template_name = "core/create.jinja" pk_url_kwarg = "counter_id" current_tab = "stocks" - success_url = reverse_lazy('stock:list') + success_url = reverse_lazy("stock:list") def get_initial(self): ret = super(StockCreateView, self).get_initial() - if 'counter_id' in self.kwargs.keys(): - ret['counter'] = self.kwargs['counter_id'] + if "counter_id" in self.kwargs.keys(): + ret["counter"] = self.kwargs["counter_id"] return ret + class StockItemCreateView(CounterAdminTabsMixin, CanCreateMixin, CreateView): """ A create view for a new StockItem """ + model = StockItem - form_class = modelform_factory(StockItem, fields=['name', 'unit_quantity', 'effective_quantity', 'minimal_quantity', 'type', 'stock_owner']) - template_name = 'core/create.jinja' + form_class = modelform_factory( + StockItem, + fields=[ + "name", + "unit_quantity", + "effective_quantity", + "minimal_quantity", + "type", + "stock_owner", + ], + ) + template_name = "core/create.jinja" pk_url_kwarg = "stock_id" current_tab = "stocks" def get_initial(self): ret = super(StockItemCreateView, self).get_initial() - if 'stock_id' in self.kwargs.keys(): - ret['stock_owner'] = self.kwargs['stock_id'] + if "stock_id" in self.kwargs.keys(): + ret["stock_owner"] = self.kwargs["stock_id"] return ret def get_success_url(self): - return reverse_lazy('stock:items_list', kwargs={'stock_id':self.object.stock_owner.id}) + return reverse_lazy( + "stock:items_list", kwargs={"stock_id": self.object.stock_owner.id} + ) class StockShoppingListView(CounterAdminTabsMixin, CanViewMixin, ListView): """ A list view for the people to know the item to buy """ + model = Stock template_name = "stock/stock_shopping_list.jinja" pk_url_kwarg = "stock_id" @@ -152,8 +197,8 @@ class StockShoppingListView(CounterAdminTabsMixin, CanViewMixin, ListView): def get_context_data(self): ret = super(StockShoppingListView, self).get_context_data() - if 'stock_id' in self.kwargs.keys(): - ret['stock'] = Stock.objects.filter(id=self.kwargs['stock_id']).first(); + if "stock_id" in self.kwargs.keys(): + ret["stock"] = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return ret @@ -161,32 +206,45 @@ class StockItemQuantityForm(forms.BaseForm): def clean(self): with transaction.atomic(): self.stock = Stock.objects.filter(id=self.stock_id).first() - shopping_list = ShoppingList(name="Courses "+self.stock.counter.name, date=timezone.now(), todo=True) + shopping_list = ShoppingList( + name="Courses " + self.stock.counter.name, + date=timezone.now(), + todo=True, + ) shopping_list.save() shopping_list.stock_owner = self.stock shopping_list.save() - for k,t in self.cleaned_data.items(): - if k == 'name': + for k, t in self.cleaned_data.items(): + if k == "name": shopping_list.name = t shopping_list.save() elif k == "comment": shopping_list.comment = t shopping_list.save() else: - if t > 0 : + if t > 0: item_id = int(k[5:]) item = StockItem.objects.filter(id=item_id).first() - shoppinglist_item = ShoppingListItem(stockitem_owner=item, name=item.name, type=item.type, tobuy_quantity=t) + shoppinglist_item = ShoppingListItem( + stockitem_owner=item, + name=item.name, + type=item.type, + tobuy_quantity=t, + ) shoppinglist_item.save() shoppinglist_item.shopping_lists.add(shopping_list) shoppinglist_item.save() return self.cleaned_data -class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView): + +class StockItemQuantityBaseFormView( + CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView +): """ docstring for StockItemOutList """ + model = StockItem template_name = "stock/shopping_list_quantity.jinja" pk_url_kwarg = "stock_id" @@ -195,24 +253,39 @@ class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailV def get_form_class(self): fields = OrderedDict() kwargs = {} - fields['name'] = forms.CharField(max_length=30, required=True, label=_('Shopping list name')) - for t in ProductType.objects.order_by('name').all(): - for i in self.stock.items.filter(type=t).order_by('name').all(): + fields["name"] = forms.CharField( + max_length=30, required=True, label=_("Shopping list name") + ) + for t in ProductType.objects.order_by("name").all(): + for i in self.stock.items.filter(type=t).order_by("name").all(): if i.effective_quantity <= i.minimal_quantity: field_name = "item-%s" % (str(i.id)) - fields[field_name] = forms.IntegerField(required=True, label=str(i), initial=0, - help_text=_(str(i.effective_quantity)+" left")) - fields['comment'] = forms.CharField(widget=forms.Textarea(attrs={"placeholder":_("Add here, items to buy that are not reference as a stock item (example : sponge, knife, mugs ...)")}), - required=False, label=_("Comments")) - kwargs['stock_id'] = self.stock.id - kwargs['base_fields'] = fields - return type('StockItemQuantityForm', (StockItemQuantityForm,), kwargs) + fields[field_name] = forms.IntegerField( + required=True, + label=str(i), + initial=0, + help_text=_(str(i.effective_quantity) + " left"), + ) + fields["comment"] = forms.CharField( + widget=forms.Textarea( + attrs={ + "placeholder": _( + "Add here, items to buy that are not reference as a stock item (example : sponge, knife, mugs ...)" + ) + } + ), + required=False, + label=_("Comments"), + ) + kwargs["stock_id"] = self.stock.id + kwargs["base_fields"] = fields + return type("StockItemQuantityForm", (StockItemQuantityForm,), kwargs) def get(self, request, *args, **kwargs): """ Simple get view """ - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return super(StockItemQuantityBaseFormView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -220,7 +293,7 @@ class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailV Handle the many possibilities of the post request """ self.object = self.get_object() - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return super(StockItemQuantityBaseFormView, self).post(request, *args, **kwargs) def form_valid(self, form): @@ -228,17 +301,20 @@ class StockItemQuantityBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailV def get_context_data(self, **kwargs): kwargs = super(StockItemQuantityBaseFormView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['stock'] = self.stock + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["stock"] = self.stock return kwargs def get_success_url(self): - return reverse_lazy('stock:shoppinglist_list', args=self.args, kwargs=self.kwargs) + return reverse_lazy( + "stock:shoppinglist_list", args=self.args, kwargs=self.kwargs + ) class StockShoppingListItemListView(CounterAdminTabsMixin, CanViewMixin, ListView): """docstring for StockShoppingListItemListView""" + model = ShoppingList template_name = "stock/shopping_list_items.jinja" pk_url_kwarg = "shoppinglist_id" @@ -246,27 +322,34 @@ class StockShoppingListItemListView(CounterAdminTabsMixin, CanViewMixin, ListVie def get_context_data(self): ret = super(StockShoppingListItemListView, self).get_context_data() - if 'shoppinglist_id' in self.kwargs.keys(): - ret['shoppinglist'] = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first(); + if "shoppinglist_id" in self.kwargs.keys(): + ret["shoppinglist"] = ShoppingList.objects.filter( + id=self.kwargs["shoppinglist_id"] + ).first() return ret + class StockShoppingListDeleteView(CounterAdminTabsMixin, CanEditMixin, DeleteView): """ Delete a ShoppingList (for the resonsible account) """ + model = ShoppingList pk_url_kwarg = "shoppinglist_id" - template_name = 'core/delete_confirm.jinja' + template_name = "core/delete_confirm.jinja" current_tab = "stocks" def get_success_url(self): - return reverse_lazy('stock:shoppinglist_list', kwargs={'stock_id':self.object.stock_owner.id}) + return reverse_lazy( + "stock:shoppinglist_list", kwargs={"stock_id": self.object.stock_owner.id} + ) class StockShopppingListSetDone(CanEditMixin, DetailView): """ Set a ShoppingList as done """ + model = ShoppingList pk_url_kwarg = "shoppinglist_id" @@ -274,17 +357,30 @@ class StockShopppingListSetDone(CanEditMixin, DetailView): self.object = self.get_object() self.object.todo = False self.object.save() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) def post(self, request, *args, **kwargs): self.object = self.get_object() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) class StockShopppingListSetTodo(CanEditMixin, DetailView): """ Set a ShoppingList as done """ + model = ShoppingList pk_url_kwarg = "shoppinglist_id" @@ -292,21 +388,37 @@ class StockShopppingListSetTodo(CanEditMixin, DetailView): self.object = self.get_object() self.object.todo = True self.object.save() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) def post(self, request, *args, **kwargs): self.object = self.get_object() - return HttpResponseRedirect(reverse('stock:shoppinglist_list', args=self.args, kwargs={'stock_id':self.object.stock_owner.id})) + return HttpResponseRedirect( + reverse( + "stock:shoppinglist_list", + args=self.args, + kwargs={"stock_id": self.object.stock_owner.id}, + ) + ) class StockUpdateAfterShopppingForm(forms.BaseForm): def clean(self): with transaction.atomic(): - self.shoppinglist = ShoppingList.objects.filter(id=self.shoppinglist_id).first() - for k,t in self.cleaned_data.items(): + self.shoppinglist = ShoppingList.objects.filter( + id=self.shoppinglist_id + ).first() + for k, t in self.cleaned_data.items(): shoppinglist_item_id = int(k[5:]) - if int(t) > 0 : - shoppinglist_item = ShoppingListItem.objects.filter(id=shoppinglist_item_id).first() + if int(t) > 0: + shoppinglist_item = ShoppingListItem.objects.filter( + id=shoppinglist_item_id + ).first() shoppinglist_item.bought_quantity = int(t) shoppinglist_item.save() shoppinglist_item.stockitem_owner.effective_quantity += int(t) @@ -315,10 +427,14 @@ class StockUpdateAfterShopppingForm(forms.BaseForm): self.shoppinglist.save() return self.cleaned_data -class StockUpdateAfterShopppingBaseFormView(CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView): + +class StockUpdateAfterShopppingBaseFormView( + CounterAdminTabsMixin, CanEditMixin, DetailView, BaseFormView +): """ docstring for StockUpdateAfterShopppingBaseFormView """ + model = ShoppingList template_name = "stock/update_after_shopping.jinja" pk_url_kwarg = "shoppinglist_id" @@ -327,26 +443,44 @@ class StockUpdateAfterShopppingBaseFormView(CounterAdminTabsMixin, CanEditMixin, def get_form_class(self): fields = OrderedDict() kwargs = {} - for t in ProductType.objects.order_by('name').all(): - for i in self.shoppinglist.shopping_items_to_buy.filter(type=t).order_by('name').all(): + for t in ProductType.objects.order_by("name").all(): + for i in ( + self.shoppinglist.shopping_items_to_buy.filter(type=t) + .order_by("name") + .all() + ): field_name = "item-%s" % (str(i.id)) - fields[field_name] = forms.CharField(max_length=30, required=True, label=str(i), - help_text=_(str(i.tobuy_quantity) + " asked")) - kwargs['shoppinglist_id'] = self.shoppinglist.id - kwargs['base_fields'] = fields - return type('StockUpdateAfterShopppingForm', (StockUpdateAfterShopppingForm,), kwargs) + fields[field_name] = forms.CharField( + max_length=30, + required=True, + label=str(i), + help_text=_(str(i.tobuy_quantity) + " asked"), + ) + kwargs["shoppinglist_id"] = self.shoppinglist.id + kwargs["base_fields"] = fields + return type( + "StockUpdateAfterShopppingForm", (StockUpdateAfterShopppingForm,), kwargs + ) def get(self, request, *args, **kwargs): - self.shoppinglist = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first() - return super(StockUpdateAfterShopppingBaseFormView, self).get(request, *args, **kwargs) + self.shoppinglist = ShoppingList.objects.filter( + id=self.kwargs["shoppinglist_id"] + ).first() + return super(StockUpdateAfterShopppingBaseFormView, self).get( + request, *args, **kwargs + ) def post(self, request, *args, **kwargs): """ Handle the many possibilities of the post request """ self.object = self.get_object() - self.shoppinglist = ShoppingList.objects.filter(id=self.kwargs['shoppinglist_id']).first() - return super(StockUpdateAfterShopppingBaseFormView, self).post(request, *args, **kwargs) + self.shoppinglist = ShoppingList.objects.filter( + id=self.kwargs["shoppinglist_id"] + ).first() + return super(StockUpdateAfterShopppingBaseFormView, self).post( + request, *args, **kwargs + ) def form_valid(self, form): """ @@ -355,37 +489,45 @@ class StockUpdateAfterShopppingBaseFormView(CounterAdminTabsMixin, CanEditMixin, return super(StockUpdateAfterShopppingBaseFormView, self).form_valid(form) def get_context_data(self, **kwargs): - kwargs = super(StockUpdateAfterShopppingBaseFormView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['shoppinglist'] = self.shoppinglist - kwargs['stock'] = self.shoppinglist.stock_owner + kwargs = super(StockUpdateAfterShopppingBaseFormView, self).get_context_data( + **kwargs + ) + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["shoppinglist"] = self.shoppinglist + kwargs["stock"] = self.shoppinglist.stock_owner return kwargs def get_success_url(self): - self.kwargs.pop('shoppinglist_id', None) - return reverse_lazy('stock:shoppinglist_list', args=self.args, kwargs=self.kwargs) + self.kwargs.pop("shoppinglist_id", None) + return reverse_lazy( + "stock:shoppinglist_list", args=self.args, kwargs=self.kwargs + ) class StockTakeItemsForm(forms.BaseForm): """ docstring for StockTakeItemsFormView """ + def clean(self): with transaction.atomic(): - for k,t in self.cleaned_data.items(): + for k, t in self.cleaned_data.items(): item_id = int(k[5:]) - if t > 0 : + if t > 0: item = StockItem.objects.filter(id=item_id).first() item.effective_quantity -= t item.save() return self.cleaned_data -class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, BaseFormView): +class StockTakeItemsBaseFormView( + CounterTabsMixin, CanEditMixin, DetailView, BaseFormView +): """ docstring for StockTakeItemsBaseFormView """ + model = StockItem template_name = "stock/stock_take_items.jinja" pk_url_kwarg = "stock_id" @@ -394,22 +536,31 @@ class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, Bas def get_form_class(self): fields = OrderedDict() kwargs = {} - for t in ProductType.objects.order_by('name').all(): - for i in self.stock.items.filter(type=t).order_by('name').all(): + for t in ProductType.objects.order_by("name").all(): + for i in self.stock.items.filter(type=t).order_by("name").all(): field_name = "item-%s" % (str(i.id)) - fields[field_name] = forms.IntegerField(required=False, label=str(i), initial=0, min_value=0, max_value=i.effective_quantity, - help_text=_("%(effective_quantity)s left" % {"effective_quantity": str(i.effective_quantity)})) + fields[field_name] = forms.IntegerField( + required=False, + label=str(i), + initial=0, + min_value=0, + max_value=i.effective_quantity, + help_text=_( + "%(effective_quantity)s left" + % {"effective_quantity": str(i.effective_quantity)} + ), + ) kwargs[field_name] = i.effective_quantity - kwargs['stock_id'] = self.stock.id - kwargs['counter_id'] = self.stock.counter.id - kwargs['base_fields'] = fields - return type('StockTakeItemsForm', (StockTakeItemsForm,), kwargs) + kwargs["stock_id"] = self.stock.id + kwargs["counter_id"] = self.stock.counter.id + kwargs["base_fields"] = fields + return type("StockTakeItemsForm", (StockTakeItemsForm,), kwargs) def get(self, request, *args, **kwargs): """ Simple get view """ - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() return super(StockTakeItemsBaseFormView, self).get(request, *args, **kwargs) def post(self, request, *args, **kwargs): @@ -417,11 +568,19 @@ class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, Bas Handle the many possibilities of the post request """ self.object = self.get_object() - self.stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() - if self.stock.counter.type == "BAR" and not ('counter_token' in self.request.session.keys() and - self.request.session['counter_token'] == self.stock.counter.token): # Also check the token to avoid the bar to be stolen - return HttpResponseRedirect(reverse_lazy('counter:details', args=self.args, - kwargs={'counter_id': self.stock.counter.id})+'?bad_location') + self.stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() + if self.stock.counter.type == "BAR" and not ( + "counter_token" in self.request.session.keys() + and self.request.session["counter_token"] == self.stock.counter.token + ): # Also check the token to avoid the bar to be stolen + return HttpResponseRedirect( + reverse_lazy( + "counter:details", + args=self.args, + kwargs={"counter_id": self.stock.counter.id}, + ) + + "?bad_location" + ) return super(StockTakeItemsBaseFormView, self).post(request, *args, **kwargs) def form_valid(self, form): @@ -429,14 +588,14 @@ class StockTakeItemsBaseFormView(CounterTabsMixin, CanEditMixin, DetailView, Bas def get_context_data(self, **kwargs): kwargs = super(StockTakeItemsBaseFormView, self).get_context_data(**kwargs) - if 'form' not in kwargs.keys(): - kwargs['form'] = self.get_form() - kwargs['stock'] = self.stock - kwargs['counter'] = self.stock.counter + if "form" not in kwargs.keys(): + kwargs["form"] = self.get_form() + kwargs["stock"] = self.stock + kwargs["counter"] = self.stock.counter return kwargs def get_success_url(self): - stock = Stock.objects.filter(id=self.kwargs['stock_id']).first() - self.kwargs['counter_id'] = stock.counter.id - self.kwargs.pop('stock_id', None) - return reverse_lazy('counter:details', args=self.args, kwargs=self.kwargs) + stock = Stock.objects.filter(id=self.kwargs["stock_id"]).first() + self.kwargs["counter_id"] = stock.counter.id + self.kwargs.pop("stock_id", None) + return reverse_lazy("counter:details", args=self.args, kwargs=self.kwargs) diff --git a/subscription/__init__.py b/subscription/__init__.py index 0a9419f8..0ace29c4 100644 --- a/subscription/__init__.py +++ b/subscription/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/subscription/admin.py b/subscription/admin.py index 16e2be40..ba96e1d4 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -29,7 +29,12 @@ from haystack.admin import SearchModelAdmin class SubscriptionAdmin(SearchModelAdmin): - search_fields = ["member__username", "subscription_start", "subscription_end", "subscription_type"] + search_fields = [ + "member__username", + "subscription_start", + "subscription_end", + "subscription_type", + ] admin.site.register(Subscription, SubscriptionAdmin) diff --git a/subscription/migrations/0001_initial.py b/subscription/migrations/0001_initial.py index a34d5b66..2be00065 100644 --- a/subscription/migrations/0001_initial.py +++ b/subscription/migrations/0001_initial.py @@ -7,28 +7,78 @@ import django.contrib.auth.models class Migration(migrations.Migration): - dependencies = [ - ('core', '0001_initial'), - ] + dependencies = [("core", "0001_initial")] operations = [ migrations.CreateModel( - name='Subscription', + name="Subscription", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)), - ('subscription_type', models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Branch cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester')], max_length=255, verbose_name='subscription type')), - ('subscription_start', models.DateField(verbose_name='subscription start')), - ('subscription_end', models.DateField(verbose_name='subscription end')), - ('payment_method', models.CharField(choices=[('CHECK', 'Check'), ('CARD', 'Credit card'), ('CASH', 'Cash'), ('EBOUTIC', 'Eboutic'), ('OTHER', 'Other')], max_length=255, verbose_name='payment method')), - ('location', models.CharField(choices=[('BELFORT', 'Belfort'), ('SEVENANS', 'Sevenans'), ('MONTBELIARD', 'Montbéliard')], max_length=20, verbose_name='location')), + ( + "id", + models.AutoField( + primary_key=True, + serialize=False, + verbose_name="ID", + auto_created=True, + ), + ), + ( + "subscription_type", + models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Branch cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ], + max_length=255, + verbose_name="subscription type", + ), + ), + ( + "subscription_start", + models.DateField(verbose_name="subscription start"), + ), + ("subscription_end", models.DateField(verbose_name="subscription end")), + ( + "payment_method", + models.CharField( + choices=[ + ("CHECK", "Check"), + ("CARD", "Credit card"), + ("CASH", "Cash"), + ("EBOUTIC", "Eboutic"), + ("OTHER", "Other"), + ], + max_length=255, + verbose_name="payment method", + ), + ), + ( + "location", + models.CharField( + choices=[ + ("BELFORT", "Belfort"), + ("SEVENANS", "Sevenans"), + ("MONTBELIARD", "Montbéliard"), + ], + max_length=20, + verbose_name="location", + ), + ), ], - options={ - 'ordering': ['subscription_start'], - }, + options={"ordering": ["subscription_start"]}, ), migrations.AddField( - model_name='subscription', - name='member', - field=models.ForeignKey(to='core.User', related_name='subscriptions'), + model_name="subscription", + name="member", + field=models.ForeignKey(to="core.User", related_name="subscriptions"), ), ] diff --git a/subscription/migrations/0002_auto_20160830_1719.py b/subscription/migrations/0002_auto_20160830_1719.py index 13605bda..a649671d 100644 --- a/subscription/migrations/0002_auto_20160830_1719.py +++ b/subscription/migrations/0002_auto_20160830_1719.py @@ -6,14 +6,21 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0001_initial'), - ] + dependencies = [("subscription", "0001_initial")] operations = [ migrations.AlterField( - model_name='subscription', - name='location', - field=models.CharField(max_length=20, verbose_name='location', choices=[('BELFORT', 'Belfort'), ('SEVENANS', 'Sevenans'), ('MONTBELIARD', 'Montbéliard'), ('EBOUTIC', 'Eboutic')]), - ), + model_name="subscription", + name="location", + field=models.CharField( + max_length=20, + verbose_name="location", + choices=[ + ("BELFORT", "Belfort"), + ("SEVENANS", "Sevenans"), + ("MONTBELIARD", "Montbéliard"), + ("EBOUTIC", "Eboutic"), + ], + ), + ) ] diff --git a/subscription/migrations/0003_auto_20160902_1914.py b/subscription/migrations/0003_auto_20160902_1914.py index d2a6e41c..d1e23fd3 100644 --- a/subscription/migrations/0003_auto_20160902_1914.py +++ b/subscription/migrations/0003_auto_20160902_1914.py @@ -6,14 +6,28 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0002_auto_20160830_1719'), - ] + dependencies = [("subscription", "0002_auto_20160830_1719")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(max_length=255, choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester')], verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + max_length=255, + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ], + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0004_auto_20170821_1849.py b/subscription/migrations/0004_auto_20170821_1849.py index c0377bda..dc2ccdae 100644 --- a/subscription/migrations/0004_auto_20170821_1849.py +++ b/subscription/migrations/0004_auto_20170821_1849.py @@ -6,14 +6,30 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0003_auto_20160902_1914'), - ] + dependencies = [("subscription", "0003_auto_20160902_1914")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(verbose_name='subscription type', choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('sixieme-de-semestre', 'One month for free'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + verbose_name="subscription type", + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("sixieme-de-semestre", "One month for free"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + ), + ) ] diff --git a/subscription/migrations/0005_auto_20170821_2054.py b/subscription/migrations/0005_auto_20170821_2054.py index 660bc97f..291e806c 100644 --- a/subscription/migrations/0005_auto_20170821_2054.py +++ b/subscription/migrations/0005_auto_20170821_2054.py @@ -6,14 +6,29 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0004_auto_20170821_1849'), - ] + dependencies = [("subscription", "0004_auto_20170821_1849")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0006_auto_20170902_1222.py b/subscription/migrations/0006_auto_20170902_1222.py index 2fd26aa1..e08d9dd3 100644 --- a/subscription/migrations/0006_auto_20170902_1222.py +++ b/subscription/migrations/0006_auto_20170902_1222.py @@ -6,14 +6,30 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0005_auto_20170821_2054'), - ] + dependencies = [("subscription", "0005_auto_20170821_2054")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(verbose_name='subscription type', choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two month for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + verbose_name="subscription type", + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two month for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + ), + ) ] diff --git a/subscription/migrations/0007_auto_20180706_1135.py b/subscription/migrations/0007_auto_20180706_1135.py index 7ddc540d..7eab1a84 100644 --- a/subscription/migrations/0007_auto_20180706_1135.py +++ b/subscription/migrations/0007_auto_20180706_1135.py @@ -7,14 +7,31 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0006_auto_20170902_1222'), - ] + dependencies = [("subscription", "0006_auto_20170902_1222")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two month for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two month for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0008_auto_20180831_2016.py b/subscription/migrations/0008_auto_20180831_2016.py index fd7b4654..8582c835 100644 --- a/subscription/migrations/0008_auto_20180831_2016.py +++ b/subscription/migrations/0008_auto_20180831_2016.py @@ -7,14 +7,32 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0007_auto_20180706_1135'), - ] + dependencies = [("subscription", "0007_auto_20180706_1135")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two month for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('six-semaines-essai', 'Six weeks for free'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two month for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("six-semaines-essai", "Six weeks for free"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0009_auto_20180920_1421.py b/subscription/migrations/0009_auto_20180920_1421.py index 4d975dcd..a4330cd2 100644 --- a/subscription/migrations/0009_auto_20180920_1421.py +++ b/subscription/migrations/0009_auto_20180920_1421.py @@ -7,14 +7,33 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0008_auto_20180831_2016'), - ] + dependencies = [("subscription", "0008_auto_20180831_2016")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two months for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('six-semaines-essai', 'Six weeks for free'), ('un-jour', 'Un jour'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two months for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("six-semaines-essai", "Six weeks for free"), + ("un-jour", "Un jour"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/migrations/0010_auto_20180920_1441.py b/subscription/migrations/0010_auto_20180920_1441.py index 97b879ad..af14dcd4 100644 --- a/subscription/migrations/0010_auto_20180920_1441.py +++ b/subscription/migrations/0010_auto_20180920_1441.py @@ -7,14 +7,33 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('subscription', '0009_auto_20180920_1421'), - ] + dependencies = [("subscription", "0009_auto_20180920_1421")] operations = [ migrations.AlterField( - model_name='subscription', - name='subscription_type', - field=models.CharField(choices=[('amicale/doceo', 'Amicale/DOCEO member'), ('assidu', 'Assidu member'), ('benevoles-euroks', "Eurok's volunteer"), ('crous', 'CROUS member'), ('cursus-alternant', 'Alternating cursus'), ('cursus-branche', 'Branch cursus'), ('cursus-tronc-commun', 'Common core cursus'), ('deux-mois-essai', 'Two months for free'), ('deux-semestres', 'Two semesters'), ('membre-honoraire', 'Honorary member'), ('reseau-ut', 'UT network member'), ('sbarro/esta', 'Sbarro/ESTA member'), ('six-semaines-essai', 'Six weeks for free'), ('un-jour', 'One day'), ('un-semestre', 'One semester'), ('un-semestre-welcome', 'One semester Welcome Week')], max_length=255, verbose_name='subscription type'), - ), + model_name="subscription", + name="subscription_type", + field=models.CharField( + choices=[ + ("amicale/doceo", "Amicale/DOCEO member"), + ("assidu", "Assidu member"), + ("benevoles-euroks", "Eurok's volunteer"), + ("crous", "CROUS member"), + ("cursus-alternant", "Alternating cursus"), + ("cursus-branche", "Branch cursus"), + ("cursus-tronc-commun", "Common core cursus"), + ("deux-mois-essai", "Two months for free"), + ("deux-semestres", "Two semesters"), + ("membre-honoraire", "Honorary member"), + ("reseau-ut", "UT network member"), + ("sbarro/esta", "Sbarro/ESTA member"), + ("six-semaines-essai", "Six weeks for free"), + ("un-jour", "One day"), + ("un-semestre", "One semester"), + ("un-semestre-welcome", "One semester Welcome Week"), + ], + max_length=255, + verbose_name="subscription type", + ), + ) ] diff --git a/subscription/models.py b/subscription/models.py index 853d2dbc..21bc91b3 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -41,74 +41,101 @@ from core.utils import get_start_of_semester def validate_type(value): if value not in settings.SITH_SUBSCRIPTIONS.keys(): - raise ValidationError(_('Bad subscription type')) + raise ValidationError(_("Bad subscription type")) def validate_payment(value): if value not in settings.SITH_SUBSCRIPTION_PAYMENT_METHOD: - raise ValidationError(_('Bad payment method')) + raise ValidationError(_("Bad payment method")) class Subscription(models.Model): - member = models.ForeignKey(User, related_name='subscriptions') - subscription_type = models.CharField(_('subscription type'), - max_length=255, - choices=((k, v['name']) for k, v in sorted(settings.SITH_SUBSCRIPTIONS.items()))) - subscription_start = models.DateField(_('subscription start')) - subscription_end = models.DateField(_('subscription end')) - payment_method = models.CharField(_('payment method'), - max_length=255, - choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD) - location = models.CharField(choices=settings.SITH_SUBSCRIPTION_LOCATIONS, - max_length=20, verbose_name=_('location')) + member = models.ForeignKey(User, related_name="subscriptions") + subscription_type = models.CharField( + _("subscription type"), + max_length=255, + choices=( + (k, v["name"]) for k, v in sorted(settings.SITH_SUBSCRIPTIONS.items()) + ), + ) + subscription_start = models.DateField(_("subscription start")) + subscription_end = models.DateField(_("subscription end")) + payment_method = models.CharField( + _("payment method"), + max_length=255, + choices=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD, + ) + location = models.CharField( + choices=settings.SITH_SUBSCRIPTION_LOCATIONS, + max_length=20, + verbose_name=_("location"), + ) class Meta: - ordering = ['subscription_start', ] + ordering = ["subscription_start"] def clean(self): try: - for s in Subscription.objects.filter(member=self.member).exclude(pk=self.pk).all(): - if s.is_valid_now() and s.subscription_end - timedelta(weeks=settings.SITH_SUBSCRIPTION_END) > date.today(): - raise ValidationError(_("You can not subscribe many time for the same period")) + for s in ( + Subscription.objects.filter(member=self.member) + .exclude(pk=self.pk) + .all() + ): + if ( + s.is_valid_now() + and s.subscription_end + - timedelta(weeks=settings.SITH_SUBSCRIPTION_END) + > date.today() + ): + raise ValidationError( + _("You can not subscribe many time for the same period") + ) except: # This should not happen, because the form should have handled the data before, but sadly, it still - # calls the model validation :'( - # TODO see SubscriptionForm's clean method + # calls the model validation :'( + # TODO see SubscriptionForm's clean method raise ValidationError(_("Subscription error")) def save(self): super(Subscription, self).save() from counter.models import Customer + if not Customer.objects.filter(user=self.member).exists(): - last_id = Customer.objects.count() + 1504 # Number to keep a continuity with the old site - Customer(user=self.member, account_id=Customer.generate_account_id(last_id + 1), amount=0).save() - form = PasswordResetForm({'email': self.member.email}) + last_id = ( + Customer.objects.count() + 1504 + ) # Number to keep a continuity with the old site + Customer( + user=self.member, + account_id=Customer.generate_account_id(last_id + 1), + amount=0, + ).save() + form = PasswordResetForm({"email": self.member.email}) if form.is_valid(): - form.save(use_https=True, email_template_name='core/new_user_email.jinja', - subject_template_name='core/new_user_email_subject.jinja', from_email="ae@utbm.fr") + form.save( + use_https=True, + email_template_name="core/new_user_email.jinja", + subject_template_name="core/new_user_email_subject.jinja", + from_email="ae@utbm.fr", + ) self.member.make_home() if settings.IS_OLD_MYSQL_PRESENT: import MySQLdb + try: # Create subscription on the old site: TODO remove me! - LOCATION = { - "SEVENANS": 5, - "BELFORT": 6, - "MONTBELIARD": 9, - "EBOUTIC": 5, - } + LOCATION = {"SEVENANS": 5, "BELFORT": 6, "MONTBELIARD": 9, "EBOUTIC": 5} TYPE = { - 'un-semestre': 0, - 'deux-semestres': 1, - 'cursus-tronc-commun': 2, - 'cursus-branche': 3, - 'membre-honoraire': 4, - 'assidu': 5, - 'amicale/doceo': 6, - 'reseau-ut': 7, - 'crous': 8, - 'sbarro/esta': 9, - 'cursus-alternant': 10, - 'welcome-semestre': 11, - 'deux-mois-essai': 12, + "un-semestre": 0, + "deux-semestres": 1, + "cursus-tronc-commun": 2, + "cursus-branche": 3, + "membre-honoraire": 4, + "assidu": 5, + "amicale/doceo": 6, + "reseau-ut": 7, + "crous": 8, + "sbarro/esta": 9, + "cursus-alternant": 10, + "welcome-semestre": 11, + "deux-mois-essai": 12, } PAYMENT = { "CHECK": 1, @@ -121,25 +148,36 @@ class Subscription(models.Model): db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) c = db.cursor() - c.execute("""INSERT INTO ae_cotisations (id_utilisateur, date_cotis, date_fin_cotis, mode_paiement_cotis, - type_cotis, id_comptoir) VALUES (%s, %s, %s, %s, %s, %s)""", (self.member.id, self.subscription_start, - self.subscription_end, PAYMENT[self.payment_method], TYPE[self.subscription_type], - LOCATION[self.location])) + c.execute( + """INSERT INTO ae_cotisations (id_utilisateur, date_cotis, date_fin_cotis, mode_paiement_cotis, + type_cotis, id_comptoir) VALUES (%s, %s, %s, %s, %s, %s)""", + ( + self.member.id, + self.subscription_start, + self.subscription_end, + PAYMENT[self.payment_method], + TYPE[self.subscription_type], + LOCATION[self.location], + ), + ) db.commit() except Exception as e: with open(settings.BASE_DIR + "/subscription_fail.log", "a") as f: - print("FAIL to add subscription to %s to old site" % (self.member), file=f) + print( + "FAIL to add subscription to %s to old site" % (self.member), + file=f, + ) print("Reason: %s" % (repr(e)), file=f) db.rollback() def get_absolute_url(self): - return reverse('core:user_edit', kwargs={'user_id': self.member.pk}) + return reverse("core:user_edit", kwargs={"user_id": self.member.pk}) def __str__(self): if hasattr(self, "member") and self.member is not None: - return self.member.username + ' - ' + str(self.pk) + return self.member.username + " - " + str(self.pk) else: - return 'No user - ' + str(self.pk) + return "No user - " + str(self.pk) @staticmethod def compute_start(d=None, duration=1, user=None): @@ -176,21 +214,43 @@ class Subscription(models.Model): if start is None: start = Subscription.compute_start(duration=duration, user=user) - return start + relativedelta(months=round(6*duration),days=math.ceil((6*duration - round(6*duration)) * 30)) - + return start + relativedelta( + months=round(6 * duration), + days=math.ceil((6 * duration - round(6 * duration)) * 30), + ) + def can_be_edited_by(self, user): return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root def is_valid_now(self): - return self.subscription_start <= date.today() and date.today() <= self.subscription_end + return ( + self.subscription_start <= date.today() + and date.today() <= self.subscription_end + ) def guy_test(date, duration=4): - print(str(date) + " - " + str(duration) + " -> " + str(Subscription.compute_start(date, duration))) + print( + str(date) + + " - " + + str(duration) + + " -> " + + str(Subscription.compute_start(date, duration)) + ) def bibou_test(duration, date=date.today()): - print(str(date) + " - " + str(duration) + " -> " + str(Subscription.compute_end(duration, Subscription.compute_start(date, duration)))) + print( + str(date) + + " - " + + str(duration) + + " -> " + + str( + Subscription.compute_end( + duration, Subscription.compute_start(date, duration) + ) + ) + ) def guy(): @@ -202,7 +262,7 @@ def guy(): guy_test(date(2015, 2, 11)) guy_test(date(2015, 8, 17)) guy_test(date(2015, 9, 17)) - print('=' * 80) + print("=" * 80) guy_test(date(2015, 7, 11), 1) guy_test(date(2015, 8, 11), 2) guy_test(date(2015, 2, 17), 3) @@ -211,7 +271,7 @@ def guy(): guy_test(date(2015, 2, 11), 2) guy_test(date(2015, 8, 17), 3) guy_test(date(2015, 9, 17), 4) - print('=' * 80) + print("=" * 80) bibou_test(1, date(2015, 2, 18)) bibou_test(2, date(2015, 2, 18)) bibou_test(3, date(2015, 2, 18)) @@ -220,7 +280,7 @@ def guy(): bibou_test(2, date(2015, 9, 18)) bibou_test(3, date(2015, 9, 18)) bibou_test(4, date(2015, 9, 18)) - print('=' * 80) + print("=" * 80) bibou_test(1, date(2000, 2, 29)) bibou_test(2, date(2000, 2, 29)) bibou_test(1, date(2000, 5, 31)) diff --git a/subscription/tests.py b/subscription/tests.py index 6003df62..895e6596 100644 --- a/subscription/tests.py +++ b/subscription/tests.py @@ -34,6 +34,7 @@ from django.core.management import call_command class FakeDate(date): """A fake replacement for date that can be mocked for testing.""" + def __new__(cls, *args, **kwargs): return date.__new__(date, *args, **kwargs) @@ -43,8 +44,7 @@ def date_mock_today(year, month, day): class SubscriptionUnitTest(TestCase): - - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_start_dates_sliding_without_start(self): date_mock_today(2015, 9, 18) d = Subscription.compute_start(duration=1) @@ -52,12 +52,14 @@ class SubscriptionUnitTest(TestCase): self.assertTrue(Subscription.compute_start(duration=2) == date(2015, 9, 18)) def test_start_dates_sliding_with_start(self): - self.assertTrue(Subscription.compute_start(date(2015, 5, 17), 1) == - date(2015, 5, 17)) - self.assertTrue(Subscription.compute_start(date(2015, 5, 17), 2) == - date(2015, 5, 17)) + self.assertTrue( + Subscription.compute_start(date(2015, 5, 17), 1) == date(2015, 5, 17) + ) + self.assertTrue( + Subscription.compute_start(date(2015, 5, 17), 2) == date(2015, 5, 17) + ) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_start_dates_not_sliding_without_start(self): date_mock_today(2015, 5, 17) self.assertTrue(Subscription.compute_start(duration=3) == date(2015, 2, 15)) @@ -67,10 +69,14 @@ class SubscriptionUnitTest(TestCase): self.assertTrue(Subscription.compute_start(duration=4) == date(2015, 8, 15)) def test_start_dates_not_sliding_with_start(self): - self.assertTrue(Subscription.compute_start(date(2015, 5, 17), 3) == date(2015, 2, 15)) - self.assertTrue(Subscription.compute_start(date(2015, 1, 11), 3) == date(2014, 8, 15)) + self.assertTrue( + Subscription.compute_start(date(2015, 5, 17), 3) == date(2015, 2, 15) + ) + self.assertTrue( + Subscription.compute_start(date(2015, 1, 11), 3) == date(2014, 8, 15) + ) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_end_dates_sliding(self): date_mock_today(2015, 9, 18) d = Subscription.compute_end(2) @@ -78,7 +84,7 @@ class SubscriptionUnitTest(TestCase): d = Subscription.compute_end(1) self.assertTrue(d == date(2016, 3, 18)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_end_dates_not_sliding_without_start(self): date_mock_today(2015, 9, 18) d = Subscription.compute_end(duration=3) @@ -86,7 +92,7 @@ class SubscriptionUnitTest(TestCase): d = Subscription.compute_end(duration=4) self.assertTrue(d == date(2017, 8, 15)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_end_dates_with_float(self): date_mock_today(2015, 9, 18) d = Subscription.compute_end(duration=0.33) @@ -102,6 +108,7 @@ class SubscriptionUnitTest(TestCase): d = Subscription.compute_end(duration=4, start=date(2015, 9, 18)) self.assertTrue(d == date(2017, 9, 18)) + class SubscriptionIntegrationTest(TestCase): def setUp(self): call_command("populate") @@ -109,98 +116,130 @@ class SubscriptionIntegrationTest(TestCase): def test_duration_two_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=0.33, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.33, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2017, 10, 29)) def test_duration_two_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS['un-jour']['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["un-jour"]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2017, 8, 30)) def test_duration_three_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=0.5, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.5, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2017, 11, 29)) def test_duration_four_months(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2017, 8, 29) - s.subscription_end = s.compute_end(duration=0.67, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.67, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2017, 12, 30)) - + def test_duration_six_weeks(self): - s = Subscription(member=User.objects.filter(pk=self.user.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=User.objects.filter(pk=self.user.pk).first(), + subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[3], + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2018, 9, 1) - s.subscription_end = s.compute_end(duration=0.23, - start=s.subscription_start) + s.subscription_end = s.compute_end(duration=0.23, start=s.subscription_start) s.save() self.assertTrue(s.subscription_end == date(2018, 10, 13)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_dates_sliding_with_subscribed_user(self): user = User.objects.filter(pk=self.user.pk).first() - s = Subscription(member=user, subscription_type='deux-semestres', - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=user, + subscription_type="deux-semestres", + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2015, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2016, 8, 29)) date_mock_today(2016, 8, 25) - d = Subscription.compute_end(duration=settings.SITH_SUBSCRIPTIONS['deux-semestres']['duration'], - user=user) - + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) self.assertTrue(d == date(2017, 8, 29)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_dates_renewal_sliding_during_two_free_monthes(self): user = User.objects.filter(pk=self.user.pk).first() - s = Subscription(member=user, subscription_type='deux-mois-essai', - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=user, + subscription_type="deux-mois-essai", + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2015, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2015, 10, 29)) date_mock_today(2015, 9, 25) - d = Subscription.compute_end(duration=settings.SITH_SUBSCRIPTIONS['deux-semestres']['duration'], - user=user) + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) self.assertTrue(d == date(2016, 10, 29)) - @mock.patch('subscription.models.date', FakeDate) + @mock.patch("subscription.models.date", FakeDate) def test_dates_renewal_sliding_after_two_free_monthes(self): user = User.objects.filter(pk=self.user.pk).first() - s = Subscription(member=user, subscription_type='deux-mois-essai', - payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) + s = Subscription( + member=user, + subscription_type="deux-mois-essai", + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0], + ) s.subscription_start = date(2015, 8, 29) - s.subscription_end = s.compute_end(duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], - start=s.subscription_start) + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) s.save() self.assertTrue(s.subscription_end == date(2015, 10, 29)) date_mock_today(2015, 11, 5) - d = Subscription.compute_end(duration=settings.SITH_SUBSCRIPTIONS['deux-semestres']['duration'], - user=user) + d = Subscription.compute_end( + duration=settings.SITH_SUBSCRIPTIONS["deux-semestres"]["duration"], + user=user, + ) self.assertTrue(d == date(2016, 11, 5)) - diff --git a/subscription/urls.py b/subscription/urls.py index 4d31199d..e271c0e1 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -28,6 +28,6 @@ from subscription.views import * urlpatterns = [ # Subscription views - url(r'^$', NewSubscription.as_view(), name='subscription'), - url(r'stats', SubscriptionsStatsView.as_view(), name='stats'), + url(r"^$", NewSubscription.as_view(), name="subscription"), + url(r"stats", SubscriptionsStatsView.as_view(), name="stats"), ] diff --git a/subscription/views.py b/subscription/views.py index a2e5f7a8..81e1c921 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -40,29 +40,40 @@ from core.models import User class SelectionDateForm(forms.Form): def __init__(self, *args, **kwargs): super(SelectionDateForm, self).__init__(*args, **kwargs) - self.fields['start_date'] = forms.DateTimeField( - ['%Y-%m-%d %H:%M:%S'], label=_("Start date"), - widget=SelectDateTime, required=True) - self.fields['end_date'] = forms.DateTimeField( - ['%Y-%m-%d %H:%M:%S'], label=_("End date"), - widget=SelectDateTime, required=True) + self.fields["start_date"] = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Start date"), + widget=SelectDateTime, + required=True, + ) + self.fields["end_date"] = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + widget=SelectDateTime, + required=True, + ) class SubscriptionForm(forms.ModelForm): class Meta: model = Subscription - fields = ['member', 'subscription_type', 'payment_method', 'location'] - member = AutoCompleteSelectField('users', required=False, help_text=None) + fields = ["member", "subscription_type", "payment_method", "location"] + + member = AutoCompleteSelectField("users", required=False, help_text=None) def __init__(self, *args, **kwargs): super(SubscriptionForm, self).__init__(*args, **kwargs) # Add fields to allow basic user creation - self.fields['last_name'] = forms.CharField(max_length=User._meta.get_field('last_name').max_length) - self.fields['first_name'] = forms.CharField(max_length=User._meta.get_field('first_name').max_length) - self.fields['email'] = forms.EmailField() - self.fields.move_to_end('subscription_type') - self.fields.move_to_end('payment_method') - self.fields.move_to_end('location') + self.fields["last_name"] = forms.CharField( + max_length=User._meta.get_field("last_name").max_length + ) + self.fields["first_name"] = forms.CharField( + max_length=User._meta.get_field("first_name").max_length + ) + self.fields["email"] = forms.EmailField() + self.fields.move_to_end("subscription_type") + self.fields.move_to_end("payment_method") + self.fields.move_to_end("location") def clean_member(self): subscriber = self.cleaned_data.get("member") @@ -72,19 +83,26 @@ class SubscriptionForm(forms.ModelForm): def clean(self): cleaned_data = super(SubscriptionForm, self).clean() - if (cleaned_data.get("member") is None - and "last_name" not in self.errors.as_data() - and "first_name" not in self.errors.as_data() - and "email" not in self.errors.as_data()): + if ( + cleaned_data.get("member") is None + and "last_name" not in self.errors.as_data() + and "first_name" not in self.errors.as_data() + and "email" not in self.errors.as_data() + ): self.errors.pop("member", None) if self.errors: return cleaned_data if User.objects.filter(email=cleaned_data.get("email")).first() is not None: - self.add_error("email", ValidationError(_("A user with that email address already exists"))) + self.add_error( + "email", + ValidationError(_("A user with that email address already exists")), + ) else: - u = User(last_name=self.cleaned_data.get("last_name"), - first_name=self.cleaned_data.get("first_name"), - email=self.cleaned_data.get("email")) + u = User( + last_name=self.cleaned_data.get("last_name"), + first_name=self.cleaned_data.get("first_name"), + email=self.cleaned_data.get("email"), + ) u.generate_username() u.set_password(str(random.randrange(1000000, 10000000))) u.save() @@ -96,12 +114,16 @@ class SubscriptionForm(forms.ModelForm): if cleaned_data.get("member") is None: # This should be handled here, but it is done in the Subscription model's clean method # TODO investigate why! - raise ValidationError(_("You must either choose an existing user or create a new one properly")) + raise ValidationError( + _( + "You must either choose an existing user or create a new one properly" + ) + ) return cleaned_data class NewSubscription(CreateView): - template_name = 'subscription/subscription.jinja' + template_name = "subscription/subscription.jinja" form_class = SubscriptionForm def dispatch(self, request, *arg, **kwargs): @@ -111,18 +133,26 @@ class NewSubscription(CreateView): raise PermissionDenied def get_initial(self): - if 'member' in self.request.GET.keys(): - return {'member': self.request.GET['member'], 'subscription_type': 'deux-semestres'} - return {'subscription_type': 'deux-semestres'} + if "member" in self.request.GET.keys(): + return { + "member": self.request.GET["member"], + "subscription_type": "deux-semestres", + } + return {"subscription_type": "deux-semestres"} def form_valid(self, form): form.instance.subscription_start = Subscription.compute_start( - duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], - user=form.instance.member) + duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][ + "duration" + ], + user=form.instance.member, + ) form.instance.subscription_end = Subscription.compute_end( - duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type]['duration'], + duration=settings.SITH_SUBSCRIPTIONS[form.instance.subscription_type][ + "duration" + ], start=form.instance.subscription_start, - user=form.instance.member + user=form.instance.member, ) return super(NewSubscription, self).form_valid(form) @@ -133,41 +163,41 @@ class SubscriptionsStatsView(FormView): def dispatch(self, request, *arg, **kwargs): import datetime + self.start_date = datetime.datetime.today() self.end_date = self.start_date - res = super(SubscriptionsStatsView, self).dispatch( - request, *arg, **kwargs) + res = super(SubscriptionsStatsView, self).dispatch(request, *arg, **kwargs) if request.user.is_root or request.user.is_board_member: return res raise PermissionDenied def post(self, request, *args, **kwargs): self.form = self.get_form() - self.start_date = self.form['start_date'] - self.end_date = self.form['end_date'] - res = super(SubscriptionsStatsView, self).post( - request, *args, **kwargs) + self.start_date = self.form["start_date"] + self.end_date = self.form["end_date"] + res = super(SubscriptionsStatsView, self).post(request, *args, **kwargs) if request.user.is_root or request.user.is_board_member: return res raise PermissionDenied def get_initial(self): init = { - 'start_date': self.start_date.strftime('%Y-%m-%d %H:%M:%S'), - 'end_date': self.end_date.strftime('%Y-%m-%d %H:%M:%S') + "start_date": self.start_date.strftime("%Y-%m-%d %H:%M:%S"), + "end_date": self.end_date.strftime("%Y-%m-%d %H:%M:%S"), } return init def get_context_data(self, **kwargs): from subscription.models import Subscription + kwargs = super(SubscriptionsStatsView, self).get_context_data(**kwargs) - kwargs['subscriptions_total'] = Subscription.objects.filter( - subscription_end__gte=self.end_date, - subscription_start__lte=self.start_date) - kwargs['subscriptions_types'] = settings.SITH_SUBSCRIPTIONS - kwargs['payment_types'] = settings.SITH_COUNTER_PAYMENT_METHOD - kwargs['locations'] = settings.SITH_SUBSCRIPTION_LOCATIONS + kwargs["subscriptions_total"] = Subscription.objects.filter( + subscription_end__gte=self.end_date, subscription_start__lte=self.start_date + ) + kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS + kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD + kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS return kwargs def get_success_url(self, **kwargs): - return reverse_lazy('subscriptions:stats') + return reverse_lazy("subscriptions:stats") diff --git a/trombi/__init__.py b/trombi/__init__.py index 55a358e2..e9ebba43 100644 --- a/trombi/__init__.py +++ b/trombi/__init__.py @@ -21,4 +21,3 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - diff --git a/trombi/migrations/0001_initial.py b/trombi/migrations/0001_initial.py index 8c1f9a67..4f850f3d 100644 --- a/trombi/migrations/0001_initial.py +++ b/trombi/migrations/0001_initial.py @@ -10,46 +10,134 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('club', '0007_auto_20170324_0917'), + ("club", "0007_auto_20170324_0917"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( - name='Trombi', + name="Trombi", fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('subscription_deadline', models.DateField(default=datetime.date.today, help_text='Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.', verbose_name='subscription deadline')), - ('comments_deadline', models.DateField(default=datetime.date.today, help_text="After this date, users won't be able to make comments anymore.", verbose_name='comments deadline')), - ('max_chars', models.IntegerField(default=400, help_text='Maximum number of characters allowed in a comment.', verbose_name='maximum characters')), - ('club', models.OneToOneField(to='club.Club', related_name='trombi')), + ( + "id", + models.AutoField( + serialize=False, + auto_created=True, + verbose_name="ID", + primary_key=True, + ), + ), + ( + "subscription_deadline", + models.DateField( + default=datetime.date.today, + help_text="Before this date, users are allowed to subscribe to this Trombi. After this date, users subscribed will be allowed to comment on each other.", + verbose_name="subscription deadline", + ), + ), + ( + "comments_deadline", + models.DateField( + default=datetime.date.today, + help_text="After this date, users won't be able to make comments anymore.", + verbose_name="comments deadline", + ), + ), + ( + "max_chars", + models.IntegerField( + default=400, + help_text="Maximum number of characters allowed in a comment.", + verbose_name="maximum characters", + ), + ), + ("club", models.OneToOneField(to="club.Club", related_name="trombi")), ], ), migrations.CreateModel( - name='TrombiComment', + name="TrombiComment", fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('content', models.TextField(default='', verbose_name='content')), + ( + "id", + models.AutoField( + serialize=False, + auto_created=True, + verbose_name="ID", + primary_key=True, + ), + ), + ("content", models.TextField(default="", verbose_name="content")), ], ), migrations.CreateModel( - name='TrombiUser', + name="TrombiUser", fields=[ - ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)), - ('profile_pict', models.ImageField(upload_to='trombi', blank=True, help_text='The profile picture you want in the trombi (warning: this picture may be published)', verbose_name='profile pict', null=True)), - ('scrub_pict', models.ImageField(upload_to='trombi', blank=True, help_text='The scrub picture you want in the trombi (warning: this picture may be published)', verbose_name='scrub pict', null=True)), - ('trombi', models.ForeignKey(to='trombi.Trombi', blank=True, verbose_name='trombi', related_name='users', on_delete=django.db.models.deletion.SET_NULL, null=True)), - ('user', models.OneToOneField(verbose_name='trombi user', to=settings.AUTH_USER_MODEL, related_name='trombi_user')), + ( + "id", + models.AutoField( + serialize=False, + auto_created=True, + verbose_name="ID", + primary_key=True, + ), + ), + ( + "profile_pict", + models.ImageField( + upload_to="trombi", + blank=True, + help_text="The profile picture you want in the trombi (warning: this picture may be published)", + verbose_name="profile pict", + null=True, + ), + ), + ( + "scrub_pict", + models.ImageField( + upload_to="trombi", + blank=True, + help_text="The scrub picture you want in the trombi (warning: this picture may be published)", + verbose_name="scrub pict", + null=True, + ), + ), + ( + "trombi", + models.ForeignKey( + to="trombi.Trombi", + blank=True, + verbose_name="trombi", + related_name="users", + on_delete=django.db.models.deletion.SET_NULL, + null=True, + ), + ), + ( + "user", + models.OneToOneField( + verbose_name="trombi user", + to=settings.AUTH_USER_MODEL, + related_name="trombi_user", + ), + ), ], ), migrations.AddField( - model_name='trombicomment', - name='author', - field=models.ForeignKey(to='trombi.TrombiUser', verbose_name='author', related_name='given_comments'), + model_name="trombicomment", + name="author", + field=models.ForeignKey( + to="trombi.TrombiUser", + verbose_name="author", + related_name="given_comments", + ), ), migrations.AddField( - model_name='trombicomment', - name='target', - field=models.ForeignKey(to='trombi.TrombiUser', verbose_name='target', related_name='received_comments'), + model_name="trombicomment", + name="target", + field=models.ForeignKey( + to="trombi.TrombiUser", + verbose_name="target", + related_name="received_comments", + ), ), ] diff --git a/trombi/migrations/0002_trombi_show_profiles.py b/trombi/migrations/0002_trombi_show_profiles.py index 3cb2f508..a3977b94 100644 --- a/trombi/migrations/0002_trombi_show_profiles.py +++ b/trombi/migrations/0002_trombi_show_profiles.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('trombi', '0001_initial'), - ] + dependencies = [("trombi", "0001_initial")] operations = [ migrations.AddField( - model_name='trombi', - name='show_profiles', - field=models.BooleanField(default=True, verbose_name='show users profiles to each other'), - ), + model_name="trombi", + name="show_profiles", + field=models.BooleanField( + default=True, verbose_name="show users profiles to each other" + ), + ) ] diff --git a/trombi/migrations/0003_trombicomment_is_moderated.py b/trombi/migrations/0003_trombicomment_is_moderated.py index 9089b305..2c44bc61 100644 --- a/trombi/migrations/0003_trombicomment_is_moderated.py +++ b/trombi/migrations/0003_trombicomment_is_moderated.py @@ -6,14 +6,14 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('trombi', '0002_trombi_show_profiles'), - ] + dependencies = [("trombi", "0002_trombi_show_profiles")] operations = [ migrations.AddField( - model_name='trombicomment', - name='is_moderated', - field=models.BooleanField(default=False, verbose_name='is the comment moderated'), - ), + model_name="trombicomment", + name="is_moderated", + field=models.BooleanField( + default=False, verbose_name="is the comment moderated" + ), + ) ] diff --git a/trombi/migrations/0004_trombiclubmembership.py b/trombi/migrations/0004_trombiclubmembership.py index dc1c6e09..2f6d3023 100644 --- a/trombi/migrations/0004_trombiclubmembership.py +++ b/trombi/migrations/0004_trombiclubmembership.py @@ -6,23 +6,46 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('trombi', '0003_trombicomment_is_moderated'), - ] + dependencies = [("trombi", "0003_trombicomment_is_moderated")] operations = [ migrations.CreateModel( - name='TrombiClubMembership', + name="TrombiClubMembership", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('club', models.CharField(default='', max_length=32, verbose_name='club')), - ('role', models.CharField(default='', max_length=64, verbose_name='role')), - ('start', models.CharField(default='', max_length=16, verbose_name='start')), - ('end', models.CharField(default='', max_length=16, verbose_name='end')), - ('user', models.ForeignKey(verbose_name='user', related_name='memberships', to='trombi.TrombiUser')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "club", + models.CharField(default="", max_length=32, verbose_name="club"), + ), + ( + "role", + models.CharField(default="", max_length=64, verbose_name="role"), + ), + ( + "start", + models.CharField(default="", max_length=16, verbose_name="start"), + ), + ( + "end", + models.CharField(default="", max_length=16, verbose_name="end"), + ), + ( + "user", + models.ForeignKey( + verbose_name="user", + related_name="memberships", + to="trombi.TrombiUser", + ), + ), ], - options={ - 'ordering': ['id'], - }, - ), + options={"ordering": ["id"]}, + ) ] diff --git a/trombi/models.py b/trombi/models.py index c7533560..f60400de 100644 --- a/trombi/models.py +++ b/trombi/models.py @@ -42,8 +42,11 @@ class TrombiManager(models.Manager): class AvailableTrombiManager(models.Manager): def get_queryset(self): - return super(AvailableTrombiManager, - self).get_queryset().filter(subscription_deadline__gte=date.today()) + return ( + super(AvailableTrombiManager, self) + .get_queryset() + .filter(subscription_deadline__gte=date.today()) + ) class Trombi(models.Model): @@ -52,17 +55,32 @@ class Trombi(models.Model): It contains the deadlines for the users, and the link to the club that makes its Trombi. """ - subscription_deadline = models.DateField(_('subscription deadline'), - default=date.today, help_text=_("Before this date, users are " - "allowed to subscribe to this Trombi. " - "After this date, users subscribed will be allowed to comment on each other.")) - comments_deadline = models.DateField(_('comments deadline'), - default=date.today, help_text=_("After this date, users won't be " - "able to make comments anymore.")) - max_chars = models.IntegerField(_('maximum characters'), default=400, - help_text=_('Maximum number of characters allowed in a comment.')) - show_profiles = models.BooleanField(_("show users profiles to each other"), default=True) - club = models.OneToOneField(Club, related_name='trombi') + + subscription_deadline = models.DateField( + _("subscription deadline"), + default=date.today, + help_text=_( + "Before this date, users are " + "allowed to subscribe to this Trombi. " + "After this date, users subscribed will be allowed to comment on each other." + ), + ) + comments_deadline = models.DateField( + _("comments deadline"), + default=date.today, + help_text=_( + "After this date, users won't be " "able to make comments anymore." + ), + ) + max_chars = models.IntegerField( + _("maximum characters"), + default=400, + help_text=_("Maximum number of characters allowed in a comment."), + ) + show_profiles = models.BooleanField( + _("show users profiles to each other"), default=True + ) + club = models.OneToOneField(Club, related_name="trombi") objects = TrombiManager() availables = AvailableTrombiManager() @@ -72,11 +90,15 @@ class Trombi(models.Model): def clean(self): if self.subscription_deadline > self.comments_deadline: - raise ValidationError(_("Closing the subscriptions after the " - "comments is definitively not a good idea.")) + raise ValidationError( + _( + "Closing the subscriptions after the " + "comments is definitively not a good idea." + ) + ) def get_absolute_url(self): - return reverse('trombi:detail', kwargs={'trombi_id': self.id}) + return reverse("trombi:detail", kwargs={"trombi_id": self.id}) def is_owned_by(self, user): return user.can_edit(self.club) @@ -93,12 +115,36 @@ class TrombiUser(models.Model): It also adds the pictures to the profile without needing all the security like the other SithFiles. """ - user = models.OneToOneField(User, verbose_name=_("trombi user"), related_name='trombi_user') - trombi = models.ForeignKey(Trombi, verbose_name=_("trombi"), related_name='users', blank=True, null=True, on_delete=models.SET_NULL) - profile_pict = models.ImageField(upload_to='trombi', verbose_name=_("profile pict"), null=True, blank=True, - help_text=_("The profile picture you want in the trombi (warning: this picture may be published)")) - scrub_pict = models.ImageField(upload_to='trombi', verbose_name=_("scrub pict"), null=True, blank=True, - help_text=_("The scrub picture you want in the trombi (warning: this picture may be published)")) + + user = models.OneToOneField( + User, verbose_name=_("trombi user"), related_name="trombi_user" + ) + trombi = models.ForeignKey( + Trombi, + verbose_name=_("trombi"), + related_name="users", + blank=True, + null=True, + on_delete=models.SET_NULL, + ) + profile_pict = models.ImageField( + upload_to="trombi", + verbose_name=_("profile pict"), + null=True, + blank=True, + help_text=_( + "The profile picture you want in the trombi (warning: this picture may be published)" + ), + ) + scrub_pict = models.ImageField( + upload_to="trombi", + verbose_name=_("scrub pict"), + null=True, + blank=True, + help_text=_( + "The scrub picture you want in the trombi (warning: this picture may be published)" + ), + ) def __str__(self): return str(self.user) @@ -108,7 +154,9 @@ class TrombiUser(models.Model): def make_memberships(self): self.memberships.all().delete() - for m in self.user.memberships.filter(role__gt=settings.SITH_MAXIMUM_FREE_ROLE).order_by('end_date'): + for m in self.user.memberships.filter( + role__gt=settings.SITH_MAXIMUM_FREE_ROLE + ).order_by("end_date"): role = str(settings.SITH_CLUB_ROLES[m.role]) if m.description: role += " (%s)" % m.description @@ -130,8 +178,13 @@ class TrombiComment(models.Model): This represent a comment given by someone to someone else in the same Trombi instance. """ - author = models.ForeignKey(TrombiUser, verbose_name=_("author"), related_name='given_comments') - target = models.ForeignKey(TrombiUser, verbose_name=_("target"), related_name='received_comments') + + author = models.ForeignKey( + TrombiUser, verbose_name=_("author"), related_name="given_comments" + ) + target = models.ForeignKey( + TrombiUser, verbose_name=_("target"), related_name="received_comments" + ) content = models.TextField(_("content"), default="") is_moderated = models.BooleanField(_("is the comment moderated"), default=False) @@ -145,14 +198,17 @@ class TrombiClubMembership(models.Model): """ This represent a membership to a club """ - user = models.ForeignKey(TrombiUser, verbose_name=_("user"), related_name='memberships') + + user = models.ForeignKey( + TrombiUser, verbose_name=_("user"), related_name="memberships" + ) club = models.CharField(_("club"), max_length=32, default="") role = models.CharField(_("role"), max_length=64, default="") start = models.CharField(_("start"), max_length=16, default="") end = models.CharField(_("end"), max_length=16, default="") class Meta: - ordering = ['id'] + ordering = ["id"] def __str__(self): return "%s - %s - %s (%s)" % (self.user, self.club, self.role, self.start) @@ -161,4 +217,4 @@ class TrombiClubMembership(models.Model): return user.id == self.user.user.id or user.can_edit(self.user.trombi) def get_absolute_url(self): - return reverse('trombi:profile') + return reverse("trombi:profile") diff --git a/trombi/urls.py b/trombi/urls.py index a1a7085e..a20b2722 100644 --- a/trombi/urls.py +++ b/trombi/urls.py @@ -27,20 +27,56 @@ from django.conf.urls import url from trombi.views import * urlpatterns = [ - url(r'^(?P[0-9]+)/new$', TrombiCreateView.as_view(), name='create'), - url(r'^(?P[0-9]+)/export$', TrombiExportView.as_view(), name='export'), - url(r'^(?P[0-9]+)/edit$', TrombiEditView.as_view(), name='edit'), - url(r'^(?P[0-9]+)/moderate_comments$', TrombiModerateCommentsView.as_view(), name='moderate_comments'), - url(r'^(?P[0-9]+)/moderate$', TrombiModerateCommentView.as_view(), name='moderate_comment'), - url(r'^user/(?P[0-9]+)/delete$', TrombiDeleteUserView.as_view(), name='delete_user'), - url(r'^(?P[0-9]+)$', TrombiDetailView.as_view(), name='detail'), - url(r'^(?P[0-9]+)/new_comment$', TrombiCommentCreateView.as_view(), name='new_comment'), - url(r'^(?P[0-9]+)/profile$', UserTrombiProfileView.as_view(), name='user_profile'), - url(r'^comment/(?P[0-9]+)/edit$', TrombiCommentEditView.as_view(), name='edit_comment'), - url(r'^tools$', UserTrombiToolsView.as_view(), name='user_tools'), - url(r'^profile$', UserTrombiEditProfileView.as_view(), name='profile'), - url(r'^pictures$', UserTrombiEditPicturesView.as_view(), name='pictures'), - url(r'^reset_memberships$', UserTrombiResetClubMembershipsView.as_view(), name='reset_memberships'), - url(r'^membership/(?P[0-9]+)/edit$', UserTrombiEditMembershipView.as_view(), name='edit_membership'), - url(r'^membership/(?P[0-9]+)/delete$', UserTrombiDeleteMembershipView.as_view(), name='delete_membership'), + url(r"^(?P[0-9]+)/new$", TrombiCreateView.as_view(), name="create"), + url(r"^(?P[0-9]+)/export$", TrombiExportView.as_view(), name="export"), + url(r"^(?P[0-9]+)/edit$", TrombiEditView.as_view(), name="edit"), + url( + r"^(?P[0-9]+)/moderate_comments$", + TrombiModerateCommentsView.as_view(), + name="moderate_comments", + ), + url( + r"^(?P[0-9]+)/moderate$", + TrombiModerateCommentView.as_view(), + name="moderate_comment", + ), + url( + r"^user/(?P[0-9]+)/delete$", + TrombiDeleteUserView.as_view(), + name="delete_user", + ), + url(r"^(?P[0-9]+)$", TrombiDetailView.as_view(), name="detail"), + url( + r"^(?P[0-9]+)/new_comment$", + TrombiCommentCreateView.as_view(), + name="new_comment", + ), + url( + r"^(?P[0-9]+)/profile$", + UserTrombiProfileView.as_view(), + name="user_profile", + ), + url( + r"^comment/(?P[0-9]+)/edit$", + TrombiCommentEditView.as_view(), + name="edit_comment", + ), + url(r"^tools$", UserTrombiToolsView.as_view(), name="user_tools"), + url(r"^profile$", UserTrombiEditProfileView.as_view(), name="profile"), + url(r"^pictures$", UserTrombiEditPicturesView.as_view(), name="pictures"), + url( + r"^reset_memberships$", + UserTrombiResetClubMembershipsView.as_view(), + name="reset_memberships", + ), + url( + r"^membership/(?P[0-9]+)/edit$", + UserTrombiEditMembershipView.as_view(), + name="edit_membership", + ), + url( + r"^membership/(?P[0-9]+)/delete$", + UserTrombiDeleteMembershipView.as_view(), + name="delete_membership", + ), ] diff --git a/trombi/views.py b/trombi/views.py index 2b28bc59..86bc324e 100644 --- a/trombi/views.py +++ b/trombi/views.py @@ -38,7 +38,14 @@ from datetime import date from trombi.models import Trombi, TrombiUser, TrombiComment, TrombiClubMembership from core.views.forms import SelectDate -from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, CanCreateMixin, QuickNotifMixin +from core.views import ( + CanViewMixin, + CanEditMixin, + CanEditPropMixin, + TabedViewMixin, + CanCreateMixin, + QuickNotifMixin, +) from core.models import User from club.models import Club @@ -49,29 +56,35 @@ class TrombiTabsMixin(TabedViewMixin): def get_list_of_tabs(self): tab_list = [] - tab_list.append({ - 'url': reverse('trombi:user_tools'), - 'slug': 'tools', - 'name': _("Tools"), - }) - tab_list.append({ - 'url': reverse('trombi:profile'), - 'slug': 'profile', - 'name': _("My profile"), - }) - tab_list.append({ - 'url': reverse('trombi:pictures'), - 'slug': 'pictures', - 'name': _("My pictures"), - }) + tab_list.append( + {"url": reverse("trombi:user_tools"), "slug": "tools", "name": _("Tools")} + ) + tab_list.append( + { + "url": reverse("trombi:profile"), + "slug": "profile", + "name": _("My profile"), + } + ) + tab_list.append( + { + "url": reverse("trombi:pictures"), + "slug": "pictures", + "name": _("My pictures"), + } + ) try: trombi = self.request.user.trombi_user.trombi if self.request.user.is_owner(trombi): - tab_list.append({ - 'url': reverse('trombi:detail', kwargs={'trombi_id': trombi.id}), - 'slug': 'admin_tools', - 'name': _("Admin tools"), - }) + tab_list.append( + { + "url": reverse( + "trombi:detail", kwargs={"trombi_id": trombi.id} + ), + "slug": "admin_tools", + "name": _("Admin tools"), + } + ) except: pass return tab_list @@ -80,20 +93,23 @@ class TrombiTabsMixin(TabedViewMixin): class TrombiForm(forms.ModelForm): class Meta: model = Trombi - fields = ['subscription_deadline', 'comments_deadline', 'max_chars', 'show_profiles'] - widgets = { - 'subscription_deadline': SelectDate, - 'comments_deadline': SelectDate, - } + fields = [ + "subscription_deadline", + "comments_deadline", + "max_chars", + "show_profiles", + ] + widgets = {"subscription_deadline": SelectDate, "comments_deadline": SelectDate} class TrombiCreateView(CanCreateMixin, CreateView): """ Create a trombi for a club """ + model = Trombi form_class = TrombiForm - template_name = 'core/create.jinja' + template_name = "core/create.jinja" def post(self, request, *args, **kwargs): """ @@ -101,7 +117,7 @@ class TrombiCreateView(CanCreateMixin, CreateView): """ form = self.get_form() if form.is_valid(): - club = get_object_or_404(Club, id=self.kwargs['club_id']) + club = get_object_or_404(Club, id=self.kwargs["club_id"]) form.instance.club = club ret = self.form_valid(form) return ret @@ -112,8 +128,8 @@ class TrombiCreateView(CanCreateMixin, CreateView): class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): model = Trombi form_class = TrombiForm - template_name = 'core/edit.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" def get_success_url(self): @@ -121,12 +137,15 @@ class TrombiEditView(CanEditPropMixin, TrombiTabsMixin, UpdateView): class AddUserForm(forms.Form): - user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) + user = AutoCompleteSelectField( + "users", required=True, label=_("Select user"), help_text=None + ) + class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): model = Trombi - template_name = 'trombi/detail.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "trombi/detail.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" def post(self, request, *args, **kwargs): @@ -134,43 +153,51 @@ class TrombiDetailView(CanEditMixin, QuickNotifMixin, TrombiTabsMixin, DetailVie form = AddUserForm(request.POST) if form.is_valid(): try: - TrombiUser(user=form.cleaned_data['user'], trombi=self.object).save() + TrombiUser(user=form.cleaned_data["user"], trombi=self.object).save() self.quick_notif_list.append("qn_success") - except: # We don't care about duplicate keys + except: # We don't care about duplicate keys self.quick_notif_list.append("qn_fail") return super(TrombiDetailView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(TrombiDetailView, self).get_context_data(**kwargs) - kwargs['form'] = AddUserForm() + kwargs["form"] = AddUserForm() return kwargs + class TrombiExportView(CanEditMixin, TrombiTabsMixin, DetailView): model = Trombi - template_name = 'trombi/export.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "trombi/export.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" + class TrombiDeleteUserView(CanEditPropMixin, TrombiTabsMixin, DeleteView): model = TrombiUser - pk_url_kwarg = 'user_id' - template_name = 'core/delete_confirm.jinja' + pk_url_kwarg = "user_id" + template_name = "core/delete_confirm.jinja" current_tab = "admin_tools" def get_success_url(self): - return reverse('trombi:detail', kwargs={'trombi_id': self.object.trombi.id}) + "?qn_success" + return ( + reverse("trombi:detail", kwargs={"trombi_id": self.object.trombi.id}) + + "?qn_success" + ) -class TrombiModerateCommentsView(CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView): +class TrombiModerateCommentsView( + CanEditPropMixin, QuickNotifMixin, TrombiTabsMixin, DetailView +): model = Trombi - template_name = 'trombi/comment_moderation.jinja' - pk_url_kwarg = 'trombi_id' + template_name = "trombi/comment_moderation.jinja" + pk_url_kwarg = "trombi_id" current_tab = "admin_tools" def get_context_data(self, **kwargs): kwargs = super(TrombiModerateCommentsView, self).get_context_data(**kwargs) - kwargs['comments'] = TrombiComment.objects.filter(is_moderated=False, - author__trombi__id=self.object.id).exclude(target__user__id=self.request.user.id) + kwargs["comments"] = TrombiComment.objects.filter( + is_moderated=False, author__trombi__id=self.object.id + ).exclude(target__user__id=self.request.user.id) return kwargs @@ -181,8 +208,8 @@ class TrombiModerateForm(forms.Form): class TrombiModerateCommentView(DetailView): model = TrombiComment - template_name = 'core/edit.jinja' - pk_url_kwarg = 'comment_id' + template_name = "core/edit.jinja" + pk_url_kwarg = "comment_id" def dispatch(self, request, *args, **kwargs): self.object = self.get_object() @@ -192,79 +219,107 @@ class TrombiModerateCommentView(DetailView): def post(self, request, *args, **kwargs): if "action" in request.POST: - if request.POST['action'] == "accept": + if request.POST["action"] == "accept": self.object.is_moderated = True self.object.save() - return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id}) + "?qn_success") - elif request.POST['action'] == "reject": - return super(TrombiModerateCommentView, self).get(request, *args, **kwargs) - elif request.POST['action'] == "delete" and "reason" in request.POST.keys(): + return redirect( + reverse( + "trombi:moderate_comments", + kwargs={"trombi_id": self.object.author.trombi.id}, + ) + + "?qn_success" + ) + elif request.POST["action"] == "reject": + return super(TrombiModerateCommentView, self).get( + request, *args, **kwargs + ) + elif request.POST["action"] == "delete" and "reason" in request.POST.keys(): self.object.author.user.email_user( subject="[%s] %s" % (settings.SITH_NAME, _("Rejected comment")), - message=_("Your comment to %(target)s on the Trombi \"%(trombi)s\" was rejected for the following " - "reason: %(reason)s\n\n" - "Your comment was:\n\n%(content)s" - ) % { - 'target': self.object.target.user.get_display_name(), - 'trombi': self.object.author.trombi, - 'reason': request.POST["reason"], - 'content': self.object.content, + message=_( + 'Your comment to %(target)s on the Trombi "%(trombi)s" was rejected for the following ' + "reason: %(reason)s\n\n" + "Your comment was:\n\n%(content)s" + ) + % { + "target": self.object.target.user.get_display_name(), + "trombi": self.object.author.trombi, + "reason": request.POST["reason"], + "content": self.object.content, }, ) self.object.delete() - return redirect(reverse('trombi:moderate_comments', kwargs={'trombi_id': self.object.author.trombi.id}) + "?qn_success") + return redirect( + reverse( + "trombi:moderate_comments", + kwargs={"trombi_id": self.object.author.trombi.id}, + ) + + "?qn_success" + ) raise Http404 def get_context_data(self, **kwargs): kwargs = super(TrombiModerateCommentView, self).get_context_data(**kwargs) - kwargs['form'] = TrombiModerateForm() + kwargs["form"] = TrombiModerateForm() return kwargs + # User side class TrombiModelChoiceField(forms.ModelChoiceField): def label_from_instance(self, obj): - return _("%(name)s (deadline: %(date)s)") % {'name': str(obj), 'date': str(obj.subscription_deadline)} + return _("%(name)s (deadline: %(date)s)") % { + "name": str(obj), + "date": str(obj.subscription_deadline), + } class UserTrombiForm(forms.Form): - trombi = TrombiModelChoiceField(Trombi.availables.all(), required=False, label=_("Select trombi"), - help_text=_("This allows you to subscribe to a Trombi. " - "Be aware that you can subscribe only once, so don't play with that, " - "or you will expose yourself to the admins' wrath!")) + trombi = TrombiModelChoiceField( + Trombi.availables.all(), + required=False, + label=_("Select trombi"), + help_text=_( + "This allows you to subscribe to a Trombi. " + "Be aware that you can subscribe only once, so don't play with that, " + "or you will expose yourself to the admins' wrath!" + ), + ) class UserTrombiToolsView(QuickNotifMixin, TrombiTabsMixin, TemplateView): """ Display a user's trombi tools """ + template_name = "trombi/user_tools.jinja" current_tab = "tools" def post(self, request, *args, **kwargs): self.form = UserTrombiForm(request.POST) if self.form.is_valid(): - trombi_user = TrombiUser(user=request.user, - trombi=self.form.cleaned_data['trombi']) + trombi_user = TrombiUser( + user=request.user, trombi=self.form.cleaned_data["trombi"] + ) trombi_user.save() - self.quick_notif_list += ['qn_success'] + self.quick_notif_list += ["qn_success"] return super(UserTrombiToolsView, self).get(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super(UserTrombiToolsView, self).get_context_data(**kwargs) - kwargs['user'] = self.request.user - if not hasattr(self.request.user, 'trombi_user'): - kwargs['subscribe_form'] = UserTrombiForm() + kwargs["user"] = self.request.user + if not hasattr(self.request.user, "trombi_user"): + kwargs["subscribe_form"] = UserTrombiForm() else: - kwargs['trombi'] = self.request.user.trombi_user.trombi - kwargs['date'] = date + kwargs["trombi"] = self.request.user.trombi_user.trombi + kwargs["date"] = date return kwargs class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): model = TrombiUser - fields = ['profile_pict', 'scrub_pict'] + fields = ["profile_pict", "scrub_pict"] template_name = "core/edit.jinja" current_tab = "pictures" @@ -272,19 +327,27 @@ class UserTrombiEditPicturesView(TrombiTabsMixin, UpdateView): return self.request.user.trombi_user def get_success_url(self): - return reverse('trombi:user_tools') + "?qn_success" + return reverse("trombi:user_tools") + "?qn_success" class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): model = User - form_class = modelform_factory(User, - fields=['second_email', 'phone', 'department', 'dpt_option', - 'quote', 'parent_address'], - labels={ - 'second_email': _("Personal email (not UTBM)"), - 'phone': _("Phone"), - 'parent_address': _("Native town"), - }) + form_class = modelform_factory( + User, + fields=[ + "second_email", + "phone", + "department", + "dpt_option", + "quote", + "parent_address", + ], + labels={ + "second_email": _("Personal email (not UTBM)"), + "phone": _("Phone"), + "parent_address": _("Native town"), + }, + ) template_name = "trombi/edit_profile.jinja" current_tab = "profile" @@ -292,7 +355,7 @@ class UserTrombiEditProfileView(QuickNotifMixin, TrombiTabsMixin, UpdateView): return self.request.user def get_success_url(self): - return reverse('trombi:user_tools') + "?qn_success" + return reverse("trombi:user_tools") + "?qn_success" class UserTrombiResetClubMembershipsView(RedirectView): @@ -304,29 +367,34 @@ class UserTrombiResetClubMembershipsView(RedirectView): return redirect(self.get_success_url()) def get_success_url(self): - return reverse('trombi:profile') + "?qn_success" + return reverse("trombi:profile") + "?qn_success" class UserTrombiDeleteMembershipView(TrombiTabsMixin, CanEditMixin, DeleteView): model = TrombiClubMembership pk_url_kwarg = "membership_id" template_name = "core/delete_confirm.jinja" - success_url = reverse_lazy('trombi:profile') + success_url = reverse_lazy("trombi:profile") current_tab = "profile" def get_success_url(self): - return super(UserTrombiDeleteMembershipView, self).get_success_url() + "?qn_success" + return ( + super(UserTrombiDeleteMembershipView, self).get_success_url() + + "?qn_success" + ) class UserTrombiEditMembershipView(CanEditMixin, TrombiTabsMixin, UpdateView): model = TrombiClubMembership pk_url_kwarg = "membership_id" - fields = ['role', 'start', 'end'] + fields = ["role", "start", "end"] template_name = "core/edit.jinja" current_tab = "profile" def get_success_url(self): - return super(UserTrombiEditMembershipView, self).get_success_url() + "?qn_success" + return ( + super(UserTrombiEditMembershipView, self).get_success_url() + "?qn_success" + ) class UserTrombiProfileView(TrombiTabsMixin, DetailView): @@ -338,52 +406,69 @@ class UserTrombiProfileView(TrombiTabsMixin, DetailView): def get(self, request, *args, **kwargs): self.object = self.get_object() - if (self.object.trombi.id != request.user.trombi_user.trombi.id or - self.object.user.id == request.user.id or - not self.object.trombi.show_profiles): + if ( + self.object.trombi.id != request.user.trombi_user.trombi.id + or self.object.user.id == request.user.id + or not self.object.trombi.show_profiles + ): raise Http404() return super(UserTrombiProfileView, self).get(request, *args, **kwargs) -class TrombiCommentFormView(): +class TrombiCommentFormView: """ Create/edit a trombi comment """ + model = TrombiComment - fields = ['content'] - template_name = 'trombi/comment.jinja' + fields = ["content"] + template_name = "trombi/comment.jinja" def get_form_class(self): self.trombi = self.request.user.trombi_user.trombi if date.today() <= self.trombi.subscription_deadline: - raise Http404(_("You can not yet write comment, you must wait for " - "the subscription deadline to be passed.")) + raise Http404( + _( + "You can not yet write comment, you must wait for " + "the subscription deadline to be passed." + ) + ) if self.trombi.comments_deadline < date.today(): - raise Http404(_("You can not write comment anymore, the deadline is " - "already passed.")) - return modelform_factory(self.model, fields=self.fields, - widgets={ - 'content': forms.widgets.Textarea(attrs={'maxlength': self.trombi.max_chars}) - }, - help_texts={ - 'content': _("Maximum characters: %(max_length)s") % {'max_length': self.trombi.max_chars} - }) + raise Http404( + _( + "You can not write comment anymore, the deadline is " + "already passed." + ) + ) + return modelform_factory( + self.model, + fields=self.fields, + widgets={ + "content": forms.widgets.Textarea( + attrs={"maxlength": self.trombi.max_chars} + ) + }, + help_texts={ + "content": _("Maximum characters: %(max_length)s") + % {"max_length": self.trombi.max_chars} + }, + ) def get_success_url(self): - return reverse('trombi:user_tools') + "?qn_success" + return reverse("trombi:user_tools") + "?qn_success" def get_context_data(self, **kwargs): kwargs = super(TrombiCommentFormView, self).get_context_data(**kwargs) - if 'user_id' in self.kwargs.keys(): - kwargs['target'] = get_object_or_404(TrombiUser, id=self.kwargs['user_id']) + if "user_id" in self.kwargs.keys(): + kwargs["target"] = get_object_or_404(TrombiUser, id=self.kwargs["user_id"]) else: - kwargs['target'] = self.object.target + kwargs["target"] = self.object.target return kwargs class TrombiCommentCreateView(TrombiCommentFormView, CreateView): def form_valid(self, form): - target = get_object_or_404(TrombiUser, id=self.kwargs['user_id']) + target = get_object_or_404(TrombiUser, id=self.kwargs["user_id"]) author = self.request.user.trombi_user form.instance.author = author form.instance.target = target