From ec197224712062e5e894a7f9d8b5c00e091b02a4 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Tue, 9 May 2023 15:08:21 +0200 Subject: [PATCH 01/14] Update flake8 to work better MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (Partly with black, although that linter still shouldn’t be used here) --- .flake8 | 2 ++ pyproject.toml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 7c08b352..0365fd42 100644 --- a/.flake8 +++ b/.flake8 @@ -4,6 +4,8 @@ ignore = D1 # Missing blank line after last docstring section D413 + # Makes it possible to split long (boolean) equations on multiple lines + E203 exclude = .git, __pycache__, diff --git a/pyproject.toml b/pyproject.toml index c2282232..e350275c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,3 @@ [tool.black] -line-length = 119 +line-length = 127 +skip-string-normalization = true From 110a045be15c9fdedd00da07659d442a469abda7 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Tue, 9 May 2023 15:26:58 +0200 Subject: [PATCH 02/14] Fix flake8 not checking sort order by adding the dependency and add isort as a dependency --- dev-requirements.in | 3 +++ dev-requirements.txt | 18 ++++++++++++++++-- pyproject.toml | 3 +++ requirements.txt | 6 +----- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/dev-requirements.in b/dev-requirements.in index 1763a02d..36fa358a 100644 --- a/dev-requirements.in +++ b/dev-requirements.in @@ -3,3 +3,6 @@ flake8 flake8-docstrings pep8-naming coverage +autopep8 +isort +flake8-import-order diff --git a/dev-requirements.txt b/dev-requirements.txt index ec2f375f..25a99328 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,9 +1,11 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile dev-requirements.in # +autopep8==2.0.2 + # via -r dev-requirements.in coverage==7.0.0 # via -r dev-requirements.in flake8==6.0.0 @@ -13,15 +15,27 @@ flake8==6.0.0 # pep8-naming flake8-docstrings==1.6.0 # via -r dev-requirements.in +flake8-import-order==0.18.2 + # via -r dev-requirements.in +isort==5.12.0 + # via -r dev-requirements.in mccabe==0.7.0 # via flake8 pep8-naming==0.13.3 # via -r dev-requirements.in pycodestyle==2.10.0 - # via flake8 + # via + # autopep8 + # flake8 + # flake8-import-order pydocstyle==6.1.1 # via flake8-docstrings pyflakes==3.0.1 # via flake8 snowballstemmer==2.2.0 # via pydocstyle +tomli==2.0.1 + # via autopep8 + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/pyproject.toml b/pyproject.toml index e350275c..888ce75c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,6 @@ [tool.black] line-length = 127 skip-string-normalization = true + +[tool.isort] +profile = "pycharm" diff --git a/requirements.txt b/requirements.txt index 966e5888..e3fe9489 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,11 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # pip-compile requirements.in # asgiref==3.6.0 # via django -backports-zoneinfo==0.2.1 ; python_version < "3.9" - # via - # -r requirements.in - # django certifi==2022.12.7 # via requests cffi==1.15.1 From 4298a62697f196b840190977faad6f3891fdef6f Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Tue, 9 May 2023 15:48:24 +0200 Subject: [PATCH 03/14] Flake8: Actually add the correct operator to the ignore and selection, and add an extra possible .venv path to the excluded paths --- .flake8 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.flake8 b/.flake8 index 0365fd42..7b1409ca 100644 --- a/.flake8 +++ b/.flake8 @@ -5,12 +5,15 @@ ignore = # Missing blank line after last docstring section D413 # Makes it possible to split long (boolean) equations on multiple lines - E203 + W503 +extend-select = + W504 exclude = .git, __pycache__, */migrations, venv + .venv max-complexity = 10 max-line-length = 127 From 72e973397301e0aaaf6f176b9ef4e04cd945cd74 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Tue, 9 May 2023 16:15:42 +0200 Subject: [PATCH 04/14] Add line length to isort --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 888ce75c..0c757a59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,4 @@ skip-string-normalization = true [tool.isort] profile = "pycharm" +line_length = 127 From fc88513c9e3cac3595f589942528d1fb6ab94740 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Tue, 16 May 2023 16:08:51 +0200 Subject: [PATCH 05/14] Revert requirements.txt --- requirements.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e3fe9489..966e5888 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,15 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile requirements.in # asgiref==3.6.0 # via django +backports-zoneinfo==0.2.1 ; python_version < "3.9" + # via + # -r requirements.in + # django certifi==2022.12.7 # via requests cffi==1.15.1 From d66bb74b9141cae1043bbf80fba4668d159c68ba Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 17:55:17 +0200 Subject: [PATCH 06/14] Add black and change line length to 88, the default of black (edit: and also add glob patterns exclusion of migrations) --- .github/workflows/ci.yml | 1 + dev-requirements.in | 1 + dev-requirements.txt | 18 +++++++++++++++++- pyproject.toml | 9 +++++---- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3fa4719..5cf52f31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,3 +50,4 @@ jobs: uses: codecov/codecov-action@v3 - name: Lint with flake8 run: flake8 + - uses: psf/black@stable diff --git a/dev-requirements.in b/dev-requirements.in index 36fa358a..9cdac5d7 100644 --- a/dev-requirements.in +++ b/dev-requirements.in @@ -6,3 +6,4 @@ coverage autopep8 isort flake8-import-order +black diff --git a/dev-requirements.txt b/dev-requirements.txt index 25a99328..53411f2e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -6,6 +6,10 @@ # autopep8==2.0.2 # via -r dev-requirements.in +black==23.3.0 + # via -r dev-requirements.in +click==8.1.3 + # via black coverage==7.0.0 # via -r dev-requirements.in flake8==6.0.0 @@ -21,8 +25,18 @@ isort==5.12.0 # via -r dev-requirements.in mccabe==0.7.0 # via flake8 +mypy-extensions==1.0.0 + # via black +packaging==22.0 + # via + # -c requirements.txt + # black +pathspec==0.11.1 + # via black pep8-naming==0.13.3 # via -r dev-requirements.in +platformdirs==3.5.1 + # via black pycodestyle==2.10.0 # via # autopep8 @@ -35,7 +49,9 @@ pyflakes==3.0.1 snowballstemmer==2.2.0 # via pydocstyle tomli==2.0.1 - # via autopep8 + # via + # autopep8 + # black # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/pyproject.toml b/pyproject.toml index 0c757a59..29a2c240 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,8 @@ [tool.black] -line-length = 127 -skip-string-normalization = true +line-length = 88 +extend-exclude = ".*/migrations/.*.py" [tool.isort] -profile = "pycharm" -line_length = 127 +profile = "black" +line_length = 88 +extend_skip_glob = ["*/migrations/*.py"] From 37079c3b2ee3fc4100c56b041d90faf5c5fbbe48 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 17:56:07 +0200 Subject: [PATCH 07/14] =?UTF-8?q?Reformat=20without=20string=20'=E2=86=92"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- allauthproviders/quadrivium/provider.py | 2 +- allauthproviders/quadrivium/views.py | 10 +- creditmanagement/admin.py | 15 +- creditmanagement/csv.py | 44 ++- creditmanagement/forms.py | 107 ++++-- creditmanagement/migrations/0001_initial.py | 131 ++++++- .../migrations/0002_auto_20190203_1923.py | 41 ++- .../migrations/0003_merge_20190203_1925.py | 4 +- .../migrations/0004_auto_20190203_2324.py | 55 ++- .../migrations/0005_usercredit_view.py | 4 +- .../migrations/0006_auto_20190213_1452.py | 1 - .../migrations/0007_usercredit_view_update.py | 5 +- .../0008_pendingdiningtransaction.py | 86 ++++- .../migrations/0009_usercredit_view_update.py | 6 +- .../migrations/0010_auto_20200817_1230.py | 16 +- .../migrations/0011_account_transaction.py | 87 ++++- .../migrations/0012_auto_20200824_0135.py | 2 - .../migrations/0013_auto_20210319_2310.py | 9 +- .../migrations/0014_auto_20210320_0015.py | 1 - .../migrations/0015_auto_20220428_2113.py | 22 +- .../migrations/0016_unfold_cancel_column.py | 11 +- .../migrations/0017_remove_cancel_column.py | 15 +- creditmanagement/models.py | 42 ++- creditmanagement/tests/test_models.py | 12 +- creditmanagement/urls.py | 6 +- creditmanagement/views.py | 15 +- dining/admin.py | 25 +- dining/forms.py | 196 ++++++++--- dining/models.py | 130 +++++-- dining/templatetags/dining_tags.py | 19 +- dining/tests/test_forms.py | 331 ++++++++++++------ dining/tests/test_forms_diningentry.py | 87 +++-- dining/tests/test_forms_sendreminderform.py | 30 +- dining/tests/test_models.py | 62 +++- dining/urls.py | 62 +++- dining/views.py | 250 +++++++++---- general/forms.py | 35 +- general/mail_control.py | 28 +- general/migrations/0001_initial.py | 21 +- general/migrations/0002_auto_20190227_1203.py | 19 +- .../0003_remove_siteupdate_version.py | 1 - general/models.py | 13 +- general/urls.py | 8 +- general/util.py | 4 +- general/views.py | 31 +- scaladining/admin.py | 1 + scaladining/context_processors.py | 4 +- scaladining/settings.py | 22 +- userdetails/admin.py | 12 +- userdetails/externalaccounts.py | 12 +- userdetails/forms.py | 43 ++- userdetails/migrations/0001_initial.py | 155 ++++++-- .../migrations/0002_auto_20190203_2324.py | 27 +- .../migrations/0003_auto_20190204_0025.py | 1 - .../migrations/0004_user_external_link.py | 1 - .../migrations/0005_auto_20190207_1510.py | 1 - .../migrations/0006_auto_20190207_1744.py | 8 +- .../0007_association_is_choosable.py | 6 +- .../migrations/0008_remove_user_is_staff.py | 1 - userdetails/migrations/0009_user_is_staff.py | 7 +- .../migrations/0010_auto_20190304_2050.py | 5 +- .../migrations/0011_auto_20190429_1303.py | 4 +- .../migrations/0012_auto_20190429_1306.py | 6 +- .../migrations/0013_auto_20190429_2232.py | 1 - .../0014_remove_user_external_link.py | 1 - .../migrations/0015_auto_20190508_1905.py | 11 +- .../migrations/0016_association_social_app.py | 9 +- ...association_balance_update_instructions.py | 1 - .../0018_association_has_site_stats_access.py | 1 - .../migrations/0019_auto_20200823_1602.py | 19 +- .../migrations/0020_auto_20210319_2310.py | 5 +- .../migrations/0021_auto_20221224_1346.py | 8 +- userdetails/models.py | 58 ++- userdetails/tests/test_externalaccounts.py | 20 +- userdetails/tests/test_forms.py | 20 +- userdetails/tests/test_models.py | 24 +- userdetails/urls.py | 156 +++++++-- userdetails/views.py | 13 +- userdetails/views_association.py | 136 +++++-- userdetails/views_user_settings.py | 20 +- utils/testing/form_test_utils.py | 14 +- utils/testing/patch_utils.py | 12 +- utils/tests/test_form_validation.py | 50 ++- 83 files changed, 2263 insertions(+), 733 deletions(-) diff --git a/allauthproviders/quadrivium/provider.py b/allauthproviders/quadrivium/provider.py index c75b2591..8e56ba5a 100644 --- a/allauthproviders/quadrivium/provider.py +++ b/allauthproviders/quadrivium/provider.py @@ -41,7 +41,7 @@ def extract_common_fields(self, data): username=data.get('preferred_username', data.get('given_name')), email=data.get('email'), first_name=data.get('given_name'), - last_name=data.get('family_name') + last_name=data.get('family_name'), ) diff --git a/allauthproviders/quadrivium/views.py b/allauthproviders/quadrivium/views.py index db960b62..a1984dbe 100644 --- a/allauthproviders/quadrivium/views.py +++ b/allauthproviders/quadrivium/views.py @@ -1,5 +1,9 @@ import requests -from allauth.socialaccount.providers.oauth2.views import OAuth2Adapter, OAuth2CallbackView, OAuth2LoginView +from allauth.socialaccount.providers.oauth2.views import ( + OAuth2Adapter, + OAuth2CallbackView, + OAuth2LoginView, +) from .provider import QuadriviumProvider @@ -8,7 +12,9 @@ class QuadriviumOAuth2Adapter(OAuth2Adapter): provider_id = QuadriviumProvider.id access_token_url = 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/token' - authorize_url = 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/auth' + authorize_url = ( + 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/auth' + ) profile_url = 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/userinfo' def complete_login(self, request, app, token, **kwargs): diff --git a/creditmanagement/admin.py b/creditmanagement/admin.py index 1c5d3f84..42cd2bde 100644 --- a/creditmanagement/admin.py +++ b/creditmanagement/admin.py @@ -45,7 +45,13 @@ class AccountAdmin(admin.ModelAdmin): ordering = ('special', 'association__name', 'user__first_name', 'user__last_name') list_display = ('__str__', 'get_balance', 'negative_since') list_filter = (AccountTypeListFilter,) - search_fields = ('user__first_name', 'user__last_name', 'user__username', 'association__name', 'special') + search_fields = ( + 'user__first_name', + 'user__last_name', + 'user__username', + 'association__name', + 'special', + ) def has_change_permission(self, request, obj=None): return False @@ -53,6 +59,7 @@ def has_change_permission(self, request, obj=None): class SourceTypeListFilter(AccountTypeListFilter): """Allows filtering on the source account type.""" + title = "source account type" parameter_name = 'source_type' user_query = Q(source__user__isnull=False) @@ -62,6 +69,7 @@ class SourceTypeListFilter(AccountTypeListFilter): class TargetTypeListFilter(AccountTypeListFilter): """Allows filtering on the target account type.""" + title = "target account type" parameter_name = 'target_type' user_query = Q(target__user__isnull=False) @@ -78,7 +86,10 @@ class TransactionAdmin(admin.ModelAdmin): list_filter = (SourceTypeListFilter, TargetTypeListFilter) fields = ('source', 'target', 'amount', 'moment', 'description', 'created_by') - readonly_fields = ('moment', 'created_by') # Only applicable for the add transaction form (changing is not allowed) + readonly_fields = ( + 'moment', + 'created_by', + ) # Only applicable for the add transaction form (changing is not allowed) autocomplete_fields = ('source', 'target') def has_change_permission(self, request, obj=None): diff --git a/creditmanagement/csv.py b/creditmanagement/csv.py index 3f299874..bd3733fa 100644 --- a/creditmanagement/csv.py +++ b/creditmanagement/csv.py @@ -7,7 +7,9 @@ from creditmanagement.models import Transaction, Account -def write_transactions_csv(csv_file, transactions: Iterable[Transaction], account_self: Account): +def write_transactions_csv( + csv_file, transactions: Iterable[Transaction], account_self: Account +): """Writes a transactions CSV file to the given file object. Args: @@ -17,7 +19,18 @@ def write_transactions_csv(csv_file, transactions: Iterable[Transaction], accoun opposite account is used for the (counterpart) name column. """ csv_writer = csv.writer(csv_file) - csv_writer.writerow(['date', 'direction', 'account_type', 'name', 'email', 'amount', 'description', 'created_by']) + csv_writer.writerow( + [ + 'date', + 'direction', + 'account_type', + 'name', + 'email', + 'amount', + 'description', + 'created_by', + ] + ) for t in transactions: # Determine direction and counterparty @@ -31,9 +44,30 @@ def write_transactions_csv(csv_file, transactions: Iterable[Transaction], accoun raise ValueError("Transaction does not involve account_self") # Set timezone to ours (Europe/Amsterdam) and get rid of microseconds - date = t.moment.astimezone(get_default_timezone()).replace(microsecond=0).isoformat() - account_type = 'user' if counterparty.user else 'association' if counterparty.association else 'special' + date = ( + t.moment.astimezone(get_default_timezone()) + .replace(microsecond=0) + .isoformat() + ) + account_type = ( + 'user' + if counterparty.user + else 'association' + if counterparty.association + else 'special' + ) name = str(counterparty) email = counterparty.user.email if account_type == 'user' else '' - csv_writer.writerow([date, direction, account_type, name, email, t.amount, t.description, str(t.created_by)]) + csv_writer.writerow( + [ + date, + direction, + account_type, + name, + email, + t.amount, + t.description, + str(t.created_by), + ] + ) diff --git a/creditmanagement/forms.py b/creditmanagement/forms.py index 2b3307b2..49f384f4 100644 --- a/creditmanagement/forms.py +++ b/creditmanagement/forms.py @@ -10,16 +10,20 @@ from userdetails.models import User, Association # Form fields which are used in transaction forms -USER_FORM_FIELD = forms.ModelChoiceField(User.objects.filter(is_active=True), - required=False, - widget=ModelSelect2(url='people_autocomplete'), - label="User") -ASSOCIATION_FORM_FIELD = forms.ModelChoiceField(Association.objects.all(), - required=False, - label="Association") -SPECIAL_FORM_FIELD = forms.ModelChoiceField(Account.objects.filter(special__isnull=False), - required=False, - label="Bookkeeping account") +USER_FORM_FIELD = forms.ModelChoiceField( + User.objects.filter(is_active=True), + required=False, + widget=ModelSelect2(url='people_autocomplete'), + label="User", +) +ASSOCIATION_FORM_FIELD = forms.ModelChoiceField( + Association.objects.all(), required=False, label="Association" +) +SPECIAL_FORM_FIELD = forms.ModelChoiceField( + Account.objects.filter(special__isnull=False), + required=False, + label="Bookkeeping account", +) def one_of(*args) -> Tuple[Any, int]: @@ -32,7 +36,10 @@ def one_of(*args) -> Tuple[Any, int]: for i in range(len(args)): if args[i]: if el: - return None, -1 # Another True element was already found so multiple evaluate to True + return ( + None, + -1, + ) # Another True element was already found so multiple evaluate to True el = args[i] idx = i return el, idx @@ -50,7 +57,13 @@ class TransactionForm(forms.ModelForm): class Meta: model = Transaction - fields = ['origin', 'amount', 'target_user', 'target_association', 'description'] + fields = [ + 'origin', + 'amount', + 'target_user', + 'target_association', + 'description', + ] help_texts = { 'description': "E.g. deposit or withdrawal via board member.", } @@ -66,13 +79,17 @@ def __init__(self, source: Account, user: User, *args, **kwargs): self.instance.source = source self.instance.created_by = user self.fields['origin'].initial = str(source) - self.fields['target_association'].help_text = ("Provide a user or an association who will receive the money. " - "You can't provide both a user and an association.") + self.fields['target_association'].help_text = ( + "Provide a user or an association who will receive the money. " + "You can't provide both a user and an association." + ) def clean(self): cleaned_data = super().clean() # Check that there's exactly 1 one user or association set - target_el, idx = one_of(cleaned_data.get('target_user'), cleaned_data.get('target_association')) + target_el, idx = one_of( + cleaned_data.get('target_user'), cleaned_data.get('target_association') + ) if not target_el: raise ValidationError("Provide exactly one of user or association.") @@ -109,8 +126,16 @@ class SiteWideTransactionForm(forms.ModelForm): class Meta: model = Transaction - fields = ['source_user', 'source_association', 'source_special', - 'target_user', 'target_association', 'target_special', 'amount', 'description'] + fields = [ + 'source_user', + 'source_association', + 'source_special', + 'target_user', + 'target_association', + 'target_special', + 'amount', + 'description', + ] def __init__(self, user: User, *args, **kwargs): """Constructor. @@ -125,15 +150,19 @@ def clean(self): """Converts the source and target fields into actual accounts.""" cleaned_data = super().clean() # Get source - source_el, source_idx = one_of(cleaned_data.get('source_user'), - cleaned_data.get('source_association'), - cleaned_data.get('source_special')) + source_el, source_idx = one_of( + cleaned_data.get('source_user'), + cleaned_data.get('source_association'), + cleaned_data.get('source_special'), + ) if not source_el: raise ValidationError("Provide exactly 1 transaction source.") # Target - target_el, target_idx = one_of(cleaned_data.get('target_user'), - cleaned_data.get('target_association'), - cleaned_data.get('target_special')) + target_el, target_idx = one_of( + cleaned_data.get('target_user'), + cleaned_data.get('target_association'), + cleaned_data.get('target_special'), + ) if not target_el: raise ValidationError("Provide exactly 1 transaction target.") # Convert to actual accounts (in the case of a user or association object) @@ -160,7 +189,12 @@ def save(self, commit=True, request=None): # Send mail if the source is a user source = self.instance.source if source.user: - send_templated_mail('mail/transaction_created', source.user, {'transaction': instance}, request) + send_templated_mail( + 'mail/transaction_created', + source.user, + {'transaction': instance}, + request, + ) return instance @@ -168,8 +202,11 @@ def save(self, commit=True, request=None): class ClearOpenExpensesForm(forms.Form): """Creates transactions for all members of this association who are negative.""" - description = forms.CharField(max_length=150, help_text="Is displayed on each user's transaction overview, " - "e.g. in the case of Quadrivium it could be 'Q-rekening'.") + description = forms.CharField( + max_length=150, + help_text="Is displayed on each user's transaction overview, " + "e.g. in the case of Quadrivium it could be 'Q-rekening'.", + ) def __init__(self, *args, association=None, user=None, **kwargs): # Calculate and create the transactions that need to be applied @@ -180,17 +217,19 @@ def __init__(self, *args, association=None, user=None, **kwargs): # But they will show up in the association members list and can be rejected there manually. # is_active=True, usermembership__association=association, - usermembership__is_verified=True + usermembership__is_verified=True, ) self.transactions = [] for m in members: balance = m.account.get_balance() if balance < 0: # Construct a transaction for each member with negative balance - tx = Transaction(source=association.account, - target=m.account, - amount=-balance, - created_by=user) # Description needs to be set later + tx = Transaction( + source=association.account, + target=m.account, + amount=-balance, + created_by=user, + ) # Description needs to be set later self.transactions.append(tx) super().__init__(*args, **kwargs) @@ -215,7 +254,11 @@ class AccountPickerForm(forms.Form): def clean(self): cleaned_data = super().clean() - fields = (bool(cleaned_data['user']), bool(cleaned_data['association']), bool(cleaned_data['special'])) + fields = ( + bool(cleaned_data['user']), + bool(cleaned_data['association']), + bool(cleaned_data['special']), + ) if sum(fields) != 1: raise ValidationError("Select 1 of the fields.") return super().clean() diff --git a/creditmanagement/migrations/0001_initial.py b/creditmanagement/migrations/0001_initial.py index 0bb1ca11..de62b844 100644 --- a/creditmanagement/migrations/0001_initial.py +++ b/creditmanagement/migrations/0001_initial.py @@ -9,7 +9,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -21,11 +20,32 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PendingDiningTransaction', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, max_digits=4, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Money transferred')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + max_digits=4, + validators=[ + django.core.validators.MinValueValidator(Decimal('0.01')) + ], + verbose_name='Money transferred', + ), + ), ('order_moment', models.DateTimeField(default=datetime.datetime.now)), ('confirm_moment', models.DateTimeField(default=datetime.datetime.now)), - ('description', models.CharField(blank=True, default='', max_length=50)), + ( + 'description', + models.CharField(blank=True, default='', max_length=50), + ), ], options={ 'managed': False, @@ -34,11 +54,32 @@ class Migration(migrations.Migration): migrations.CreateModel( name='FixedTransaction', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, max_digits=4, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Money transferred')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + max_digits=4, + validators=[ + django.core.validators.MinValueValidator(Decimal('0.01')) + ], + verbose_name='Money transferred', + ), + ), ('order_moment', models.DateTimeField(default=datetime.datetime.now)), ('confirm_moment', models.DateTimeField(default=datetime.datetime.now)), - ('description', models.CharField(blank=True, default='', max_length=50)), + ( + 'description', + models.CharField(blank=True, default='', max_length=50), + ), ], options={ 'abstract': False, @@ -47,18 +88,57 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PendingDiningListTracker', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), ], ), migrations.CreateModel( name='PendingTransaction', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, max_digits=4, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Money transferred')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + max_digits=4, + validators=[ + django.core.validators.MinValueValidator(Decimal('0.01')) + ], + verbose_name='Money transferred', + ), + ), ('order_moment', models.DateTimeField(default=datetime.datetime.now)), ('confirm_moment', models.DateTimeField(default=datetime.datetime.now)), - ('description', models.CharField(blank=True, default='', max_length=50)), - ('source_association', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingtransaction_transaction_source', to='userdetails.Association', verbose_name='The association giving the money')), + ( + 'description', + models.CharField(blank=True, default='', max_length=50), + ), + ( + 'source_association', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingtransaction_transaction_source', + to='userdetails.Association', + verbose_name='The association giving the money', + ), + ), ], options={ 'abstract': False, @@ -67,16 +147,37 @@ class Migration(migrations.Migration): migrations.AddField( model_name='pendingtransaction', name='source_user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingtransaction_transaction_source', to=settings.AUTH_USER_MODEL, verbose_name='The user giving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingtransaction_transaction_source', + to=settings.AUTH_USER_MODEL, + verbose_name='The user giving the money', + ), ), migrations.AddField( model_name='pendingtransaction', name='target_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingtransaction_transaction_target', to='userdetails.Association', verbose_name='The association recieving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingtransaction_transaction_target', + to='userdetails.Association', + verbose_name='The association recieving the money', + ), ), migrations.AddField( model_name='pendingtransaction', name='target_user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingtransaction_transaction_target', to=settings.AUTH_USER_MODEL, verbose_name='The user receiving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingtransaction_transaction_target', + to=settings.AUTH_USER_MODEL, + verbose_name='The user receiving the money', + ), ), ] diff --git a/creditmanagement/migrations/0002_auto_20190203_1923.py b/creditmanagement/migrations/0002_auto_20190203_1923.py index c5813d52..a3ce6af7 100644 --- a/creditmanagement/migrations/0002_auto_20190203_1923.py +++ b/creditmanagement/migrations/0002_auto_20190203_1923.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -20,26 +19,56 @@ class Migration(migrations.Migration): migrations.AddField( model_name='pendingdininglisttracker', name='dining_list', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='dining.DiningList'), + field=models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, to='dining.DiningList' + ), ), migrations.AddField( model_name='fixedtransaction', name='source_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fixedtransaction_transaction_source', to='userdetails.Association', verbose_name='The association giving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='fixedtransaction_transaction_source', + to='userdetails.Association', + verbose_name='The association giving the money', + ), ), migrations.AddField( model_name='fixedtransaction', name='source_user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fixedtransaction_transaction_source', to=settings.AUTH_USER_MODEL, verbose_name='The user giving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='fixedtransaction_transaction_source', + to=settings.AUTH_USER_MODEL, + verbose_name='The user giving the money', + ), ), migrations.AddField( model_name='fixedtransaction', name='target_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fixedtransaction_transaction_target', to='userdetails.Association', verbose_name='The association recieving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='fixedtransaction_transaction_target', + to='userdetails.Association', + verbose_name='The association recieving the money', + ), ), migrations.AddField( model_name='fixedtransaction', name='target_user', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fixedtransaction_transaction_target', to=settings.AUTH_USER_MODEL, verbose_name='The user receiving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='fixedtransaction_transaction_target', + to=settings.AUTH_USER_MODEL, + verbose_name='The user receiving the money', + ), ), ] diff --git a/creditmanagement/migrations/0003_merge_20190203_1925.py b/creditmanagement/migrations/0003_merge_20190203_1925.py index 0905eb54..c0bf137a 100644 --- a/creditmanagement/migrations/0003_merge_20190203_1925.py +++ b/creditmanagement/migrations/0003_merge_20190203_1925.py @@ -4,10 +4,8 @@ class Migration(migrations.Migration): - dependencies = [ ('creditmanagement', '0002_auto_20190203_1923'), ] - operations = [ - ] + operations = [] diff --git a/creditmanagement/migrations/0004_auto_20190203_2324.py b/creditmanagement/migrations/0004_auto_20190203_2324.py index ce1e62b9..dbd8d870 100644 --- a/creditmanagement/migrations/0004_auto_20190203_2324.py +++ b/creditmanagement/migrations/0004_auto_20190203_2324.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0002_auto_20190203_2324'), ('creditmanagement', '0003_merge_20190203_1925'), @@ -15,31 +14,73 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='PendingTransaction', name='source_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingtransaction_transaction_source', to='userdetails.Association', verbose_name='The association giving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingtransaction_transaction_source', + to='userdetails.Association', + verbose_name='The association giving the money', + ), ), migrations.AlterField( model_name='PendingTransaction', name='target_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingtransaction_transaction_target', to='userdetails.Association', verbose_name='The association recieving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingtransaction_transaction_target', + to='userdetails.Association', + verbose_name='The association recieving the money', + ), ), migrations.AlterField( model_name='FixedTransaction', name='source_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fixedtransaction_transaction_source', to='userdetails.Association', verbose_name='The association giving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='fixedtransaction_transaction_source', + to='userdetails.Association', + verbose_name='The association giving the money', + ), ), migrations.AlterField( model_name='FixedTransaction', name='target_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fixedtransaction_transaction_target', to='userdetails.Association', verbose_name='The association recieving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='fixedtransaction_transaction_target', + to='userdetails.Association', + verbose_name='The association recieving the money', + ), ), migrations.AlterField( model_name='PendingDiningTransaction', name='source_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingdiningtransaction_transaction_source', to='userdetails.Association', verbose_name='The association giving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingdiningtransaction_transaction_source', + to='userdetails.Association', + verbose_name='The association giving the money', + ), ), migrations.AlterField( model_name='PendingDiningTransaction', name='target_association', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pendingdiningtransaction_transaction_target', to='userdetails.Association', verbose_name='The association recieving the money'), + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='pendingdiningtransaction_transaction_target', + to='userdetails.Association', + verbose_name='The association recieving the money', + ), ), ] diff --git a/creditmanagement/migrations/0005_usercredit_view.py b/creditmanagement/migrations/0005_usercredit_view.py index bc724b14..c6fab3f5 100644 --- a/creditmanagement/migrations/0005_usercredit_view.py +++ b/creditmanagement/migrations/0005_usercredit_view.py @@ -5,12 +5,10 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('userdetails', '0001_initial'), ('creditmanagement', '0004_auto_20190203_2324'), ] - operations = [ - ] + operations = [] diff --git a/creditmanagement/migrations/0006_auto_20190213_1452.py b/creditmanagement/migrations/0006_auto_20190213_1452.py index 8e8b6ce5..3c8a9b3c 100644 --- a/creditmanagement/migrations/0006_auto_20190213_1452.py +++ b/creditmanagement/migrations/0006_auto_20190213_1452.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('creditmanagement', '0005_usercredit_view'), ] diff --git a/creditmanagement/migrations/0007_usercredit_view_update.py b/creditmanagement/migrations/0007_usercredit_view_update.py index 9c4329e4..6bda0fe7 100644 --- a/creditmanagement/migrations/0007_usercredit_view_update.py +++ b/creditmanagement/migrations/0007_usercredit_view_update.py @@ -2,12 +2,9 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0007_association_is_choosable'), ('creditmanagement', '0006_auto_20190213_1452'), ] - operations = [ - ] - + operations = [] diff --git a/creditmanagement/migrations/0008_pendingdiningtransaction.py b/creditmanagement/migrations/0008_pendingdiningtransaction.py index 7b292b26..3a4dea1e 100644 --- a/creditmanagement/migrations/0008_pendingdiningtransaction.py +++ b/creditmanagement/migrations/0008_pendingdiningtransaction.py @@ -9,7 +9,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('userdetails', '0008_remove_user_is_staff'), @@ -24,15 +23,82 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PendingDiningTransaction', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('source_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='pendingdiningtransaction_transaction_source', to=settings.AUTH_USER_MODEL, verbose_name='The user giving the money')), - ('source_association', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='pendingdiningtransaction_transaction_source', to='userdetails.Association', verbose_name='The association giving the money')), - ('amount', models.DecimalField(decimal_places=2, max_digits=4, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Money transferred')), - ('target_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='pendingdiningtransaction_transaction_target', to=settings.AUTH_USER_MODEL, verbose_name='The user receiving the money')), - ('target_association', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='pendingdiningtransaction_transaction_target', to='userdetails.Association', verbose_name='The association recieving the money')), - ('order_moment', models.DateTimeField(default=django.utils.timezone.now)), - ('confirm_moment', models.DateTimeField(blank=True, default=django.utils.timezone.now)), - ('description', models.CharField(blank=True, default='', max_length=50)), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'source_user', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name='pendingdiningtransaction_transaction_source', + to=settings.AUTH_USER_MODEL, + verbose_name='The user giving the money', + ), + ), + ( + 'source_association', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name='pendingdiningtransaction_transaction_source', + to='userdetails.Association', + verbose_name='The association giving the money', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + max_digits=4, + validators=[ + django.core.validators.MinValueValidator(Decimal('0.01')) + ], + verbose_name='Money transferred', + ), + ), + ( + 'target_user', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name='pendingdiningtransaction_transaction_target', + to=settings.AUTH_USER_MODEL, + verbose_name='The user receiving the money', + ), + ), + ( + 'target_association', + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name='pendingdiningtransaction_transaction_target', + to='userdetails.Association', + verbose_name='The association recieving the money', + ), + ), + ( + 'order_moment', + models.DateTimeField(default=django.utils.timezone.now), + ), + ( + 'confirm_moment', + models.DateTimeField(blank=True, default=django.utils.timezone.now), + ), + ( + 'description', + models.CharField(blank=True, default='', max_length=50), + ), ], options={ 'abstract': False, diff --git a/creditmanagement/migrations/0009_usercredit_view_update.py b/creditmanagement/migrations/0009_usercredit_view_update.py index d3c0a16b..16e117cf 100644 --- a/creditmanagement/migrations/0009_usercredit_view_update.py +++ b/creditmanagement/migrations/0009_usercredit_view_update.py @@ -2,13 +2,9 @@ class Migration(migrations.Migration): - - dependencies = [ ('userdetails', '0017_association_balance_update_instructions'), ('creditmanagement', '0008_pendingdiningtransaction'), ] - operations = [ - ] - + operations = [] diff --git a/creditmanagement/migrations/0010_auto_20200817_1230.py b/creditmanagement/migrations/0010_auto_20200817_1230.py index 615827db..c0c707d5 100644 --- a/creditmanagement/migrations/0010_auto_20200817_1230.py +++ b/creditmanagement/migrations/0010_auto_20200817_1230.py @@ -7,7 +7,6 @@ class Migration(migrations.Migration): - dependencies = [ ('creditmanagement', '0009_usercredit_view_update'), ] @@ -16,14 +15,23 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='fixedtransaction', name='amount', - field=models.DecimalField(decimal_places=2, max_digits=5, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Money transferred'), + field=models.DecimalField( + decimal_places=2, + max_digits=5, + validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], + verbose_name='Money transferred', + ), ), migrations.AlterField( model_name='pendingtransaction', name='amount', - field=models.DecimalField(decimal_places=2, max_digits=5, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], verbose_name='Money transferred'), + field=models.DecimalField( + decimal_places=2, + max_digits=5, + validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], + verbose_name='Money transferred', + ), ), - # This migration contained a CreateView and DeleteView for the same model. # Please never do a delete and create of the same model in the same migration!! # It relies on the assumption that the migrations are applied in this order. If you checked that that is the diff --git a/creditmanagement/migrations/0011_account_transaction.py b/creditmanagement/migrations/0011_account_transaction.py index c07ef646..49eb6389 100644 --- a/creditmanagement/migrations/0011_account_transaction.py +++ b/creditmanagement/migrations/0011_account_transaction.py @@ -9,7 +9,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('userdetails', '0019_auto_20200823_1602'), @@ -20,23 +19,91 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Account', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('association', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='userdetails.Association')), - ('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'association', + models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to='userdetails.Association', + ), + ), + ( + 'user', + models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( name='Transaction', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.DecimalField(decimal_places=2, max_digits=8, validators=[django.core.validators.MinValueValidator(Decimal('0.01'))])), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'amount', + models.DecimalField( + decimal_places=2, + max_digits=8, + validators=[ + django.core.validators.MinValueValidator(Decimal('0.01')) + ], + ), + ), ('moment', models.DateTimeField(default=django.utils.timezone.now)), ('description', models.CharField(max_length=150)), ('cancelled', models.DateTimeField(null=True)), - ('cancelled_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='transaction_cancelled_set', to=settings.AUTH_USER_MODEL)), - ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transaction_set', to=settings.AUTH_USER_MODEL)), - ('source', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transaction_source_set', to='creditmanagement.Account')), - ('target', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transaction_target_set', to='creditmanagement.Account')), + ( + 'cancelled_by', + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name='transaction_cancelled_set', + to=settings.AUTH_USER_MODEL, + ), + ), + ( + 'created_by', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='transaction_set', + to=settings.AUTH_USER_MODEL, + ), + ), + ( + 'source', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='transaction_source_set', + to='creditmanagement.Account', + ), + ), + ( + 'target', + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name='transaction_target_set', + to='creditmanagement.Account', + ), + ), ], ), ] diff --git a/creditmanagement/migrations/0012_auto_20200824_0135.py b/creditmanagement/migrations/0012_auto_20200824_0135.py index e67c77a5..2019e6f2 100644 --- a/creditmanagement/migrations/0012_auto_20200824_0135.py +++ b/creditmanagement/migrations/0012_auto_20200824_0135.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('creditmanagement', '0011_account_transaction'), ] @@ -45,6 +44,5 @@ class Migration(migrations.Migration): ), migrations.DeleteModel( # (This model had managed=False) name='PendingDiningTransaction', - ), ] diff --git a/creditmanagement/migrations/0013_auto_20210319_2310.py b/creditmanagement/migrations/0013_auto_20210319_2310.py index bb80de0d..591c85d2 100644 --- a/creditmanagement/migrations/0013_auto_20210319_2310.py +++ b/creditmanagement/migrations/0013_auto_20210319_2310.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('creditmanagement', '0012_auto_20200824_0135'), ] @@ -13,6 +12,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='account', name='special', - field=models.CharField(choices=[('kitchen_cost', 'Kitchen cost')], default=None, max_length=30, null=True, unique=True), + field=models.CharField( + choices=[('kitchen_cost', 'Kitchen cost')], + default=None, + max_length=30, + null=True, + unique=True, + ), ), ] diff --git a/creditmanagement/migrations/0014_auto_20210320_0015.py b/creditmanagement/migrations/0014_auto_20210320_0015.py index c1874b95..e17df657 100644 --- a/creditmanagement/migrations/0014_auto_20210320_0015.py +++ b/creditmanagement/migrations/0014_auto_20210320_0015.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('creditmanagement', '0013_auto_20210319_2310'), ] diff --git a/creditmanagement/migrations/0015_auto_20220428_2113.py b/creditmanagement/migrations/0015_auto_20220428_2113.py index 4c0a9e39..9c904f94 100644 --- a/creditmanagement/migrations/0015_auto_20220428_2113.py +++ b/creditmanagement/migrations/0015_auto_20220428_2113.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('userdetails', '0020_auto_20210319_2310'), @@ -17,16 +16,31 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='account', name='association', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='userdetails.association'), + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='userdetails.association', + ), ), migrations.AlterField( model_name='account', name='special', - field=models.CharField(blank=True, choices=[('kitchen_cost', 'Kitchen cost')], default=None, max_length=30, null=True, unique=True), + field=models.CharField( + blank=True, + choices=[('kitchen_cost', 'Kitchen cost')], + default=None, + max_length=30, + null=True, + unique=True, + ), ), migrations.AlterField( model_name='account', name='user', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/creditmanagement/migrations/0016_unfold_cancel_column.py b/creditmanagement/migrations/0016_unfold_cancel_column.py index b7b2d2a6..7ada7067 100644 --- a/creditmanagement/migrations/0016_unfold_cancel_column.py +++ b/creditmanagement/migrations/0016_unfold_cancel_column.py @@ -47,7 +47,7 @@ def forward(apps, schema_editor): u = User.objects.create( username="transaction_cancel_migration_user", email="invalid2@localhost", - first_name="Transaction Cancel Migration User" + first_name="Transaction Cancel Migration User", ) for tx in Transaction.objects.exclude(cancelled=None): @@ -57,7 +57,10 @@ def forward(apps, schema_editor): source=tx.target, target=tx.source, amount=tx.amount, - moment=tx.moment + timedelta(seconds=1), # If the moment was exactly equal, ordering might go wrong + moment=tx.moment + + timedelta( + seconds=1 + ), # If the moment was exactly equal, ordering might go wrong description=f'Refund "{tx.description}"', created_by=u, ) @@ -79,5 +82,7 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(forward, reverse_code=migrations.RunPython.noop, elidable=True) + migrations.RunPython( + forward, reverse_code=migrations.RunPython.noop, elidable=True + ) ] diff --git a/creditmanagement/migrations/0017_remove_cancel_column.py b/creditmanagement/migrations/0017_remove_cancel_column.py index 092ec252..2f9722d3 100644 --- a/creditmanagement/migrations/0017_remove_cancel_column.py +++ b/creditmanagement/migrations/0017_remove_cancel_column.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('userdetails', '0022_move_allergies'), @@ -25,12 +24,22 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='account', name='association', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='userdetails.association'), + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to='userdetails.association', + ), ), migrations.AlterField( model_name='account', name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( model_name='transaction', diff --git a/creditmanagement/models.py b/creditmanagement/models.py index baf5342c..40c51ee0 100644 --- a/creditmanagement/models.py +++ b/creditmanagement/models.py @@ -26,7 +26,9 @@ class Account(models.Model): # An account can only have one of user or association or special user = models.OneToOneField(User, on_delete=models.CASCADE, blank=True, null=True) - association = models.OneToOneField(Association, on_delete=models.CASCADE, blank=True, null=True) + association = models.OneToOneField( + Association, on_delete=models.CASCADE, blank=True, null=True + ) # Special accounts are used for bookkeeping # (The special accounts listed here are automatically created using a receiver.) @@ -36,11 +38,17 @@ class Account(models.Model): ] SPECIAL_ACCOUNT_DESCRIPTION = { 'kitchen_cost': "Account which receives the kitchen payments. " - "The balance indicates the money that is payed for kitchen usage " - "(minus withdraws from this account).", + "The balance indicates the money that is payed for kitchen usage " + "(minus withdraws from this account).", } - special = models.CharField(max_length=30, unique=True, blank=True, null=True, default=None, - choices=SPECIAL_ACCOUNTS) + special = models.CharField( + max_length=30, + unique=True, + blank=True, + null=True, + default=None, + choices=SPECIAL_ACCOUNTS, + ) objects = AccountManager() @@ -48,8 +56,12 @@ def get_balance(self) -> Decimal: qs = Transaction.objects.all() # 2 separate queries for the source and target sums # If there are no rows, the value will be made 0.00 - source_sum = qs.filter(source=self).aggregate(sum=Sum('amount'))['sum'] or Decimal('0.00') - target_sum = qs.filter(target=self).aggregate(sum=Sum('amount'))['sum'] or Decimal('0.00') + source_sum = qs.filter(source=self).aggregate(sum=Sum('amount'))[ + 'sum' + ] or Decimal('0.00') + target_sum = qs.filter(target=self).aggregate(sum=Sum('amount'))[ + 'sum' + ] or Decimal('0.00') return target_sum - source_sum get_balance.short_description = "Balance" # (used in admin site) @@ -113,13 +125,21 @@ def filter_account(self, account: Account): class Transaction(models.Model): # We do not enforce that source != target because those rows are not harmful. - source = models.ForeignKey(Account, on_delete=models.PROTECT, related_name='transaction_source_set') - target = models.ForeignKey(Account, on_delete=models.PROTECT, related_name='transaction_target_set') + source = models.ForeignKey( + Account, on_delete=models.PROTECT, related_name='transaction_source_set' + ) + target = models.ForeignKey( + Account, on_delete=models.PROTECT, related_name='transaction_target_set' + ) # Amount can only be (strictly) positive - amount = models.DecimalField(decimal_places=2, max_digits=8, validators=[MinValueValidator(Decimal('0.01'))]) + amount = models.DecimalField( + decimal_places=2, max_digits=8, validators=[MinValueValidator(Decimal('0.01'))] + ) moment = models.DateTimeField(default=timezone.now) description = models.CharField(max_length=1000) - created_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='transaction_set') + created_by = models.ForeignKey( + User, on_delete=models.PROTECT, related_name='transaction_set' + ) objects = TransactionQuerySet.as_manager() diff --git a/creditmanagement/tests/test_models.py b/creditmanagement/tests/test_models.py index ac78b64a..f0d6ea83 100644 --- a/creditmanagement/tests/test_models.py +++ b/creditmanagement/tests/test_models.py @@ -21,14 +21,20 @@ def test_balance_no_tx(self): def test_balance(self): """Tests balance when there are at least 1 source and target transaction.""" - Transaction.objects.create(source=self.a1, target=self.a2, amount=Decimal('8.30'), created_by=self.u) - Transaction.objects.create(source=self.a2, target=self.a1, amount=Decimal('9.88'), created_by=self.u) + Transaction.objects.create( + source=self.a1, target=self.a2, amount=Decimal('8.30'), created_by=self.u + ) + Transaction.objects.create( + source=self.a2, target=self.a1, amount=Decimal('9.88'), created_by=self.u + ) self.assertEqual(self.a1.get_balance(), Decimal('1.58')) self.assertEqual(self.a2.get_balance(), Decimal('-1.58')) def test_reversal(self): """Tests the reversal transaction.""" - tx = Transaction.objects.create(source=self.a1, target=self.a2, amount=Decimal('2.64'), created_by=self.u) + tx = Transaction.objects.create( + source=self.a1, target=self.a2, amount=Decimal('2.64'), created_by=self.u + ) tx.reversal(self.u).save() self.assertEqual(self.a1.get_balance(), Decimal('0.00')) self.assertEqual(self.a2.get_balance(), Decimal('0.00')) diff --git a/creditmanagement/urls.py b/creditmanagement/urls.py index 38dcec78..ed948744 100644 --- a/creditmanagement/urls.py +++ b/creditmanagement/urls.py @@ -1,6 +1,10 @@ from django.urls import path -from creditmanagement.views import TransactionListView, TransactionAddView, TransactionCSVView +from creditmanagement.views import ( + TransactionListView, + TransactionAddView, + TransactionCSVView, +) app_name = 'credits' diff --git a/creditmanagement/views.py b/creditmanagement/views.py index 34034ce1..d21c114b 100644 --- a/creditmanagement/views.py +++ b/creditmanagement/views.py @@ -15,7 +15,9 @@ class TransactionListView(LoginRequiredMixin, ListView): paginate_by = 20 def get_queryset(self): - return Transaction.objects.filter_account(self.request.user.account).order_by('-moment') + return Transaction.objects.filter_account(self.request.user.account).order_by( + '-moment' + ) class TransactionCSVView(LoginRequiredMixin, View): @@ -24,7 +26,9 @@ class TransactionCSVView(LoginRequiredMixin, View): def get(self, request, *args, **kwargs): response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="user_transactions.csv"' - qs = Transaction.objects.filter_account(request.user.account).order_by('-moment') + qs = Transaction.objects.filter_account(request.user.account).order_by( + '-moment' + ) write_transactions_csv(response, qs, request.user.account) return response @@ -35,6 +39,7 @@ class TransactionFormView(FormView): A subclass needs to override get_source(), set the success URL and set the template name. """ + form_class = TransactionForm def get_form_kwargs(self): @@ -45,7 +50,9 @@ def get_form_kwargs(self): def form_valid(self, form): form.save() - messages.add_message(self.request, messages.SUCCESS, "Transaction has been successfully created.") + messages.add_message( + self.request, messages.SUCCESS, "Transaction has been successfully created." + ) return HttpResponseRedirect(self.get_success_url()) def get_source(self) -> Account: @@ -55,10 +62,12 @@ def get_source(self) -> Account: class TransactionAddView(LoginRequiredMixin, TransactionFormView): """View where a user can transfer money to someone else.""" + template_name = "credit_management/transaction_add.html" success_url = reverse_lazy('credits:transaction_list') def get_source(self) -> Account: return self.request.user.account + # MoneyObtainmentView is removed in favor of Site Credits tab diff --git a/dining/admin.py b/dining/admin.py index 3ca3badf..336e6c59 100644 --- a/dining/admin.py +++ b/dining/admin.py @@ -1,13 +1,32 @@ from django.contrib import admin -from dining.models import DiningDayAnnouncement, DiningComment, DiningList, DiningEntry, DeletedList +from dining.models import ( + DiningDayAnnouncement, + DiningComment, + DiningList, + DiningEntry, + DeletedList, +) @admin.register(DiningEntry) class DiningEntryAdmin(admin.ModelAdmin): - list_display = ('id', 'dining_list', 'user', 'external_name', 'has_shopped', 'has_cooked', 'has_cleaned') + list_display = ( + 'id', + 'dining_list', + 'user', + 'external_name', + 'has_shopped', + 'has_cooked', + 'has_cleaned', + ) list_filter = ('dining_list__date',) - search_fields = ('user__first_name', 'user__last_name', 'user__username', 'user__email') + search_fields = ( + 'user__first_name', + 'user__last_name', + 'user__username', + 'user__email', + ) # We do not allow adding/deleting/changing dining entries because money is involved. diff --git a/dining/forms.py b/dining/forms.py index eab8945a..83fe9a6f 100644 --- a/dining/forms.py +++ b/dining/forms.py @@ -15,7 +15,13 @@ from django.utils import timezone from creditmanagement.models import Transaction, Account -from dining.models import DiningList, DiningComment, DiningEntry, PaymentReminderLock, DeletedList +from dining.models import ( + DiningList, + DiningComment, + DiningEntry, + PaymentReminderLock, + DeletedList, +) from general.forms import ConcurrenflictFormMixin from general.mail_control import construct_templated_mail from general.util import SelectWithDisabled @@ -39,7 +45,9 @@ def _clean_form(form): if not form.is_valid(): validation_errors = [] for field, errors in form.errors.items(): - validation_errors.extend(["{}: {}".format(field, error) for error in errors]) + validation_errors.extend( + ["{}: {}".format(field, error) for error in errors] + ) raise ValidationError(validation_errors) @@ -49,9 +57,13 @@ class ServeTimeCheckMixin: def clean_serve_time(self): serve_time = self.cleaned_data["serve_time"] if serve_time < settings.KITCHEN_USE_START_TIME: - raise ValidationError("Kitchen can't be used this early", code="kitchen_start_time") + raise ValidationError( + "Kitchen can't be used this early", code="kitchen_start_time" + ) if serve_time > settings.KITCHEN_USE_END_TIME: - raise ValidationError("Kitchen can't be used this late", code="kitchen_close_time") + raise ValidationError( + "Kitchen can't be used this late", code="kitchen_close_time" + ) return serve_time def set_bounds(self, field: str, attr: Literal["max"] | Literal["min"], value: str): @@ -92,20 +104,28 @@ def __init__(self, creator: User, *args, **kwargs): associations = associations.exclude(usermembership__in=denied_memberships) # Filter out unavailable associations (those that have a dining list already on this day) - dining_lists = DiningList.objects.filter(date=self.instance.date, association=OuterRef("pk")) - available = associations.annotate(occupied=Exists(dining_lists)).filter(occupied=False) - unavailable = associations.annotate(occupied=Exists(dining_lists)).filter(occupied=True) + dining_lists = DiningList.objects.filter( + date=self.instance.date, association=OuterRef("pk") + ) + available = associations.annotate(occupied=Exists(dining_lists)).filter( + occupied=False + ) + unavailable = associations.annotate(occupied=Exists(dining_lists)).filter( + occupied=True + ) if unavailable.exists(): - help_text = ( - "Some of your associations are not available since they already have a dining list for this date." - ) + help_text = "Some of your associations are not available since they already have a dining list for this date." else: help_text = "" - widget = SelectWithDisabled(disabled_choices=[(a.pk, a.name) for a in unavailable]) + widget = SelectWithDisabled( + disabled_choices=[(a.pk, a.name) for a in unavailable] + ) - self.fields["association"] = forms.ModelChoiceField(queryset=available, widget=widget, help_text=help_text) + self.fields["association"] = forms.ModelChoiceField( + queryset=available, widget=widget, help_text=help_text + ) if len(available) == 1: self.initial["association"] = available[0].pk @@ -116,12 +136,18 @@ def __init__(self, creator: User, *args, **kwargs): self.cleaned_data = {} self.add_error( None, - ValidationError("You are not a member of any association and thus can not claim a dining list"), + ValidationError( + "You are not a member of any association and thus can not claim a dining list" + ), ) self.set_bounds("max_diners", "min", settings.MIN_SLOT_DINER_MAXIMUM) - self.set_bounds("serve_time", "min", settings.KITCHEN_USE_START_TIME.strftime("%H:%M")) - self.set_bounds("serve_time", "max", settings.KITCHEN_USE_END_TIME.strftime("%H:%M")) + self.set_bounds( + "serve_time", "min", settings.KITCHEN_USE_START_TIME.strftime("%H:%M") + ) + self.set_bounds( + "serve_time", "max", settings.KITCHEN_USE_END_TIME.strftime("%H:%M") + ) def clean(self): # Note: uniqueness for date+association is implicitly enforced using the association form field @@ -134,7 +160,11 @@ def clean(self): # Check if user has enough money to claim a slot min_balance_exception = creator.has_min_balance_exception() - if not min_balance_exception and creator.account.get_balance() < settings.MINIMUM_BALANCE_FOR_DINING_SLOT_CLAIM: + if ( + not min_balance_exception + and creator.account.get_balance() + < settings.MINIMUM_BALANCE_FOR_DINING_SLOT_CLAIM + ): raise ValidationError("Your balance is too low to claim a slot") # Check if user does not already own another dining list this day @@ -144,9 +174,15 @@ def clean(self): # If date is valid if self.instance.date < timezone.now().date(): raise ValidationError("This date is in the past") - if self.instance.date == timezone.now().date() and timezone.now().time() > settings.DINING_SLOT_CLAIM_CLOSURE_TIME: + if ( + self.instance.date == timezone.now().date() + and timezone.now().time() > settings.DINING_SLOT_CLAIM_CLOSURE_TIME + ): raise ValidationError("It's too late to claim any dining slots") - if self.instance.date > timezone.now().date() + settings.DINING_SLOT_CLAIM_AHEAD: + if ( + self.instance.date + > timezone.now().date() + settings.DINING_SLOT_CLAIM_AHEAD + ): raise ValidationError("Dining list is too far in the future") return cleaned_data @@ -184,16 +220,24 @@ class Meta: model = DiningList fields = ["owners", "dish", "serve_time", "max_diners", "sign_up_deadline"] widgets = { - "owners": ModelSelect2Multiple(url="people_autocomplete", attrs={"data-minimum-input-length": "1"}), + "owners": ModelSelect2Multiple( + url="people_autocomplete", attrs={"data-minimum-input-length": "1"} + ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["serve_time"].widget.input_type = "time" self.fields["sign_up_deadline"].widget.input_type = "datetime-local" - self.set_bounds("serve_time", "min", settings.KITCHEN_USE_START_TIME.strftime("%H:%M")) - self.set_bounds("serve_time", "max", settings.KITCHEN_USE_END_TIME.strftime("%H:%M")) - self.set_bounds("sign_up_deadline", "max", self.instance.date.strftime("%Y-%m-%dT23:59")) + self.set_bounds( + "serve_time", "min", settings.KITCHEN_USE_START_TIME.strftime("%H:%M") + ) + self.set_bounds( + "serve_time", "max", settings.KITCHEN_USE_END_TIME.strftime("%H:%M") + ) + self.set_bounds( + "sign_up_deadline", "max", self.instance.date.strftime("%Y-%m-%dT23:59") + ) self.set_bounds("max_diners", "min", settings.MIN_SLOT_DINER_MAXIMUM) @@ -222,8 +266,12 @@ def clean(self): # Sanity check: do not allow both dinner cost total and dinner cost per person if dinner_cost_total and dining_cost: - msg = "Please only provide either dinner cost total or dinner cost per person" - self.add_error("dining_cost_total", ValidationError(msg, code="duplicate_cost")) + msg = ( + "Please only provide either dinner cost total or dinner cost per person" + ) + self.add_error( + "dining_cost_total", ValidationError(msg, code="duplicate_cost") + ) self.add_error("dining_cost", ValidationError(msg, code="duplicate_cost")) elif dinner_cost_total: # Total dinner cost provided: calculate dining cost per person and apply @@ -231,7 +279,9 @@ def clean(self): cost = dinner_cost_total / self.instance.diners.count() else: msg = "Can't calculate dinner cost per person as there are no diners" - raise ValidationError({"dining_cost_total": ValidationError(msg, code="costs_no_diners")}) + raise ValidationError( + {"dining_cost_total": ValidationError(msg, code="costs_no_diners")} + ) # Round up to remove missing cents cost = Decimal(cost).quantize(Decimal(".01"), rounding=ROUND_UP) @@ -252,7 +302,9 @@ class Meta: fields = ("user",) widgets = { # User needs to type at least 1 character, could change it to 2 - "user": ModelSelect2(url="people_autocomplete", attrs={"data-minimum-input-length": "1"}) + "user": ModelSelect2( + url="people_autocomplete", attrs={"data-minimum-input-length": "1"} + ) } def get_user(self): @@ -271,7 +323,9 @@ def clean(self): # Adjustable if not dining_list.is_adjustable(): - raise ValidationError("Dining list can no longer be adjusted", code="closed") + raise ValidationError( + "Dining list can no longer be adjusted", code="closed" + ) # Closed (exception for owner) if not dining_list.is_owner(creator) and not dining_list.is_open(): @@ -283,12 +337,21 @@ def clean(self): if dining_list.limit_signups_to_association_only: # User should be verified association member, except when the entry creator is owner - if not dining_list.is_owner(creator) and not user.is_verified_member_of(dining_list.association): - raise ValidationError("Dining list is limited to members only", code="members_only") + if not dining_list.is_owner(creator) and not user.is_verified_member_of( + dining_list.association + ): + raise ValidationError( + "Dining list is limited to members only", code="members_only" + ) # User balance check - if not user.has_min_balance_exception() and user.account.get_balance() < settings.MINIMUM_BALANCE_FOR_DINING_SIGN_UP: - raise ValidationError("The balance of the user is too low to add", code="no_money") + if ( + not user.has_min_balance_exception() + and user.account.get_balance() < settings.MINIMUM_BALANCE_FOR_DINING_SIGN_UP + ): + raise ValidationError( + "The balance of the user is too low to add", code="no_money" + ) return cleaned_data @@ -359,7 +422,11 @@ def clean(self): ) # Check permission: either she's owner, or the entry is about herself, or she created the entry - if not is_owner and self.entry.user != self.deleter and self.entry.created_by != self.deleter: + if ( + not is_owner + and self.entry.user != self.deleter + and self.entry.created_by != self.deleter + ): raise ValidationError("Can only delete own entries", code="not_owner") return cleaned_data @@ -382,7 +449,11 @@ class DiningListDeleteForm(forms.ModelForm): This will refund all kitchen costs. """ - reason = forms.CharField(max_length=1000, required=False, help_text="You can optionally provide a reason.") + reason = forms.CharField( + max_length=1000, + required=False, + help_text="You can optionally provide a reason.", + ) class Meta: model = DiningList @@ -391,7 +462,10 @@ class Meta: def clean(self): cleaned_data = super().clean() if not self.instance.is_adjustable(): - raise ValidationError("The dining list is locked, changes can no longer be made", code="locked") + raise ValidationError( + "The dining list is locked, changes can no longer be made", + code="locked", + ) return cleaned_data def execute(self, deleted_by): @@ -404,7 +478,9 @@ def execute(self, deleted_by): DeletedList.objects.create( deleted_by=deleted_by, reason=self.cleaned_data["reason"], - json_list=serialize("json", DiningList.objects.filter(pk=self.instance.pk)), + json_list=serialize( + "json", DiningList.objects.filter(pk=self.instance.pk) + ), json_diners=serialize("json", self.instance.dining_entries.all()), ) @@ -412,7 +488,9 @@ def execute(self, deleted_by): for entry in self.instance.dining_entries.all(): form = DiningEntryDeleteForm(entry, deleted_by, {}) if not form.is_valid(): - raise RuntimeError("Could not validate dining entry while deleting a dining list") + raise RuntimeError( + "Could not validate dining entry while deleting a dining list" + ) form.execute() # Delete dining list @@ -428,7 +506,11 @@ def execute_and_notify(self, request, day_view_url): deleted_by = request.user # Construct mails - recipients = [x.user for x in self.instance.internal_dining_entries() if x.user != deleted_by] + recipients = [ + x.user + for x in self.instance.internal_dining_entries() + if x.user != deleted_by + ] messages = construct_templated_mail( "mail/dining_list_deleted", recipients, @@ -448,7 +530,9 @@ def execute_and_notify(self, request, day_view_url): class DiningCommentForm(forms.ModelForm): - message = forms.CharField(max_length=10000, label="Comment", widget=forms.Textarea(attrs={"rows": "3"})) + message = forms.CharField( + max_length=10000, label="Comment", widget=forms.Textarea(attrs={"rows": "3"}) + ) class Meta: model = DiningComment @@ -464,10 +548,16 @@ def __init__(self, *args, dining_list: DiningList = None, **kwargs): def clean(self): # Verify that there are people to inform - if not DiningEntry.objects.filter(dining_list=self.dining_list, has_paid=False).exists(): - raise ValidationError("There was nobody to inform, everybody has paid", code="all_paid") + if not DiningEntry.objects.filter( + dining_list=self.dining_list, has_paid=False + ).exists(): + raise ValidationError( + "There was nobody to inform, everybody has paid", code="all_paid" + ) if self.dining_list.payment_link == "": - raise ValidationError("There was no payment url defined", code="payment_url_missing") + raise ValidationError( + "There was no payment url defined", code="payment_url_missing" + ) def get_user_recipients(self) -> QuerySet: """Returns the users that need to pay themselves, excluding external entries. @@ -475,7 +565,9 @@ def get_user_recipients(self) -> QuerySet: Returns: A QuerySet of User instances. """ - unpaid_user_entries = self.dining_list.internal_dining_entries().filter(has_paid=False) + unpaid_user_entries = self.dining_list.internal_dining_entries().filter( + has_paid=False + ) return User.objects.filter(diningentry__in=unpaid_user_entries) def get_guest_recipients(self) -> Dict[User, List[str]]: @@ -485,11 +577,17 @@ def get_guest_recipients(self) -> Dict[User, List[str]]: A dictionary from User to a list of guest names who were added by the user. """ - unpaid_guest_entries = self.dining_list.external_dining_entries().filter(has_paid=False) + unpaid_guest_entries = self.dining_list.external_dining_entries().filter( + has_paid=False + ) recipients = {} - for user in User.objects.filter(diningentry__in=unpaid_guest_entries).distinct(): - recipients[user] = [e.get_name() for e in unpaid_guest_entries.filter(user=user)] + for user in User.objects.filter( + diningentry__in=unpaid_guest_entries + ).distinct(): + recipients[user] = [ + e.get_name() for e in unpaid_guest_entries.filter(user=user) + ] return recipients def construct_messages(self, request) -> List[EmailMessage]: @@ -549,9 +647,13 @@ def send_reminder(self, request, nowait=False) -> bool: # You need to use PostgreSQL as database. with transaction.atomic(): # Acquire a lock on the dining list row. This may block. - DiningList.objects.select_for_update(nowait=nowait).get(pk=self.dining_list.pk) + DiningList.objects.select_for_update(nowait=nowait).get( + pk=self.dining_list.pk + ) # Retrieve the row that stores the last sent time. - lock, _ = PaymentReminderLock.objects.get_or_create(dining_list=self.dining_list) + lock, _ = PaymentReminderLock.objects.get_or_create( + dining_list=self.dining_list + ) if lock.sent and timezone.now() - lock.sent < timedelta(seconds=30): # A mail was sent too recently. return False diff --git a/dining/models.py b/dining/models.py index a032397a..e57dee08 100644 --- a/dining/models.py +++ b/dining/models.py @@ -17,8 +17,14 @@ class DiningListManager(models.Manager): def available_slots(self, date): """Returns the number of available slots on the given date.""" # Get slots occupied by announcements - announce_slots = DiningDayAnnouncement.objects.filter(date=date).aggregate(Sum('slots_occupy')) - announce_slots = 0 if announce_slots['slots_occupy__sum'] is None else announce_slots['slots_occupy__sum'] + announce_slots = DiningDayAnnouncement.objects.filter(date=date).aggregate( + Sum('slots_occupy') + ) + announce_slots = ( + 0 + if announce_slots['slots_occupy__sum'] is None + else announce_slots['slots_occupy__sum'] + ) return settings.MAX_SLOT_NUMBER - len(self.filter(date=date)) - announce_slots @@ -27,41 +33,67 @@ class DiningList(models.Model): The following fields may not be changed after creation: kitchen_cost! """ + date = models.DateField() """Todo: the date+association combination determines the URL. This makes it impossible to have multiple dining lists of the same association on the same day. Probably need to change that""" association = models.ForeignKey(Association, on_delete=models.PROTECT) - owners = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='owned_dining_lists', - help_text='Owners can manage the dining list.') - - sign_up_deadline = models.DateTimeField(help_text="The time before users need to sign up.") + owners = models.ManyToManyField( + settings.AUTH_USER_MODEL, + related_name='owned_dining_lists', + help_text='Owners can manage the dining list.', + ) + + sign_up_deadline = models.DateTimeField( + help_text="The time before users need to sign up." + ) serve_time = models.TimeField(default=time(18, 00)) dish = models.CharField(default="", max_length=100, blank=True) # The days adjustable is implemented to prevent adjustment in credits or aid due to a deletion of a user account. adjustable_duration = models.DurationField( default=settings.TRANSACTION_PENDING_DURATION, - help_text="How long the dining list can be adjusted after its date.") + help_text="How long the dining list can be adjusted after its date.", + ) # Todo: implement limit in the views. limit_signups_to_association_only = models.BooleanField( - default=False, help_text="Whether only members of the given association can sign up.") - - kitchen_cost = models.DecimalField(decimal_places=2, verbose_name="kitchen cost per person", max_digits=10, - default=settings.KITCHEN_COST, validators=[MinValueValidator(Decimal('0.00'))]) - - dining_cost = models.DecimalField(decimal_places=2, verbose_name="dinner cost per person", max_digits=5, - blank=True, null=True, default=None, - validators=[MinValueValidator(Decimal('0.00'))]) + default=False, + help_text="Whether only members of the given association can sign up.", + ) + + kitchen_cost = models.DecimalField( + decimal_places=2, + verbose_name="kitchen cost per person", + max_digits=10, + default=settings.KITCHEN_COST, + validators=[MinValueValidator(Decimal('0.00'))], + ) + + dining_cost = models.DecimalField( + decimal_places=2, + verbose_name="dinner cost per person", + max_digits=5, + blank=True, + null=True, + default=None, + validators=[MinValueValidator(Decimal('0.00'))], + ) auto_pay = models.BooleanField(default=False) # Why max_length=2000? -> https://stackoverflow.com/q/417142/2373688 - payment_link = models.URLField(blank=True, max_length=2000, help_text="Link for payment, e.g. a Tikkie link.") + payment_link = models.URLField( + blank=True, max_length=2000, help_text="Link for payment, e.g. a Tikkie link." + ) - max_diners = models.IntegerField(default=20, validators=[MinValueValidator(settings.MIN_SLOT_DINER_MAXIMUM)]) + max_diners = models.IntegerField( + default=20, validators=[MinValueValidator(settings.MIN_SLOT_DINER_MAXIMUM)] + ) - diners = models.ManyToManyField(User, through='DiningEntry', through_fields=('dining_list', 'user')) + diners = models.ManyToManyField( + User, through='DiningEntry', through_fields=('dining_list', 'user') + ) objects = DiningListManager() @@ -75,7 +107,7 @@ def is_owner(self, user: User) -> bool: def is_adjustable(self): """Whether the dining list has not expired and can still be modified.""" - days_since_date = (self.date + self.adjustable_duration) + days_since_date = self.date + self.adjustable_duration return days_since_date >= timezone.now().date() is_adjustable.boolean = True @@ -86,7 +118,7 @@ def clean(self): self.sign_up_deadline = datetime.combine( date=self.date, time=settings.DINING_LIST_CLOSURE_TIME, - tzinfo=timezone.get_default_timezone() + tzinfo=timezone.get_default_timezone(), ) def is_open(self): @@ -102,9 +134,13 @@ def __str__(self): def get_absolute_url(self): from django.shortcuts import reverse + slug = self.association.slug d = self.date - return reverse('slot_details', kwargs={'year': d.year, 'month': d.month, 'day': d.day, 'identifier': slug}) + return reverse( + 'slot_details', + kwargs={'year': d.year, 'month': d.month, 'day': d.day, 'identifier': slug}, + ) def internal_dining_entries(self): """All dining entries that are not for external people.""" @@ -122,7 +158,11 @@ def clean_fields(self, exclude=None): if not exclude or 'sign_up_deadline' not in exclude: if self.sign_up_deadline and self.sign_up_deadline.date() > self.date: raise ValidationError( - {'sign_up_deadline': ["Sign up deadline can't be later than the day dinner is served."]} + { + 'sign_up_deadline': [ + "Sign up deadline can't be later than the day dinner is served." + ] + } ) @@ -136,14 +176,21 @@ def external(self): class DiningEntry(models.Model): """Represents an entry on a dining list.""" - dining_list = models.ForeignKey(DiningList, on_delete=models.PROTECT, related_name='dining_entries') + + dining_list = models.ForeignKey( + DiningList, on_delete=models.PROTECT, related_name='dining_entries' + ) # This is the person who is responsible for the kitchen cost. It will be the same as the transaction source. user = models.ForeignKey(User, on_delete=models.PROTECT) - created_by = models.ForeignKey(User, on_delete=models.PROTECT, related_name='created_dining_entries') + created_by = models.ForeignKey( + User, on_delete=models.PROTECT, related_name='created_dining_entries' + ) # The transaction that belongs to this entry. - transaction = models.OneToOneField(Transaction, on_delete=models.PROTECT, null=True, blank=True) + transaction = models.OneToOneField( + Transaction, on_delete=models.PROTECT, null=True, blank=True + ) # If a name is provided, this entry is external. external_name = models.CharField(max_length=100, blank=True) @@ -178,8 +225,14 @@ def clean(self): # # It might happen that self.user did not clean. In that case the attribute is not available. if not self.pk and self.is_internal() and hasattr(self, 'user'): - if DiningEntry.objects.internal().filter(user=self.user.pk, dining_list=self.dining_list).exists(): - raise ValidationError("User is already on the dining list", code='user_already_present') + if ( + DiningEntry.objects.internal() + .filter(user=self.user.pk, dining_list=self.dining_list) + .exists() + ): + raise ValidationError( + "User is already on the dining list", code='user_already_present' + ) class DiningComment(models.Model): @@ -192,6 +245,7 @@ class DiningComment(models.Model): class DiningCommentVisitTracker(AbstractVisitTracker): """Tracks whether certain comments have been read, i.e. the last time the comments page was visited.""" + dining_list = models.ForeignKey(DiningList, on_delete=models.CASCADE) @classmethod @@ -207,10 +261,14 @@ def get_latest_visit(cls, dining_list, user, update=False): """ try: if update: - latest_visit_obj, created = cls.objects.get_or_create(user=user, dining_list=dining_list) + latest_visit_obj, created = cls.objects.get_or_create( + user=user, dining_list=dining_list + ) else: try: - latest_visit_obj = cls.objects.get(user=user, dining_list=dining_list) + latest_visit_obj = cls.objects.get( + user=user, dining_list=dining_list + ) except cls.DoesNotExist: return None except MultipleObjectsReturned: @@ -228,14 +286,18 @@ def get_latest_visit(cls, dining_list, user, update=False): return timestamp def __str__(self): - return "{dining_list} - {user}".format(dining_list=self.dining_list, user=self.user) + return "{dining_list} - {user}".format( + dining_list=self.dining_list, user=self.user + ) class DiningDayAnnouncement(models.Model): date = models.DateField() title = models.CharField(max_length=100) text = models.TextField() - slots_occupy = models.IntegerField(default=0, help_text="The amount of slots this occupies") + slots_occupy = models.IntegerField( + default=0, help_text="The amount of slots this occupies" + ) def __str__(self): return self.title @@ -248,13 +310,17 @@ class PaymentReminderLock(models.Model): See SlotPaymentView. """ + # We use primary_key=True to prevent an unnecessary auto id column. - dining_list = models.OneToOneField(DiningList, on_delete=models.CASCADE, primary_key=True) # Key + dining_list = models.OneToOneField( + DiningList, on_delete=models.CASCADE, primary_key=True + ) # Key sent = models.DateTimeField(null=True) # Value class DeletedList(models.Model): """For audit purposes, keep a log of deleted dining lists.""" + deleted_by = models.ForeignKey(User, on_delete=models.PROTECT) date = models.DateTimeField('deletion date', default=timezone.now) reason = models.TextField() diff --git a/dining/templatetags/dining_tags.py b/dining/templatetags/dining_tags.py index 85ffa569..786c679c 100644 --- a/dining/templatetags/dining_tags.py +++ b/dining/templatetags/dining_tags.py @@ -38,8 +38,12 @@ def can_add_others(dining_list, user): is_adjustable = dining_list.is_adjustable() is_owner = dining_list.is_owner(user) has_room = dining_list.is_open() and dining_list.has_room() - limited = dining_list.limit_signups_to_association_only and not user.usermembership_set.filter( - association=dining_list.association).exists() + limited = ( + dining_list.limit_signups_to_association_only + and not user.usermembership_set.filter( + association=dining_list.association + ).exists() + ) return is_adjustable and (is_owner or (has_room and not limited)) @@ -57,7 +61,11 @@ def can_delete_entry(entry, user): @register.filter def get_entry(dining_list, user): """Gets the user entry (not external) for given user.""" - return DiningEntry.objects.internal().filter(dining_list=dining_list, user=user).first() + return ( + DiningEntry.objects.internal() + .filter(dining_list=dining_list, user=user) + .first() + ) @register.filter @@ -92,7 +100,10 @@ def dining_list_creation_open(date: datetime.date) -> bool: """ if date < timezone.now().date(): return False - if date == timezone.now().date() and settings.DINING_SLOT_CLAIM_CLOSURE_TIME < timezone.now().time(): + if ( + date == timezone.now().date() + and settings.DINING_SLOT_CLAIM_CLOSURE_TIME < timezone.now().time() + ): # Too late for today return False return True diff --git a/dining/tests/test_forms.py b/dining/tests/test_forms.py index 45b67443..6ca6e5eb 100644 --- a/dining/tests/test_forms.py +++ b/dining/tests/test_forms.py @@ -10,9 +10,16 @@ from django.utils import timezone from creditmanagement.models import Account, Transaction -from dining.forms import CreateSlotForm, \ - DiningEntryDeleteForm, DiningInfoForm, DiningPaymentForm, SendReminderForm, DiningListDeleteForm, \ - DiningEntryInternalForm, DiningEntryExternalForm +from dining.forms import ( + CreateSlotForm, + DiningEntryDeleteForm, + DiningInfoForm, + DiningPaymentForm, + SendReminderForm, + DiningListDeleteForm, + DiningEntryInternalForm, + DiningEntryExternalForm, +) from dining.models import DiningList, DiningEntry from general.forms import ConcurrenflictFormMixin from userdetails.models import Association, User, UserMembership @@ -24,14 +31,24 @@ class CreateSlotFormTestCase(TestCase): def setUp(self): self.association1 = Association.objects.create(name="Quadrivium") self.user1 = User.objects.create_user('jan') - UserMembership.objects.create(related_user=self.user1, association=self.association1, is_verified=True, - verified_on=timezone.now()) + UserMembership.objects.create( + related_user=self.user1, + association=self.association1, + is_verified=True, + verified_on=timezone.now(), + ) # Date two days in the future self.dining_date = timezone.now().date() + timedelta(days=2) - self.form_data = {'dish': 'Kwark', 'association': str(self.association1.pk), 'max_diners': '18', - 'serve_time': '17:00'} + self.form_data = { + 'dish': 'Kwark', + 'association': str(self.association1.pk), + 'max_diners': '18', + 'serve_time': '17:00', + } self.dining_list = DiningList(date=self.dining_date) - self.form = CreateSlotForm(self.user1, self.form_data, instance=self.dining_list) + self.form = CreateSlotForm( + self.user1, self.form_data, instance=self.dining_list + ) def test_creation(self): self.assertTrue(self.form.is_valid()) @@ -56,9 +73,15 @@ def test_invalid_association(self): Source: https://docs.djangoproject.com/en/2.2/ref/forms/fields/#disabled """ association = Association.objects.create(name='Knights') - form_data = {'dish': 'Boter', 'association': str(association.pk), 'max_diners': '20', - 'serve_time': '18:00'} - form = CreateSlotForm(self.user1, form_data, instance=DiningList(date=self.dining_date)) + form_data = { + 'dish': 'Boter', + 'association': str(association.pk), + 'max_diners': '20', + 'serve_time': '18:00', + } + form = CreateSlotForm( + self.user1, form_data, instance=DiningList(date=self.dining_date) + ) self.assertTrue(form.is_valid()) # Check that the actual association is not Knights but Quadrivium self.assertEqual(self.association1, form.instance.association) @@ -70,28 +93,40 @@ def test_association_unique_for_date(self): # Try creating another one with same association dl = DiningList(date=self.dining_date) - data = {'dish': 'Kwark', 'association': str(self.association1.pk), 'max_diners': '18', - 'serve_time': '17:00'} + data = { + 'dish': 'Kwark', + 'association': str(self.association1.pk), + 'max_diners': '18', + 'serve_time': '17:00', + } form = CreateSlotForm(self.user1, data, instance=dl) self.assertFalse(form.is_valid()) self.assertTrue(form.has_error('association')) def test_insufficient_balance(self): - Transaction.objects.create(source=self.user1.account, - target=self.association1.account, - amount=Decimal('99'), - created_by=self.user1) + Transaction.objects.create( + source=self.user1.account, + target=self.association1.account, + amount=Decimal('99'), + created_by=self.user1, + ) self.assertFalse(self.form.is_valid()) def test_insufficient_balance_exception(self): - Transaction.objects.create(source=self.user1.account, - target=self.association1.account, - amount=Decimal('99'), - created_by=self.user1) + Transaction.objects.create( + source=self.user1.account, + target=self.association1.account, + amount=Decimal('99'), + created_by=self.user1, + ) # Make user member of another association that has the exception association = Association.objects.create(name='Q', has_min_exception=True) - UserMembership.objects.create(related_user=self.user1, association=association, is_verified=True, - verified_on=timezone.now()) + UserMembership.objects.create( + related_user=self.user1, + association=association, + is_verified=True, + verified_on=timezone.now(), + ) self.assertTrue(self.form.is_valid()) def test_serve_time_too_late(self): @@ -155,15 +190,15 @@ def test_transaction_db_creation(self): self.assertIsNotNone(instance.transaction) self.assertEqual(instance.transaction.amount, self.dining_list.kitchen_cost) self.assertEqual(instance.transaction.source, added_user.account) - self.assertEqual(instance.transaction.target, Account.objects.get(special='kitchen_cost')) + self.assertEqual( + instance.transaction.target, Account.objects.get(special='kitchen_cost') + ) self.assertEqual(instance.created_by, self.user) @patch_time() def test_prevent_doubles(self): DiningEntry.objects.create( - dining_list=self.dining_list, - user=self.user, - created_by=self.user + dining_list=self.dining_list, user=self.user, created_by=self.user ) self.assertFormHasError({'user': self.user}, code='user_already_present') @@ -171,28 +206,42 @@ def test_prevent_doubles(self): def test_sign_up_deadline(self): """Asserts that the form can not be used after the timelimit.""" # Verify testcase data - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) self.assertFormHasError({'user': self.user}, code='closed') self.assertFormValid( {'user': self.user}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first()) + instance=DiningEntry( + dining_list=self.dining_list, created_by=self.dining_list.owners.first() + ), ) @patch_time() def test_room(self): """Asserts that the form can not be used after the timelimit.""" # Verify testcase data - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) # Fill the dininglist with meaningless entries other_user = User.objects.get(id=1) for i in range(14): - DiningEntry.objects.create(dining_list=self.dining_list, user=other_user, created_by=other_user) + DiningEntry.objects.create( + dining_list=self.dining_list, user=other_user, created_by=other_user + ) self.dining_list.max_diners = 14 self.assertFormHasError({'user': self.user}, code='full') self.assertFormValid( {'user': self.user}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first()) + instance=DiningEntry( + dining_list=self.dining_list, created_by=self.dining_list.owners.first() + ), ) @patch_time() @@ -201,7 +250,11 @@ def test_association_only_limitation(self): # Verify testcase data self.dining_list.limit_signups_to_association_only = True self.dining_list.save() - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) # user 2 is member of the association self.assertFormValid({'user': self.user}) # user 4 is not a member @@ -209,7 +262,9 @@ def test_association_only_limitation(self): # owners can override self.assertFormValid( {'user': User.objects.get(id=4)}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first()) + instance=DiningEntry( + dining_list=self.dining_list, created_by=self.dining_list.owners.first() + ), ) @patch_time() @@ -219,14 +274,17 @@ def test_minimum_balance(self): self.assertFormHasError({'user': self.user}, code='no_money') self.assertFormHasError( {'user': self.user}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first()), - code='no_money' + instance=DiningEntry( + dining_list=self.dining_list, + created_by=self.dining_list.owners.first(), + ), + code='no_money', ) admin = User.objects.filter(is_superuser=True).first() self.assertFormHasError( {'user': admin}, instance=DiningEntry(dining_list=self.dining_list, created_by=admin), - code='no_money' + code='no_money', ) @patch_time(dt=datetime(2022, 5, 30, 12, 0)) @@ -244,7 +302,9 @@ def setUp(self): self.user = User.objects.get(id=2) def get_form_kwargs(self, **kwargs): - entry = DiningEntry(dining_list=self.dining_list, created_by=self.user, user=self.user) + entry = DiningEntry( + dining_list=self.dining_list, created_by=self.user, user=self.user + ) kwargs.setdefault('instance', entry) return super().get_form_kwargs(**kwargs) @@ -280,35 +340,53 @@ def test_transaction_db_creation(self): self.assertIsNotNone(instance.transaction) self.assertEqual(instance.transaction.amount, self.dining_list.kitchen_cost) self.assertEqual(instance.transaction.source, self.user.account) - self.assertEqual(instance.transaction.target, Account.objects.get(special='kitchen_cost')) + self.assertEqual( + instance.transaction.target, Account.objects.get(special='kitchen_cost') + ) self.assertEqual(instance.created_by, self.user) @patch_time(dt=datetime(2022, 4, 26, 18, 0)) def test_sign_up_deadline(self): # Verify testcase data - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) self.assertFormHasError({'external_name': 'my guest'}, code='closed') self.assertFormValid( {'external_name': 'my guest'}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first(), - user=self.user) + instance=DiningEntry( + dining_list=self.dining_list, + created_by=self.dining_list.owners.first(), + user=self.user, + ), ) @patch_time() def test_room(self): # Verify testcase data - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) # Fill the dininglist with meaningless entries other_user = User.objects.get(id=1) for i in range(14): - DiningEntry.objects.create(dining_list=self.dining_list, user=other_user, created_by=other_user) + DiningEntry.objects.create( + dining_list=self.dining_list, user=other_user, created_by=other_user + ) self.dining_list.max_diners = 14 self.assertFormHasError({'external_name': 'my guest'}, code='full') self.assertFormValid( {'external_name': 'my guest'}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first(), - user=self.user) + instance=DiningEntry( + dining_list=self.dining_list, + created_by=self.dining_list.owners.first(), + user=self.user, + ), ) @patch_time() @@ -316,21 +394,31 @@ def test_association_only_limitation(self): # Verify testcase data self.dining_list.limit_signups_to_association_only = True self.dining_list.save() - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) # user 2 is member of the association self.assertFormValid({'external_name': 'my guest'}) # user 4 is not a member self.assertFormHasError( {'external_name': 'my guest'}, code='members_only', - instance=DiningEntry(dining_list=self.dining_list, created_by=User.objects.get(id=4), - user=User.objects.get(id=4)) + instance=DiningEntry( + dining_list=self.dining_list, + created_by=User.objects.get(id=4), + user=User.objects.get(id=4), + ), ) # owners can override self.assertFormValid( {'external_name': 'my guest'}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first(), - user=self.user) + instance=DiningEntry( + dining_list=self.dining_list, + created_by=self.dining_list.owners.first(), + user=self.user, + ), ) @patch_time() @@ -340,15 +428,20 @@ def test_minimum_balance(self): self.assertFormHasError({'external_name': 'my guest'}, code='no_money') self.assertFormHasError( {'external_name': 'my guest'}, - instance=DiningEntry(dining_list=self.dining_list, created_by=self.dining_list.owners.first(), - user=self.user), - code='no_money' + instance=DiningEntry( + dining_list=self.dining_list, + created_by=self.dining_list.owners.first(), + user=self.user, + ), + code='no_money', ) admin = User.objects.filter(is_superuser=True).first() self.assertFormHasError( {'external_name': 'my guest'}, - instance=DiningEntry(dining_list=self.dining_list, created_by=admin, user=self.user), - code='no_money' + instance=DiningEntry( + dining_list=self.dining_list, created_by=admin, user=self.user + ), + code='no_money', ) @patch_time(dt=datetime(2022, 5, 30, 12, 0)) @@ -393,7 +486,7 @@ def test_db_transaction_deletion(self): dining_list = DiningList.objects.create( date=date(2123, 4, 5), sign_up_deadline=datetime(2123, 4, 5, tzinfo=timezone.utc), - association=self.dining_list.association + association=self.dining_list.association, ) # We manually create the entry and transaction, so that our test doesn't depend on other code. @@ -405,8 +498,8 @@ def test_db_transaction_deletion(self): source=user.account, target=Account.objects.get(special='kitchen_cost'), amount=Decimal('2.18'), - created_by=user - ) + created_by=user, + ), ) # Confirm our balance is negative self.assertEqual(user.account.get_balance(), Decimal('-2.18')) @@ -421,7 +514,11 @@ def test_db_transaction_deletion(self): def test_sign_up_deadline(self): """Asserts that the form can not be used after the sign-up deadline unless owner.""" # Verify testcase data - self.assertNotIn(self.user, self.dining_list.owners.all(), "Incorrect test data, user should not be owner") + self.assertNotIn( + self.user, + self.dining_list.owners.all(), + "Incorrect test data, user should not be owner", + ) self.assertFormHasError({}, code='closed') self.assertFormValid({}, deleter=self.dining_list.owners.first()) @@ -457,7 +554,9 @@ def test_db_list_deletion(self): dining_list_id = self.dining_list.id self.assertFormValid({}).execute(self.user) - self.assertFalse(DiningEntry.objects.filter(dining_list__id=dining_list_id).exists()) + self.assertFalse( + DiningEntry.objects.filter(dining_list__id=dining_list_id).exists() + ) @patch_time() def test_db_transaction_cancellation(self): @@ -469,7 +568,7 @@ def test_db_transaction_cancellation(self): self.assertEqual( Transaction.objects.filter().count(), - old_cancelled_transaction_count + diner_count + old_cancelled_transaction_count + diner_count, ) def test_form_editing_time_limit(self): @@ -501,23 +600,27 @@ def test_widget_replacements(self): @patch_time() def test_form_valid(self): - self.assertFormValid({ - 'owners': [1], - 'dish': "My delicious dish", - 'serve_time': time(18, 00), - 'max_diners': 15, - 'sign_up_deadline': datetime(2022, 4, 26, 15, 0), - }) + self.assertFormValid( + { + 'owners': [1], + 'dish': "My delicious dish", + 'serve_time': time(18, 00), + 'max_diners': 15, + 'sign_up_deadline': datetime(2022, 4, 26, 15, 0), + } + ) @patch_time() def test_db_update(self): - updated_dining_list = self.assertFormValid({ - 'owners': [4], - 'dish': "New dish", - 'serve_time': time(17, 5), - 'max_diners': 14, - 'sign_up_deadline': datetime(2022, 4, 26, 12, 00), - }).save() + updated_dining_list = self.assertFormValid( + { + 'owners': [4], + 'dish': "New dish", + 'serve_time': time(17, 5), + 'max_diners': 14, + 'sign_up_deadline': datetime(2022, 4, 26, 12, 00), + } + ).save() # Update the dining_list self.dining_list.refresh_from_db() @@ -527,22 +630,29 @@ def test_db_update(self): self.assertEqual(updated_dining_list.dish, "New dish") self.assertEqual(updated_dining_list.serve_time, time(17, 5)) self.assertEqual(updated_dining_list.max_diners, 14) - self.assertNotEqual(updated_dining_list.sign_up_deadline.time(), self.dining_list.sign_up_deadline) + self.assertNotEqual( + updated_dining_list.sign_up_deadline.time(), + self.dining_list.sign_up_deadline, + ) def test_kitchen_open_time_validity(self): """Asserts that the meal can't be served before the kitchen opening time.""" - dt = datetime.combine(date(2000, 1, 1), settings.KITCHEN_USE_START_TIME) - timedelta(minutes=1) - self.assertFormHasError({ - 'serve_time': dt.time() - }, code='kitchen_start_time', field='serve_time') + dt = datetime.combine( + date(2000, 1, 1), settings.KITCHEN_USE_START_TIME + ) - timedelta(minutes=1) + self.assertFormHasError( + {'serve_time': dt.time()}, code='kitchen_start_time', field='serve_time' + ) def test_kitchen_close_time_validity(self): """Asserts that the meal can't be served after the kitchen closing time.""" - dt = datetime.combine(date(2000, 1, 1), settings.KITCHEN_USE_END_TIME) + timedelta(minutes=1) + dt = datetime.combine( + date(2000, 1, 1), settings.KITCHEN_USE_END_TIME + ) + timedelta(minutes=1) - self.assertFormHasError({ - 'serve_time': dt.time() - }, code='kitchen_close_time', field='serve_time') + self.assertFormHasError( + {'serve_time': dt.time()}, code='kitchen_close_time', field='serve_time' + ) class TestDiningPaymentForm(FormValidityMixin, TestCase): @@ -563,30 +673,43 @@ def test_class(self): @patch_time() def test_form_valid(self): - self.assertFormValid({ - 'payment_link': "https://www.google.com/", - }) + self.assertFormValid( + { + 'payment_link': "https://www.google.com/", + } + ) self.assertFormValid({}) @patch_time() def test_dining_cost_conflict(self): """Assert that an error is raised when both dinner_cost and dinner_cost_total are defined.""" - self.assertFormHasError({ - 'dining_cost_total': 12, - 'dining_cost': 4, - }, code='duplicate_cost', field='dining_cost') - self.assertFormHasError({ - 'dining_cost_total': 12, - 'dining_cost': 4, - }, code='duplicate_cost', field='dining_cost_total') + self.assertFormHasError( + { + 'dining_cost_total': 12, + 'dining_cost': 4, + }, + code='duplicate_cost', + field='dining_cost', + ) + self.assertFormHasError( + { + 'dining_cost_total': 12, + 'dining_cost': 4, + }, + code='duplicate_cost', + field='dining_cost_total', + ) @patch_time() def test_dining_cost_total_empty_diners(self): """Assert an error is raised for costs when there are no diners on the dining list.""" self.dining_list.dining_entries.all().delete() - self.assertFormHasError({ - 'dining_cost_total': 12, - }, code='costs_no_diners') + self.assertFormHasError( + { + 'dining_cost_total': 12, + }, + code='costs_no_diners', + ) @patch_time() def test_dining_cost_total(self): @@ -651,10 +774,12 @@ def test_mail_sending_users(self, mock_mail): # Assert that the correct number of recipients are documented self.assertEqual( len(calls[0]['args'][1]), - DiningEntry.objects.internal().filter( + DiningEntry.objects.internal() + .filter( dining_list=self.dining_list, has_paid=False, - ).count() + ) + .count(), ) self.assertEqual(calls[0]['context']['dining_list'], self.dining_list) @@ -668,7 +793,9 @@ def test_mail_sending_external(self, mock_mail): request.user = User.objects.get(id=1) form.send_reminder(request=request) - calls = self.assert_has_call(mock_mail, arg_1="mail/dining_payment_reminder_external") + calls = self.assert_has_call( + mock_mail, arg_1="mail/dining_payment_reminder_external" + ) self.assertEqual(len(calls), 2) # ids 2 and 3 added unpaid guests self.assertEqual(calls[0]['context']['dining_list'], self.dining_list) diff --git a/dining/tests/test_forms_diningentry.py b/dining/tests/test_forms_diningentry.py index 75904301..b93d115c 100644 --- a/dining/tests/test_forms_diningentry.py +++ b/dining/tests/test_forms_diningentry.py @@ -6,7 +6,11 @@ from django.utils import timezone from creditmanagement.models import Transaction -from dining.forms import DiningEntryDeleteForm, DiningEntryInternalForm, DiningEntryExternalForm +from dining.forms import ( + DiningEntryDeleteForm, + DiningEntryInternalForm, + DiningEntryExternalForm, +) from dining.models import DiningList, DiningEntry from userdetails.models import User, Association, UserMembership @@ -33,10 +37,15 @@ def setUpTestData(cls): def setUp(self): # Not in setUpTestData to ensure that it is fresh for every test case - self.dining_list = DiningList.objects.create(date=date(2089, 1, 1), association=self.association, - sign_up_deadline=datetime(2088, 1, 1, tzinfo=timezone.utc)) + self.dining_list = DiningList.objects.create( + date=date(2089, 1, 1), + association=self.association, + sign_up_deadline=datetime(2088, 1, 1, tzinfo=timezone.utc), + ) self.dining_list.owners.add(self.user) - self.dining_entry = DiningEntry(dining_list=self.dining_list, created_by=self.user2) + self.dining_entry = DiningEntry( + dining_list=self.dining_list, created_by=self.user2 + ) self.post_data = {'user': str(self.user2.pk)} self.form = DiningEntryInternalForm(self.post_data, instance=self.dining_entry) @@ -50,13 +59,17 @@ def test_dining_list_not_adjustable(self): self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'closed')) def test_dining_list_closed(self): - self.dining_list.sign_up_deadline = datetime(2000, 1, 1, tzinfo=timezone.utc) # Close list + self.dining_list.sign_up_deadline = datetime( + 2000, 1, 1, tzinfo=timezone.utc + ) # Close list self.assertFalse(self.form.is_valid()) self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'closed')) def test_dining_list_closed_owner(self): """Tests closed exception for list owner.""" - self.dining_list.sign_up_deadline = datetime(2000, 1, 1, tzinfo=timezone.utc) # Close list + self.dining_list.sign_up_deadline = datetime( + 2000, 1, 1, tzinfo=timezone.utc + ) # Close list self.dining_entry.created_by = self.user # Entry creator is dining list owner self.assertTrue(self.form.is_valid()) @@ -72,8 +85,12 @@ def test_dining_list_no_room_owner(self): def test_limited_to_association_is_member(self): self.dining_list.limit_signups_to_association_only = True - UserMembership.objects.create(related_user=self.user2, association=self.association, - is_verified=True, verified_on=timezone.now()) + UserMembership.objects.create( + related_user=self.user2, + association=self.association, + is_verified=True, + verified_on=timezone.now(), + ) self.assertTrue(self.form.is_valid()) def test_limited_to_association_is_not_member(self): @@ -83,22 +100,32 @@ def test_limited_to_association_is_not_member(self): def test_balance_too_low(self): # Move money away from user2's balance. - Transaction.objects.create(source=self.user2.account, - target=self.association.account, - amount=Decimal('99'), - created_by=self.user2) + Transaction.objects.create( + source=self.user2.account, + target=self.association.account, + amount=Decimal('99'), + created_by=self.user2, + ) self.assertFalse(self.form.is_valid()) self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'no_money')) def test_balance_too_low_exception(self): # Make user member of association with exception - assoc = Association.objects.create(slug='assoc', name='Association', has_min_exception=True) - UserMembership.objects.create(related_user=self.user2, association=assoc, is_verified=True, - verified_on=timezone.now()) - Transaction.objects.create(source=self.user2.account, - target=self.association.account, - amount=Decimal('99'), - created_by=self.user2) + assoc = Association.objects.create( + slug='assoc', name='Association', has_min_exception=True + ) + UserMembership.objects.create( + related_user=self.user2, + association=assoc, + is_verified=True, + verified_on=timezone.now(), + ) + Transaction.objects.create( + source=self.user2.account, + target=self.association.account, + amount=Decimal('99'), + created_by=self.user2, + ) self.assertTrue(self.form.is_valid()) def test_invalid_user(self): @@ -117,10 +144,15 @@ def setUpTestData(cls): def setUp(self): # Not in setUpTestData to ensure that it is fresh for every test case - self.dining_list = DiningList.objects.create(date=date(2089, 1, 1), association=self.association, - sign_up_deadline=datetime(2088, 1, 1, tzinfo=timezone.utc)) + self.dining_list = DiningList.objects.create( + date=date(2089, 1, 1), + association=self.association, + sign_up_deadline=datetime(2088, 1, 1, tzinfo=timezone.utc), + ) self.dining_list.owners.add(self.user) - self.dining_entry = DiningEntry(dining_list=self.dining_list, user=self.user2, created_by=self.user2) + self.dining_entry = DiningEntry( + dining_list=self.dining_list, user=self.user2, created_by=self.user2 + ) self.post_data = {'external_name': 'Ankie'} def test_form(self): @@ -133,10 +165,15 @@ def setUp(self): self.user1 = User.objects.create_user('ankie', email='ankie@universe.cat') self.user2 = User.objects.create_user('noortje', email='noortje@universe.cat') self.association = Association.objects.create(name='C&M') - self.dining_list = DiningList.objects.create(date=date(2100, 1, 1), association=self.association, - sign_up_deadline=datetime(2100, 1, 1, tzinfo=timezone.utc)) + self.dining_list = DiningList.objects.create( + date=date(2100, 1, 1), + association=self.association, + sign_up_deadline=datetime(2100, 1, 1, tzinfo=timezone.utc), + ) self.dining_list.owners.add(self.user1) - self.entry = DiningEntry(user=self.user2, created_by=self.user2, dining_list=self.dining_list) + self.entry = DiningEntry( + user=self.user2, created_by=self.user2, dining_list=self.dining_list + ) self.form = DiningEntryDeleteForm(self.entry, self.user2, {}) def test_valid(self): diff --git a/dining/tests/test_forms_sendreminderform.py b/dining/tests/test_forms_sendreminderform.py index e11145ff..70d00c8f 100644 --- a/dining/tests/test_forms_sendreminderform.py +++ b/dining/tests/test_forms_sendreminderform.py @@ -77,7 +77,9 @@ def test_two_guests(self): self.create_dining_entry(self.user, has_paid=False, guest_name='Guest 1') self.create_dining_entry(self.user, has_paid=False, guest_name='Guest 2') self.assertEqual(list(self.form.get_user_recipients()), []) - self.assertEqual(self.form.get_guest_recipients(), {self.user: ['Guest 1', 'Guest 2']}) + self.assertEqual( + self.form.get_guest_recipients(), {self.user: ['Guest 1', 'Guest 2']} + ) def test_arbitrary(self): """Tests with an arbitrary dining list with all cases. @@ -86,16 +88,23 @@ def test_arbitrary(self): case above. We also verify construct_messages(). """ # Create 4 users. - u = [User.objects.create(username=f'{i}', email=f'{i}@localhost') for i in range(4)] + u = [ + User.objects.create(username=f'{i}', email=f'{i}@localhost') + for i in range(4) + ] # Create 1 of each possible case. self.create_dining_entry(u[0], has_paid=False) self.create_dining_entry(u[1], has_paid=True) self.create_dining_entry(u[2], has_paid=False, guest_name='Guest 1') - self.create_dining_entry(u[2], has_paid=False, guest_name='Guest 2') # Same user, different guest + self.create_dining_entry( + u[2], has_paid=False, guest_name='Guest 2' + ) # Same user, different guest self.create_dining_entry(u[3], has_paid=True, guest_name='Guest 3') self.assertEqual(list(self.form.get_user_recipients()), [u[0]]) - self.assertEqual(self.form.get_guest_recipients(), {u[2]: ['Guest 1', 'Guest 2']}) + self.assertEqual( + self.form.get_guest_recipients(), {u[2]: ['Guest 1', 'Guest 2']} + ) # For construct_messages() we just confirm that it has the correct # recipients. If we wanted to test that the contexts are correct, we @@ -117,11 +126,14 @@ class SendReminderFormLockTestCase(TransactionTestCase): @skipUnlessDBFeature('has_select_for_update', 'has_select_for_update_nowait') def test_send_reminder(self): """Tests that send_reminder() doesn't send multiple emails simultaneously.""" - form = SendReminderForm({}, dining_list=DiningList.objects.create( - date=date(2020, 1, 1), - association=Association.objects.create(slug='assoc'), - sign_up_deadline=datetime(2020, 1, 1, 12, 0, tzinfo=timezone.utc), - )) + form = SendReminderForm( + {}, + dining_list=DiningList.objects.create( + date=date(2020, 1, 1), + association=Association.objects.create(slug='assoc'), + sign_up_deadline=datetime(2020, 1, 1, 12, 0, tzinfo=timezone.utc), + ), + ) request = HttpRequest() request.user = User.objects.create() diff --git a/dining/tests/test_models.py b/dining/tests/test_models.py index d05abed3..2cdaddbe 100644 --- a/dining/tests/test_models.py +++ b/dining/tests/test_models.py @@ -17,19 +17,36 @@ def setUpTestData(cls): cls.association = Association.objects.create() def setUp(self): - self.dining_list = DiningList(date=date(2123, 1, 2), - sign_up_deadline=datetime(2100, 2, 2, tzinfo=timezone.utc), - association=self.association) + self.dining_list = DiningList( + date=date(2123, 1, 2), + sign_up_deadline=datetime(2100, 2, 2, tzinfo=timezone.utc), + association=self.association, + ) def test_is_open(self): - list = DiningList.objects.create(date=date(2015, 1, 1), association=self.association, - sign_up_deadline=datetime(2015, 1, 1, 17, 00, tzinfo=timezone.utc)) + list = DiningList.objects.create( + date=date(2015, 1, 1), + association=self.association, + sign_up_deadline=datetime(2015, 1, 1, 17, 00, tzinfo=timezone.utc), + ) - with patch.object(timezone, 'now', return_value=datetime(2015, 1, 1, 16, 59, tzinfo=timezone.utc)): + with patch.object( + timezone, + 'now', + return_value=datetime(2015, 1, 1, 16, 59, tzinfo=timezone.utc), + ): self.assertTrue(list.is_open()) - with patch.object(timezone, 'now', return_value=datetime(2015, 1, 1, 17, 00, tzinfo=timezone.utc)): + with patch.object( + timezone, + 'now', + return_value=datetime(2015, 1, 1, 17, 00, tzinfo=timezone.utc), + ): self.assertFalse(list.is_open()) - with patch.object(timezone, 'now', return_value=datetime(2015, 1, 1, 17, 1, tzinfo=timezone.utc)): + with patch.object( + timezone, + 'now', + return_value=datetime(2015, 1, 1, 17, 1, tzinfo=timezone.utc), + ): self.assertFalse(list.is_open()) def test_is_owner(self): @@ -56,9 +73,11 @@ def setUpTestData(cls): cls.association = Association.objects.create() def setUp(self): - self.dining_list = DiningList(date=date(2123, 1, 2), - sign_up_deadline=datetime(2100, 2, 2, tzinfo=timezone.utc), - association=self.association) + self.dining_list = DiningList( + date=date(2123, 1, 2), + sign_up_deadline=datetime(2100, 2, 2, tzinfo=timezone.utc), + association=self.association, + ) def test_sign_up_deadline_valid(self): self.dining_list.full_clean() # Shouldn't raise @@ -75,18 +94,29 @@ def setUpTestData(cls): cls.dining_list = DiningList.objects.create( date=date(2123, 2, 1), association=Association.objects.create(slug='assoc'), - sign_up_deadline=datetime(2100, 1, 1, tzinfo=timezone.utc) + sign_up_deadline=datetime(2100, 1, 1, tzinfo=timezone.utc), ) def test_clean_valid_entry(self): - entry = DiningEntry(dining_list=self.dining_list, user=self.user, created_by=self.user) + entry = DiningEntry( + dining_list=self.dining_list, user=self.user, created_by=self.user + ) entry.full_clean() # No ValidationError def test_clean_duplicate_entry(self): - DiningEntry.objects.create(dining_list=self.dining_list, user=self.user, created_by=self.user) - entry = DiningEntry(dining_list=self.dining_list, user=self.user, created_by=self.user) + DiningEntry.objects.create( + dining_list=self.dining_list, user=self.user, created_by=self.user + ) + entry = DiningEntry( + dining_list=self.dining_list, user=self.user, created_by=self.user + ) self.assertRaises(ValidationError, entry.full_clean) def test_external_name(self): - entry = DiningEntry(user=self.user, dining_list=self.dining_list, external_name='Piet', created_by=self.user) + entry = DiningEntry( + user=self.user, + dining_list=self.dining_list, + external_name='Piet', + created_by=self.user, + ) entry.full_clean() # No ValidationError diff --git a/dining/urls.py b/dining/urls.py index 3456f0fd..adaf785b 100644 --- a/dining/urls.py +++ b/dining/urls.py @@ -6,18 +6,52 @@ urlpatterns = [ path('', index, name='index'), path('csv/', views.DailyDinersCSVView.as_view(), name="diners_csv"), - path('///', include([ - path('', DayView.as_view(), name='day_view'), - path('add/', views.NewSlotView.as_view(), name='new_slot'), - path('/', include([ - path('', views.SlotInfoView.as_view(), name='slot_details'), - path('list/', views.SlotListView.as_view(), name='slot_list'), - path('allergy/', views.SlotAllergyView.as_view(), name='slot_allergy'), - path('entry/add/', views.EntryAddView.as_view(), name='entry_add'), - path('change/', views.SlotInfoChangeView.as_view(), name='slot_change'), - path('delete/', views.SlotDeleteView.as_view(), name='slot_delete'), - path('inform_payment/', views.SlotPaymentView.as_view(), name='slot_inform_payment'), - ])), - ])), - path('entries//delete/', views.EntryDeleteView.as_view(), name='entry_delete'), + path( + '///', + include( + [ + path('', DayView.as_view(), name='day_view'), + path('add/', views.NewSlotView.as_view(), name='new_slot'), + path( + '/', + include( + [ + path('', views.SlotInfoView.as_view(), name='slot_details'), + path( + 'list/', views.SlotListView.as_view(), name='slot_list' + ), + path( + 'allergy/', + views.SlotAllergyView.as_view(), + name='slot_allergy', + ), + path( + 'entry/add/', + views.EntryAddView.as_view(), + name='entry_add', + ), + path( + 'change/', + views.SlotInfoChangeView.as_view(), + name='slot_change', + ), + path( + 'delete/', + views.SlotDeleteView.as_view(), + name='slot_delete', + ), + path( + 'inform_payment/', + views.SlotPaymentView.as_view(), + name='slot_inform_payment', + ), + ] + ), + ), + ] + ), + ), + path( + 'entries//delete/', views.EntryDeleteView.as_view(), name='entry_delete' + ), ] diff --git a/dining/views.py b/dining/views.py index f6a70f71..37b5a105 100644 --- a/dining/views.py +++ b/dining/views.py @@ -6,7 +6,12 @@ from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied from django.db import transaction from django.db.models import Q, Count -from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden, HttpResponse +from django.http import ( + Http404, + HttpResponseRedirect, + HttpResponseForbidden, + HttpResponse, +) from django.shortcuts import redirect, get_object_or_404 from django.urls import reverse from django.utils import timezone @@ -15,10 +20,24 @@ from django.views.generic.detail import SingleObjectMixin from dining.datesequence import sequenced_date -from dining.forms import CreateSlotForm, DiningEntryDeleteForm, DiningCommentForm, DiningInfoForm, DiningPaymentForm, \ - DiningListDeleteForm, SendReminderForm, \ - DiningEntryExternalForm, DiningEntryInternalForm -from dining.models import DiningList, DiningDayAnnouncement, DiningCommentVisitTracker, DiningEntry, DiningComment +from dining.forms import ( + CreateSlotForm, + DiningEntryDeleteForm, + DiningCommentForm, + DiningInfoForm, + DiningPaymentForm, + DiningListDeleteForm, + SendReminderForm, + DiningEntryExternalForm, + DiningEntryInternalForm, +) +from dining.models import ( + DiningList, + DiningDayAnnouncement, + DiningCommentVisitTracker, + DiningEntry, + DiningComment, +) from general.mail_control import send_templated_mail from userdetails.models import User, Association @@ -35,10 +54,14 @@ class DayMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'date': self.date, - 'date_diff': (self.date - date.today()).days, # Nr. of days between date and today - }) + context.update( + { + 'date': self.date, + 'date_diff': ( + self.date - date.today() + ).days, # Nr. of days between date and today + } + ) return context def init_date(self): @@ -47,7 +70,9 @@ def init_date(self): # Already initialized return try: - self.date = sequenced_date.fromdate(date(self.kwargs['year'], self.kwargs['month'], self.kwargs['day'])) + self.date = sequenced_date.fromdate( + date(self.kwargs['year'], self.kwargs['month'], self.kwargs['day']) + ) except ValueError: raise Http404('Invalid date') @@ -91,7 +116,6 @@ class DailyDinersCSVView(LoginRequiredMixin, View): """Returns a CSV file with all diners of that day.""" def get(self, request, *args, **kwargs): - # Only superusers can access this page if not request.user.is_superuser: return HttpResponseForbidden @@ -112,8 +136,13 @@ def get(self, request, *args, **kwargs): date_start = date_end # Count all dining entries in the given period - entry_count = Count('diningentry', filter=(Q(diningentry__dining_list__date__lte=date_end) & Q( - diningentry__dining_list__date__gte=date_start))) + entry_count = Count( + 'diningentry', + filter=( + Q(diningentry__dining_list__date__lte=date_end) + & Q(diningentry__dining_list__date__gte=date_start) + ), + ) # Annotate the counts to the user users = User.objects.annotate(diningentry_count=entry_count) @@ -128,7 +157,9 @@ def get(self, request, *args, **kwargs): # Create the CSV file # Set up response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="association_members.csv"' + response[ + 'Content-Disposition' + ] = 'attachment; filename="association_members.csv"' csv_writer = csv.writer(response) # Write header @@ -158,19 +189,26 @@ def get(self, request, *args, **kwargs): class NewSlotView(LoginRequiredMixin, DayMixin, TemplateView): """Creation page for a new dining list.""" + template_name = "dining_lists/dining_add.html" def get_context_data(self, **kwargs): context = super().get_context_data() - context.update({ - 'slot_form': CreateSlotForm(self.request.user, instance=DiningList(date=self.date)) - }) + context.update( + { + 'slot_form': CreateSlotForm( + self.request.user, instance=DiningList(date=self.date) + ) + } + ) return context def post(self, request, *args, **kwargs): context = self.get_context_data() - context['slot_form'] = CreateSlotForm(request.user, request.POST, instance=DiningList(date=self.date)) + context['slot_form'] = CreateSlotForm( + request.user, request.POST, instance=DiningList(date=self.date) + ) if context['slot_form'].is_valid(): dining_list = context['slot_form'].save() @@ -187,9 +225,11 @@ class DiningListMixin(DayMixin): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'dining_list': self.dining_list, - }) + context.update( + { + 'dining_list': self.dining_list, + } + ) return context def init_dining_list(self): @@ -199,7 +239,9 @@ def init_dining_list(self): return # Needs initialized date self.init_date() - self.dining_list = get_object_or_404(DiningList, date=self.date, association__slug=self.kwargs['identifier']) + self.dining_list = get_object_or_404( + DiningList, date=self.date, association__slug=self.kwargs['identifier'] + ) def dispatch(self, request, *args, **kwargs): self.init_dining_list() @@ -219,19 +261,25 @@ def get_context_data(self, **kwargs): # Get the amount of messages context['comments_total'] = self.dining_list.diningcomment_set.count() # Get the amount of unread messages - view_time = DiningCommentVisitTracker.get_latest_visit(user=self.request.user, dining_list=self.dining_list) + view_time = DiningCommentVisitTracker.get_latest_visit( + user=self.request.user, dining_list=self.dining_list + ) if view_time is None: context['comments_unread'] = context['comments_total'] else: - context['comments_unread'] = self.dining_list.diningcomment_set.filter(timestamp__gte=view_time).count() + context['comments_unread'] = self.dining_list.diningcomment_set.filter( + timestamp__gte=view_time + ).count() return context # We use 2 different terminologies for the same thing, 'slot' and 'dining list'. We should get rid of 'slot'. + class SlotMixin(LoginRequiredMixin, DiningListMixin, UpdateSlotViewTrackerMixin): """Mixin for a dining list detail page.""" + pass @@ -240,10 +288,12 @@ class EntryAddView(LoginRequiredMixin, DiningListMixin, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'user_form': DiningEntryInternalForm(), - 'external_form': DiningEntryExternalForm(), - }) + context.update( + { + 'user_form': DiningEntryInternalForm(), + 'external_form': DiningEntryExternalForm(), + } + ) return context def post(self, request, *args, **kwargs): @@ -252,7 +302,9 @@ def post(self, request, *args, **kwargs): # Do form shenanigans if 'add_external' in request.POST: - entry = DiningEntry(user=request.user, dining_list=self.dining_list, created_by=request.user) + entry = DiningEntry( + user=request.user, dining_list=self.dining_list, created_by=request.user + ) form = DiningEntryExternalForm(request.POST, instance=entry) else: entry = DiningEntry(dining_list=self.dining_list, created_by=request.user) @@ -267,21 +319,33 @@ def post(self, request, *args, **kwargs): 'mail/dining_entry_added_by', entry.user, context={'entry': entry, 'dining_list': entry.dining_list}, - request=request + request=request, ) messages.success( request, - "You successfully added {} to the dining list".format(entry.user.get_short_name()) + "You successfully added {} to the dining list".format( + entry.user.get_short_name() + ), ) # The entry is for an external diner, provide a message. if entry.is_external(): - messages.success(request, "You successfully added {} to the dining list".format(entry.external_name)) + messages.success( + request, + "You successfully added {} to the dining list".format( + entry.external_name + ), + ) else: # The form was invalid, put the errors in a message. for field, errors in form.errors.items(): for error in errors: - messages.error(request, "{}: {}".format(field, error) if field != NON_FIELD_ERRORS else error) + messages.error( + request, + "{}: {}".format(field, error) + if field != NON_FIELD_ERRORS + else error, + ) # Always redirect to the dining list page return redirect(self.dining_list) @@ -309,11 +373,22 @@ def post(self, request, *args, **kwargs): # Send a mail when someone else does the removal if entry.user != request.user: - context = {'entry': entry, 'dining_list': entry.dining_list, 'remover': request.user} + context = { + 'entry': entry, + 'dining_list': entry.dining_list, + 'remover': request.user, + } if entry.is_external(): - send_templated_mail('mail/dining_entry_external_removed_by', entry.user, context, request) + send_templated_mail( + 'mail/dining_entry_external_removed_by', + entry.user, + context, + request, + ) else: - send_templated_mail('mail/dining_entry_removed_by', entry.user, context, request) + send_templated_mail( + 'mail/dining_entry_removed_by', entry.user, context, request + ) else: for error in form.non_field_errors(): @@ -340,10 +415,14 @@ def can_edit_stats(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'entries': self.dining_list.dining_entries.order_by('user__first_name', 'user__last_name', 'external_name'), - 'can_edit_stats': self.can_edit_stats(), - }) + context.update( + { + 'entries': self.dining_list.dining_entries.order_by( + 'user__first_name', 'user__last_name', 'external_name' + ), + 'can_edit_stats': self.can_edit_stats(), + } + ) return context def post(self, request, *args, **kwargs): @@ -356,7 +435,9 @@ def post(self, request, *args, **kwargs): # get_object_or_404(). If we did not do that, the user could change all # dining entries across all lists. - entry = get_object_or_404(DiningEntry, id=request.POST.get('entry_id'), dining_list=self.dining_list) + entry = get_object_or_404( + DiningEntry, id=request.POST.get('entry_id'), dining_list=self.dining_list + ) # We toggle the given stat value, based on the previous value as was submitted by the form. stat = request.POST.get('toggle') @@ -373,15 +454,21 @@ def post(self, request, *args, **kwargs): return HttpResponseRedirect(self.reverse('slot_list')) -class SlotInfoView(LoginRequiredMixin, DiningListMixin, UpdateSlotViewTrackerMixin, FormView): +class SlotInfoView( + LoginRequiredMixin, DiningListMixin, UpdateSlotViewTrackerMixin, FormView +): template_name = "dining_lists/dining_slot_info.html" form_class = DiningCommentForm def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs.update({ - 'instance': DiningComment(dining_list=self.dining_list, poster=self.request.user), - }) + kwargs.update( + { + 'instance': DiningComment( + dining_list=self.dining_list, poster=self.request.user + ), + } + ) return kwargs def get_success_url(self): @@ -389,15 +476,19 @@ def get_success_url(self): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'comments': self.dining_list.diningcomment_set.order_by('-pinned_to_top', 'timestamp').all(), - 'last_visited': DiningCommentVisitTracker.get_latest_visit( - user=self.request.user, - dining_list=self.dining_list, - update=True - ), - 'number_of_allergies': self.dining_list.internal_dining_entries().exclude(user__allergies='').count(), - }) + context.update( + { + 'comments': self.dining_list.diningcomment_set.order_by( + '-pinned_to_top', 'timestamp' + ).all(), + 'last_visited': DiningCommentVisitTracker.get_latest_visit( + user=self.request.user, dining_list=self.dining_list, update=True + ), + 'number_of_allergies': self.dining_list.internal_dining_entries() + .exclude(user__allergies='') + .count(), + } + ) return context def form_valid(self, form): @@ -411,9 +502,9 @@ class SlotAllergyView(SlotMixin, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) entries = self.dining_list.internal_dining_entries().exclude(user__allergies='') - context.update({ - 'allergy_entries': entries.order_by('user__first_name', 'user__last_name') - }) + context.update( + {'allergy_entries': entries.order_by('user__first_name', 'user__last_name')} + ) return context @@ -437,10 +528,14 @@ class SlotInfoChangeView(SlotMixin, SlotOwnerMixin, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'info_form': DiningInfoForm(instance=self.dining_list, prefix='info'), - 'payment_form': DiningPaymentForm(instance=self.dining_list, prefix='payment'), - }) + context.update( + { + 'info_form': DiningInfoForm(instance=self.dining_list, prefix='info'), + 'payment_form': DiningPaymentForm( + instance=self.dining_list, prefix='payment' + ), + } + ) return context def post(self, request, *args, **kwargs): @@ -459,8 +554,12 @@ def post(self, request, *args, **kwargs): (perhaps it was meant that way?). """ - info_form = DiningInfoForm(request.POST, instance=self.dining_list, prefix='info') - payment_form = DiningPaymentForm(request.POST, instance=self.dining_list, prefix='payment') + info_form = DiningInfoForm( + request.POST, instance=self.dining_list, prefix='info' + ) + payment_form = DiningPaymentForm( + request.POST, instance=self.dining_list, prefix='payment' + ) # Save and redirect if forms are valid, stay otherwise if info_form.is_valid() and payment_form.is_valid(): @@ -470,10 +569,12 @@ def post(self, request, *args, **kwargs): return HttpResponseRedirect(self.reverse('slot_details')) - context.update({ - 'info_form': info_form, - 'payment_form': payment_form, - }) + context.update( + { + 'info_form': info_form, + 'payment_form': payment_form, + } + ) return self.render_to_response(context) @@ -483,14 +584,17 @@ class SlotDeleteView(SlotMixin, SlotOwnerMixin, FormView): Page is only available for slot owners. """ + template_name = "dining_lists/dining_slot_delete.html" form_class = DiningListDeleteForm def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs.update({ - 'instance': self.dining_list, - }) + kwargs.update( + { + 'instance': self.dining_list, + } + ) return kwargs def get_day_view_url(self): @@ -508,6 +612,7 @@ def form_valid(self, form): class SlotPaymentView(SlotMixin, SlotOwnerMixin, FormView): """A view class that allows dining list owners to send payment reminders.""" + form_class = SendReminderForm def get(self, request, *args, **kwargs): @@ -530,7 +635,10 @@ def form_invalid(self, form): def form_valid(self, form): success = form.send_reminder(self.request) if not success: - messages.success(self.request, "Diners have been informed recently, you can send a new mail momentarily") + messages.success( + self.request, + "Diners have been informed recently, you can send a new mail momentarily", + ) else: messages.success(self.request, "Diners have been informed") return super().form_valid(form) diff --git a/general/forms.py b/general/forms.py index 5d675bba..281b3a41 100644 --- a/general/forms.py +++ b/general/forms.py @@ -17,7 +17,9 @@ def __init__(self, *args, initial=None, **kwargs): initial = {} initial.setdefault('date_end', timezone.now()) - initial.setdefault('date_start', initial['date_end'] - timezone.timedelta(days=365)) + initial.setdefault( + 'date_start', initial['date_end'] - timezone.timedelta(days=365) + ) super().__init__(*args, initial=initial, **kwargs) @@ -26,7 +28,9 @@ def clean(self): date_start = cleaned_data.get('date_start') date_end = cleaned_data.get('date_end') if date_start and date_end and date_start > date_end: - raise ValidationError("The end date is further in the past than the starting date") + raise ValidationError( + "The end date is further in the past than the starting date" + ) return cleaned_data @@ -43,12 +47,15 @@ class ConcurrenflictFormMixin: def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields[self.concurrenflict_field_name] = forms.CharField(widget=forms.HiddenInput, label="", - required=False) + self.fields[self.concurrenflict_field_name] = forms.CharField( + widget=forms.HiddenInput, label="", required=False + ) instance = kwargs.get('instance', None) if instance: self._concurrenflict_json_data = serializers.serialize('json', [instance]) - self.fields[self.concurrenflict_field_name].initial = self._concurrenflict_json_data + self.fields[ + self.concurrenflict_field_name + ].initial = self._concurrenflict_json_data def clean(self): # noqa: C901 # This function is too complex and ugly, should just get rid of it @@ -58,13 +65,14 @@ def clean(self): # noqa: C901 json_at_post = self._concurrenflict_json_data # we want to keep using the initial data set in __init__() self.data = self.data.copy() - self.data[self.add_prefix(self.concurrenflict_field_name)] = self._concurrenflict_json_data + self.data[ + self.add_prefix(self.concurrenflict_field_name) + ] = self._concurrenflict_json_data have_diff = False # if json_at_post is None then this is an add() rather than a change(), so # there's no old record that could have changed while this one was being worked on if json_at_post and json_at_get and (json_at_post != json_at_get): - json_data_before = json.loads(json_at_get) json_data_after = json.loads(json_at_post) @@ -105,12 +113,16 @@ def clean(self): # noqa: C901 # ''' % {'html_name': fake_form[key].html_name} if key in m2m_after: - value_after_string = ', '.join([str(v) for v in value_after.all()]) + value_after_string = ', '.join( + [str(v) for v in value_after.all()] + ) else: value_after_string = str(value_after) # temp_field = fake_form[key] msg = mark_safe( - u'This field has been changed by someone else to: %s' % (value_after_string,)) + u'This field has been changed by someone else to: %s' + % (value_after_string,) + ) self.add_error(key, msg) # These fields are no longer valid. Remove them from the @@ -119,6 +131,9 @@ def clean(self): # noqa: C901 del cleaned_data[key] if have_diff: - self.add_error(NON_FIELD_ERRORS, "The data has been changed by someone else since you started editing it") + self.add_error( + NON_FIELD_ERRORS, + "The data has been changed by someone else since you started editing it", + ) return cleaned_data diff --git a/general/mail_control.py b/general/mail_control.py index 345a29d2..483e1974 100644 --- a/general/mail_control.py +++ b/general/mail_control.py @@ -9,7 +9,9 @@ from userdetails.models import User -def get_mail_context(recipient: User, extra_context: dict = None, request: HttpRequest = None): +def get_mail_context( + recipient: User, extra_context: dict = None, request: HttpRequest = None +): """Creates the context used in mail templates.""" # This is how Django does it with their password reset email current_site = get_current_site(request) @@ -25,7 +27,9 @@ def get_mail_context(recipient: User, extra_context: dict = None, request: HttpR } -def construct_templated_mail(template_dir: str, recipients, context: dict = None, request=None) -> List[EmailMessage]: +def construct_templated_mail( + template_dir: str, recipients, context: dict = None, request=None +) -> List[EmailMessage]: """Constructs email messages. See send_templated_mail() for an explanation of the arguments. @@ -37,17 +41,27 @@ def construct_templated_mail(template_dir: str, recipients, context: dict = None for recipient in recipients: # Render templates local_context = get_mail_context(recipient, context, request) - subject = render_to_string(template_dir + '/subject.txt', context=local_context, request=request).strip() - html_body = render_to_string(template_dir + '/body.html', context=local_context, request=request) - text_body = render_to_string(template_dir + '/body.txt', context=local_context, request=request) + subject = render_to_string( + template_dir + '/subject.txt', context=local_context, request=request + ).strip() + html_body = render_to_string( + template_dir + '/body.html', context=local_context, request=request + ) + text_body = render_to_string( + template_dir + '/body.txt', context=local_context, request=request + ) # Create message - message = EmailMultiAlternatives(subject=subject, body=text_body, to=[recipient.email]) + message = EmailMultiAlternatives( + subject=subject, body=text_body, to=[recipient.email] + ) message.attach_alternative(html_body, 'text/html') messages.append(message) return messages -def send_templated_mail(template_dir: str, recipients, context: dict = None, request=None): +def send_templated_mail( + template_dir: str, recipients, context: dict = None, request=None +): """Sends a mail using a template. Args: diff --git a/general/migrations/0001_initial.py b/general/migrations/0001_initial.py index f17c6a25..b7989c45 100644 --- a/general/migrations/0001_initial.py +++ b/general/migrations/0001_initial.py @@ -4,19 +4,30 @@ class Migration(migrations.Migration): - initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( name='SiteUpdate', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), ('date', models.DateField(auto_now_add=True, unique=True)), - ('version', models.CharField(help_text='The current version', max_length=16, unique=True)), + ( + 'version', + models.CharField( + help_text='The current version', max_length=16, unique=True + ), + ), ('title', models.CharField(max_length=140, unique=True)), ('message', models.TextField()), ], diff --git a/general/migrations/0002_auto_20190227_1203.py b/general/migrations/0002_auto_20190227_1203.py index 0b2919a2..4b92ad3c 100644 --- a/general/migrations/0002_auto_20190227_1203.py +++ b/general/migrations/0002_auto_20190227_1203.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('general', '0001_initial'), @@ -16,10 +15,24 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PageVisitTracker', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), ('timestamp', models.DateTimeField(auto_now_add=True)), ('page', models.IntegerField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + 'user', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ 'abstract': False, diff --git a/general/migrations/0003_remove_siteupdate_version.py b/general/migrations/0003_remove_siteupdate_version.py index 77991371..1b202733 100644 --- a/general/migrations/0003_remove_siteupdate_version.py +++ b/general/migrations/0003_remove_siteupdate_version.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('general', '0002_auto_20190227_1203'), ] diff --git a/general/models.py b/general/models.py index c4f1652c..8cf24a1b 100644 --- a/general/models.py +++ b/general/models.py @@ -5,12 +5,15 @@ class SiteUpdate(models.Model): """Contains setting related to the dining lists and use of the dining lists.""" + date = models.DateTimeField(auto_now_add=True, unique=True) title = models.CharField(max_length=140, unique=True) message = models.TextField() def __str__(self): - return "{date}: {title}".format(date=self.date.strftime("%Y-%m-%d"), title=self.title) + return "{date}: {title}".format( + date=self.date.strftime("%Y-%m-%d"), title=self.title + ) # I've removed the 'mail_users' method on purpose, we should not have that @@ -47,10 +50,14 @@ def get_latest_visit(cls, page_name, user, update=False): :param user: The user visiting the page """ if update: - latest_visit_obj, created = cls.objects.get_or_create(user=user, page=cls.__get_page_int__(page_name)) + latest_visit_obj, created = cls.objects.get_or_create( + user=user, page=cls.__get_page_int__(page_name) + ) else: try: - latest_visit_obj = cls.objects.get(user=user, page=cls.__get_page_int__(page_name)) + latest_visit_obj = cls.objects.get( + user=user, page=cls.__get_page_int__(page_name) + ) except cls.DoesNotExist: return None diff --git a/general/urls.py b/general/urls.py index b4e78994..3c28aee6 100644 --- a/general/urls.py +++ b/general/urls.py @@ -5,6 +5,10 @@ path('updates/', views.SiteUpdateView.as_view(), name='site_updates'), path('help/', views.HelpPageView.as_view(), name='help_page'), path('rules/', views.RulesPageView.as_view(), name='rules_and_regulations'), - path('upgrade_instructions/', views.UpgradeBalanceInstructionsView.as_view(), name='upgrade_instructions'), - path('mail_layout/', views.EmailTemplateView.as_view()) + path( + 'upgrade_instructions/', + views.UpgradeBalanceInstructionsView.as_view(), + name='upgrade_instructions', + ), + path('mail_layout/', views.EmailTemplateView.as_view()), ] diff --git a/general/util.py b/general/util.py index fa96f0c2..5cfce6e5 100644 --- a/general/util.py +++ b/general/util.py @@ -32,7 +32,9 @@ def optgroups(self, name, value, attrs=None): # Append disabled choices as singleton groups index = len(groups) for option_value, option_label in self.disabled_choices: - option = self.create_option(name, option_value, option_label, False, index, attrs=attrs) + option = self.create_option( + name, option_value, option_label, False, index, attrs=attrs + ) # Modify option so that it is disabled option['attrs']['disabled'] = 'disabled' # Add option to group diff --git a/general/views.py b/general/views.py index 2b3fb377..72318b8a 100644 --- a/general/views.py +++ b/general/views.py @@ -55,7 +55,9 @@ def get_context_data(self, **kwargs): except ObjectDoesNotExist: latest_update = timezone.now() - context['latest_visit'] = PageVisitTracker.get_latest_visit('updates', self.request.user, update=True) + context['latest_visit'] = PageVisitTracker.get_latest_visit( + 'updates', self.request.user, update=True + ) context['latest_update'] = latest_update return context @@ -79,10 +81,12 @@ def get_context_data(self, **kwargs): build_date = getenv('BUILD_TIMESTAMP') if build_date: build_date = datetime.fromtimestamp(float(build_date), timezone.utc) - context.update({ - 'build_date': build_date, - 'commit_sha': getenv('COMMIT_SHA'), - }) + context.update( + { + 'build_date': build_date, + 'commit_sha': getenv('COMMIT_SHA'), + } + ) return context @@ -94,7 +98,9 @@ class RulesPageView(View): def get(self, request): # Store the recent updates/visit data in the local context if request.user.is_authenticated: - self.context['latest_visit'] = PageVisitTracker.get_latest_visit('rules', request.user, update=True) + self.context['latest_visit'] = PageVisitTracker.get_latest_visit( + 'rules', request.user, update=True + ) self.context['latest_update'] = self.change_date return render(request, self.template, self.context) @@ -117,9 +123,12 @@ def get_context_data(self, **kwargs): if self.request.user.is_authenticated: # Separated for a possible prefilter to be implemented later (e.g. if active in kitchen) associations = Association.objects.order_by('slug') - context['user_associations'] = associations.filter(usermembership__related_user=self.request.user) - context['other_associations'] = associations. \ - exclude(id__in=context['user_associations'].values_list('id', flat=True)) + context['user_associations'] = associations.filter( + usermembership__related_user=self.request.user + ) + context['other_associations'] = associations.exclude( + id__in=context['user_associations'].values_list('id', flat=True) + ) else: context['other_associations'] = Association.objects.all() @@ -153,7 +162,9 @@ def __getattr__(self, key): def __getitem__(self, key): item = self._dict.get(key, None) if item is None: - return EmailTemplateView.create_new_factory(name="{name}.{key}".format(name=self._name, key=key)) + return EmailTemplateView.create_new_factory( + name="{name}.{key}".format(name=self._name, key=key) + ) else: return item diff --git a/scaladining/admin.py b/scaladining/admin.py index b283a385..6abfd04e 100644 --- a/scaladining/admin.py +++ b/scaladining/admin.py @@ -3,6 +3,7 @@ class MyAdminSite(AdminSite): """Custom admin site for some minor branding.""" + site_header = "Scala app administration" # site_title = "Scala app admin panel" diff --git a/scaladining/context_processors.py b/scaladining/context_processors.py index a9afad78..a40106f7 100644 --- a/scaladining/context_processors.py +++ b/scaladining/context_processors.py @@ -3,4 +3,6 @@ def scala(request): """Adds some variables to every template context.""" - return {'MINIMUM_BALANCE_FOR_DINING_SIGN_UP': settings.MINIMUM_BALANCE_FOR_DINING_SIGN_UP} + return { + 'MINIMUM_BALANCE_FOR_DINING_SIGN_UP': settings.MINIMUM_BALANCE_FOR_DINING_SIGN_UP + } diff --git a/scaladining/settings.py b/scaladining/settings.py index 9703f62b..1cd19b08 100644 --- a/scaladining/settings.py +++ b/scaladining/settings.py @@ -27,7 +27,9 @@ def file_parser(value): DEBUG = env.bool('DINING_DEBUG', default=False) -SECRET_KEY = env.str('DINING_SECRET_KEY', default='') or env.file('DINING_SECRET_KEY_FILE') +SECRET_KEY = env.str('DINING_SECRET_KEY', default='') or env.file( + 'DINING_SECRET_KEY_FILE' +) ALLOWED_HOSTS = env.list('DINING_ALLOWED_HOSTS', default='') @@ -36,7 +38,6 @@ def file_parser(value): # django.contrib.admin is replaced by scaladining.apps.MyAdminConfig INSTALLED_APPS = [ 'whitenoise.runserver_nostatic', # For static files - 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -44,15 +45,12 @@ def file_parser(value): 'django.contrib.staticfiles', 'django.contrib.humanize', 'django.contrib.sites', - 'dining.apps.DiningConfig', 'creditmanagement.apps.CreditManagementConfig', 'general.apps.GeneralConfig', 'scaladining.apps.MyAdminConfig', - 'allauth.account', # This needs to be before userdetails due to admin.site.unregister 'userdetails.apps.UserDetailsConfig', - 'dal', 'dal_select2', 'widget_tweaks', @@ -87,7 +85,7 @@ def file_parser(value): 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - 'scaladining.context_processors.scala' + 'scaladining.context_processors.scala', ], }, }, @@ -109,9 +107,13 @@ def file_parser(value): MEDIA_ROOT = env.str('DINING_MEDIA_ROOT', default=os.path.join(BASE_DIR, 'uploads')) MEDIA_URL = env.str('DINING_MEDIA_URL', default='/media/') -DATABASES = {'default': env.dj_db_url('DINING_DATABASE_URL', default='sqlite:///db.sqlite3')} +DATABASES = { + 'default': env.dj_db_url('DINING_DATABASE_URL', default='sqlite:///db.sqlite3') +} if not DATABASES['default'].get('PASSWORD'): - DATABASES['default']['PASSWORD'] = env.file('DINING_DATABASE_PASSWORD_FILE', default='') + DATABASES['default']['PASSWORD'] = env.file( + 'DINING_DATABASE_PASSWORD_FILE', default='' + ) AUTH_PASSWORD_VALIDATORS = [ { @@ -156,7 +158,9 @@ def file_parser(value): EMAIL_HOST = email_config['EMAIL_HOST'] EMAIL_PORT = email_config['EMAIL_PORT'] EMAIL_HOST_USER = email_config['EMAIL_HOST_USER'] -EMAIL_HOST_PASSWORD = email_config['EMAIL_HOST_PASSWORD'] or env.file('DINING_EMAIL_PASSWORD_FILE', default='') +EMAIL_HOST_PASSWORD = email_config['EMAIL_HOST_PASSWORD'] or env.file( + 'DINING_EMAIL_PASSWORD_FILE', default='' +) EMAIL_BACKEND = email_config['EMAIL_BACKEND'] EMAIL_USE_TLS = email_config['EMAIL_USE_TLS'] EMAIL_USE_SSL = email_config['EMAIL_USE_SSL'] diff --git a/userdetails/admin.py b/userdetails/admin.py index 654f6789..cee3930c 100644 --- a/userdetails/admin.py +++ b/userdetails/admin.py @@ -9,12 +9,14 @@ class AssociationLinks(admin.TabularInline): """Membership inline.""" + model = UserMembership extra = 0 class MemberOfFilter(admin.SimpleListFilter): """Creates a filter that filters users on the association they are part of (unvalidated).""" + # Human-readable title which will be displayed in the # right admin sidebar just above the filter options. title = 'Member of association' @@ -24,7 +26,10 @@ class MemberOfFilter(admin.SimpleListFilter): def lookups(self, request, model_admin): """Returns a list of tuples representing all the associations as displayed in the table.""" - return Association.objects.all().values_list('pk', 'name', ) + return Association.objects.all().values_list( + 'pk', + 'name', + ) def queryset(self, request, queryset): """Returns the filtered querysets containing all members of the selected associations.""" @@ -32,7 +37,9 @@ def queryset(self, request, queryset): return queryset # Find all members in the UserMemberships model containing the selected association - a = UserMembership.objects.filter(association=self.value()).values_list('related_user_id') + a = UserMembership.objects.filter(association=self.value()).values_list( + 'related_user_id' + ) return queryset.filter(pk__in=a) @@ -67,6 +74,7 @@ class GroupAdminForm(forms.ModelForm): (As opposed to Djangos standard location in the user page.) """ + users = forms.ModelMultipleChoiceField( User.objects.all(), widget=admin.widgets.FilteredSelectMultiple('Users', False), diff --git a/userdetails/externalaccounts.py b/userdetails/externalaccounts.py index 22e374ac..e27d22cc 100644 --- a/userdetails/externalaccounts.py +++ b/userdetails/externalaccounts.py @@ -18,7 +18,9 @@ def _create_membership(socialaccount, request): if not linked_associations: warnings.warn('No associations linked to the external account') for association in linked_associations: - membership = UserMembership.objects.filter(related_user=user, association=association).first() + membership = UserMembership.objects.filter( + related_user=user, association=association + ).first() if membership: # There exists a membership already, verify it if needed if not membership.is_verified: @@ -26,8 +28,12 @@ def _create_membership(socialaccount, request): membership.verified_on = timezone.now() membership.save() else: - UserMembership.objects.create(related_user=user, association=association, is_verified=True, - verified_on=timezone.now()) + UserMembership.objects.create( + related_user=user, + association=association, + is_verified=True, + verified_on=timezone.now(), + ) @receiver(user_signed_up) diff --git a/userdetails/forms.py b/userdetails/forms.py index bb8d1ac4..2cab1127 100644 --- a/userdetails/forms.py +++ b/userdetails/forms.py @@ -16,13 +16,23 @@ class RegisterUserForm(UserCreationForm): # By default, this field is required. associations = forms.ModelMultipleChoiceField( queryset=Association.objects.filter(is_choosable=True), - widget=forms.CheckboxSelectMultiple() + widget=forms.CheckboxSelectMultiple(), ) class Meta: model = User - fields = ('username', 'password1', 'password2', 'email', 'first_name', 'last_name', 'allergies') - field_classes = {'username': UsernameField} # This adds HTML attributes for semantics, see UserCreationForm. + fields = ( + 'username', + 'password1', + 'password2', + 'email', + 'first_name', + 'last_name', + 'allergies', + ) + field_classes = { + 'username': UsernameField + } # This adds HTML attributes for semantics, see UserCreationForm. def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -44,7 +54,9 @@ def save(self, commit=True): with transaction.atomic(): user.save() for association in self.cleaned_data['associations']: - UserMembership.objects.create(related_user=user, association=association) + UserMembership.objects.create( + related_user=user, association=association + ) return user @@ -59,7 +71,9 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['name'].disabled = True self.fields['name'].initial = str(self.instance) - self.fields['name'].help_text = "Contact the site administrator if you want to change your name." + self.fields[ + 'name' + ].help_text = "Contact the site administrator if you want to change your name." self.fields['email'].disabled = True self.fields['email'].required = False # To hide the asterisk. @@ -68,7 +82,6 @@ def __init__(self, *args, **kwargs): class AssociationLinkForm(forms.Form): - def __init__(self, user, *args, **kwargs): super().__init__(*args, **kwargs) self.user = user @@ -78,14 +91,20 @@ def __init__(self, user, *args, **kwargs): # 'distinct' is necessary because otherwise there will be a large # number of duplicate associations returned, resulting in a slow page # load. - associations = Association.objects.filter( - Q(is_choosable=True) | Q(usermembership__related_user=user) - ).distinct().order_by('slug') + associations = ( + Association.objects.filter( + Q(is_choosable=True) | Q(usermembership__related_user=user) + ) + .distinct() + .order_by('slug') + ) for association in associations: # Find membership. try: - membership = UserMembership.objects.get(related_user=user, association=association) + membership = UserMembership.objects.get( + related_user=user, association=association + ) except UserMembership.DoesNotExist: membership = None @@ -126,7 +145,9 @@ def save(self): # Selected but no membership exists, we need to create it. if chosen and not membership: - UserMembership.objects.create(related_user=self.user, association=association) + UserMembership.objects.create( + related_user=self.user, association=association + ) # Selected but the membership was rejected, set as pending. # diff --git a/userdetails/migrations/0001_initial.py b/userdetails/migrations/0001_initial.py index e20dd5cd..8fdbafdd 100644 --- a/userdetails/migrations/0001_initial.py +++ b/userdetails/migrations/0001_initial.py @@ -9,7 +9,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ @@ -20,8 +19,19 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserMembership', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_on', models.DateField(auto_created=True, blank=True, null=True)), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), + ( + 'created_on', + models.DateField(auto_created=True, blank=True, null=True), + ), ('is_verified', models.BooleanField(default=False)), ('verified_on', models.DateField(blank=True, default=None, null=True)), ], @@ -29,17 +39,85 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ( + 'id', + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name='ID', + ), + ), ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ( + 'last_login', + models.DateTimeField( + blank=True, null=True, verbose_name='last login' + ), + ), + ( + 'is_superuser', + models.BooleanField( + default=False, + help_text='Designates that this user has all permissions without explicitly assigning them.', + verbose_name='superuser status', + ), + ), + ( + 'username', + models.CharField( + error_messages={ + 'unique': 'A user with that username already exists.' + }, + help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name='username', + ), + ), + ( + 'first_name', + models.CharField( + blank=True, max_length=30, verbose_name='first name' + ), + ), + ( + 'last_name', + models.CharField( + blank=True, max_length=150, verbose_name='last name' + ), + ), + ( + 'email', + models.EmailField( + blank=True, max_length=254, verbose_name='email address' + ), + ), + ( + 'is_staff', + models.BooleanField( + default=False, + help_text='Designates whether the user can log into this admin site.', + verbose_name='staff status', + ), + ), + ( + 'is_active', + models.BooleanField( + default=True, + help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', + verbose_name='active', + ), + ), + ( + 'date_joined', + models.DateTimeField( + default=django.utils.timezone.now, verbose_name='date joined' + ), + ), ], options={ 'verbose_name': 'user', @@ -52,8 +130,7 @@ class Migration(migrations.Migration): ), migrations.CreateModel( name='Association', - fields=[ - ], + fields=[], options={ 'proxy': True, 'indexes': [], @@ -66,7 +143,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='AssociationDetails', fields=[ - ('association', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='userdetails.Association')), + ( + 'association', + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + primary_key=True, + serialize=False, + to='userdetails.Association', + ), + ), ('image', models.ImageField(upload_to='')), ('shorthand', models.SlugField(max_length=10)), ], @@ -74,28 +159,56 @@ class Migration(migrations.Migration): migrations.CreateModel( name='UserDetail', fields=[ - ('related_user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='details', serialize=False, to=settings.AUTH_USER_MODEL)), + ( + 'related_user', + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + primary_key=True, + related_name='details', + serialize=False, + to=settings.AUTH_USER_MODEL, + ), + ), ('phone_number', models.CharField(blank=True, max_length=15)), ], ), migrations.AddField( model_name='user', name='groups', - field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), + field=models.ManyToManyField( + blank=True, + help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', + related_name='user_set', + related_query_name='user', + to='auth.Group', + verbose_name='groups', + ), ), migrations.AddField( model_name='user', name='user_permissions', - field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), + field=models.ManyToManyField( + blank=True, + help_text='Specific permissions for this user.', + related_name='user_set', + related_query_name='user', + to='auth.Permission', + verbose_name='user permissions', + ), ), migrations.AddField( model_name='usermembership', name='association', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='userdetails.Association'), + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='userdetails.Association', + ), ), migrations.AddField( model_name='usermembership', name='related_user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/userdetails/migrations/0002_auto_20190203_2324.py b/userdetails/migrations/0002_auto_20190203_2324.py index 8c52d71f..d7fce6fb 100644 --- a/userdetails/migrations/0002_auto_20190203_2324.py +++ b/userdetails/migrations/0002_auto_20190203_2324.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ ('auth', '0009_alter_user_last_name_max_length'), ('userdetails', '0001_initial'), @@ -23,7 +22,17 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Association', fields=[ - ('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.Group')), + ( + 'group_ptr', + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to='auth.Group', + ), + ), ('slug', models.SlugField(max_length=10)), ('image', models.ImageField(blank=True, upload_to='')), ], @@ -38,11 +47,21 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='User', name='groups', - field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'), + field=models.ManyToManyField( + blank=True, + help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', + related_name='user_set', + related_query_name='user', + to='auth.Group', + verbose_name='groups', + ), ), migrations.AlterField( model_name='UserMembership', name='association', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='userdetails.Association'), + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to='userdetails.Association', + ), ), ] diff --git a/userdetails/migrations/0003_auto_20190204_0025.py b/userdetails/migrations/0003_auto_20190204_0025.py index 142a77aa..f95c4495 100644 --- a/userdetails/migrations/0003_auto_20190204_0025.py +++ b/userdetails/migrations/0003_auto_20190204_0025.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0002_auto_20190203_2324'), ] diff --git a/userdetails/migrations/0004_user_external_link.py b/userdetails/migrations/0004_user_external_link.py index f0b89895..7594b1cf 100644 --- a/userdetails/migrations/0004_user_external_link.py +++ b/userdetails/migrations/0004_user_external_link.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0003_auto_20190204_0025'), ] diff --git a/userdetails/migrations/0005_auto_20190207_1510.py b/userdetails/migrations/0005_auto_20190207_1510.py index 6f7248a2..d4bdd3fa 100644 --- a/userdetails/migrations/0005_auto_20190207_1510.py +++ b/userdetails/migrations/0005_auto_20190207_1510.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0004_user_external_link'), ] diff --git a/userdetails/migrations/0006_auto_20190207_1744.py b/userdetails/migrations/0006_auto_20190207_1744.py index 1b3978a7..c0f064a5 100644 --- a/userdetails/migrations/0006_auto_20190207_1744.py +++ b/userdetails/migrations/0006_auto_20190207_1744.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0005_auto_20190207_1510'), ] @@ -13,6 +12,11 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='external_link', - field=models.CharField(default='', editable=False, help_text='When this is set, the account is linked to an external system.', max_length=150), + field=models.CharField( + default='', + editable=False, + help_text='When this is set, the account is linked to an external system.', + max_length=150, + ), ), ] diff --git a/userdetails/migrations/0007_association_is_choosable.py b/userdetails/migrations/0007_association_is_choosable.py index a9e97c84..3738be24 100644 --- a/userdetails/migrations/0007_association_is_choosable.py +++ b/userdetails/migrations/0007_association_is_choosable.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0006_auto_20190207_1744'), ] @@ -13,6 +12,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='association', name='is_choosable', - field=models.BooleanField(default=True, verbose_name='Whether this association can be chosen as membership by users'), + field=models.BooleanField( + default=True, + verbose_name='Whether this association can be chosen as membership by users', + ), ), ] diff --git a/userdetails/migrations/0008_remove_user_is_staff.py b/userdetails/migrations/0008_remove_user_is_staff.py index 87d638ef..d100fa82 100644 --- a/userdetails/migrations/0008_remove_user_is_staff.py +++ b/userdetails/migrations/0008_remove_user_is_staff.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0007_association_is_choosable'), ] diff --git a/userdetails/migrations/0009_user_is_staff.py b/userdetails/migrations/0009_user_is_staff.py index 57d7f437..4a70be19 100644 --- a/userdetails/migrations/0009_user_is_staff.py +++ b/userdetails/migrations/0009_user_is_staff.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0008_remove_user_is_staff'), ] @@ -13,6 +12,10 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='is_staff', - field=models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status'), + field=models.BooleanField( + default=False, + help_text='Designates whether the user can log into this admin site.', + verbose_name='staff status', + ), ), ] diff --git a/userdetails/migrations/0010_auto_20190304_2050.py b/userdetails/migrations/0010_auto_20190304_2050.py index 12a8ab5b..9bded12b 100644 --- a/userdetails/migrations/0010_auto_20190304_2050.py +++ b/userdetails/migrations/0010_auto_20190304_2050.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0009_user_is_staff'), ] @@ -13,6 +12,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='email', - field=models.EmailField(max_length=254, unique=True, verbose_name='email address'), + field=models.EmailField( + max_length=254, unique=True, verbose_name='email address' + ), ), ] diff --git a/userdetails/migrations/0011_auto_20190429_1303.py b/userdetails/migrations/0011_auto_20190429_1303.py index ec959b7a..df2cb819 100644 --- a/userdetails/migrations/0011_auto_20190429_1303.py +++ b/userdetails/migrations/0011_auto_20190429_1303.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0010_auto_20190304_2050'), ] @@ -13,8 +12,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( name='UserOverview', - fields=[ - ], + fields=[], options={ 'proxy': True, 'indexes': [], diff --git a/userdetails/migrations/0012_auto_20190429_1306.py b/userdetails/migrations/0012_auto_20190429_1306.py index a5338c2a..dba9f5f4 100644 --- a/userdetails/migrations/0012_auto_20190429_1306.py +++ b/userdetails/migrations/0012_auto_20190429_1306.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0011_auto_20190429_1303'), ] @@ -17,6 +16,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name='association', name='has_min_exception', - field=models.BooleanField(default=False, verbose_name='Whether this association has an exception to the minimum balance'), + field=models.BooleanField( + default=False, + verbose_name='Whether this association has an exception to the minimum balance', + ), ), ] diff --git a/userdetails/migrations/0013_auto_20190429_2232.py b/userdetails/migrations/0013_auto_20190429_2232.py index a5098d05..85098057 100644 --- a/userdetails/migrations/0013_auto_20190429_2232.py +++ b/userdetails/migrations/0013_auto_20190429_2232.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0012_auto_20190429_1306'), ] diff --git a/userdetails/migrations/0014_remove_user_external_link.py b/userdetails/migrations/0014_remove_user_external_link.py index 9f9220f4..75c71e1f 100644 --- a/userdetails/migrations/0014_remove_user_external_link.py +++ b/userdetails/migrations/0014_remove_user_external_link.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0013_auto_20190429_2232'), ] diff --git a/userdetails/migrations/0015_auto_20190508_1905.py b/userdetails/migrations/0015_auto_20190508_1905.py index 926fea56..b71d31da 100644 --- a/userdetails/migrations/0015_auto_20190508_1905.py +++ b/userdetails/migrations/0015_auto_20190508_1905.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0014_remove_user_external_link'), ] @@ -13,11 +12,17 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='association', name='has_min_exception', - field=models.BooleanField(default=False, help_text='If checked, this association has an exception to the minimum balance'), + field=models.BooleanField( + default=False, + help_text='If checked, this association has an exception to the minimum balance', + ), ), migrations.AlterField( model_name='association', name='is_choosable', - field=models.BooleanField(default=True, help_text='If checked, this association can be chosen as membership by users'), + field=models.BooleanField( + default=True, + help_text='If checked, this association can be chosen as membership by users', + ), ), ] diff --git a/userdetails/migrations/0016_association_social_app.py b/userdetails/migrations/0016_association_social_app.py index 682bd19b..4990bd64 100644 --- a/userdetails/migrations/0016_association_social_app.py +++ b/userdetails/migrations/0016_association_social_app.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('socialaccount', '0003_extra_data_default_dict'), ('userdetails', '0015_auto_20190508_1905'), @@ -15,6 +14,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='association', name='social_app', - field=models.ForeignKey(blank=True, help_text='A user automatically becomes member of the association if she signs up using this social app', null=True, on_delete=django.db.models.deletion.PROTECT, to='socialaccount.SocialApp'), + field=models.ForeignKey( + blank=True, + help_text='A user automatically becomes member of the association if she signs up using this social app', + null=True, + on_delete=django.db.models.deletion.PROTECT, + to='socialaccount.SocialApp', + ), ), ] diff --git a/userdetails/migrations/0017_association_balance_update_instructions.py b/userdetails/migrations/0017_association_balance_update_instructions.py index 3f5dfabc..2a1e3d6b 100644 --- a/userdetails/migrations/0017_association_balance_update_instructions.py +++ b/userdetails/migrations/0017_association_balance_update_instructions.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0016_association_social_app'), ] diff --git a/userdetails/migrations/0018_association_has_site_stats_access.py b/userdetails/migrations/0018_association_has_site_stats_access.py index 9c7868a5..bcbd8852 100644 --- a/userdetails/migrations/0018_association_has_site_stats_access.py +++ b/userdetails/migrations/0018_association_has_site_stats_access.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0017_association_balance_update_instructions'), ] diff --git a/userdetails/migrations/0019_auto_20200823_1602.py b/userdetails/migrations/0019_auto_20200823_1602.py index 402fe8d4..cf79812c 100644 --- a/userdetails/migrations/0019_auto_20200823_1602.py +++ b/userdetails/migrations/0019_auto_20200823_1602.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0018_association_has_site_stats_access'), ] @@ -14,16 +13,28 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='association', name='has_min_exception', - field=models.BooleanField(default=False, help_text='If checked, this association has an exception to the minimum balance.'), + field=models.BooleanField( + default=False, + help_text='If checked, this association has an exception to the minimum balance.', + ), ), migrations.AlterField( model_name='association', name='is_choosable', - field=models.BooleanField(default=True, help_text='If checked, this association can be chosen as membership by users.'), + field=models.BooleanField( + default=True, + help_text='If checked, this association can be chosen as membership by users.', + ), ), migrations.AlterField( model_name='association', name='social_app', - field=models.ForeignKey(blank=True, help_text='A user automatically becomes member of the association if they sign up using this social app.', null=True, on_delete=django.db.models.deletion.PROTECT, to='socialaccount.SocialApp'), + field=models.ForeignKey( + blank=True, + help_text='A user automatically becomes member of the association if they sign up using this social app.', + null=True, + on_delete=django.db.models.deletion.PROTECT, + to='socialaccount.SocialApp', + ), ), ] diff --git a/userdetails/migrations/0020_auto_20210319_2310.py b/userdetails/migrations/0020_auto_20210319_2310.py index 5f35e6dc..fbb17e64 100644 --- a/userdetails/migrations/0020_auto_20210319_2310.py +++ b/userdetails/migrations/0020_auto_20210319_2310.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0019_auto_20200823_1602'), ] @@ -13,6 +12,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='user', name='first_name', - field=models.CharField(blank=True, max_length=150, verbose_name='first name'), + field=models.CharField( + blank=True, max_length=150, verbose_name='first name' + ), ), ] diff --git a/userdetails/migrations/0021_auto_20221224_1346.py b/userdetails/migrations/0021_auto_20221224_1346.py index 3cd9f5a2..dfdeefd6 100644 --- a/userdetails/migrations/0021_auto_20221224_1346.py +++ b/userdetails/migrations/0021_auto_20221224_1346.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ('userdetails', '0020_auto_20210319_2310'), ] @@ -26,6 +25,11 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='allergies', - field=models.CharField(blank=True, help_text='E.g. gluten or vegetarian. Leave empty if not applicable.', max_length=100, verbose_name='food allergies or preferences'), + field=models.CharField( + blank=True, + help_text='E.g. gluten or vegetarian. Leave empty if not applicable.', + max_length=100, + verbose_name='food allergies or preferences', + ), ), ] diff --git a/userdetails/models.py b/userdetails/models.py index 46eb801b..d43b6de0 100644 --- a/userdetails/models.py +++ b/userdetails/models.py @@ -25,7 +25,7 @@ class User(AbstractUser): max_length=100, blank=True, help_text="E.g. gluten or vegetarian. Leave empty if not applicable.", - verbose_name="food allergies or preferences" + verbose_name="food allergies or preferences", ) objects = UserManager() @@ -45,12 +45,18 @@ def clean(self): if self.pk: qs = qs.exclude(pk=self.pk) if qs.exists(): - raise ValidationError({ - 'username': ValidationError('A user with that username already exists.', code='unique'), - }) + raise ValidationError( + { + 'username': ValidationError( + 'A user with that username already exists.', code='unique' + ), + } + ) def __str__(self): - return "{} {}".format(self.first_name, self.last_name).strip() or "@{}".format(self.username) + return "{} {}".format(self.first_name, self.last_name).strip() or "@{}".format( + self.username + ) def is_verified(self): """Whether this user is verified as part of a Scala association.""" @@ -77,11 +83,13 @@ def requires_action(self): @cached_property def requires_information_updates(self): from general.views import SiteUpdateView + return SiteUpdateView.has_new_update(self) @cached_property def requires_information_rules(self): from general.views import RulesPageView + return RulesPageView.has_new_update(self) def has_any_perm(self): @@ -112,7 +120,10 @@ def has_min_balance_exception(self): For this to hold, the association membership must be verified. """ - exceptions = [membership.association.has_min_exception for membership in self.get_verified_memberships()] + exceptions = [ + membership.association.has_min_exception + for membership in self.get_verified_memberships() + ] return True in exceptions @@ -126,14 +137,25 @@ class Association(Group): slug = models.SlugField(max_length=10) image = models.ImageField(blank=True, null=True) icon_image = models.ImageField(blank=True, null=True) - is_choosable = models.BooleanField(default=True, - help_text="If checked, this association can be chosen as membership by users.") - has_min_exception = models.BooleanField(default=False, - help_text="If checked, this association has an exception to the minimum balance.") - social_app = models.ForeignKey(SocialApp, on_delete=models.PROTECT, null=True, blank=True, - help_text="A user automatically becomes member of the association " - "if they sign up using this social app.") - balance_update_instructions = models.TextField(max_length=512, default="to be defined") + is_choosable = models.BooleanField( + default=True, + help_text="If checked, this association can be chosen as membership by users.", + ) + has_min_exception = models.BooleanField( + default=False, + help_text="If checked, this association has an exception to the minimum balance.", + ) + social_app = models.ForeignKey( + SocialApp, + on_delete=models.PROTECT, + null=True, + blank=True, + help_text="A user automatically becomes member of the association " + "if they sign up using this social app.", + ) + balance_update_instructions = models.TextField( + max_length=512, default="to be defined" + ) has_site_stats_access = models.BooleanField(default=False) objects = AssociationManager() @@ -147,7 +169,9 @@ def requires_action(self): return self.has_new_member_requests() def has_new_member_requests(self): - return UserMembership.objects.filter(association=self, verified_on__isnull=True).exists() + return UserMembership.objects.filter( + association=self, verified_on__isnull=True + ).exists() class UserMembership(models.Model): @@ -178,7 +202,9 @@ def is_member(self): return True def __str__(self): - return "{user} - {association}".format(user=self.related_user, association=self.association) + return "{user} - {association}".format( + user=self.related_user, association=self.association + ) def set_verified(self, verified): """Sets the verified state to the value of verified (True or False) and set verified_on to now and save.""" diff --git a/userdetails/tests/test_externalaccounts.py b/userdetails/tests/test_externalaccounts.py index 558da38d..791e53cf 100644 --- a/userdetails/tests/test_externalaccounts.py +++ b/userdetails/tests/test_externalaccounts.py @@ -9,10 +9,18 @@ class CreateMembershipTestCase(TestCase): def setUp(self): self.user = User.objects.create_user('ankie', 'ankie@cats.cat') - self.social_app = SocialApp.objects.create(provider='quadrivium', name='ESMG Quadrivium') - self.social_app.sites.add(Site.objects.first()) # Allauth needs a link to a Site - self.social_account = SocialAccount.objects.create(user=self.user, provider='quadrivium') - self.association = Association.objects.create(name='Q', slug='q', social_app=self.social_app) + self.social_app = SocialApp.objects.create( + provider='quadrivium', name='ESMG Quadrivium' + ) + self.social_app.sites.add( + Site.objects.first() + ) # Allauth needs a link to a Site + self.social_account = SocialAccount.objects.create( + user=self.user, provider='quadrivium' + ) + self.association = Association.objects.create( + name='Q', slug='q', social_app=self.social_app + ) self.association_not_linked = Association.objects.create(name='R', slug='r') def test_create_membership(self): @@ -22,7 +30,9 @@ def test_create_membership(self): def test_verify(self): # Create unverified - membership = UserMembership.objects.create(related_user=self.user, association=self.association) + membership = UserMembership.objects.create( + related_user=self.user, association=self.association + ) _create_membership(self.social_account, None) membership.refresh_from_db() self.assertTrue(membership.is_verified) diff --git a/userdetails/tests/test_forms.py b/userdetails/tests/test_forms.py index 567495a1..6a7c8010 100644 --- a/userdetails/tests/test_forms.py +++ b/userdetails/tests/test_forms.py @@ -9,15 +9,17 @@ def test_save_memberships(self): """Tests membership creation during form save.""" a1 = Association.objects.create(name='a1') Association.objects.create(name='a2') - form = RegisterUserForm({ - 'first_name': 'Test', - 'last_name': 'User', - 'username': 'user', - 'email': 'user@localhost', - 'password1': 'yda7yum7MDV0ncw-hmw', - 'password2': 'yda7yum7MDV0ncw-hmw', - 'associations': [a1], - }) + form = RegisterUserForm( + { + 'first_name': 'Test', + 'last_name': 'User', + 'username': 'user', + 'email': 'user@localhost', + 'password1': 'yda7yum7MDV0ncw-hmw', + 'password2': 'yda7yum7MDV0ncw-hmw', + 'associations': [a1], + } + ) self.assertTrue(form.is_valid()) user = form.save() memberships = list(user.usermembership_set.all()) diff --git a/userdetails/tests/test_models.py b/userdetails/tests/test_models.py index 2451ee13..fe7e41c9 100644 --- a/userdetails/tests/test_models.py +++ b/userdetails/tests/test_models.py @@ -30,14 +30,22 @@ def test_has_min_balance_exception_no_membership(self): def test_has_min_balance_exception_false(self): association = Association.objects.create() - UserMembership.objects.create(related_user=self.user, association=association, is_verified=True, - verified_on=timezone.now()) + UserMembership.objects.create( + related_user=self.user, + association=association, + is_verified=True, + verified_on=timezone.now(), + ) self.assertFalse(self.user.has_min_balance_exception()) def test_has_min_balance_exception_true(self): association = Association.objects.create(has_min_exception=True) - UserMembership.objects.create(related_user=self.user, association=association, is_verified=True, - verified_on=timezone.now()) + UserMembership.objects.create( + related_user=self.user, + association=association, + is_verified=True, + verified_on=timezone.now(), + ) self.assertTrue(self.user.has_min_balance_exception()) def test_has_min_balance_exception_unverified_membership(self): @@ -60,14 +68,18 @@ def setUpTestData(cls): cls.association = Association.objects.create() def test_set_verified_true(self): - membership = UserMembership.objects.create(related_user=self.user, association=self.association) + membership = UserMembership.objects.create( + related_user=self.user, association=self.association + ) self.assertIsNone(membership.get_verified_state()) membership.set_verified(True) membership.refresh_from_db() # To check if it's saved self.assertIs(membership.get_verified_state(), True) def test_set_verified_false(self): - membership = UserMembership.objects.create(related_user=self.user, association=self.association) + membership = UserMembership.objects.create( + related_user=self.user, association=self.association + ) self.assertIsNone(membership.get_verified_state()) membership.set_verified(False) membership.refresh_from_db() # To check if it's saved diff --git a/userdetails/urls.py b/userdetails/urls.py index 9f39b680..157d7b86 100644 --- a/userdetails/urls.py +++ b/userdetails/urls.py @@ -1,46 +1,132 @@ from allauth.account.views import LoginView from django.urls import path, include -from userdetails.views import RegisterView, DiningJoinHistoryView, DiningClaimHistoryView, PeopleAutocompleteView -from userdetails.views_association import AssociationTransactionListView, AssociationTransactionsCSVView, \ - MembersOverview, \ - MembersEditView, AssociationOverview, AssociationSettingsView, SiteDiningView, SiteCreditView, \ - AutoCreateNegativeCreditsView, AssociationTransactionAddView, SiteTransactionView, SiteCreditDetailView +from userdetails.views import ( + RegisterView, + DiningJoinHistoryView, + DiningClaimHistoryView, + PeopleAutocompleteView, +) +from userdetails.views_association import ( + AssociationTransactionListView, + AssociationTransactionsCSVView, + MembersOverview, + MembersEditView, + AssociationOverview, + AssociationSettingsView, + SiteDiningView, + SiteCreditView, + AutoCreateNegativeCreditsView, + AssociationTransactionAddView, + SiteTransactionView, + SiteCreditDetailView, +) from userdetails.views_user_settings import SettingsProfileView urlpatterns = [ - path('association//', include([ - path('', AssociationOverview.as_view(), name='association_overview'), - path('transactions/', include([ - path('', AssociationTransactionListView.as_view(), name='association_credits'), - path('process_negatives/', AutoCreateNegativeCreditsView.as_view(), name='association_process_negatives'), - path('csv/', AssociationTransactionsCSVView.as_view(), name='association_transactions_csv'), - path('add/', AssociationTransactionAddView.as_view(), name='association_transaction_add'), - ])), - path('members/', MembersOverview.as_view(), name='association_members'), - path('members/edit/', MembersEditView.as_view(), name='association_members_edit'), - path('settings/', AssociationSettingsView.as_view(), name='association_settings'), - path('site_stats/', include([ - path('dining/', SiteDiningView.as_view(), name='association_site_dining_stats'), - path('credit/', SiteCreditView.as_view(), name='association_site_credit_stats'), - path('credit/add/', SiteTransactionView.as_view(), name='association_site_transaction_add'), - path('credit/account//', SiteCreditDetailView.as_view(), name='association_site_credit_detail'), - ])), - ])), - - path('statistics/', include([ - path('joined/', DiningJoinHistoryView.as_view(), name='history_lists'), - path('joined//', DiningJoinHistoryView.as_view(), name='history_lists'), - path('claimed/', DiningClaimHistoryView.as_view(), name='history_claimed_lists'), - path('claimed//', DiningClaimHistoryView.as_view(), name='history_claimed_lists'), - ])), - + path( + 'association//', + include( + [ + path('', AssociationOverview.as_view(), name='association_overview'), + path( + 'transactions/', + include( + [ + path( + '', + AssociationTransactionListView.as_view(), + name='association_credits', + ), + path( + 'process_negatives/', + AutoCreateNegativeCreditsView.as_view(), + name='association_process_negatives', + ), + path( + 'csv/', + AssociationTransactionsCSVView.as_view(), + name='association_transactions_csv', + ), + path( + 'add/', + AssociationTransactionAddView.as_view(), + name='association_transaction_add', + ), + ] + ), + ), + path('members/', MembersOverview.as_view(), name='association_members'), + path( + 'members/edit/', + MembersEditView.as_view(), + name='association_members_edit', + ), + path( + 'settings/', + AssociationSettingsView.as_view(), + name='association_settings', + ), + path( + 'site_stats/', + include( + [ + path( + 'dining/', + SiteDiningView.as_view(), + name='association_site_dining_stats', + ), + path( + 'credit/', + SiteCreditView.as_view(), + name='association_site_credit_stats', + ), + path( + 'credit/add/', + SiteTransactionView.as_view(), + name='association_site_transaction_add', + ), + path( + 'credit/account//', + SiteCreditDetailView.as_view(), + name='association_site_credit_detail', + ), + ] + ), + ), + ] + ), + ), + path( + 'statistics/', + include( + [ + path('joined/', DiningJoinHistoryView.as_view(), name='history_lists'), + path( + 'joined//', + DiningJoinHistoryView.as_view(), + name='history_lists', + ), + path( + 'claimed/', + DiningClaimHistoryView.as_view(), + name='history_claimed_lists', + ), + path( + 'claimed//', + DiningClaimHistoryView.as_view(), + name='history_claimed_lists', + ), + ] + ), + ), path('settings/', SettingsProfileView.as_view(), name='settings_account'), - # Override allauth login and sign up page with our registration page path('login/', LoginView.as_view(), name='account_login'), path('signup/', RegisterView.as_view(), name='account_signup'), - - path('people-autocomplete/', PeopleAutocompleteView.as_view(), name='people_autocomplete'), - + path( + 'people-autocomplete/', + PeopleAutocompleteView.as_view(), + name='people_autocomplete', + ), ] diff --git a/userdetails/views.py b/userdetails/views.py index 80df2312..ae26fd68 100644 --- a/userdetails/views.py +++ b/userdetails/views.py @@ -28,7 +28,11 @@ class DiningJoinHistoryView(LoginRequiredMixin, ListView): paginate_by = 20 def get_queryset(self): - return DiningEntry.objects.internal().filter(user=self.request.user).order_by('-dining_list__date') + return ( + DiningEntry.objects.internal() + .filter(user=self.request.user) + .order_by('-dining_list__date') + ) class DiningClaimHistoryView(LoginRequiredMixin, ListView): @@ -40,7 +44,6 @@ def get_queryset(self): class PeopleAutocompleteView(LoginRequiredMixin, Select2QuerySetView): - # django-autocomplete-light does infinite scrolling by default, but doesn't seem to trigger when paginate_by has a # lower value (e.g. 5) # paginate_by = 10 @@ -48,9 +51,9 @@ class PeopleAutocompleteView(LoginRequiredMixin, Select2QuerySetView): def get_queryset(self): qs = User.objects.filter(is_active=True) if self.q: - qs = qs.annotate(full_name=Concat('first_name', - Value(' '), - 'last_name')).filter(full_name__icontains=self.q) + qs = qs.annotate( + full_name=Concat('first_name', Value(' '), 'last_name') + ).filter(full_name__icontains=self.q) return qs def get_result_label(self, result): diff --git a/userdetails/views_association.py b/userdetails/views_association.py index e499007a..8c00051d 100644 --- a/userdetails/views_association.py +++ b/userdetails/views_association.py @@ -24,6 +24,7 @@ class AssociationBoardMixin: """Gathers association data and verifies that the user is a board member.""" + association = None def get_context_data(self, **kwargs): @@ -34,7 +35,9 @@ def get_context_data(self, **kwargs): def dispatch(self, request, *args, **kwargs): """Gets association and checks if user is board member.""" - self.association = get_object_or_404(Association, slug=kwargs['association_name']) + self.association = get_object_or_404( + Association, slug=kwargs['association_name'] + ) if not request.user.groups.filter(id=self.association.id): raise PermissionDenied return super().dispatch(request, *args, **kwargs) @@ -48,26 +51,38 @@ def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) -class AssociationTransactionListView(LoginRequiredMixin, AssociationBoardMixin, ListView): +class AssociationTransactionListView( + LoginRequiredMixin, AssociationBoardMixin, ListView +): template_name = "accounts/association_credits.html" paginate_by = 100 def get_queryset(self): - return Transaction.objects.filter_account(self.association.account).order_by('-moment') + return Transaction.objects.filter_account(self.association.account).order_by( + '-moment' + ) -class AssociationTransactionAddView(LoginRequiredMixin, AssociationBoardMixin, TransactionFormView): +class AssociationTransactionAddView( + LoginRequiredMixin, AssociationBoardMixin, TransactionFormView +): """View where an association can transfer money to someone else.""" + template_name = 'accounts/association_credits_transaction.html' def get_source(self) -> Account: return self.association.account def get_success_url(self): - return reverse('association_credits', kwargs={'association_name': self.kwargs.get('association_name')}) + return reverse( + 'association_credits', + kwargs={'association_name': self.kwargs.get('association_name')}, + ) -class AutoCreateNegativeCreditsView(LoginRequiredMixin, AssociationBoardMixin, FormView): +class AutoCreateNegativeCreditsView( + LoginRequiredMixin, AssociationBoardMixin, FormView +): template_name = "accounts/association_correct_negatives.html" form_class = ClearOpenExpensesForm @@ -82,16 +97,22 @@ def get_form_kwargs(self): def form_valid(self, form): form.save() - messages.success(self.request, "Member credits have successfully been processed") + messages.success( + self.request, "Member credits have successfully been processed" + ) return super().form_valid(form) def get_success_url(self): - return reverse('association_credits', kwargs={'association_name': self.association.slug}) + return reverse( + 'association_credits', kwargs={'association_name': self.association.slug} + ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Sum all transaction amounts - context['transactions_sum'] = sum(tx.amount for tx in context['form'].transactions) + context['transactions_sum'] = sum( + tx.amount for tx in context['form'].transactions + ) return context @@ -100,8 +121,12 @@ class AssociationTransactionsCSVView(LoginRequiredMixin, AssociationBoardMixin, def get(self, request, *args, **kwargs): response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="association_transactions.csv"' - qs = Transaction.objects.filter_account(self.association.account).order_by('-moment') + response[ + 'Content-Disposition' + ] = 'attachment; filename="association_transactions.csv"' + qs = Transaction.objects.filter_account(self.association.account).order_by( + '-moment' + ) write_transactions_csv(response, qs, self.association.account) return response @@ -113,7 +138,9 @@ class MembersOverview(LoginRequiredMixin, AssociationBoardMixin, ListView): def get_queryset(self): # We include inactive users who are still a member of the association. return User.objects.filter( - Q(usermembership__association=self.association) & Q(usermembership__is_verified=True)) + Q(usermembership__association=self.association) + & Q(usermembership__is_verified=True) + ) class AssociationOverview(LoginRequiredMixin, AssociationBoardMixin, TemplateView): @@ -121,8 +148,9 @@ class AssociationOverview(LoginRequiredMixin, AssociationBoardMixin, TemplateVie def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['pending_memberships'] = UserMembership.objects.filter(association=self.association, - verified_on__isnull=True) + context['pending_memberships'] = UserMembership.objects.filter( + association=self.association, verified_on__isnull=True + ) return context @@ -131,8 +159,9 @@ class MembersEditView(LoginRequiredMixin, AssociationBoardMixin, ListView): paginate_by = 50 def get_queryset(self): - return UserMembership.objects.filter(Q(association=self.association)).order_by('is_verified', 'verified_on', - 'created_on') + return UserMembership.objects.filter(Q(association=self.association)).order_by( + 'is_verified', 'verified_on', 'created_on' + ) def _alter_state(self, verified, id): """Alter the state of the given user membership. @@ -183,7 +212,9 @@ def post(self, request, association_name=None): if form.is_valid(): form.save() - messages.add_message(request, messages.SUCCESS, "Changes successfully saved.") + messages.add_message( + request, messages.SUCCESS, "Changes successfully saved." + ) return HttpResponseRedirect(request.path_info) context = self.get_context_data() @@ -191,52 +222,69 @@ def post(self, request, association_name=None): return render(request, self.template_name, context) -class SiteDiningView(AssociationBoardMixin, AssociationHasSiteAccessMixin, DateRangeFilterMixin, - TemplateView): +class SiteDiningView( + AssociationBoardMixin, + AssociationHasSiteAccessMixin, + DateRangeFilterMixin, + TemplateView, +): template_name = "accounts/site_dining_stats.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.date_range_form.is_valid(): - dining_lists = DiningList.objects.filter(date__gte=self.date_start, date__lte=self.date_end) + dining_lists = DiningList.objects.filter( + date__gte=self.date_start, date__lte=self.date_end + ) association_stats = {} # Get general data for each association for association in Association.objects.all(): # Some general statistics cooked_for = DiningEntry.objects.filter( - dining_list__association=association, - dining_list__in=dining_lists) - memberships = UserMembership.objects.filter(association=association, is_verified=True) + dining_list__association=association, dining_list__in=dining_lists + ) + memberships = UserMembership.objects.filter( + association=association, is_verified=True + ) members = User.objects.filter(usermembership__in=memberships) cooked_for_own = cooked_for.filter(user__in=members) association_stats[association.id] = { 'association': association, - 'lists_claimed': dining_lists.filter(association=association).count(), + 'lists_claimed': dining_lists.filter( + association=association + ).count(), 'cooked_for': cooked_for.count(), 'cooked_for_own': cooked_for_own.count(), 'weighted_eaters': 0, } # Get general data for all members. Note: this is done here as the length of members is significantly longer # than the number of associations so this should be quicker - users = User.objects.filter(diningentry__dining_list__in=dining_lists).annotate( - dining_entry_count=Count('diningentry')) + users = User.objects.filter( + diningentry__dining_list__in=dining_lists + ).annotate(dining_entry_count=Count('diningentry')) for user in users: - memberships = UserMembership.objects.filter(is_verified=True, related_user=user) + memberships = UserMembership.objects.filter( + is_verified=True, related_user=user + ) if memberships: user_weight = user.dining_entry_count / memberships.count() for membership in memberships: - association_stats[membership.association_id]['weighted_eaters'] += user_weight + association_stats[membership.association_id][ + 'weighted_eaters' + ] += user_weight context['stats'] = association_stats return context -class SiteCreditView(AssociationBoardMixin, AssociationHasSiteAccessMixin, TemplateView): +class SiteCreditView( + AssociationBoardMixin, AssociationHasSiteAccessMixin, TemplateView +): """Shows an overview of the site wide credit details.""" template_name = "accounts/site_credit.html" @@ -250,16 +298,22 @@ def get_context_data(self, **kwargs): return context -class SiteTransactionView(AssociationBoardMixin, AssociationHasSiteAccessMixin, FormView): +class SiteTransactionView( + AssociationBoardMixin, AssociationHasSiteAccessMixin, FormView +): """View that allows creating site-wide transactions with arbitrary source. This is only meant to be used for the highest boss. """ + template_name = 'accounts/site_credit_transaction.html' form_class = SiteWideTransactionForm def get_success_url(self): - return reverse('association_site_credit_stats', kwargs={'association_name': self.kwargs['association_name']}) + return reverse( + 'association_site_credit_stats', + kwargs={'association_name': self.kwargs['association_name']}, + ) def get_form_kwargs(self): kwargs = super().get_form_kwargs() @@ -272,11 +326,17 @@ def form_valid(self, form): return super().form_valid(form) -class SiteCreditDetailView(AssociationBoardMixin, AssociationHasSiteAccessMixin, DateRangeFilterMixin, DetailView): +class SiteCreditDetailView( + AssociationBoardMixin, + AssociationHasSiteAccessMixin, + DateRangeFilterMixin, + DetailView, +): """Shows details for an account. Only allows displaying details for bookkeeping accounts. """ + template_name = 'accounts/site_credit_detail.html' model = Account slug_field = 'special' # Finds the object from the 'slug' URL parameter @@ -295,9 +355,15 @@ def get_context_data(self, **kwargs): # Handle income/outcome flow # We only handle and show the form if we're on page 1 if page_obj.number == 1 and self.date_range_form.is_valid(): - qs = Transaction.objects.filter(moment__gte=self.date_start, moment__lte=self.date_end) - influx = qs.filter(target=account).aggregate(sum=Sum('amount'))['sum'] or Decimal('0.00') - outflux = qs.filter(source=account).aggregate(sum=Sum('amount'))['sum'] or Decimal('0.00') + qs = Transaction.objects.filter( + moment__gte=self.date_start, moment__lte=self.date_end + ) + influx = qs.filter(target=account).aggregate(sum=Sum('amount'))[ + 'sum' + ] or Decimal('0.00') + outflux = qs.filter(source=account).aggregate(sum=Sum('amount'))[ + 'sum' + ] or Decimal('0.00') context['dining_balance'] = { 'influx': influx, diff --git a/userdetails/views_user_settings.py b/userdetails/views_user_settings.py index ae5a0c67..4b198651 100644 --- a/userdetails/views_user_settings.py +++ b/userdetails/views_user_settings.py @@ -11,10 +11,12 @@ class SettingsProfileView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context.update({ - 'form': UserForm(instance=self.request.user), - 'association_links_form': AssociationLinkForm(self.request.user), - }) + context.update( + { + 'form': UserForm(instance=self.request.user), + 'association_links_form': AssociationLinkForm(self.request.user), + } + ) return context def post(self, request, *args, **kwargs): @@ -29,8 +31,10 @@ def post(self, request, *args, **kwargs): # A form was not valid. context = self.get_context_data() - context.update({ - 'form': user_form, - 'association_links_form': membership_form, - }) + context.update( + { + 'form': user_form, + 'association_links_form': membership_form, + } + ) return self.render_to_response(context) diff --git a/utils/testing/form_test_utils.py b/utils/testing/form_test_utils.py index 92a49eb5..88d5d619 100644 --- a/utils/testing/form_test_utils.py +++ b/utils/testing/form_test_utils.py @@ -1,7 +1,9 @@ # flake8: noqa: N802 + class FormValidityMixin: """A mixin for TestCase classes designed to add form functionality.""" + form_class = None def get_form_kwargs(self, **kwargs): @@ -35,7 +37,9 @@ def assertFormValid(self, data, form_class=None, **kwargs): form = self.build_form(data, form_class=form_class, **kwargs) if not form.is_valid(): - fail_message = "The form was not valid. At least one error was encountered: " + fail_message = ( + "The form was not valid. At least one error was encountered: " + ) invalidation_errors = form.errors.as_data() error_key = list(invalidation_errors.keys())[0] @@ -65,8 +69,12 @@ def assertFormHasError(self, data, code, form_class=None, field=None, **kwargs): errors = form.errors.as_data().get(field, []) else: # Each value in form.errors is a list of ValidationError instances. We flatten this list here. - errors = [item for sublist in form.errors.as_data().values() for item in sublist] + errors = [ + item for sublist in form.errors.as_data().values() for item in sublist + ] # Verify that we have an error AND that all errors have the correct code. if not errors or not all((e.code == code for e in errors)): - raise AssertionError(f"Form did not contain an error with code '{code}', with field={field}.") + raise AssertionError( + f"Form did not contain an error with code '{code}', with field={field}." + ) diff --git a/utils/testing/patch_utils.py b/utils/testing/patch_utils.py index 647912e7..c61f7f50 100644 --- a/utils/testing/patch_utils.py +++ b/utils/testing/patch_utils.py @@ -17,7 +17,8 @@ def patch_time(dt=None): """ if dt is not None and not isinstance(dt, datetime.datetime) and callable(dt): raise ValueError( - "Patch time incorrectly called. Make sure you patched through @patch_time() and not @patch_time") + "Patch time incorrectly called. Make sure you patched through @patch_time() and not @patch_time" + ) def wrapper_func(func): def inner(*args, **kwargs): @@ -42,7 +43,6 @@ def adjust_now_time(): class TestPatchMixin: - @staticmethod def assert_has_no_call(mock: Mock, **kwargs): """Asserts that a given mock does not have a call with the (partially) given attributes. @@ -57,7 +57,9 @@ def assert_has_no_call(mock: Mock, **kwargs): except AssertionError: return else: - raise AssertionError(f"At least one undesired call was made with the given attributes: {kwargs}") + raise AssertionError( + f"At least one undesired call was made with the given attributes: {kwargs}" + ) @staticmethod def assert_has_call(mock: Mock, **kwargs): @@ -95,5 +97,7 @@ def assert_has_call(mock: Mock, **kwargs): valid_calls.append({'args': kall[0], **kall[1]}) if not valid_calls: - raise AssertionError(f"No calls were made with the desired attributes: {kwargs}") + raise AssertionError( + f"No calls were made with the desired attributes: {kwargs}" + ) return valid_calls diff --git a/utils/tests/test_form_validation.py b/utils/tests/test_form_validation.py index a8ecb06b..da42efdf 100644 --- a/utils/tests/test_form_validation.py +++ b/utils/tests/test_form_validation.py @@ -8,6 +8,7 @@ class TestFormValidityMixin(FormValidityMixin, TestCase): class TestForm(forms.Form): """Fictive form for Form testing used in TestFormValidityMixin.""" + main_field = forms.CharField(required=False) fake_field = forms.CharField(required=False) @@ -45,46 +46,67 @@ def test_assert_has_field(self): "{field_name} was not a field in {form_class_name}".format( field_name='missing_field', form_class_name='TestForm', - ) + ), ) def test_assert_form_valid(self): # This should not raise an error self.assertFormValid({'main_field': "ok"}) # This should - error = self.raises_assertion_error(self.assertFormValid, {'main_field': "break_field"}) + error = self.raises_assertion_error( + self.assertFormValid, {'main_field': "break_field"} + ) self.assertEqual( error.__str__(), "The form was not valid. At least one error was encountered: '{exception_text}' in '{location}'".format( - exception_text='Test field exception', - location='main_field' - ) + exception_text='Test field exception', location='main_field' + ), ) def test_assert_form_has_error_in_field(self): self.assertFormHasError({'main_field': 'break_field'}, 'invalid_field') - self.assertFormHasError({'main_field': 'break_field'}, 'invalid_field', field='main_field') + self.assertFormHasError( + {'main_field': 'break_field'}, 'invalid_field', field='main_field' + ) # Error is in main_field not fake_field with self.assertRaises(AssertionError): - self.assertFormHasError({'main_field': 'break_field'}, 'invalid_form', field='fake_field') + self.assertFormHasError( + {'main_field': 'break_field'}, 'invalid_form', field='fake_field' + ) # This next data raises an error, just not the one with this code with self.assertRaises(AssertionError): - self.assertFormHasError({'main_field': 'break_field'}, 'invalid_data', field='main_field') + self.assertFormHasError( + {'main_field': 'break_field'}, 'invalid_data', field='main_field' + ) def test_assert_form_has_error_in_form(self): self.assertFormHasError({'main_field': 'break_form'}, 'invalid_form') # Should raise AssertionError because the form contains no errors. - error = self.raises_assertion_error(self.assertFormHasError, {'main_field': 'break_nothing'}, 'invalid_form') + error = self.raises_assertion_error( + self.assertFormHasError, {'main_field': 'break_nothing'}, 'invalid_form' + ) self.assertEqual(error.__str__(), "The form contained no errors") # Error is not in main_field, but elsewhere - error = self.raises_assertion_error(self.assertFormHasError, {'main_field': 'break_form'}, 'invalid_form', - field='main_field') - self.assertEqual(str(error), "Form did not contain an error with code 'invalid_form', with field=main_field.") + error = self.raises_assertion_error( + self.assertFormHasError, + {'main_field': 'break_form'}, + 'invalid_form', + field='main_field', + ) + self.assertEqual( + str(error), + "Form did not contain an error with code 'invalid_form', with field=main_field.", + ) # Error code is not correct, but there is another error - error = self.raises_assertion_error(self.assertFormHasError, {'main_field': 'break_form'}, 'invalid_field') - self.assertEqual(str(error), "Form did not contain an error with code 'invalid_field', with field=None.") + error = self.raises_assertion_error( + self.assertFormHasError, {'main_field': 'break_form'}, 'invalid_field' + ) + self.assertEqual( + str(error), + "Form did not contain an error with code 'invalid_field', with field=None.", + ) From 87ddf24356f15143780b469ad006b3e95273cd7b Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 17:57:10 +0200 Subject: [PATCH 08/14] Change strings --- allauthproviders/quadrivium/provider.py | 26 +- allauthproviders/quadrivium/views.py | 8 +- creditmanagement/admin.py | 46 ++-- creditmanagement/apps.py | 2 +- creditmanagement/csv.py | 28 +-- creditmanagement/forms.py | 68 ++--- creditmanagement/models.py | 26 +- creditmanagement/templatetags/credit_tags.py | 2 +- creditmanagement/tests/test_models.py | 18 +- creditmanagement/urls.py | 8 +- creditmanagement/views.py | 14 +- dining/admin.py | 36 +-- dining/apps.py | 2 +- dining/models.py | 42 ++-- dining/templatetags/dining_tags.py | 8 +- dining/tests/test_forms.py | 252 +++++++++---------- dining/tests/test_forms_diningentry.py | 50 ++-- dining/tests/test_forms_sendreminderform.py | 32 +-- dining/tests/test_models.py | 16 +- dining/urls.py | 38 +-- dining/views.py | 150 +++++------ general/apps.py | 2 +- general/forms.py | 32 +-- general/mail_control.py | 20 +- general/urls.py | 12 +- general/util.py | 2 +- general/views.py | 52 ++-- scaladining/apps.py | 2 +- scaladining/context_processors.py | 2 +- scaladining/scala_settings.py | 6 +- scaladining/settings.py | 178 ++++++------- scaladining/urls.py | 12 +- userdetails/admin.py | 32 +-- userdetails/apps.py | 2 +- userdetails/externalaccounts.py | 6 +- userdetails/forms.py | 46 ++-- userdetails/forms_allauth.py | 10 +- userdetails/models.py | 4 +- userdetails/tests/test_externalaccounts.py | 10 +- userdetails/tests/test_forms.py | 18 +- userdetails/tests/test_models.py | 10 +- userdetails/urls.py | 76 +++--- userdetails/views.py | 10 +- userdetails/views_association.py | 102 ++++---- userdetails/views_user_settings.py | 10 +- utils/testing/__init__.py | 2 +- utils/testing/patch_utils.py | 6 +- utils/tests/test_form_validation.py | 40 +-- 48 files changed, 788 insertions(+), 788 deletions(-) diff --git a/allauthproviders/quadrivium/provider.py b/allauthproviders/quadrivium/provider.py index 8e56ba5a..adfcf506 100644 --- a/allauthproviders/quadrivium/provider.py +++ b/allauthproviders/quadrivium/provider.py @@ -8,40 +8,40 @@ class QuadriviumAccount(ProviderAccount): def to_str(self): # String that is displayed e.g. on the linked accounts page data = self.account.extra_data - first_name = data.get('given_name') - last_name = data.get('family_name') - username = data.get('preferred_username') + first_name = data.get("given_name") + last_name = data.get("family_name") + username = data.get("preferred_username") if not first_name or not last_name or not username: return super().to_str() return "{} {} ({})".format(first_name, last_name, username) class QuadriviumProvider(AssociationProvider): - id = 'quadrivium' - name = 'ESMG Quadrivium' + id = "quadrivium" + name = "ESMG Quadrivium" account_class = QuadriviumAccount - logo = static('images/allauthproviders/quadrivium.svg') + logo = static("images/allauthproviders/quadrivium.svg") def get_scope(self, request): - return ['openid', 'email'] + return ["openid", "email"] def get_auth_params(self, request, action): # Don't know what this does, but I'll leave it in case it does something ret = super().get_auth_params(request, action) if action == AuthAction.REAUTHENTICATE: - ret['prompt'] = 'select_account' + ret["prompt"] = "select_account" return ret def extract_uid(self, data): - return str(data['sub']) + return str(data["sub"]) def extract_common_fields(self, data): return dict( - username=data.get('preferred_username', data.get('given_name')), - email=data.get('email'), - first_name=data.get('given_name'), - last_name=data.get('family_name'), + username=data.get("preferred_username", data.get("given_name")), + email=data.get("email"), + first_name=data.get("given_name"), + last_name=data.get("family_name"), ) diff --git a/allauthproviders/quadrivium/views.py b/allauthproviders/quadrivium/views.py index a1984dbe..311b6d19 100644 --- a/allauthproviders/quadrivium/views.py +++ b/allauthproviders/quadrivium/views.py @@ -11,14 +11,14 @@ class QuadriviumOAuth2Adapter(OAuth2Adapter): provider_id = QuadriviumProvider.id - access_token_url = 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/token' + access_token_url = "https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/token" authorize_url = ( - 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/auth' + "https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/auth" ) - profile_url = 'https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/userinfo' + profile_url = "https://keycloak2.esmgquadrivium.nl/auth/realms/q/protocol/openid-connect/userinfo" def complete_login(self, request, app, token, **kwargs): - auth = {'Authorization': 'Bearer ' + token.token} + auth = {"Authorization": "Bearer " + token.token} resp = requests.get(self.profile_url, headers=auth) resp.raise_for_status() extra_data = resp.json() diff --git a/creditmanagement/admin.py b/creditmanagement/admin.py index 42cd2bde..ca133b7d 100644 --- a/creditmanagement/admin.py +++ b/creditmanagement/admin.py @@ -7,8 +7,8 @@ class AccountTypeListFilter(admin.SimpleListFilter): """Allows filtering on account type which is either user, association or special.""" - title = 'account type' # (displayed in side bar) - parameter_name = 'type' # (used in URL query) + title = "account type" # (displayed in side bar) + parameter_name = "type" # (used in URL query) # Queries can be overridden in a subclass user_query = Q(user__isnull=False) @@ -18,17 +18,17 @@ class AccountTypeListFilter(admin.SimpleListFilter): def lookups(self, request, model_admin): # First element is URL param, second element is display value return ( - ('user', "User"), - ('association', "Association"), - ('special', "Special"), + ("user", "User"), + ("association", "Association"), + ("special", "Special"), ) def queryset(self, request, queryset): - if self.value() == 'user': + if self.value() == "user": return queryset.filter(self.user_query) - if self.value() == 'association': + if self.value() == "association": return queryset.filter(self.association_query) - if self.value() == 'special': + if self.value() == "special": return queryset.filter(self.special_query) @@ -42,15 +42,15 @@ class AccountAdmin(admin.ModelAdmin): you could change the user or association linked to the account. """ - ordering = ('special', 'association__name', 'user__first_name', 'user__last_name') - list_display = ('__str__', 'get_balance', 'negative_since') + ordering = ("special", "association__name", "user__first_name", "user__last_name") + list_display = ("__str__", "get_balance", "negative_since") list_filter = (AccountTypeListFilter,) search_fields = ( - 'user__first_name', - 'user__last_name', - 'user__username', - 'association__name', - 'special', + "user__first_name", + "user__last_name", + "user__username", + "association__name", + "special", ) def has_change_permission(self, request, obj=None): @@ -61,7 +61,7 @@ class SourceTypeListFilter(AccountTypeListFilter): """Allows filtering on the source account type.""" title = "source account type" - parameter_name = 'source_type' + parameter_name = "source_type" user_query = Q(source__user__isnull=False) association_query = Q(source__association__isnull=False) special_query = Q(source__special__isnull=False) @@ -71,7 +71,7 @@ class TargetTypeListFilter(AccountTypeListFilter): """Allows filtering on the target account type.""" title = "target account type" - parameter_name = 'target_type' + parameter_name = "target_type" user_query = Q(target__user__isnull=False) association_query = Q(target__association__isnull=False) special_query = Q(target__special__isnull=False) @@ -81,16 +81,16 @@ class TargetTypeListFilter(AccountTypeListFilter): class TransactionAdmin(admin.ModelAdmin): """The transaction admin enables viewing transactions and creating new transactions.""" - ordering = ('-moment',) - list_display = ('moment', 'source', 'target', 'amount', 'description') + ordering = ("-moment",) + list_display = ("moment", "source", "target", "amount", "description") list_filter = (SourceTypeListFilter, TargetTypeListFilter) - fields = ('source', 'target', 'amount', 'moment', 'description', 'created_by') + fields = ("source", "target", "amount", "moment", "description", "created_by") readonly_fields = ( - 'moment', - 'created_by', + "moment", + "created_by", ) # Only applicable for the add transaction form (changing is not allowed) - autocomplete_fields = ('source', 'target') + autocomplete_fields = ("source", "target") def has_change_permission(self, request, obj=None): return False diff --git a/creditmanagement/apps.py b/creditmanagement/apps.py index be17d564..fc9ca8ef 100644 --- a/creditmanagement/apps.py +++ b/creditmanagement/apps.py @@ -2,7 +2,7 @@ class CreditManagementConfig(AppConfig): - name = 'creditmanagement' + name = "creditmanagement" def ready(self): # noinspection PyUnresolvedReferences diff --git a/creditmanagement/csv.py b/creditmanagement/csv.py index bd3733fa..ac91fced 100644 --- a/creditmanagement/csv.py +++ b/creditmanagement/csv.py @@ -21,24 +21,24 @@ def write_transactions_csv( csv_writer = csv.writer(csv_file) csv_writer.writerow( [ - 'date', - 'direction', - 'account_type', - 'name', - 'email', - 'amount', - 'description', - 'created_by', + "date", + "direction", + "account_type", + "name", + "email", + "amount", + "description", + "created_by", ] ) for t in transactions: # Determine direction and counterparty if t.source == account_self: - direction = 'out' + direction = "out" counterparty = t.target elif t.target == account_self: - direction = 'in' + direction = "in" counterparty = t.source else: raise ValueError("Transaction does not involve account_self") @@ -50,14 +50,14 @@ def write_transactions_csv( .isoformat() ) account_type = ( - 'user' + "user" if counterparty.user - else 'association' + else "association" if counterparty.association - else 'special' + else "special" ) name = str(counterparty) - email = counterparty.user.email if account_type == 'user' else '' + email = counterparty.user.email if account_type == "user" else "" csv_writer.writerow( [ diff --git a/creditmanagement/forms.py b/creditmanagement/forms.py index 49f384f4..f6958f4f 100644 --- a/creditmanagement/forms.py +++ b/creditmanagement/forms.py @@ -13,7 +13,7 @@ USER_FORM_FIELD = forms.ModelChoiceField( User.objects.filter(is_active=True), required=False, - widget=ModelSelect2(url='people_autocomplete'), + widget=ModelSelect2(url="people_autocomplete"), label="User", ) ASSOCIATION_FORM_FIELD = forms.ModelChoiceField( @@ -58,14 +58,14 @@ class TransactionForm(forms.ModelForm): class Meta: model = Transaction fields = [ - 'origin', - 'amount', - 'target_user', - 'target_association', - 'description', + "origin", + "amount", + "target_user", + "target_association", + "description", ] help_texts = { - 'description': "E.g. deposit or withdrawal via board member.", + "description": "E.g. deposit or withdrawal via board member.", } def __init__(self, source: Account, user: User, *args, **kwargs): @@ -78,8 +78,8 @@ def __init__(self, source: Account, user: User, *args, **kwargs): super().__init__(*args, **kwargs) self.instance.source = source self.instance.created_by = user - self.fields['origin'].initial = str(source) - self.fields['target_association'].help_text = ( + self.fields["origin"].initial = str(source) + self.fields["target_association"].help_text = ( "Provide a user or an association who will receive the money. " "You can't provide both a user and an association." ) @@ -88,7 +88,7 @@ def clean(self): cleaned_data = super().clean() # Check that there's exactly 1 one user or association set target_el, idx = one_of( - cleaned_data.get('target_user'), cleaned_data.get('target_association') + cleaned_data.get("target_user"), cleaned_data.get("target_association") ) if not target_el: raise ValidationError("Provide exactly one of user or association.") @@ -104,7 +104,7 @@ def clean(self): # We block transactions made by this form that make a user account balance negative # (Note that there's a race condition here, but it is not an issue in practice.) source = self.instance.source # type: Account - if source.user and source.get_balance() < cleaned_data.get('amount'): + if source.user and source.get_balance() < cleaned_data.get("amount"): raise ValidationError("Your balance is insufficient.") return cleaned_data @@ -127,14 +127,14 @@ class SiteWideTransactionForm(forms.ModelForm): class Meta: model = Transaction fields = [ - 'source_user', - 'source_association', - 'source_special', - 'target_user', - 'target_association', - 'target_special', - 'amount', - 'description', + "source_user", + "source_association", + "source_special", + "target_user", + "target_association", + "target_special", + "amount", + "description", ] def __init__(self, user: User, *args, **kwargs): @@ -151,17 +151,17 @@ def clean(self): cleaned_data = super().clean() # Get source source_el, source_idx = one_of( - cleaned_data.get('source_user'), - cleaned_data.get('source_association'), - cleaned_data.get('source_special'), + cleaned_data.get("source_user"), + cleaned_data.get("source_association"), + cleaned_data.get("source_special"), ) if not source_el: raise ValidationError("Provide exactly 1 transaction source.") # Target target_el, target_idx = one_of( - cleaned_data.get('target_user'), - cleaned_data.get('target_association'), - cleaned_data.get('target_special'), + cleaned_data.get("target_user"), + cleaned_data.get("target_association"), + cleaned_data.get("target_special"), ) if not target_el: raise ValidationError("Provide exactly 1 transaction target.") @@ -190,9 +190,9 @@ def save(self, commit=True, request=None): source = self.instance.source if source.user: send_templated_mail( - 'mail/transaction_created', + "mail/transaction_created", source.user, - {'transaction': instance}, + {"transaction": instance}, request, ) @@ -237,7 +237,7 @@ def save(self): """Saves the transactions to the database.""" if not self.is_valid(): raise RuntimeError - desc = self.cleaned_data.get('description') + desc = self.cleaned_data.get("description") with transaction.atomic(): for tx in self.transactions: tx.description = desc @@ -255,9 +255,9 @@ class AccountPickerForm(forms.Form): def clean(self): cleaned_data = super().clean() fields = ( - bool(cleaned_data['user']), - bool(cleaned_data['association']), - bool(cleaned_data['special']), + bool(cleaned_data["user"]), + bool(cleaned_data["association"]), + bool(cleaned_data["special"]), ) if sum(fields) != 1: raise ValidationError("Select 1 of the fields.") @@ -265,13 +265,13 @@ def clean(self): def get_account(self) -> Account: """Returns the account that was picked.""" - user = self.cleaned_data['user'] + user = self.cleaned_data["user"] if user: return user.account - association = self.cleaned_data['association'] + association = self.cleaned_data["association"] if association: return association.account - special = self.cleaned_data['special'] + special = self.cleaned_data["special"] if special: return special raise RuntimeError diff --git a/creditmanagement/models.py b/creditmanagement/models.py index 40c51ee0..b3193f29 100644 --- a/creditmanagement/models.py +++ b/creditmanagement/models.py @@ -34,10 +34,10 @@ class Account(models.Model): # (The special accounts listed here are automatically created using a receiver.) SPECIAL_ACCOUNTS = [ - ('kitchen_cost', 'Kitchen cost'), + ("kitchen_cost", "Kitchen cost"), ] SPECIAL_ACCOUNT_DESCRIPTION = { - 'kitchen_cost': "Account which receives the kitchen payments. " + "kitchen_cost": "Account which receives the kitchen payments. " "The balance indicates the money that is payed for kitchen usage " "(minus withdraws from this account).", } @@ -56,12 +56,12 @@ def get_balance(self) -> Decimal: qs = Transaction.objects.all() # 2 separate queries for the source and target sums # If there are no rows, the value will be made 0.00 - source_sum = qs.filter(source=self).aggregate(sum=Sum('amount'))[ - 'sum' - ] or Decimal('0.00') - target_sum = qs.filter(target=self).aggregate(sum=Sum('amount'))[ - 'sum' - ] or Decimal('0.00') + source_sum = qs.filter(source=self).aggregate(sum=Sum("amount"))[ + "sum" + ] or Decimal("0.00") + target_sum = qs.filter(target=self).aggregate(sum=Sum("amount"))[ + "sum" + ] or Decimal("0.00") return target_sum - source_sum get_balance.short_description = "Balance" # (used in admin site) @@ -89,7 +89,7 @@ def negative_since(self) -> Optional[datetime]: return None # Loop over all transactions from new to old, while reversing the balance - transactions = Transaction.objects.filter_account(self).order_by('-moment') + transactions = Transaction.objects.filter_account(self).order_by("-moment") for tx in transactions: if tx.source == self: balance += tx.amount @@ -126,19 +126,19 @@ def filter_account(self, account: Account): class Transaction(models.Model): # We do not enforce that source != target because those rows are not harmful. source = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name='transaction_source_set' + Account, on_delete=models.PROTECT, related_name="transaction_source_set" ) target = models.ForeignKey( - Account, on_delete=models.PROTECT, related_name='transaction_target_set' + Account, on_delete=models.PROTECT, related_name="transaction_target_set" ) # Amount can only be (strictly) positive amount = models.DecimalField( - decimal_places=2, max_digits=8, validators=[MinValueValidator(Decimal('0.01'))] + decimal_places=2, max_digits=8, validators=[MinValueValidator(Decimal("0.01"))] ) moment = models.DateTimeField(default=timezone.now) description = models.CharField(max_length=1000) created_by = models.ForeignKey( - User, on_delete=models.PROTECT, related_name='transaction_set' + User, on_delete=models.PROTECT, related_name="transaction_set" ) objects = TransactionQuerySet.as_manager() diff --git a/creditmanagement/templatetags/credit_tags.py b/creditmanagement/templatetags/credit_tags.py index 49d54263..3693e0e8 100644 --- a/creditmanagement/templatetags/credit_tags.py +++ b/creditmanagement/templatetags/credit_tags.py @@ -8,7 +8,7 @@ def euro(value): """Format for euro values.""" amount = "{:.2f}".format(abs(value)) - v = "{}€{}".format('-' if value < 0 else '', amount) + v = "{}€{}".format("-" if value < 0 else "", amount) return mark_safe(v) diff --git a/creditmanagement/tests/test_models.py b/creditmanagement/tests/test_models.py index f0d6ea83..94baa0c7 100644 --- a/creditmanagement/tests/test_models.py +++ b/creditmanagement/tests/test_models.py @@ -13,28 +13,28 @@ class CreditTestCase(TestCase): def setUpTestData(cls): cls.a1 = Account.objects.create() cls.a2 = Account.objects.create() - cls.u = User.objects.create(username='user') + cls.u = User.objects.create(username="user") def test_balance_no_tx(self): """Tests balance when there are no transactions.""" - self.assertEqual(self.a1.get_balance(), Decimal('0.00')) + self.assertEqual(self.a1.get_balance(), Decimal("0.00")) def test_balance(self): """Tests balance when there are at least 1 source and target transaction.""" Transaction.objects.create( - source=self.a1, target=self.a2, amount=Decimal('8.30'), created_by=self.u + source=self.a1, target=self.a2, amount=Decimal("8.30"), created_by=self.u ) Transaction.objects.create( - source=self.a2, target=self.a1, amount=Decimal('9.88'), created_by=self.u + source=self.a2, target=self.a1, amount=Decimal("9.88"), created_by=self.u ) - self.assertEqual(self.a1.get_balance(), Decimal('1.58')) - self.assertEqual(self.a2.get_balance(), Decimal('-1.58')) + self.assertEqual(self.a1.get_balance(), Decimal("1.58")) + self.assertEqual(self.a2.get_balance(), Decimal("-1.58")) def test_reversal(self): """Tests the reversal transaction.""" tx = Transaction.objects.create( - source=self.a1, target=self.a2, amount=Decimal('2.64'), created_by=self.u + source=self.a1, target=self.a2, amount=Decimal("2.64"), created_by=self.u ) tx.reversal(self.u).save() - self.assertEqual(self.a1.get_balance(), Decimal('0.00')) - self.assertEqual(self.a2.get_balance(), Decimal('0.00')) + self.assertEqual(self.a1.get_balance(), Decimal("0.00")) + self.assertEqual(self.a2.get_balance(), Decimal("0.00")) diff --git a/creditmanagement/urls.py b/creditmanagement/urls.py index ed948744..cd06186c 100644 --- a/creditmanagement/urls.py +++ b/creditmanagement/urls.py @@ -6,10 +6,10 @@ TransactionCSVView, ) -app_name = 'credits' +app_name = "credits" urlpatterns = [ - path('transactions/', TransactionListView.as_view(), name='transaction_list'), - path('transactions/add/', TransactionAddView.as_view(), name='transaction_add'), - path('transactions/csv/', TransactionCSVView.as_view(), name='transaction_csv'), + path("transactions/", TransactionListView.as_view(), name="transaction_list"), + path("transactions/add/", TransactionAddView.as_view(), name="transaction_add"), + path("transactions/csv/", TransactionCSVView.as_view(), name="transaction_csv"), ] diff --git a/creditmanagement/views.py b/creditmanagement/views.py index d21c114b..08fd079e 100644 --- a/creditmanagement/views.py +++ b/creditmanagement/views.py @@ -16,7 +16,7 @@ class TransactionListView(LoginRequiredMixin, ListView): def get_queryset(self): return Transaction.objects.filter_account(self.request.user.account).order_by( - '-moment' + "-moment" ) @@ -24,10 +24,10 @@ class TransactionCSVView(LoginRequiredMixin, View): """Returns a CSV with transactions of the current user.""" def get(self, request, *args, **kwargs): - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="user_transactions.csv"' + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = 'attachment; filename="user_transactions.csv"' qs = Transaction.objects.filter_account(request.user.account).order_by( - '-moment' + "-moment" ) write_transactions_csv(response, qs, request.user.account) return response @@ -44,8 +44,8 @@ class TransactionFormView(FormView): def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs['source'] = self.get_source() # Set transaction source/origin - kwargs['user'] = self.request.user # Set created_by + kwargs["source"] = self.get_source() # Set transaction source/origin + kwargs["user"] = self.request.user # Set created_by return kwargs def form_valid(self, form): @@ -64,7 +64,7 @@ class TransactionAddView(LoginRequiredMixin, TransactionFormView): """View where a user can transfer money to someone else.""" template_name = "credit_management/transaction_add.html" - success_url = reverse_lazy('credits:transaction_list') + success_url = reverse_lazy("credits:transaction_list") def get_source(self) -> Account: return self.request.user.account diff --git a/dining/admin.py b/dining/admin.py index 336e6c59..58e04192 100644 --- a/dining/admin.py +++ b/dining/admin.py @@ -12,20 +12,20 @@ @admin.register(DiningEntry) class DiningEntryAdmin(admin.ModelAdmin): list_display = ( - 'id', - 'dining_list', - 'user', - 'external_name', - 'has_shopped', - 'has_cooked', - 'has_cleaned', + "id", + "dining_list", + "user", + "external_name", + "has_shopped", + "has_cooked", + "has_cleaned", ) - list_filter = ('dining_list__date',) + list_filter = ("dining_list__date",) search_fields = ( - 'user__first_name', - 'user__last_name', - 'user__username', - 'user__email', + "user__first_name", + "user__last_name", + "user__username", + "user__email", ) # We do not allow adding/deleting/changing dining entries because money is involved. @@ -42,16 +42,16 @@ def has_change_permission(self, request, obj=None): @admin.register(DiningList) class DiningListAdmin(admin.ModelAdmin): - list_display = ('date', 'association', 'dish', 'is_adjustable') - list_filter = ('association', 'date') - readonly_fields = ('date', 'diners', 'association') - filter_horizontal = ('owners',) + list_display = ("date", "association", "dish", "is_adjustable") + list_filter = ("association", "date") + readonly_fields = ("date", "diners", "association") + filter_horizontal = ("owners",) @admin.register(DiningDayAnnouncement) class DiningDayAnnouncementAdmin(admin.ModelAdmin): - list_display = ('title', 'date', 'slots_occupy') - list_filter = ('date', 'slots_occupy') + list_display = ("title", "date", "slots_occupy") + list_filter = ("date", "slots_occupy") admin.site.register(DiningComment) diff --git a/dining/apps.py b/dining/apps.py index 68d37906..18b70a87 100644 --- a/dining/apps.py +++ b/dining/apps.py @@ -2,7 +2,7 @@ class DiningConfig(AppConfig): - name = 'dining' + name = "dining" def ready(self): # Put receivers here diff --git a/dining/models.py b/dining/models.py index e57dee08..fcb20bbf 100644 --- a/dining/models.py +++ b/dining/models.py @@ -18,12 +18,12 @@ def available_slots(self, date): """Returns the number of available slots on the given date.""" # Get slots occupied by announcements announce_slots = DiningDayAnnouncement.objects.filter(date=date).aggregate( - Sum('slots_occupy') + Sum("slots_occupy") ) announce_slots = ( 0 - if announce_slots['slots_occupy__sum'] is None - else announce_slots['slots_occupy__sum'] + if announce_slots["slots_occupy__sum"] is None + else announce_slots["slots_occupy__sum"] ) return settings.MAX_SLOT_NUMBER - len(self.filter(date=date)) - announce_slots @@ -41,8 +41,8 @@ class DiningList(models.Model): association = models.ForeignKey(Association, on_delete=models.PROTECT) owners = models.ManyToManyField( settings.AUTH_USER_MODEL, - related_name='owned_dining_lists', - help_text='Owners can manage the dining list.', + related_name="owned_dining_lists", + help_text="Owners can manage the dining list.", ) sign_up_deadline = models.DateTimeField( @@ -67,7 +67,7 @@ class DiningList(models.Model): verbose_name="kitchen cost per person", max_digits=10, default=settings.KITCHEN_COST, - validators=[MinValueValidator(Decimal('0.00'))], + validators=[MinValueValidator(Decimal("0.00"))], ) dining_cost = models.DecimalField( @@ -77,7 +77,7 @@ class DiningList(models.Model): blank=True, null=True, default=None, - validators=[MinValueValidator(Decimal('0.00'))], + validators=[MinValueValidator(Decimal("0.00"))], ) auto_pay = models.BooleanField(default=False) @@ -92,7 +92,7 @@ class DiningList(models.Model): ) diners = models.ManyToManyField( - User, through='DiningEntry', through_fields=('dining_list', 'user') + User, through="DiningEntry", through_fields=("dining_list", "user") ) objects = DiningListManager() @@ -138,8 +138,8 @@ def get_absolute_url(self): slug = self.association.slug d = self.date return reverse( - 'slot_details', - kwargs={'year': d.year, 'month': d.month, 'day': d.day, 'identifier': slug}, + "slot_details", + kwargs={"year": d.year, "month": d.month, "day": d.day, "identifier": slug}, ) def internal_dining_entries(self): @@ -155,11 +155,11 @@ def clean_fields(self, exclude=None): # Validate sign up deadline. # # We can't put this in clean(), because then forms which put this field in the exclude list break. - if not exclude or 'sign_up_deadline' not in exclude: + if not exclude or "sign_up_deadline" not in exclude: if self.sign_up_deadline and self.sign_up_deadline.date() > self.date: raise ValidationError( { - 'sign_up_deadline': [ + "sign_up_deadline": [ "Sign up deadline can't be later than the day dinner is served." ] } @@ -178,13 +178,13 @@ class DiningEntry(models.Model): """Represents an entry on a dining list.""" dining_list = models.ForeignKey( - DiningList, on_delete=models.PROTECT, related_name='dining_entries' + DiningList, on_delete=models.PROTECT, related_name="dining_entries" ) # This is the person who is responsible for the kitchen cost. It will be the same as the transaction source. user = models.ForeignKey(User, on_delete=models.PROTECT) created_by = models.ForeignKey( - User, on_delete=models.PROTECT, related_name='created_dining_entries' + User, on_delete=models.PROTECT, related_name="created_dining_entries" ) # The transaction that belongs to this entry. @@ -205,7 +205,7 @@ class DiningEntry(models.Model): objects = DiningEntryManager() class Meta: - verbose_name_plural = 'dining entries' + verbose_name_plural = "dining entries" def get_name(self): """Return name of diner.""" @@ -224,14 +224,14 @@ def clean(self): # Check for duplicate internal entry, when this entry is being created (i.e. self.pk is not set). # # It might happen that self.user did not clean. In that case the attribute is not available. - if not self.pk and self.is_internal() and hasattr(self, 'user'): + if not self.pk and self.is_internal() and hasattr(self, "user"): if ( DiningEntry.objects.internal() .filter(user=self.user.pk, dining_list=self.dining_list) .exists() ): raise ValidationError( - "User is already on the dining list", code='user_already_present' + "User is already on the dining list", code="user_already_present" ) @@ -322,10 +322,10 @@ class DeletedList(models.Model): """For audit purposes, keep a log of deleted dining lists.""" deleted_by = models.ForeignKey(User, on_delete=models.PROTECT) - date = models.DateTimeField('deletion date', default=timezone.now) + date = models.DateTimeField("deletion date", default=timezone.now) reason = models.TextField() - json_list = models.TextField('JSON dining list') - json_diners = models.TextField('JSON dining entries') + json_list = models.TextField("JSON dining list") + json_diners = models.TextField("JSON dining entries") def __str__(self): - return f'Deleted on {self.date.date()} by {self.deleted_by}' + return f"Deleted on {self.date.date()} by {self.deleted_by}" diff --git a/dining/templatetags/dining_tags.py b/dining/templatetags/dining_tags.py index 786c679c..8f6d1f39 100644 --- a/dining/templatetags/dining_tags.py +++ b/dining/templatetags/dining_tags.py @@ -16,7 +16,7 @@ def can_join(dining_list, user): # Try creating an entry entry = DiningEntry(dining_list=dining_list, created_by=user) - form = DiningEntryInternalForm({'user': str(user.pk)}, instance=entry) + form = DiningEntryInternalForm({"user": str(user.pk)}, instance=entry) return form.is_valid() @@ -24,7 +24,7 @@ def can_join(dining_list, user): def cant_join_reason(dining_list, user): """Returns the reason why someone can't join (raises exception when they can join).""" entry = DiningEntry(dining_list=dining_list, created_by=user) - form = DiningEntryInternalForm({'user': str(user.pk)}, instance=entry) + form = DiningEntryInternalForm({"user": str(user.pk)}, instance=entry) return form.non_field_errors()[0] @@ -149,8 +149,8 @@ def short_owners_string(dining_list: DiningList) -> str: if len(owners) > 1: first_names = [o.first_name for o in owners] # Join by comma and 'and' - return '{} and {}'.format(', '.join(first_names[:-1]), first_names[-1]) + return "{} and {}".format(", ".join(first_names[:-1]), first_names[-1]) elif len(owners) == 1: return owners[0].get_full_name() else: - return '' + return "" diff --git a/dining/tests/test_forms.py b/dining/tests/test_forms.py index 6ca6e5eb..2e310600 100644 --- a/dining/tests/test_forms.py +++ b/dining/tests/test_forms.py @@ -30,7 +30,7 @@ class CreateSlotFormTestCase(TestCase): def setUp(self): self.association1 = Association.objects.create(name="Quadrivium") - self.user1 = User.objects.create_user('jan') + self.user1 = User.objects.create_user("jan") UserMembership.objects.create( related_user=self.user1, association=self.association1, @@ -40,10 +40,10 @@ def setUp(self): # Date two days in the future self.dining_date = timezone.now().date() + timedelta(days=2) self.form_data = { - 'dish': 'Kwark', - 'association': str(self.association1.pk), - 'max_diners': '18', - 'serve_time': '17:00', + "dish": "Kwark", + "association": str(self.association1.pk), + "max_diners": "18", + "serve_time": "17:00", } self.dining_list = DiningList(date=self.dining_date) self.form = CreateSlotForm( @@ -56,7 +56,7 @@ def test_creation(self): dining_list.refresh_from_db() # Assert - self.assertEqual('Kwark', dining_list.dish) + self.assertEqual("Kwark", dining_list.dish) self.assertEqual(self.association1, dining_list.association) self.assertEqual(18, dining_list.max_diners) self.assertEqual(time(17, 00), dining_list.serve_time) @@ -72,12 +72,12 @@ def test_invalid_association(self): Source: https://docs.djangoproject.com/en/2.2/ref/forms/fields/#disabled """ - association = Association.objects.create(name='Knights') + association = Association.objects.create(name="Knights") form_data = { - 'dish': 'Boter', - 'association': str(association.pk), - 'max_diners': '20', - 'serve_time': '18:00', + "dish": "Boter", + "association": str(association.pk), + "max_diners": "20", + "serve_time": "18:00", } form = CreateSlotForm( self.user1, form_data, instance=DiningList(date=self.dining_date) @@ -94,20 +94,20 @@ def test_association_unique_for_date(self): # Try creating another one with same association dl = DiningList(date=self.dining_date) data = { - 'dish': 'Kwark', - 'association': str(self.association1.pk), - 'max_diners': '18', - 'serve_time': '17:00', + "dish": "Kwark", + "association": str(self.association1.pk), + "max_diners": "18", + "serve_time": "17:00", } form = CreateSlotForm(self.user1, data, instance=dl) self.assertFalse(form.is_valid()) - self.assertTrue(form.has_error('association')) + self.assertTrue(form.has_error("association")) def test_insufficient_balance(self): Transaction.objects.create( source=self.user1.account, target=self.association1.account, - amount=Decimal('99'), + amount=Decimal("99"), created_by=self.user1, ) self.assertFalse(self.form.is_valid()) @@ -116,11 +116,11 @@ def test_insufficient_balance_exception(self): Transaction.objects.create( source=self.user1.account, target=self.association1.account, - amount=Decimal('99'), + amount=Decimal("99"), created_by=self.user1, ) # Make user member of another association that has the exception - association = Association.objects.create(name='Q', has_min_exception=True) + association = Association.objects.create(name="Q", has_min_exception=True) UserMembership.objects.create( related_user=self.user1, association=association, @@ -131,17 +131,17 @@ def test_insufficient_balance_exception(self): def test_serve_time_too_late(self): # Actually tests a different class, but put here for convenience, to test it via the CreateSlotForm class - self.form_data['serve_time'] = '23:30' + self.form_data["serve_time"] = "23:30" self.assertFalse(self.form.is_valid()) def test_serve_time_too_early(self): # Actually tests a different class, but put here for convenience, to test it via the CreateSlotForm class - self.form_data['serve_time'] = '11:00' + self.form_data["serve_time"] = "11:00" self.assertFalse(self.form.is_valid()) class TestDiningEntryInternalForm(FormValidityMixin, TestCase): - fixtures = ['base', 'base_credits', 'dining_lists'] + fixtures = ["base", "base_credits", "dining_lists"] form_class = DiningEntryInternalForm def setUp(self): @@ -150,23 +150,23 @@ def setUp(self): def get_form_kwargs(self, **kwargs): entry = DiningEntry(dining_list=self.dining_list, created_by=self.user) - kwargs.setdefault('instance', entry) + kwargs.setdefault("instance", entry) return super().get_form_kwargs(**kwargs) def test_widget_replacements(self): form = self.build_form({}) - self.assertIsInstance(form.fields['user'].widget, ModelSelect2) + self.assertIsInstance(form.fields["user"].widget, ModelSelect2) @patch_time() def test_form_valid(self): """Asserts that the testing environment standard is valid.""" # Check for self creation, useful to guarantee a baseline of current testing environment - self.assertFormValid({'user': self.user}) + self.assertFormValid({"user": self.user}) @patch_time() def test_entry_db_creation(self): """Tests the database object creation for an entry.""" - form = self.assertFormValid({'user': User.objects.get(id=4)}) + form = self.assertFormValid({"user": User.objects.get(id=4)}) instance = form.save() self.assertTrue(DiningEntry.objects.filter(id=instance.id).exists()) @@ -184,14 +184,14 @@ def test_transaction_db_creation(self): added_user = User.objects.get(id=4) # Save the instance - form = self.assertFormValid({'user': added_user}) + form = self.assertFormValid({"user": added_user}) instance = form.save() self.assertIsNotNone(instance.transaction) self.assertEqual(instance.transaction.amount, self.dining_list.kitchen_cost) self.assertEqual(instance.transaction.source, added_user.account) self.assertEqual( - instance.transaction.target, Account.objects.get(special='kitchen_cost') + instance.transaction.target, Account.objects.get(special="kitchen_cost") ) self.assertEqual(instance.created_by, self.user) @@ -200,7 +200,7 @@ def test_prevent_doubles(self): DiningEntry.objects.create( dining_list=self.dining_list, user=self.user, created_by=self.user ) - self.assertFormHasError({'user': self.user}, code='user_already_present') + self.assertFormHasError({"user": self.user}, code="user_already_present") @patch_time(dt=datetime(2022, 4, 26, 18, 0)) def test_sign_up_deadline(self): @@ -211,9 +211,9 @@ def test_sign_up_deadline(self): self.dining_list.owners.all(), "Incorrect test data, user should not be owner", ) - self.assertFormHasError({'user': self.user}, code='closed') + self.assertFormHasError({"user": self.user}, code="closed") self.assertFormValid( - {'user': self.user}, + {"user": self.user}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first() ), @@ -236,9 +236,9 @@ def test_room(self): ) self.dining_list.max_diners = 14 - self.assertFormHasError({'user': self.user}, code='full') + self.assertFormHasError({"user": self.user}, code="full") self.assertFormValid( - {'user': self.user}, + {"user": self.user}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first() ), @@ -256,12 +256,12 @@ def test_association_only_limitation(self): "Incorrect test data, user should not be owner", ) # user 2 is member of the association - self.assertFormValid({'user': self.user}) + self.assertFormValid({"user": self.user}) # user 4 is not a member - self.assertFormHasError({'user': User.objects.get(id=4)}, code='members_only') + self.assertFormHasError({"user": User.objects.get(id=4)}, code="members_only") # owners can override self.assertFormValid( - {'user': User.objects.get(id=4)}, + {"user": User.objects.get(id=4)}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first() ), @@ -271,30 +271,30 @@ def test_association_only_limitation(self): def test_minimum_balance(self): with self.settings(MINIMUM_BALANCE_FOR_DINING_SIGN_UP=100): # For minimum balance, nobody has an exception, not even admins - self.assertFormHasError({'user': self.user}, code='no_money') + self.assertFormHasError({"user": self.user}, code="no_money") self.assertFormHasError( - {'user': self.user}, + {"user": self.user}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first(), ), - code='no_money', + code="no_money", ) admin = User.objects.filter(is_superuser=True).first() self.assertFormHasError( - {'user': admin}, + {"user": admin}, instance=DiningEntry(dining_list=self.dining_list, created_by=admin), - code='no_money', + code="no_money", ) @patch_time(dt=datetime(2022, 5, 30, 12, 0)) def test_form_editing_time_limit(self): """Asserts that the form can not be used after the timelimit.""" - self.assertFormHasError({'user': self.user}, code='closed') + self.assertFormHasError({"user": self.user}, code="closed") class TestDiningEntryExternalForm(FormValidityMixin, TestCase): - fixtures = ['base', 'base_credits', 'dining_lists'] + fixtures = ["base", "base_credits", "dining_lists"] form_class = DiningEntryExternalForm def setUp(self): @@ -305,26 +305,26 @@ def get_form_kwargs(self, **kwargs): entry = DiningEntry( dining_list=self.dining_list, created_by=self.user, user=self.user ) - kwargs.setdefault('instance', entry) + kwargs.setdefault("instance", entry) return super().get_form_kwargs(**kwargs) @patch_time() def test_form_valid(self): """Asserts that the testing environment standard is valid.""" # Check for self creation, useful to guarantee a baseline of current testing environment - self.assertFormValid({'external_name': 'my_guest'}) + self.assertFormValid({"external_name": "my_guest"}) @patch_time() def test_entry_db_creation(self): """Tests the database object creation for an entry.""" - form = self.assertFormValid({'external_name': 'my guest'}) + form = self.assertFormValid({"external_name": "my guest"}) instance = form.save() self.assertTrue(DiningEntry.objects.filter(id=instance.id).exists()) instance.refresh_from_db() self.assertEqual(instance.user, self.user) self.assertEqual(instance.created_by, self.user) - self.assertEqual(instance.external_name, 'my guest') + self.assertEqual(instance.external_name, "my guest") @patch_time() def test_transaction_db_creation(self): @@ -334,14 +334,14 @@ def test_transaction_db_creation(self): self.dining_list.save() # Save the instance - form = self.assertFormValid({'external_name': 'my guest'}) + form = self.assertFormValid({"external_name": "my guest"}) instance = form.save() self.assertIsNotNone(instance.transaction) self.assertEqual(instance.transaction.amount, self.dining_list.kitchen_cost) self.assertEqual(instance.transaction.source, self.user.account) self.assertEqual( - instance.transaction.target, Account.objects.get(special='kitchen_cost') + instance.transaction.target, Account.objects.get(special="kitchen_cost") ) self.assertEqual(instance.created_by, self.user) @@ -353,9 +353,9 @@ def test_sign_up_deadline(self): self.dining_list.owners.all(), "Incorrect test data, user should not be owner", ) - self.assertFormHasError({'external_name': 'my guest'}, code='closed') + self.assertFormHasError({"external_name": "my guest"}, code="closed") self.assertFormValid( - {'external_name': 'my guest'}, + {"external_name": "my guest"}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first(), @@ -379,9 +379,9 @@ def test_room(self): ) self.dining_list.max_diners = 14 - self.assertFormHasError({'external_name': 'my guest'}, code='full') + self.assertFormHasError({"external_name": "my guest"}, code="full") self.assertFormValid( - {'external_name': 'my guest'}, + {"external_name": "my guest"}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first(), @@ -400,11 +400,11 @@ def test_association_only_limitation(self): "Incorrect test data, user should not be owner", ) # user 2 is member of the association - self.assertFormValid({'external_name': 'my guest'}) + self.assertFormValid({"external_name": "my guest"}) # user 4 is not a member self.assertFormHasError( - {'external_name': 'my guest'}, - code='members_only', + {"external_name": "my guest"}, + code="members_only", instance=DiningEntry( dining_list=self.dining_list, created_by=User.objects.get(id=4), @@ -413,7 +413,7 @@ def test_association_only_limitation(self): ) # owners can override self.assertFormValid( - {'external_name': 'my guest'}, + {"external_name": "my guest"}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first(), @@ -425,33 +425,33 @@ def test_association_only_limitation(self): def test_minimum_balance(self): with self.settings(MINIMUM_BALANCE_FOR_DINING_SIGN_UP=100): # For minimum balance, nobody has an exception, not even admins - self.assertFormHasError({'external_name': 'my guest'}, code='no_money') + self.assertFormHasError({"external_name": "my guest"}, code="no_money") self.assertFormHasError( - {'external_name': 'my guest'}, + {"external_name": "my guest"}, instance=DiningEntry( dining_list=self.dining_list, created_by=self.dining_list.owners.first(), user=self.user, ), - code='no_money', + code="no_money", ) admin = User.objects.filter(is_superuser=True).first() self.assertFormHasError( - {'external_name': 'my guest'}, + {"external_name": "my guest"}, instance=DiningEntry( dining_list=self.dining_list, created_by=admin, user=self.user ), - code='no_money', + code="no_money", ) @patch_time(dt=datetime(2022, 5, 30, 12, 0)) def test_form_editing_time_limit(self): """Asserts that the form can not be used after the timelimit.""" - self.assertFormHasError({'external_name': 'my guest'}, code='closed') + self.assertFormHasError({"external_name": "my guest"}, code="closed") class TestDiningEntryDeleteForm(FormValidityMixin, TestCase): - fixtures = ['base', 'dining_lists'] + fixtures = ["base", "dining_lists"] form_class = DiningEntryDeleteForm def setUp(self): @@ -460,8 +460,8 @@ def setUp(self): self.dining_list = self.entry.dining_list def get_form_kwargs(self, **kwargs): - kwargs.setdefault('entry', self.entry) - kwargs.setdefault('deleter', self.user) + kwargs.setdefault("entry", self.entry) + kwargs.setdefault("deleter", self.user) return super(TestDiningEntryDeleteForm, self).get_form_kwargs(**kwargs) @patch_time() @@ -480,7 +480,7 @@ def test_db_entry_deletion(self): def test_db_transaction_deletion(self): """Tests that a reversal transaction is created upon deletion.""" # We create a new user because the user created in setUp() already has transactions. - user = User.objects.create_user('user345876') + user = User.objects.create_user("user345876") # We create a new dining list because self.dining_list is not adjustable which makes the form invalid. dining_list = DiningList.objects.create( @@ -496,19 +496,19 @@ def test_db_transaction_deletion(self): created_by=user, transaction=Transaction.objects.create( source=user.account, - target=Account.objects.get(special='kitchen_cost'), - amount=Decimal('2.18'), + target=Account.objects.get(special="kitchen_cost"), + amount=Decimal("2.18"), created_by=user, ), ) # Confirm our balance is negative - self.assertEqual(user.account.get_balance(), Decimal('-2.18')) + self.assertEqual(user.account.get_balance(), Decimal("-2.18")) # Execute form form = DiningEntryDeleteForm(entry, user, {}) self.assertTrue(form.is_valid()) form.execute() # Assert that our balance is now zero - self.assertEqual(user.account.get_balance(), Decimal('0.00')) + self.assertEqual(user.account.get_balance(), Decimal("0.00")) @patch_time(dt=datetime(2022, 4, 26, 18, 0)) def test_sign_up_deadline(self): @@ -519,21 +519,21 @@ def test_sign_up_deadline(self): self.dining_list.owners.all(), "Incorrect test data, user should not be owner", ) - self.assertFormHasError({}, code='closed') + self.assertFormHasError({}, code="closed") self.assertFormValid({}, deleter=self.dining_list.owners.first()) @patch_time() def test_ownership(self): - self.assertFormHasError({}, code='not_owner', deleter=User.objects.get(id=5)) + self.assertFormHasError({}, code="not_owner", deleter=User.objects.get(id=5)) @patch_time(dt=datetime(2022, 5, 30, 12, 0)) def test_form_editing_time_limit(self): """Asserts that the form can not be used after the timelimit.""" - self.assertFormHasError({}, code='locked') + self.assertFormHasError({}, code="locked") class TestDiningListDeleteForm(FormValidityMixin, TestCase): - fixtures = ['base', 'dining_lists'] + fixtures = ["base", "dining_lists"] form_class = DiningListDeleteForm def setUp(self): @@ -541,7 +541,7 @@ def setUp(self): self.dining_list = DiningList.objects.get(id=1) def get_form_kwargs(self, **kwargs): - kwargs.setdefault('instance', self.dining_list) + kwargs.setdefault("instance", self.dining_list) return super().get_form_kwargs(**kwargs) @patch_time() @@ -575,11 +575,11 @@ def test_form_editing_time_limit(self): """Asserts that the form can not be used after the timelimit.""" # The form will be locked by default because the dining list instance has a date in the past. form = DiningListDeleteForm({}, instance=self.dining_list) - self.assertTrue(form.has_error(NON_FIELD_ERRORS, code='locked')) + self.assertTrue(form.has_error(NON_FIELD_ERRORS, code="locked")) class TestDiningInfoForm(FormValidityMixin, TestCase): - fixtures = ['base', 'dining_lists'] + fixtures = ["base", "dining_lists"] form_class = DiningInfoForm def setUp(self): @@ -587,7 +587,7 @@ def setUp(self): self.user = User.objects.get(id=1) def get_form_kwargs(self, **kwargs): - kwargs.setdefault('instance', self.dining_list) + kwargs.setdefault("instance", self.dining_list) return super(TestDiningInfoForm, self).get_form_kwargs(**kwargs) def test_class(self): @@ -596,17 +596,17 @@ def test_class(self): def test_widget_replacements(self): form = self.build_form({}) - self.assertIsInstance(form.fields['owners'].widget, ModelSelect2Multiple) + self.assertIsInstance(form.fields["owners"].widget, ModelSelect2Multiple) @patch_time() def test_form_valid(self): self.assertFormValid( { - 'owners': [1], - 'dish': "My delicious dish", - 'serve_time': time(18, 00), - 'max_diners': 15, - 'sign_up_deadline': datetime(2022, 4, 26, 15, 0), + "owners": [1], + "dish": "My delicious dish", + "serve_time": time(18, 00), + "max_diners": 15, + "sign_up_deadline": datetime(2022, 4, 26, 15, 0), } ) @@ -614,11 +614,11 @@ def test_form_valid(self): def test_db_update(self): updated_dining_list = self.assertFormValid( { - 'owners': [4], - 'dish': "New dish", - 'serve_time': time(17, 5), - 'max_diners': 14, - 'sign_up_deadline': datetime(2022, 4, 26, 12, 00), + "owners": [4], + "dish": "New dish", + "serve_time": time(17, 5), + "max_diners": 14, + "sign_up_deadline": datetime(2022, 4, 26, 12, 00), } ).save() @@ -641,7 +641,7 @@ def test_kitchen_open_time_validity(self): date(2000, 1, 1), settings.KITCHEN_USE_START_TIME ) - timedelta(minutes=1) self.assertFormHasError( - {'serve_time': dt.time()}, code='kitchen_start_time', field='serve_time' + {"serve_time": dt.time()}, code="kitchen_start_time", field="serve_time" ) def test_kitchen_close_time_validity(self): @@ -651,12 +651,12 @@ def test_kitchen_close_time_validity(self): ) + timedelta(minutes=1) self.assertFormHasError( - {'serve_time': dt.time()}, code='kitchen_close_time', field='serve_time' + {"serve_time": dt.time()}, code="kitchen_close_time", field="serve_time" ) class TestDiningPaymentForm(FormValidityMixin, TestCase): - fixtures = ['base', 'dining_lists'] + fixtures = ["base", "dining_lists"] form_class = DiningPaymentForm def setUp(self): @@ -664,7 +664,7 @@ def setUp(self): self.user = User.objects.get(id=1) def get_form_kwargs(self, **kwargs): - kwargs.setdefault('instance', self.dining_list) + kwargs.setdefault("instance", self.dining_list) return super(TestDiningPaymentForm, self).get_form_kwargs(**kwargs) def test_class(self): @@ -675,7 +675,7 @@ def test_class(self): def test_form_valid(self): self.assertFormValid( { - 'payment_link': "https://www.google.com/", + "payment_link": "https://www.google.com/", } ) self.assertFormValid({}) @@ -685,19 +685,19 @@ def test_dining_cost_conflict(self): """Assert that an error is raised when both dinner_cost and dinner_cost_total are defined.""" self.assertFormHasError( { - 'dining_cost_total': 12, - 'dining_cost': 4, + "dining_cost_total": 12, + "dining_cost": 4, }, - code='duplicate_cost', - field='dining_cost', + code="duplicate_cost", + field="dining_cost", ) self.assertFormHasError( { - 'dining_cost_total': 12, - 'dining_cost': 4, + "dining_cost_total": 12, + "dining_cost": 4, }, - code='duplicate_cost', - field='dining_cost_total', + code="duplicate_cost", + field="dining_cost_total", ) @patch_time() @@ -706,30 +706,30 @@ def test_dining_cost_total_empty_diners(self): self.dining_list.dining_entries.all().delete() self.assertFormHasError( { - 'dining_cost_total': 12, + "dining_cost_total": 12, }, - code='costs_no_diners', + code="costs_no_diners", ) @patch_time() def test_dining_cost_total(self): """Test that dining cost is correctly computed from total cost.""" - form = self.assertFormValid({'dining_cost_total': 16}) - self.assertIsNone(form.cleaned_data['dining_cost_total']) - self.assertEqual(form.cleaned_data['dining_cost'], 2) + form = self.assertFormValid({"dining_cost_total": 16}) + self.assertIsNone(form.cleaned_data["dining_cost_total"]) + self.assertEqual(form.cleaned_data["dining_cost"], 2) # Test that it rounds up - form = self.assertFormValid({'dining_cost_total': 15.95}) - self.assertIsNone(form.cleaned_data['dining_cost_total']) - self.assertEqual(form.cleaned_data['dining_cost'], 2) + form = self.assertFormValid({"dining_cost_total": 15.95}) + self.assertIsNone(form.cleaned_data["dining_cost_total"]) + self.assertEqual(form.cleaned_data["dining_cost"], 2) class TestSendReminderForm(FormValidityMixin, TestPatchMixin, TestCase): - fixtures = ['base', 'dining_lists'] + fixtures = ["base", "dining_lists"] form_class = SendReminderForm def get_form_kwargs(self, **kwargs): - kwargs.setdefault('dining_list', self.dining_list) + kwargs.setdefault("dining_list", self.dining_list) return super(TestSendReminderForm, self).get_form_kwargs(**kwargs) def setUp(self): @@ -742,12 +742,12 @@ def test_is_valid(self): def test_invalid_all_paid(self): # Make all users paid self.dining_list.dining_entries.update(has_paid=True) - self.assertFormHasError({}, code='all_paid') + self.assertFormHasError({}, code="all_paid") def test_invalid_missing_payment_link(self): self.dining_list.payment_link = "" self.dining_list.save() - self.assertFormHasError({}, code='payment_url_missing') + self.assertFormHasError({}, code="payment_url_missing") # This is unnecessary. It is perfectly fine to call send_templated_mail with 0 recipients. @@ -763,7 +763,7 @@ def test_invalid_missing_payment_link(self): # # mock_mail.assert_not_called() - @patch('dining.forms.construct_templated_mail') + @patch("dining.forms.construct_templated_mail") def test_mail_sending_users(self, mock_mail): form = self.assertFormValid({}) request = HttpRequest() @@ -773,7 +773,7 @@ def test_mail_sending_users(self, mock_mail): calls = self.assert_has_call(mock_mail, arg_1="mail/dining_payment_reminder") # Assert that the correct number of recipients are documented self.assertEqual( - len(calls[0]['args'][1]), + len(calls[0]["args"][1]), DiningEntry.objects.internal() .filter( dining_list=self.dining_list, @@ -782,10 +782,10 @@ def test_mail_sending_users(self, mock_mail): .count(), ) - self.assertEqual(calls[0]['context']['dining_list'], self.dining_list) - self.assertEqual(calls[0]['context']['reminder'], User.objects.get(id=1)) + self.assertEqual(calls[0]["context"]["dining_list"], self.dining_list) + self.assertEqual(calls[0]["context"]["reminder"], User.objects.get(id=1)) - @patch('dining.forms.construct_templated_mail') + @patch("dining.forms.construct_templated_mail") def test_mail_sending_external(self, mock_mail): """Tests handling of messaging for external guests added by a certain user.""" form = self.assertFormValid({}) @@ -798,9 +798,9 @@ def test_mail_sending_external(self, mock_mail): ) self.assertEqual(len(calls), 2) # ids 2 and 3 added unpaid guests - self.assertEqual(calls[0]['context']['dining_list'], self.dining_list) - self.assertEqual(calls[0]['context']['reminder'], User.objects.get(id=1)) - self.assertEqual(calls[0]['args'][1], User.objects.get(id=2)) - self.assertEqual(len(calls[0]['context']['guests']), 2) - self.assertEqual(calls[1]['args'][1], User.objects.get(id=3)) - self.assertEqual(len(calls[1]['context']['guests']), 1) + self.assertEqual(calls[0]["context"]["dining_list"], self.dining_list) + self.assertEqual(calls[0]["context"]["reminder"], User.objects.get(id=1)) + self.assertEqual(calls[0]["args"][1], User.objects.get(id=2)) + self.assertEqual(len(calls[0]["context"]["guests"]), 2) + self.assertEqual(calls[1]["args"][1], User.objects.get(id=3)) + self.assertEqual(len(calls[1]["context"]["guests"]), 1) diff --git a/dining/tests/test_forms_diningentry.py b/dining/tests/test_forms_diningentry.py index b93d115c..5477582b 100644 --- a/dining/tests/test_forms_diningentry.py +++ b/dining/tests/test_forms_diningentry.py @@ -17,14 +17,14 @@ def _create_dining_list(**kwargs): """Creates a dining list with defaults if omitted.""" - if 'association' not in kwargs: - kwargs['association'] = Association.objects.create() - if 'date' not in kwargs: - kwargs['date'] = date(2018, 1, 4) - if 'sign_up_deadline' not in kwargs: - kwargs['sign_up_deadline'] = datetime.combine(kwargs['date'], time(17, 00)) + if "association" not in kwargs: + kwargs["association"] = Association.objects.create() + if "date" not in kwargs: + kwargs["date"] = date(2018, 1, 4) + if "sign_up_deadline" not in kwargs: + kwargs["sign_up_deadline"] = datetime.combine(kwargs["date"], time(17, 00)) dl = DiningList.objects.create(**kwargs) - dl.owners.add(User.objects.create_user('tessa', 'tessa@punt.nl')) + dl.owners.add(User.objects.create_user("tessa", "tessa@punt.nl")) return dl @@ -32,8 +32,8 @@ class DiningEntryInternalFormTestCase(TestCase): @classmethod def setUpTestData(cls): cls.association = Association.objects.create() - cls.user = User.objects.create_user('jan') - cls.user2 = User.objects.create_user('noortje', email='noortje@cat.cat') + cls.user = User.objects.create_user("jan") + cls.user2 = User.objects.create_user("noortje", email="noortje@cat.cat") def setUp(self): # Not in setUpTestData to ensure that it is fresh for every test case @@ -46,7 +46,7 @@ def setUp(self): self.dining_entry = DiningEntry( dining_list=self.dining_list, created_by=self.user2 ) - self.post_data = {'user': str(self.user2.pk)} + self.post_data = {"user": str(self.user2.pk)} self.form = DiningEntryInternalForm(self.post_data, instance=self.dining_entry) def test_form(self): @@ -56,14 +56,14 @@ def test_dining_list_not_adjustable(self): self.dining_list.date = date(2000, 1, 2) self.dining_list.sign_up_deadline = datetime(2000, 1, 1, tzinfo=timezone.utc) self.assertFalse(self.form.is_valid()) - self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'closed')) + self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, "closed")) def test_dining_list_closed(self): self.dining_list.sign_up_deadline = datetime( 2000, 1, 1, tzinfo=timezone.utc ) # Close list self.assertFalse(self.form.is_valid()) - self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'closed')) + self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, "closed")) def test_dining_list_closed_owner(self): """Tests closed exception for list owner.""" @@ -76,7 +76,7 @@ def test_dining_list_closed_owner(self): def test_dining_list_no_room(self): self.dining_list.max_diners = 0 self.assertFalse(self.form.is_valid()) - self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'full')) + self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, "full")) def test_dining_list_no_room_owner(self): self.dining_list.max_diners = 0 @@ -96,23 +96,23 @@ def test_limited_to_association_is_member(self): def test_limited_to_association_is_not_member(self): self.dining_list.limit_signups_to_association_only = True self.assertFalse(self.form.is_valid()) - self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'members_only')) + self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, "members_only")) def test_balance_too_low(self): # Move money away from user2's balance. Transaction.objects.create( source=self.user2.account, target=self.association.account, - amount=Decimal('99'), + amount=Decimal("99"), created_by=self.user2, ) self.assertFalse(self.form.is_valid()) - self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, 'no_money')) + self.assertTrue(self.form.has_error(NON_FIELD_ERRORS, "no_money")) def test_balance_too_low_exception(self): # Make user member of association with exception assoc = Association.objects.create( - slug='assoc', name='Association', has_min_exception=True + slug="assoc", name="Association", has_min_exception=True ) UserMembership.objects.create( related_user=self.user2, @@ -123,13 +123,13 @@ def test_balance_too_low_exception(self): Transaction.objects.create( source=self.user2.account, target=self.association.account, - amount=Decimal('99'), + amount=Decimal("99"), created_by=self.user2, ) self.assertTrue(self.form.is_valid()) def test_invalid_user(self): - self.post_data['user'] = '100' + self.post_data["user"] = "100" self.assertFalse(self.form.is_valid()) @@ -139,8 +139,8 @@ class DiningEntryExternalFormTestCase(TestCase): @classmethod def setUpTestData(cls): cls.association = Association.objects.create() - cls.user = User.objects.create_user('jan') - cls.user2 = User.objects.create_user('noortje', email='noortje@cat.cat') + cls.user = User.objects.create_user("jan") + cls.user2 = User.objects.create_user("noortje", email="noortje@cat.cat") def setUp(self): # Not in setUpTestData to ensure that it is fresh for every test case @@ -153,7 +153,7 @@ def setUp(self): self.dining_entry = DiningEntry( dining_list=self.dining_list, user=self.user2, created_by=self.user2 ) - self.post_data = {'external_name': 'Ankie'} + self.post_data = {"external_name": "Ankie"} def test_form(self): form = DiningEntryExternalForm(self.post_data, instance=self.dining_entry) @@ -162,9 +162,9 @@ def test_form(self): class DiningEntryDeleteFormTestCase(TestCase): def setUp(self): - self.user1 = User.objects.create_user('ankie', email='ankie@universe.cat') - self.user2 = User.objects.create_user('noortje', email='noortje@universe.cat') - self.association = Association.objects.create(name='C&M') + self.user1 = User.objects.create_user("ankie", email="ankie@universe.cat") + self.user2 = User.objects.create_user("noortje", email="noortje@universe.cat") + self.association = Association.objects.create(name="C&M") self.dining_list = DiningList.objects.create( date=date(2100, 1, 1), association=self.association, diff --git a/dining/tests/test_forms_sendreminderform.py b/dining/tests/test_forms_sendreminderform.py index 70d00c8f..65815bfd 100644 --- a/dining/tests/test_forms_sendreminderform.py +++ b/dining/tests/test_forms_sendreminderform.py @@ -22,7 +22,7 @@ class SendReminderFormTestCase(TestCase): def setUpTestData(cls): cls.dining_list = DiningList.objects.create( date=date(2020, 1, 1), - association=Association.objects.create(slug='assoc'), + association=Association.objects.create(slug="assoc"), sign_up_deadline=datetime(2020, 1, 1, 12, 0, tzinfo=timezone.utc), ) cls.user = User.objects.create() @@ -62,23 +62,23 @@ def test_user_not_paid(self): def test_guest_paid(self): """Tests that a guest who paid is not included.""" - self.create_dining_entry(self.user, has_paid=True, guest_name='Guest') + self.create_dining_entry(self.user, has_paid=True, guest_name="Guest") self.assertEqual(list(self.form.get_user_recipients()), []) self.assertEqual(self.form.get_guest_recipients(), {}) def test_guest_not_paid(self): """Tests that a guest who didn't pay is included.""" - self.create_dining_entry(self.user, has_paid=False, guest_name='Guest') + self.create_dining_entry(self.user, has_paid=False, guest_name="Guest") self.assertEqual(list(self.form.get_user_recipients()), []) - self.assertEqual(self.form.get_guest_recipients(), {self.user: ['Guest']}) + self.assertEqual(self.form.get_guest_recipients(), {self.user: ["Guest"]}) def test_two_guests(self): """Tests for two guests from one user.""" - self.create_dining_entry(self.user, has_paid=False, guest_name='Guest 1') - self.create_dining_entry(self.user, has_paid=False, guest_name='Guest 2') + self.create_dining_entry(self.user, has_paid=False, guest_name="Guest 1") + self.create_dining_entry(self.user, has_paid=False, guest_name="Guest 2") self.assertEqual(list(self.form.get_user_recipients()), []) self.assertEqual( - self.form.get_guest_recipients(), {self.user: ['Guest 1', 'Guest 2']} + self.form.get_guest_recipients(), {self.user: ["Guest 1", "Guest 2"]} ) def test_arbitrary(self): @@ -89,21 +89,21 @@ def test_arbitrary(self): """ # Create 4 users. u = [ - User.objects.create(username=f'{i}', email=f'{i}@localhost') + User.objects.create(username=f"{i}", email=f"{i}@localhost") for i in range(4) ] # Create 1 of each possible case. self.create_dining_entry(u[0], has_paid=False) self.create_dining_entry(u[1], has_paid=True) - self.create_dining_entry(u[2], has_paid=False, guest_name='Guest 1') + self.create_dining_entry(u[2], has_paid=False, guest_name="Guest 1") self.create_dining_entry( - u[2], has_paid=False, guest_name='Guest 2' + u[2], has_paid=False, guest_name="Guest 2" ) # Same user, different guest - self.create_dining_entry(u[3], has_paid=True, guest_name='Guest 3') + self.create_dining_entry(u[3], has_paid=True, guest_name="Guest 3") self.assertEqual(list(self.form.get_user_recipients()), [u[0]]) self.assertEqual( - self.form.get_guest_recipients(), {u[2]: ['Guest 1', 'Guest 2']} + self.form.get_guest_recipients(), {u[2]: ["Guest 1", "Guest 2"]} ) # For construct_messages() we just confirm that it has the correct @@ -113,7 +113,7 @@ def test_arbitrary(self): request = HttpRequest() request.user = u[0] messages = self.form.construct_messages(request) - self.assertEqual([m.to for m in messages], [['0@localhost'], ['2@localhost']]) + self.assertEqual([m.to for m in messages], [["0@localhost"], ["2@localhost"]]) class SendReminderFormLockTestCase(TransactionTestCase): @@ -123,14 +123,14 @@ class SendReminderFormLockTestCase(TransactionTestCase): code with a database transaction. """ - @skipUnlessDBFeature('has_select_for_update', 'has_select_for_update_nowait') + @skipUnlessDBFeature("has_select_for_update", "has_select_for_update_nowait") def test_send_reminder(self): """Tests that send_reminder() doesn't send multiple emails simultaneously.""" form = SendReminderForm( {}, dining_list=DiningList.objects.create( date=date(2020, 1, 1), - association=Association.objects.create(slug='assoc'), + association=Association.objects.create(slug="assoc"), sign_up_deadline=datetime(2020, 1, 1, 12, 0, tzinfo=timezone.utc), ), ) @@ -157,7 +157,7 @@ def send_again(): except BaseException as e: self.exc = e # This might be necessary according to https://stackoverflow.com/a/1346401/2373688. - connections['default'].close() + connections["default"].close() t = threading.Thread(target=send_again) t.start() diff --git a/dining/tests/test_models.py b/dining/tests/test_models.py index 2cdaddbe..6e54028a 100644 --- a/dining/tests/test_models.py +++ b/dining/tests/test_models.py @@ -13,7 +13,7 @@ class DiningListTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user('ankie') + cls.user = User.objects.create_user("ankie") cls.association = Association.objects.create() def setUp(self): @@ -32,19 +32,19 @@ def test_is_open(self): with patch.object( timezone, - 'now', + "now", return_value=datetime(2015, 1, 1, 16, 59, tzinfo=timezone.utc), ): self.assertTrue(list.is_open()) with patch.object( timezone, - 'now', + "now", return_value=datetime(2015, 1, 1, 17, 00, tzinfo=timezone.utc), ): self.assertFalse(list.is_open()) with patch.object( timezone, - 'now', + "now", return_value=datetime(2015, 1, 1, 17, 1, tzinfo=timezone.utc), ): self.assertFalse(list.is_open()) @@ -69,7 +69,7 @@ def test_is_owner_false(self): class DiningListCleanTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user('ankie') + cls.user = User.objects.create_user("ankie") cls.association = Association.objects.create() def setUp(self): @@ -90,10 +90,10 @@ def test_sign_up_deadline_after_date(self): class DiningEntryTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user('piet') + cls.user = User.objects.create_user("piet") cls.dining_list = DiningList.objects.create( date=date(2123, 2, 1), - association=Association.objects.create(slug='assoc'), + association=Association.objects.create(slug="assoc"), sign_up_deadline=datetime(2100, 1, 1, tzinfo=timezone.utc), ) @@ -116,7 +116,7 @@ def test_external_name(self): entry = DiningEntry( user=self.user, dining_list=self.dining_list, - external_name='Piet', + external_name="Piet", created_by=self.user, ) entry.full_clean() # No ValidationError diff --git a/dining/urls.py b/dining/urls.py index adaf785b..cf7bdc91 100644 --- a/dining/urls.py +++ b/dining/urls.py @@ -4,46 +4,46 @@ from .views import DayView, index urlpatterns = [ - path('', index, name='index'), - path('csv/', views.DailyDinersCSVView.as_view(), name="diners_csv"), + path("", index, name="index"), + path("csv/", views.DailyDinersCSVView.as_view(), name="diners_csv"), path( - '///', + "///", include( [ - path('', DayView.as_view(), name='day_view'), - path('add/', views.NewSlotView.as_view(), name='new_slot'), + path("", DayView.as_view(), name="day_view"), + path("add/", views.NewSlotView.as_view(), name="new_slot"), path( - '/', + "/", include( [ - path('', views.SlotInfoView.as_view(), name='slot_details'), + path("", views.SlotInfoView.as_view(), name="slot_details"), path( - 'list/', views.SlotListView.as_view(), name='slot_list' + "list/", views.SlotListView.as_view(), name="slot_list" ), path( - 'allergy/', + "allergy/", views.SlotAllergyView.as_view(), - name='slot_allergy', + name="slot_allergy", ), path( - 'entry/add/', + "entry/add/", views.EntryAddView.as_view(), - name='entry_add', + name="entry_add", ), path( - 'change/', + "change/", views.SlotInfoChangeView.as_view(), - name='slot_change', + name="slot_change", ), path( - 'delete/', + "delete/", views.SlotDeleteView.as_view(), - name='slot_delete', + name="slot_delete", ), path( - 'inform_payment/', + "inform_payment/", views.SlotPaymentView.as_view(), - name='slot_inform_payment', + name="slot_inform_payment", ), ] ), @@ -52,6 +52,6 @@ ), ), path( - 'entries//delete/', views.EntryDeleteView.as_view(), name='entry_delete' + "entries//delete/", views.EntryDeleteView.as_view(), name="entry_delete" ), ] diff --git a/dining/views.py b/dining/views.py index 37b5a105..2af5dec3 100644 --- a/dining/views.py +++ b/dining/views.py @@ -44,7 +44,7 @@ def index(request): d = sequenced_date.upcoming() - return redirect('day_view', year=d.year, month=d.month, day=d.day) + return redirect("day_view", year=d.year, month=d.month, day=d.day) class DayMixin: @@ -56,8 +56,8 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update( { - 'date': self.date, - 'date_diff': ( + "date": self.date, + "date_diff": ( self.date - date.today() ).days, # Nr. of days between date and today } @@ -71,10 +71,10 @@ def init_date(self): return try: self.date = sequenced_date.fromdate( - date(self.kwargs['year'], self.kwargs['month'], self.kwargs['day']) + date(self.kwargs["year"], self.kwargs["month"], self.kwargs["day"]) ) except ValueError: - raise Http404('Invalid date') + raise Http404("Invalid date") def dispatch(self, request, *args, **kwargs): """Initializes date before get/post is called.""" @@ -84,9 +84,9 @@ def dispatch(self, request, *args, **kwargs): def reverse(self, *args, kwargs=None, **other_kwargs): """URL reverse which expands the date.""" kwargs = kwargs or {} - kwargs['year'] = self.date.year - kwargs['month'] = self.date.month - kwargs['day'] = self.date.day + kwargs["year"] = self.date.year + kwargs["month"] = self.date.month + kwargs["day"] = self.date.day return reverse(*args, kwargs=kwargs, **other_kwargs) @@ -103,11 +103,11 @@ class DayView(LoginRequiredMixin, DayMixin, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['dining_lists'] = DiningList.objects.filter(date=self.date) - context['Announcements'] = DiningDayAnnouncement.objects.filter(date=self.date) + context["dining_lists"] = DiningList.objects.filter(date=self.date) + context["Announcements"] = DiningDayAnnouncement.objects.filter(date=self.date) # Make the view clickable - context['interactive'] = True + context["interactive"] = True return context @@ -121,23 +121,23 @@ def get(self, request, *args, **kwargs): return HttpResponseForbidden # Get the end date - date_end = request.GET.get('to', None) + date_end = request.GET.get("to", None) if date_end: # Why do you use datetime here and not date? - date_end = datetime.strptime(date_end, '%d/%m/%y') + date_end = datetime.strptime(date_end, "%d/%m/%y") else: date_end = timezone.now() # Filter on a start date - date_start = request.GET.get('from', None) + date_start = request.GET.get("from", None) if date_start: - date_start = datetime.strptime(date_start, '%d/%m/%y') + date_start = datetime.strptime(date_start, "%d/%m/%y") else: date_start = date_end # Count all dining entries in the given period entry_count = Count( - 'diningentry', + "diningentry", filter=( Q(diningentry__dining_list__date__lte=date_end) & Q(diningentry__dining_list__date__gte=date_start) @@ -149,21 +149,21 @@ def get(self, request, *args, **kwargs): users = users.filter(diningentry_count__gt=0) # Get the related membership objects for speed optimisation - users.select_related('usermembership') + users.select_related("usermembership") # Get all associations associations = Association.objects.all() # Create the CSV file # Set up - response = HttpResponse(content_type='text/csv') + response = HttpResponse(content_type="text/csv") response[ - 'Content-Disposition' + "Content-Disposition" ] = 'attachment; filename="association_members.csv"' csv_writer = csv.writer(response) # Write header - header = ['Name', 'Joined'] + header = ["Name", "Joined"] for association in associations: header += [association.name] @@ -196,7 +196,7 @@ def get_context_data(self, **kwargs): context = super().get_context_data() context.update( { - 'slot_form': CreateSlotForm( + "slot_form": CreateSlotForm( self.request.user, instance=DiningList(date=self.date) ) } @@ -206,12 +206,12 @@ def get_context_data(self, **kwargs): def post(self, request, *args, **kwargs): context = self.get_context_data() - context['slot_form'] = CreateSlotForm( + context["slot_form"] = CreateSlotForm( request.user, request.POST, instance=DiningList(date=self.date) ) - if context['slot_form'].is_valid(): - dining_list = context['slot_form'].save() + if context["slot_form"].is_valid(): + dining_list = context["slot_form"].save() messages.success(request, "You successfully created a new dining list") return redirect(dining_list) @@ -227,7 +227,7 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update( { - 'dining_list': self.dining_list, + "dining_list": self.dining_list, } ) return context @@ -240,7 +240,7 @@ def init_dining_list(self): # Needs initialized date self.init_date() self.dining_list = get_object_or_404( - DiningList, date=self.date, association__slug=self.kwargs['identifier'] + DiningList, date=self.date, association__slug=self.kwargs["identifier"] ) def dispatch(self, request, *args, **kwargs): @@ -249,7 +249,7 @@ def dispatch(self, request, *args, **kwargs): def reverse(self, *args, kwargs=None, **other_kwargs): kwargs = kwargs or {} - kwargs['identifier'] = self.dining_list.association.slug + kwargs["identifier"] = self.dining_list.association.slug return super().reverse(*args, kwargs=kwargs, **other_kwargs) @@ -259,15 +259,15 @@ class UpdateSlotViewTrackerMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Get the amount of messages - context['comments_total'] = self.dining_list.diningcomment_set.count() + context["comments_total"] = self.dining_list.diningcomment_set.count() # Get the amount of unread messages view_time = DiningCommentVisitTracker.get_latest_visit( user=self.request.user, dining_list=self.dining_list ) if view_time is None: - context['comments_unread'] = context['comments_total'] + context["comments_unread"] = context["comments_total"] else: - context['comments_unread'] = self.dining_list.diningcomment_set.filter( + context["comments_unread"] = self.dining_list.diningcomment_set.filter( timestamp__gte=view_time ).count() @@ -290,8 +290,8 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update( { - 'user_form': DiningEntryInternalForm(), - 'external_form': DiningEntryExternalForm(), + "user_form": DiningEntryInternalForm(), + "external_form": DiningEntryExternalForm(), } ) return context @@ -301,7 +301,7 @@ def post(self, request, *args, **kwargs): # to write HTML for displaying these errors (they are already in a Django message). # Do form shenanigans - if 'add_external' in request.POST: + if "add_external" in request.POST: entry = DiningEntry( user=request.user, dining_list=self.dining_list, created_by=request.user ) @@ -316,9 +316,9 @@ def post(self, request, *args, **kwargs): # The entry is for another existing user, send a mail to them. if entry.is_internal() and entry.user != request.user: send_templated_mail( - 'mail/dining_entry_added_by', + "mail/dining_entry_added_by", entry.user, - context={'entry': entry, 'dining_list': entry.dining_list}, + context={"entry": entry, "dining_list": entry.dining_list}, request=request, ) messages.success( @@ -374,20 +374,20 @@ def post(self, request, *args, **kwargs): # Send a mail when someone else does the removal if entry.user != request.user: context = { - 'entry': entry, - 'dining_list': entry.dining_list, - 'remover': request.user, + "entry": entry, + "dining_list": entry.dining_list, + "remover": request.user, } if entry.is_external(): send_templated_mail( - 'mail/dining_entry_external_removed_by', + "mail/dining_entry_external_removed_by", entry.user, context, request, ) else: send_templated_mail( - 'mail/dining_entry_removed_by', entry.user, context, request + "mail/dining_entry_removed_by", entry.user, context, request ) else: @@ -395,7 +395,7 @@ def post(self, request, *args, **kwargs): messages.error(request, error) # Go to next - next_url = request.GET.get('next') + next_url = request.GET.get("next") if url_has_allowed_host_and_scheme(next_url, request.get_host()): return HttpResponseRedirect(next_url) else: @@ -417,10 +417,10 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update( { - 'entries': self.dining_list.dining_entries.order_by( - 'user__first_name', 'user__last_name', 'external_name' + "entries": self.dining_list.dining_entries.order_by( + "user__first_name", "user__last_name", "external_name" ), - 'can_edit_stats': self.can_edit_stats(), + "can_edit_stats": self.can_edit_stats(), } ) return context @@ -436,22 +436,22 @@ def post(self, request, *args, **kwargs): # dining entries across all lists. entry = get_object_or_404( - DiningEntry, id=request.POST.get('entry_id'), dining_list=self.dining_list + DiningEntry, id=request.POST.get("entry_id"), dining_list=self.dining_list ) # We toggle the given stat value, based on the previous value as was submitted by the form. - stat = request.POST.get('toggle') - if stat == 'shopped': - entry.has_shopped = not bool(request.POST.get('shopped_val')) - elif stat == 'cooked': - entry.has_cooked = not bool(request.POST.get('cooked_val')) - elif stat == 'cleaned': - entry.has_cleaned = not bool(request.POST.get('cleaned_val')) - elif stat == 'paid': - entry.has_paid = not bool(request.POST.get('paid_val')) + stat = request.POST.get("toggle") + if stat == "shopped": + entry.has_shopped = not bool(request.POST.get("shopped_val")) + elif stat == "cooked": + entry.has_cooked = not bool(request.POST.get("cooked_val")) + elif stat == "cleaned": + entry.has_cleaned = not bool(request.POST.get("cleaned_val")) + elif stat == "paid": + entry.has_paid = not bool(request.POST.get("paid_val")) entry.save() - return HttpResponseRedirect(self.reverse('slot_list')) + return HttpResponseRedirect(self.reverse("slot_list")) class SlotInfoView( @@ -464,7 +464,7 @@ def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs.update( { - 'instance': DiningComment( + "instance": DiningComment( dining_list=self.dining_list, poster=self.request.user ), } @@ -472,20 +472,20 @@ def get_form_kwargs(self): return kwargs def get_success_url(self): - return self.reverse('slot_details') + return self.reverse("slot_details") def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update( { - 'comments': self.dining_list.diningcomment_set.order_by( - '-pinned_to_top', 'timestamp' + "comments": self.dining_list.diningcomment_set.order_by( + "-pinned_to_top", "timestamp" ).all(), - 'last_visited': DiningCommentVisitTracker.get_latest_visit( + "last_visited": DiningCommentVisitTracker.get_latest_visit( user=self.request.user, dining_list=self.dining_list, update=True ), - 'number_of_allergies': self.dining_list.internal_dining_entries() - .exclude(user__allergies='') + "number_of_allergies": self.dining_list.internal_dining_entries() + .exclude(user__allergies="") .count(), } ) @@ -501,9 +501,9 @@ class SlotAllergyView(SlotMixin, TemplateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - entries = self.dining_list.internal_dining_entries().exclude(user__allergies='') + entries = self.dining_list.internal_dining_entries().exclude(user__allergies="") context.update( - {'allergy_entries': entries.order_by('user__first_name', 'user__last_name')} + {"allergy_entries": entries.order_by("user__first_name", "user__last_name")} ) return context @@ -530,9 +530,9 @@ def get_context_data(self, **kwargs): context.update( { - 'info_form': DiningInfoForm(instance=self.dining_list, prefix='info'), - 'payment_form': DiningPaymentForm( - instance=self.dining_list, prefix='payment' + "info_form": DiningInfoForm(instance=self.dining_list, prefix="info"), + "payment_form": DiningPaymentForm( + instance=self.dining_list, prefix="payment" ), } ) @@ -555,10 +555,10 @@ def post(self, request, *args, **kwargs): """ info_form = DiningInfoForm( - request.POST, instance=self.dining_list, prefix='info' + request.POST, instance=self.dining_list, prefix="info" ) payment_form = DiningPaymentForm( - request.POST, instance=self.dining_list, prefix='payment' + request.POST, instance=self.dining_list, prefix="payment" ) # Save and redirect if forms are valid, stay otherwise @@ -567,12 +567,12 @@ def post(self, request, *args, **kwargs): payment_form.save() messages.success(request, "Changes successfully saved") - return HttpResponseRedirect(self.reverse('slot_details')) + return HttpResponseRedirect(self.reverse("slot_details")) context.update( { - 'info_form': info_form, - 'payment_form': payment_form, + "info_form": info_form, + "payment_form": payment_form, } ) @@ -592,7 +592,7 @@ def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs.update( { - 'instance': self.dining_list, + "instance": self.dining_list, } ) return kwargs @@ -621,11 +621,11 @@ def get(self, request, *args, **kwargs): def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs['dining_list'] = self.dining_list + kwargs["dining_list"] = self.dining_list return kwargs def get_success_url(self): - return self.reverse('slot_details') + return self.reverse("slot_details") def form_invalid(self, form): for e in form.non_field_errors(): diff --git a/general/apps.py b/general/apps.py index fbd6c0e2..59b88c87 100644 --- a/general/apps.py +++ b/general/apps.py @@ -2,4 +2,4 @@ class GeneralConfig(AppConfig): - name = 'general' + name = "general" diff --git a/general/forms.py b/general/forms.py index 281b3a41..b0cc2aa8 100644 --- a/general/forms.py +++ b/general/forms.py @@ -16,17 +16,17 @@ def __init__(self, *args, initial=None, **kwargs): if initial is None: initial = {} - initial.setdefault('date_end', timezone.now()) + initial.setdefault("date_end", timezone.now()) initial.setdefault( - 'date_start', initial['date_end'] - timezone.timedelta(days=365) + "date_start", initial["date_end"] - timezone.timedelta(days=365) ) super().__init__(*args, initial=initial, **kwargs) def clean(self): cleaned_data = super().clean() - date_start = cleaned_data.get('date_start') - date_end = cleaned_data.get('date_end') + date_start = cleaned_data.get("date_start") + date_end = cleaned_data.get("date_end") if date_start and date_end and date_start > date_end: raise ValidationError( "The end date is further in the past than the starting date" @@ -42,17 +42,17 @@ class ConcurrenflictFormMixin: displays what has been changed. """ - concurrenflict_field_name = 'concurrenflict_initial' - _concurrenflict_json_data = '' + concurrenflict_field_name = "concurrenflict_initial" + _concurrenflict_json_data = "" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields[self.concurrenflict_field_name] = forms.CharField( widget=forms.HiddenInput, label="", required=False ) - instance = kwargs.get('instance', None) + instance = kwargs.get("instance", None) if instance: - self._concurrenflict_json_data = serializers.serialize('json', [instance]) + self._concurrenflict_json_data = serializers.serialize("json", [instance]) self.fields[ self.concurrenflict_field_name ].initial = self._concurrenflict_json_data @@ -76,14 +76,14 @@ def clean(self): # noqa: C901 json_data_before = json.loads(json_at_get) json_data_after = json.loads(json_at_post) - serial_data_before = next(serializers.deserialize('json', json_at_get)) + serial_data_before = next(serializers.deserialize("json", json_at_get)) model_before = serial_data_before.object m2m_before = serial_data_before.m2m_data - serial_data_after = next(serializers.deserialize('json', json_at_post)) + serial_data_after = next(serializers.deserialize("json", json_at_post)) model_after = serial_data_after.object m2m_after = serial_data_after.m2m_data - fake_form = self.__class__(instance=model_after, prefix='concurrenflict') + fake_form = self.__class__(instance=model_after, prefix="concurrenflict") for field in list(model_before._meta.fields) + list(m2m_before.keys()): try: @@ -94,11 +94,11 @@ def clean(self): # noqa: C901 continue if key not in fake_form.fields.keys(): continue - json_value_before = json_data_before[0]['fields'].get(key, None) - json_value_after = json_data_after[0]['fields'].get(key, None) + json_value_before = json_data_before[0]["fields"].get(key, None) + json_value_after = json_data_after[0]["fields"].get(key, None) if json_value_after != json_value_before: # value_before = getattr(model_before, key, m2m_before.get(key)) - value_after = getattr(model_after, key, m2m_after.get(key, '')) + value_after = getattr(model_after, key, m2m_after.get(key, "")) have_diff = True # fake_form.data[key] = value_after # js_fix = ''' @@ -113,14 +113,14 @@ def clean(self): # noqa: C901 # ''' % {'html_name': fake_form[key].html_name} if key in m2m_after: - value_after_string = ', '.join( + value_after_string = ", ".join( [str(v) for v in value_after.all()] ) else: value_after_string = str(value_after) # temp_field = fake_form[key] msg = mark_safe( - u'This field has been changed by someone else to: %s' + "This field has been changed by someone else to: %s" % (value_after_string,) ) self.add_error(key, msg) diff --git a/general/mail_control.py b/general/mail_control.py index 483e1974..97d0cf52 100644 --- a/general/mail_control.py +++ b/general/mail_control.py @@ -16,13 +16,13 @@ def get_mail_context( # This is how Django does it with their password reset email current_site = get_current_site(request) use_https = request.is_secure() if request else False - protocol = 'https' if use_https else 'http' + protocol = "https" if use_https else "http" return { - 'domain': current_site.domain, - 'site_name': current_site.name, - 'recipient': recipient, - 'protocol': protocol, - 'site_uri': '{}://{}'.format(protocol, current_site.domain), + "domain": current_site.domain, + "site_name": current_site.name, + "recipient": recipient, + "protocol": protocol, + "site_uri": "{}://{}".format(protocol, current_site.domain), **(extra_context or {}), } @@ -42,19 +42,19 @@ def construct_templated_mail( # Render templates local_context = get_mail_context(recipient, context, request) subject = render_to_string( - template_dir + '/subject.txt', context=local_context, request=request + template_dir + "/subject.txt", context=local_context, request=request ).strip() html_body = render_to_string( - template_dir + '/body.html', context=local_context, request=request + template_dir + "/body.html", context=local_context, request=request ) text_body = render_to_string( - template_dir + '/body.txt', context=local_context, request=request + template_dir + "/body.txt", context=local_context, request=request ) # Create message message = EmailMultiAlternatives( subject=subject, body=text_body, to=[recipient.email] ) - message.attach_alternative(html_body, 'text/html') + message.attach_alternative(html_body, "text/html") messages.append(message) return messages diff --git a/general/urls.py b/general/urls.py index 3c28aee6..e9a58cd6 100644 --- a/general/urls.py +++ b/general/urls.py @@ -2,13 +2,13 @@ from . import views urlpatterns = [ - path('updates/', views.SiteUpdateView.as_view(), name='site_updates'), - path('help/', views.HelpPageView.as_view(), name='help_page'), - path('rules/', views.RulesPageView.as_view(), name='rules_and_regulations'), + path("updates/", views.SiteUpdateView.as_view(), name="site_updates"), + path("help/", views.HelpPageView.as_view(), name="help_page"), + path("rules/", views.RulesPageView.as_view(), name="rules_and_regulations"), path( - 'upgrade_instructions/', + "upgrade_instructions/", views.UpgradeBalanceInstructionsView.as_view(), - name='upgrade_instructions', + name="upgrade_instructions", ), - path('mail_layout/', views.EmailTemplateView.as_view()), + path("mail_layout/", views.EmailTemplateView.as_view()), ] diff --git a/general/util.py b/general/util.py index 5cfce6e5..91b10994 100644 --- a/general/util.py +++ b/general/util.py @@ -36,7 +36,7 @@ def optgroups(self, name, value, attrs=None): name, option_value, option_label, False, index, attrs=attrs ) # Modify option so that it is disabled - option['attrs']['disabled'] = 'disabled' + option["attrs"]["disabled"] = "disabled" # Add option to group groups.append((None, [option], index)) index += 1 diff --git a/general/views.py b/general/views.py index 72318b8a..b2424707 100644 --- a/general/views.py +++ b/general/views.py @@ -22,20 +22,20 @@ class DateRangeFilterMixin: date_range_form = None def dispatch(self, request, *args, **kwargs): - if 'date_start' in request.GET and 'date_end' in request.GET: + if "date_start" in request.GET and "date_end" in request.GET: self.date_range_form = DateRangeForm(request.GET) else: self.date_range_form = DateRangeForm() if self.date_range_form.is_valid(): - self.date_start = self.date_range_form.cleaned_data['date_start'] - self.date_end = self.date_range_form.cleaned_data['date_end'] + self.date_start = self.date_range_form.cleaned_data["date_start"] + self.date_end = self.date_range_form.cleaned_data["date_end"] return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['date_range_form'] = self.date_range_form + context["date_range_form"] = self.date_range_form return context @@ -46,29 +46,29 @@ class SiteUpdateView(LoginRequiredMixin, ListView): paginate_by = 4 def get_queryset(self): - return SiteUpdate.objects.order_by('-date').all() + return SiteUpdate.objects.order_by("-date").all() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) try: - latest_update = SiteUpdate.objects.latest('date').date + latest_update = SiteUpdate.objects.latest("date").date except ObjectDoesNotExist: latest_update = timezone.now() - context['latest_visit'] = PageVisitTracker.get_latest_visit( - 'updates', self.request.user, update=True + context["latest_visit"] = PageVisitTracker.get_latest_visit( + "updates", self.request.user, update=True ) - context['latest_update'] = latest_update + context["latest_update"] = latest_update return context @staticmethod def has_new_update(user): """Checks whether a new update for the given user is present.""" - visit_timestamp = PageVisitTracker.get_latest_visit('updates', user) + visit_timestamp = PageVisitTracker.get_latest_visit("updates", user) if visit_timestamp is None: return False - return SiteUpdate.objects.latest('date').date > visit_timestamp + return SiteUpdate.objects.latest("date").date > visit_timestamp class HelpPageView(TemplateView): @@ -78,13 +78,13 @@ def get_context_data(self, **kwargs): """Loads app build date from file.""" context = super().get_context_data(**kwargs) - build_date = getenv('BUILD_TIMESTAMP') + build_date = getenv("BUILD_TIMESTAMP") if build_date: build_date = datetime.fromtimestamp(float(build_date), timezone.utc) context.update( { - 'build_date': build_date, - 'commit_sha': getenv('COMMIT_SHA'), + "build_date": build_date, + "commit_sha": getenv("COMMIT_SHA"), } ) return context @@ -98,17 +98,17 @@ class RulesPageView(View): def get(self, request): # Store the recent updates/visit data in the local context if request.user.is_authenticated: - self.context['latest_visit'] = PageVisitTracker.get_latest_visit( - 'rules', request.user, update=True + self.context["latest_visit"] = PageVisitTracker.get_latest_visit( + "rules", request.user, update=True ) - self.context['latest_update'] = self.change_date + self.context["latest_update"] = self.change_date return render(request, self.template, self.context) @staticmethod def has_new_update(user): """Checks whether a new update for the given user is present.""" - visit_timestamp = PageVisitTracker.get_latest_visit('rules', user) + visit_timestamp = PageVisitTracker.get_latest_visit("rules", user) if visit_timestamp is None: return False @@ -122,15 +122,15 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) if self.request.user.is_authenticated: # Separated for a possible prefilter to be implemented later (e.g. if active in kitchen) - associations = Association.objects.order_by('slug') - context['user_associations'] = associations.filter( + associations = Association.objects.order_by("slug") + context["user_associations"] = associations.filter( usermembership__related_user=self.request.user ) - context['other_associations'] = associations.exclude( - id__in=context['user_associations'].values_list('id', flat=True) + context["other_associations"] = associations.exclude( + id__in=context["user_associations"].values_list("id", flat=True) ) else: - context['other_associations'] = Association.objects.all() + context["other_associations"] = Association.objects.all() return context @@ -189,7 +189,7 @@ def get(self, request): if not request.user.is_superuser: return HttpResponseForbidden("You do not have permission to view this") - template_location = request.GET.get('template', None) + ".html" + template_location = request.GET.get("template", None) + ".html" try: get_template(template_location) @@ -197,6 +197,6 @@ def get(self, request): return Http404("Given template name not found") context = self.ContentFactory(dictionary=request.GET.dict()) - context['request'] = request - context['user'] = request.user + context["request"] = request + context["user"] = request.user return render(None, template_location, context) diff --git a/scaladining/apps.py b/scaladining/apps.py index 6e80f0c1..c539a9b3 100644 --- a/scaladining/apps.py +++ b/scaladining/apps.py @@ -2,4 +2,4 @@ class MyAdminConfig(AdminConfig): - default_site = 'scaladining.admin.MyAdminSite' + default_site = "scaladining.admin.MyAdminSite" diff --git a/scaladining/context_processors.py b/scaladining/context_processors.py index a40106f7..9cae730c 100644 --- a/scaladining/context_processors.py +++ b/scaladining/context_processors.py @@ -4,5 +4,5 @@ def scala(request): """Adds some variables to every template context.""" return { - 'MINIMUM_BALANCE_FOR_DINING_SIGN_UP': settings.MINIMUM_BALANCE_FOR_DINING_SIGN_UP + "MINIMUM_BALANCE_FOR_DINING_SIGN_UP": settings.MINIMUM_BALANCE_FOR_DINING_SIGN_UP } diff --git a/scaladining/scala_settings.py b/scaladining/scala_settings.py index 080dc639..0f91209c 100644 --- a/scaladining/scala_settings.py +++ b/scaladining/scala_settings.py @@ -3,7 +3,7 @@ # Maximum number of slots on each date MAX_SLOT_NUMBER = 3 -KITCHEN_COST = Decimal('0.50') +KITCHEN_COST = Decimal("0.50") MIN_SLOT_DINER_MAXIMUM = 12 @@ -17,8 +17,8 @@ KITCHEN_USE_END_TIME = time(19, 30) # Balance bottom limit -MINIMUM_BALANCE_FOR_DINING_SIGN_UP = Decimal('-2.00') + KITCHEN_COST -MINIMUM_BALANCE_FOR_DINING_SLOT_CLAIM = Decimal('-2.00') + KITCHEN_COST +MINIMUM_BALANCE_FOR_DINING_SIGN_UP = Decimal("-2.00") + KITCHEN_COST +MINIMUM_BALANCE_FOR_DINING_SLOT_CLAIM = Decimal("-2.00") + KITCHEN_COST # The duration that pending transactions should last TRANSACTION_PENDING_DURATION = timedelta(days=2) diff --git a/scaladining/settings.py b/scaladining/settings.py index 1cd19b08..579a1aa5 100644 --- a/scaladining/settings.py +++ b/scaladining/settings.py @@ -15,131 +15,131 @@ env = Env() -@env.parser_for('file') +@env.parser_for("file") def file_parser(value): if not value: - return '' + return "" with open(value) as f: return f.read() -env.read_env(path=os.path.join(BASE_DIR, '.env'), recurse=False) +env.read_env(path=os.path.join(BASE_DIR, ".env"), recurse=False) -DEBUG = env.bool('DINING_DEBUG', default=False) +DEBUG = env.bool("DINING_DEBUG", default=False) -SECRET_KEY = env.str('DINING_SECRET_KEY', default='') or env.file( - 'DINING_SECRET_KEY_FILE' +SECRET_KEY = env.str("DINING_SECRET_KEY", default="") or env.file( + "DINING_SECRET_KEY_FILE" ) -ALLOWED_HOSTS = env.list('DINING_ALLOWED_HOSTS', default='') +ALLOWED_HOSTS = env.list("DINING_ALLOWED_HOSTS", default="") -AUTH_USER_MODEL = 'userdetails.User' +AUTH_USER_MODEL = "userdetails.User" # django.contrib.admin is replaced by scaladining.apps.MyAdminConfig INSTALLED_APPS = [ - 'whitenoise.runserver_nostatic', # For static files - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.humanize', - 'django.contrib.sites', - 'dining.apps.DiningConfig', - 'creditmanagement.apps.CreditManagementConfig', - 'general.apps.GeneralConfig', - 'scaladining.apps.MyAdminConfig', - 'allauth.account', # This needs to be before userdetails due to admin.site.unregister - 'userdetails.apps.UserDetailsConfig', - 'dal', - 'dal_select2', - 'widget_tweaks', - 'allauth', - 'allauth.socialaccount', - 'allauthproviders.quadrivium', - 'fontawesomefree', + "whitenoise.runserver_nostatic", # For static files + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.humanize", + "django.contrib.sites", + "dining.apps.DiningConfig", + "creditmanagement.apps.CreditManagementConfig", + "general.apps.GeneralConfig", + "scaladining.apps.MyAdminConfig", + "allauth.account", # This needs to be before userdetails due to admin.site.unregister + "userdetails.apps.UserDetailsConfig", + "dal", + "dal_select2", + "widget_tweaks", + "allauth", + "allauth.socialaccount", + "allauthproviders.quadrivium", + "fontawesomefree", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'whitenoise.middleware.WhiteNoiseMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", # 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'scaladining.urls' +ROOT_URLCONF = "scaladining.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'assets/templates')], - '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', - 'scaladining.context_processors.scala', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [os.path.join(BASE_DIR, "assets/templates")], + "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", + "scaladining.context_processors.scala", ], }, }, ] # https://docs.djangoproject.com/en/4.1/ref/settings/#internal-ips -INTERNAL_IPS = env.list('DINING_INTERNAL_IPS', default='') +INTERNAL_IPS = env.list("DINING_INTERNAL_IPS", default="") -WSGI_APPLICATION = 'scaladining.wsgi.application' +WSGI_APPLICATION = "scaladining.wsgi.application" -LOGIN_REDIRECT_URL = '/' -LOGOUT_REDIRECT_URL = '/' +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = "/" -STATIC_ROOT = env.str('DINING_STATIC_ROOT', default=os.path.join(BASE_DIR, 'static')) -STATIC_URL = '/static/' -STATICFILES_DIRS = [os.path.join(BASE_DIR, 'assets/static')] +STATIC_ROOT = env.str("DINING_STATIC_ROOT", default=os.path.join(BASE_DIR, "static")) +STATIC_URL = "/static/" +STATICFILES_DIRS = [os.path.join(BASE_DIR, "assets/static")] # STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' -MEDIA_ROOT = env.str('DINING_MEDIA_ROOT', default=os.path.join(BASE_DIR, 'uploads')) -MEDIA_URL = env.str('DINING_MEDIA_URL', default='/media/') +MEDIA_ROOT = env.str("DINING_MEDIA_ROOT", default=os.path.join(BASE_DIR, "uploads")) +MEDIA_URL = env.str("DINING_MEDIA_URL", default="/media/") DATABASES = { - 'default': env.dj_db_url('DINING_DATABASE_URL', default='sqlite:///db.sqlite3') + "default": env.dj_db_url("DINING_DATABASE_URL", default="sqlite:///db.sqlite3") } -if not DATABASES['default'].get('PASSWORD'): - DATABASES['default']['PASSWORD'] = env.file( - 'DINING_DATABASE_PASSWORD_FILE', default='' +if not DATABASES["default"].get("PASSWORD"): + DATABASES["default"]["PASSWORD"] = env.file( + "DINING_DATABASE_PASSWORD_FILE", default="" ) AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # Internationalization -TIME_ZONE = 'Europe/Amsterdam' +TIME_ZONE = "Europe/Amsterdam" # Automatic Dutch localization with English language is difficult, # so we'll set the date formats manually to Dutch style. -DATE_FORMAT = 'l j F' # Default: N j, Y -SHORT_DATE_FORMAT = 'd-m-Y' # Default: m/d/Y -DATETIME_FORMAT = 'N j, Y, G:i' # Default: N j, Y, P -SHORT_DATETIME_FORMAT = 'd-m-Y G:i' # Default: m/d/Y P +DATE_FORMAT = "l j F" # Default: N j, Y +SHORT_DATE_FORMAT = "d-m-Y" # Default: m/d/Y +DATETIME_FORMAT = "N j, Y, G:i" # Default: N j, Y, P +SHORT_DATETIME_FORMAT = "d-m-Y G:i" # Default: m/d/Y P USE_I18N = False USE_L10N = False @@ -147,33 +147,33 @@ def file_parser(value): USE_THOUSAND_SEPARATOR = True AUTHENTICATION_BACKENDS = [ - 'django.contrib.auth.backends.ModelBackend', - 'allauth.account.auth_backends.AuthenticationBackend', + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", ] SITE_ID = 1 # See https://github.com/migonzalvar/dj-email-url -email_config = env.dj_email_url('DINING_EMAIL_URL', default='console:') -EMAIL_HOST = email_config['EMAIL_HOST'] -EMAIL_PORT = email_config['EMAIL_PORT'] -EMAIL_HOST_USER = email_config['EMAIL_HOST_USER'] -EMAIL_HOST_PASSWORD = email_config['EMAIL_HOST_PASSWORD'] or env.file( - 'DINING_EMAIL_PASSWORD_FILE', default='' +email_config = env.dj_email_url("DINING_EMAIL_URL", default="console:") +EMAIL_HOST = email_config["EMAIL_HOST"] +EMAIL_PORT = email_config["EMAIL_PORT"] +EMAIL_HOST_USER = email_config["EMAIL_HOST_USER"] +EMAIL_HOST_PASSWORD = email_config["EMAIL_HOST_PASSWORD"] or env.file( + "DINING_EMAIL_PASSWORD_FILE", default="" ) -EMAIL_BACKEND = email_config['EMAIL_BACKEND'] -EMAIL_USE_TLS = email_config['EMAIL_USE_TLS'] -EMAIL_USE_SSL = email_config['EMAIL_USE_SSL'] +EMAIL_BACKEND = email_config["EMAIL_BACKEND"] +EMAIL_USE_TLS = email_config["EMAIL_USE_TLS"] +EMAIL_USE_SSL = email_config["EMAIL_USE_SSL"] -DEFAULT_FROM_EMAIL = env.str('DINING_DEFAULT_FROM_EMAIL', default='webmaster@localhost') -SERVER_EMAIL = env.str('DINING_SERVER_EMAIL', default='root@localhost') -ADMINS = getaddresses(env.list('DINING_ADMINS', default='')) +DEFAULT_FROM_EMAIL = env.str("DINING_DEFAULT_FROM_EMAIL", default="webmaster@localhost") +SERVER_EMAIL = env.str("DINING_SERVER_EMAIL", default="root@localhost") +ADMINS = getaddresses(env.list("DINING_ADMINS", default="")) # Allauth configuration ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_FORMS = { - 'change_password': 'userdetails.forms_allauth.CustomChangePasswordForm', - 'reset_password_from_key': 'userdetails.forms_allauth.CustomResetPasswordKeyForm', + "change_password": "userdetails.forms_allauth.CustomChangePasswordForm", + "reset_password_from_key": "userdetails.forms_allauth.CustomResetPasswordKeyForm", } ACCOUNT_EMAIL_VERIFICATION = "none" ACCOUNT_LOGIN_ON_PASSWORD_RESET = True @@ -183,11 +183,11 @@ def file_parser(value): SOCIALACCOUNT_ADAPTER = "userdetails.externalaccounts.SocialAccountAdapter" # HTTP security -if env.bool('DINING_COOKIE_SECURE', default=False): +if env.bool("DINING_COOKIE_SECURE", default=False): CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True # We're running behind a proxy -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") -DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/scaladining/urls.py b/scaladining/urls.py index a653ed18..7881b7a4 100644 --- a/scaladining/urls.py +++ b/scaladining/urls.py @@ -5,10 +5,10 @@ urlpatterns = [ - path('admin/', admin.site.urls), - path('credit/', include('creditmanagement.urls')), - path('site/', include('general.urls')), - path('', include('dining.urls')), - path('accounts/', include('userdetails.urls')), - path('accounts/', include('allauth.urls')), + path("admin/", admin.site.urls), + path("credit/", include("creditmanagement.urls")), + path("site/", include("general.urls")), + path("", include("dining.urls")), + path("accounts/", include("userdetails.urls")), + path("accounts/", include("allauth.urls")), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/userdetails/admin.py b/userdetails/admin.py index cee3930c..de39a144 100644 --- a/userdetails/admin.py +++ b/userdetails/admin.py @@ -19,16 +19,16 @@ class MemberOfFilter(admin.SimpleListFilter): # Human-readable title which will be displayed in the # right admin sidebar just above the filter options. - title = 'Member of association' + title = "Member of association" # Parameter for the filter that will be used in the URL query. - parameter_name = 'associationmember' + parameter_name = "associationmember" def lookups(self, request, model_admin): """Returns a list of tuples representing all the associations as displayed in the table.""" return Association.objects.all().values_list( - 'pk', - 'name', + "pk", + "name", ) def queryset(self, request, queryset): @@ -38,7 +38,7 @@ def queryset(self, request, queryset): # Find all members in the UserMemberships model containing the selected association a = UserMembership.objects.filter(association=self.value()).values_list( - 'related_user_id' + "related_user_id" ) return queryset.filter(pk__in=a) @@ -56,15 +56,15 @@ class Meta: @admin.register(UserOverview) class CustomUserAdmin(admin.ModelAdmin): - list_display = ('username', 'first_name', 'last_name', 'is_verified', 'last_login') - list_filter = [MemberOfFilter, ('groups', BoardFilter)] + list_display = ("username", "first_name", "last_name", "is_verified", "last_login") + list_filter = [MemberOfFilter, ("groups", BoardFilter)] - readonly_fields = ('date_joined', 'last_login') + readonly_fields = ("date_joined", "last_login") inlines = [AssociationLinks] - fields = ('username', ('first_name', 'last_name'), 'date_joined', 'email') + fields = ("username", ("first_name", "last_name"), "date_joined", "email") def send_test_mail(self, request, queryset): - send_templated_mail('mail/test', queryset, request=request) + send_templated_mail("mail/test", queryset, request=request) actions = [send_test_mail] @@ -77,7 +77,7 @@ class GroupAdminForm(forms.ModelForm): users = forms.ModelMultipleChoiceField( User.objects.all(), - widget=admin.widgets.FilteredSelectMultiple('Users', False), + widget=admin.widgets.FilteredSelectMultiple("Users", False), required=False, ) @@ -86,21 +86,21 @@ def __init__(self, *args, **kwargs): # find the users part of the group if self.instance.pk: - initial_users = self.instance.user_set.values_list('pk', flat=True) - self.initial['users'] = initial_users + initial_users = self.instance.user_set.values_list("pk", flat=True) + self.initial["users"] = initial_users def save(self, *args, **kwargs): - kwargs['commit'] = True + kwargs["commit"] = True return super(GroupAdminForm, self).save(*args, **kwargs) def save_m2m(self): self.instance.user_set.clear() - self.instance.user_set.add(*self.cleaned_data['users']) + self.instance.user_set.add(*self.cleaned_data["users"]) @admin.register(Association) class AssociationAdmin(admin.ModelAdmin): - exclude = ['permissions'] + exclude = ["permissions"] form = GroupAdminForm diff --git a/userdetails/apps.py b/userdetails/apps.py index 39e76501..07d567d0 100644 --- a/userdetails/apps.py +++ b/userdetails/apps.py @@ -2,7 +2,7 @@ class UserDetailsConfig(AppConfig): - name = 'userdetails' + name = "userdetails" def ready(self): # Import to register the receivers in this module diff --git a/userdetails/externalaccounts.py b/userdetails/externalaccounts.py index e27d22cc..c4acdaac 100644 --- a/userdetails/externalaccounts.py +++ b/userdetails/externalaccounts.py @@ -16,7 +16,7 @@ def _create_membership(socialaccount, request): social_app = socialaccount.get_provider().get_app(request) linked_associations = Association.objects.filter(social_app=social_app) if not linked_associations: - warnings.warn('No associations linked to the external account') + warnings.warn("No associations linked to the external account") for association in linked_associations: membership = UserMembership.objects.filter( related_user=user, association=association @@ -39,7 +39,7 @@ def _create_membership(socialaccount, request): @receiver(user_signed_up) def automatic_association_link(sender, request, user, **kwargs): """Creates membership when someone signs up using an association account.""" - sociallogin = kwargs.get('sociallogin', None) + sociallogin = kwargs.get("sociallogin", None) if not sociallogin: # Normal registration, not using association account return @@ -60,5 +60,5 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter): def save_user(self, request, sociallogin, form=None): u = super().save_user(request, sociallogin, form) - sociallogin.state['next'] = reverse('settings_account') + sociallogin.state["next"] = reverse("settings_account") return u diff --git a/userdetails/forms.py b/userdetails/forms.py index 2cab1127..d05bbd12 100644 --- a/userdetails/forms.py +++ b/userdetails/forms.py @@ -22,30 +22,30 @@ class RegisterUserForm(UserCreationForm): class Meta: model = User fields = ( - 'username', - 'password1', - 'password2', - 'email', - 'first_name', - 'last_name', - 'allergies', + "username", + "password1", + "password2", + "email", + "first_name", + "last_name", + "allergies", ) field_classes = { - 'username': UsernameField + "username": UsernameField } # This adds HTML attributes for semantics, see UserCreationForm. def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['first_name'].required = True - self.fields['last_name'].required = True + self.fields["first_name"].required = True + self.fields["last_name"].required = True # Set headings used during rendering. # # I don't like doing this here instead of in the template or view, but # I don't know how to do that cleanly. - self.fields['username'].heading = 'Account details' - self.fields['first_name'].heading = 'Personal details' - self.fields['associations'].heading = 'Memberships' + self.fields["username"].heading = "Account details" + self.fields["first_name"].heading = "Personal details" + self.fields["associations"].heading = "Memberships" def save(self, commit=True): """Saves user and creates the memberships.""" @@ -53,7 +53,7 @@ def save(self, commit=True): if commit: with transaction.atomic(): user.save() - for association in self.cleaned_data['associations']: + for association in self.cleaned_data["associations"]: UserMembership.objects.create( related_user=user, association=association ) @@ -65,20 +65,20 @@ class UserForm(ModelForm): class Meta: model = User - fields = ('username', 'name', 'email', 'allergies') + fields = ("username", "name", "email", "allergies") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['name'].disabled = True - self.fields['name'].initial = str(self.instance) + self.fields["name"].disabled = True + self.fields["name"].initial = str(self.instance) self.fields[ - 'name' + "name" ].help_text = "Contact the site administrator if you want to change your name." - self.fields['email'].disabled = True - self.fields['email'].required = False # To hide the asterisk. + self.fields["email"].disabled = True + self.fields["email"].required = False # To hide the asterisk. # Define a heading used during rendering the form. - self.fields['allergies'].heading = "Dining" + self.fields["allergies"].heading = "Dining" class AssociationLinkForm(forms.Form): @@ -96,7 +96,7 @@ def __init__(self, user, *args, **kwargs): Q(is_choosable=True) | Q(usermembership__related_user=user) ) .distinct() - .order_by('slug') + .order_by("slug") ) for association in associations: @@ -165,4 +165,4 @@ def save(self): class AssociationSettingsForm(forms.ModelForm): class Meta: model = Association - fields = ['balance_update_instructions'] + fields = ["balance_update_instructions"] diff --git a/userdetails/forms_allauth.py b/userdetails/forms_allauth.py index b67c88d3..c4d58afe 100644 --- a/userdetails/forms_allauth.py +++ b/userdetails/forms_allauth.py @@ -5,13 +5,13 @@ class CustomChangePasswordForm(ChangePasswordForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['oldpassword'].label = _("Current password") - self.fields['password1'].label = _("New password") - self.fields['password2'].label = _("New password (again)") + self.fields["oldpassword"].label = _("Current password") + self.fields["password1"].label = _("New password") + self.fields["password2"].label = _("New password (again)") class CustomResetPasswordKeyForm(ResetPasswordKeyForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['password1'].label = _("New password") - self.fields['password2'].label = _("New password (again)") + self.fields["password1"].label = _("New password") + self.fields["password2"].label = _("New password (again)") diff --git a/userdetails/models.py b/userdetails/models.py index d43b6de0..0fa0601e 100644 --- a/userdetails/models.py +++ b/userdetails/models.py @@ -47,8 +47,8 @@ def clean(self): if qs.exists(): raise ValidationError( { - 'username': ValidationError( - 'A user with that username already exists.', code='unique' + "username": ValidationError( + "A user with that username already exists.", code="unique" ), } ) diff --git a/userdetails/tests/test_externalaccounts.py b/userdetails/tests/test_externalaccounts.py index 791e53cf..b5e45ae7 100644 --- a/userdetails/tests/test_externalaccounts.py +++ b/userdetails/tests/test_externalaccounts.py @@ -8,20 +8,20 @@ class CreateMembershipTestCase(TestCase): def setUp(self): - self.user = User.objects.create_user('ankie', 'ankie@cats.cat') + self.user = User.objects.create_user("ankie", "ankie@cats.cat") self.social_app = SocialApp.objects.create( - provider='quadrivium', name='ESMG Quadrivium' + provider="quadrivium", name="ESMG Quadrivium" ) self.social_app.sites.add( Site.objects.first() ) # Allauth needs a link to a Site self.social_account = SocialAccount.objects.create( - user=self.user, provider='quadrivium' + user=self.user, provider="quadrivium" ) self.association = Association.objects.create( - name='Q', slug='q', social_app=self.social_app + name="Q", slug="q", social_app=self.social_app ) - self.association_not_linked = Association.objects.create(name='R', slug='r') + self.association_not_linked = Association.objects.create(name="R", slug="r") def test_create_membership(self): _create_membership(self.social_account, None) diff --git a/userdetails/tests/test_forms.py b/userdetails/tests/test_forms.py index 6a7c8010..9ba43f6e 100644 --- a/userdetails/tests/test_forms.py +++ b/userdetails/tests/test_forms.py @@ -7,17 +7,17 @@ class RegisterUserFormTestCase(TestCase): def test_save_memberships(self): """Tests membership creation during form save.""" - a1 = Association.objects.create(name='a1') - Association.objects.create(name='a2') + a1 = Association.objects.create(name="a1") + Association.objects.create(name="a2") form = RegisterUserForm( { - 'first_name': 'Test', - 'last_name': 'User', - 'username': 'user', - 'email': 'user@localhost', - 'password1': 'yda7yum7MDV0ncw-hmw', - 'password2': 'yda7yum7MDV0ncw-hmw', - 'associations': [a1], + "first_name": "Test", + "last_name": "User", + "username": "user", + "email": "user@localhost", + "password1": "yda7yum7MDV0ncw-hmw", + "password2": "yda7yum7MDV0ncw-hmw", + "associations": [a1], } ) self.assertTrue(form.is_valid()) diff --git a/userdetails/tests/test_models.py b/userdetails/tests/test_models.py index fe7e41c9..b46bc938 100644 --- a/userdetails/tests/test_models.py +++ b/userdetails/tests/test_models.py @@ -15,7 +15,7 @@ def test_has_new_member_requests_false(self): self.assertFalse(self.association.has_new_member_requests()) def test_has_new_member_requests_true(self): - user = User.objects.create_user('ankie') + user = User.objects.create_user("ankie") UserMembership.objects.create(related_user=user, association=self.association) self.assertTrue(self.association.has_new_member_requests()) @@ -23,7 +23,7 @@ def test_has_new_member_requests_true(self): class UserTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user('noortje') + cls.user = User.objects.create_user("noortje") def test_has_min_balance_exception_no_membership(self): self.assertFalse(self.user.has_min_balance_exception()) @@ -56,15 +56,15 @@ def test_has_min_balance_exception_unverified_membership(self): def test_username_case_insensitive(self): """Cleaning should raise ValidationError for an existing username with different case.""" with self.assertRaises(ValidationError) as cm: - User(username='Noortje').full_clean() + User(username="Noortje").full_clean() exception = cm.exception - self.assertEqual(exception.error_dict['username'][0].code, 'unique') + self.assertEqual(exception.error_dict["username"][0].code, "unique") class UserMembershipTestCase(TestCase): @classmethod def setUpTestData(cls): - cls.user = User.objects.create_user('noortje') + cls.user = User.objects.create_user("noortje") cls.association = Association.objects.create() def test_set_verified_true(self): diff --git a/userdetails/urls.py b/userdetails/urls.py index 157d7b86..612041e6 100644 --- a/userdetails/urls.py +++ b/userdetails/urls.py @@ -25,71 +25,71 @@ urlpatterns = [ path( - 'association//', + "association//", include( [ - path('', AssociationOverview.as_view(), name='association_overview'), + path("", AssociationOverview.as_view(), name="association_overview"), path( - 'transactions/', + "transactions/", include( [ path( - '', + "", AssociationTransactionListView.as_view(), - name='association_credits', + name="association_credits", ), path( - 'process_negatives/', + "process_negatives/", AutoCreateNegativeCreditsView.as_view(), - name='association_process_negatives', + name="association_process_negatives", ), path( - 'csv/', + "csv/", AssociationTransactionsCSVView.as_view(), - name='association_transactions_csv', + name="association_transactions_csv", ), path( - 'add/', + "add/", AssociationTransactionAddView.as_view(), - name='association_transaction_add', + name="association_transaction_add", ), ] ), ), - path('members/', MembersOverview.as_view(), name='association_members'), + path("members/", MembersOverview.as_view(), name="association_members"), path( - 'members/edit/', + "members/edit/", MembersEditView.as_view(), - name='association_members_edit', + name="association_members_edit", ), path( - 'settings/', + "settings/", AssociationSettingsView.as_view(), - name='association_settings', + name="association_settings", ), path( - 'site_stats/', + "site_stats/", include( [ path( - 'dining/', + "dining/", SiteDiningView.as_view(), - name='association_site_dining_stats', + name="association_site_dining_stats", ), path( - 'credit/', + "credit/", SiteCreditView.as_view(), - name='association_site_credit_stats', + name="association_site_credit_stats", ), path( - 'credit/add/', + "credit/add/", SiteTransactionView.as_view(), - name='association_site_transaction_add', + name="association_site_transaction_add", ), path( - 'credit/account//', + "credit/account//", SiteCreditDetailView.as_view(), - name='association_site_credit_detail', + name="association_site_credit_detail", ), ] ), @@ -98,35 +98,35 @@ ), ), path( - 'statistics/', + "statistics/", include( [ - path('joined/', DiningJoinHistoryView.as_view(), name='history_lists'), + path("joined/", DiningJoinHistoryView.as_view(), name="history_lists"), path( - 'joined//', + "joined//", DiningJoinHistoryView.as_view(), - name='history_lists', + name="history_lists", ), path( - 'claimed/', + "claimed/", DiningClaimHistoryView.as_view(), - name='history_claimed_lists', + name="history_claimed_lists", ), path( - 'claimed//', + "claimed//", DiningClaimHistoryView.as_view(), - name='history_claimed_lists', + name="history_claimed_lists", ), ] ), ), - path('settings/', SettingsProfileView.as_view(), name='settings_account'), + path("settings/", SettingsProfileView.as_view(), name="settings_account"), # Override allauth login and sign up page with our registration page - path('login/', LoginView.as_view(), name='account_login'), - path('signup/', RegisterView.as_view(), name='account_signup'), + path("login/", LoginView.as_view(), name="account_login"), + path("signup/", RegisterView.as_view(), name="account_signup"), path( - 'people-autocomplete/', + "people-autocomplete/", PeopleAutocompleteView.as_view(), - name='people_autocomplete', + name="people_autocomplete", ), ] diff --git a/userdetails/views.py b/userdetails/views.py index ae26fd68..81ca8718 100644 --- a/userdetails/views.py +++ b/userdetails/views.py @@ -14,11 +14,11 @@ class RegisterView(FormView): template_name = "account/signup.html" form_class = RegisterUserForm - success_url = reverse_lazy('index') + success_url = reverse_lazy("index") def form_valid(self, form): user = form.save() - login(self.request, user, backend='django.contrib.auth.backends.ModelBackend') + login(self.request, user, backend="django.contrib.auth.backends.ModelBackend") return super().form_valid(form) @@ -31,7 +31,7 @@ def get_queryset(self): return ( DiningEntry.objects.internal() .filter(user=self.request.user) - .order_by('-dining_list__date') + .order_by("-dining_list__date") ) @@ -40,7 +40,7 @@ class DiningClaimHistoryView(LoginRequiredMixin, ListView): paginate_by = 20 def get_queryset(self): - return DiningList.objects.filter(owners=self.request.user).order_by('-date') + return DiningList.objects.filter(owners=self.request.user).order_by("-date") class PeopleAutocompleteView(LoginRequiredMixin, Select2QuerySetView): @@ -52,7 +52,7 @@ def get_queryset(self): qs = User.objects.filter(is_active=True) if self.q: qs = qs.annotate( - full_name=Concat('first_name', Value(' '), 'last_name') + full_name=Concat("first_name", Value(" "), "last_name") ).filter(full_name__icontains=self.q) return qs diff --git a/userdetails/views_association.py b/userdetails/views_association.py index 8c00051d..5c5d5f80 100644 --- a/userdetails/views_association.py +++ b/userdetails/views_association.py @@ -29,14 +29,14 @@ class AssociationBoardMixin: def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['association'] = self.association - context['notify_overview'] = self.association.has_new_member_requests() + context["association"] = self.association + context["notify_overview"] = self.association.has_new_member_requests() return context def dispatch(self, request, *args, **kwargs): """Gets association and checks if user is board member.""" self.association = get_object_or_404( - Association, slug=kwargs['association_name'] + Association, slug=kwargs["association_name"] ) if not request.user.groups.filter(id=self.association.id): raise PermissionDenied @@ -59,7 +59,7 @@ class AssociationTransactionListView( def get_queryset(self): return Transaction.objects.filter_account(self.association.account).order_by( - '-moment' + "-moment" ) @@ -68,15 +68,15 @@ class AssociationTransactionAddView( ): """View where an association can transfer money to someone else.""" - template_name = 'accounts/association_credits_transaction.html' + template_name = "accounts/association_credits_transaction.html" def get_source(self) -> Account: return self.association.account def get_success_url(self): return reverse( - 'association_credits', - kwargs={'association_name': self.kwargs.get('association_name')}, + "association_credits", + kwargs={"association_name": self.kwargs.get("association_name")}, ) @@ -91,8 +91,8 @@ def get_form_kwargs(self): if not self.association.has_min_exception: raise PermissionDenied kwargs = super().get_form_kwargs() - kwargs['association'] = self.association - kwargs['user'] = self.request.user + kwargs["association"] = self.association + kwargs["user"] = self.request.user return kwargs def form_valid(self, form): @@ -104,14 +104,14 @@ def form_valid(self, form): def get_success_url(self): return reverse( - 'association_credits', kwargs={'association_name': self.association.slug} + "association_credits", kwargs={"association_name": self.association.slug} ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Sum all transaction amounts - context['transactions_sum'] = sum( - tx.amount for tx in context['form'].transactions + context["transactions_sum"] = sum( + tx.amount for tx in context["form"].transactions ) return context @@ -120,12 +120,12 @@ class AssociationTransactionsCSVView(LoginRequiredMixin, AssociationBoardMixin, """Returns a CSV file with all transactions.""" def get(self, request, *args, **kwargs): - response = HttpResponse(content_type='text/csv') + response = HttpResponse(content_type="text/csv") response[ - 'Content-Disposition' + "Content-Disposition" ] = 'attachment; filename="association_transactions.csv"' qs = Transaction.objects.filter_account(self.association.account).order_by( - '-moment' + "-moment" ) write_transactions_csv(response, qs, self.association.account) return response @@ -148,7 +148,7 @@ class AssociationOverview(LoginRequiredMixin, AssociationBoardMixin, TemplateVie def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['pending_memberships'] = UserMembership.objects.filter( + context["pending_memberships"] = UserMembership.objects.filter( association=self.association, verified_on__isnull=True ) return context @@ -160,7 +160,7 @@ class MembersEditView(LoginRequiredMixin, AssociationBoardMixin, ListView): def get_queryset(self): return UserMembership.objects.filter(Q(association=self.association)).order_by( - 'is_verified', 'verified_on', 'created_on' + "is_verified", "verified_on", "created_on" ) def _alter_state(self, verified, id): @@ -190,7 +190,7 @@ def post(self, request, *args, **kwargs): self._alter_state(verified, id) # If next is provided, put possible error messages on the messages system and redirect - redirect_to = request.GET.get('next', None) + redirect_to = request.GET.get("next", None) if url_has_allowed_host_and_scheme(redirect_to, request.get_host()): return HttpResponseRedirect(redirect_to) @@ -202,7 +202,7 @@ class AssociationSettingsView(AssociationBoardMixin, TemplateView): def get_context_data(self, **kwargs): context = super(AssociationSettingsView, self).get_context_data(**kwargs) - context['form'] = AssociationSettingsForm(instance=self.association) + context["form"] = AssociationSettingsForm(instance=self.association) return context @@ -218,7 +218,7 @@ def post(self, request, association_name=None): return HttpResponseRedirect(request.path_info) context = self.get_context_data() - context['form'] = form + context["form"] = form return render(request, self.template_name, context) @@ -253,19 +253,19 @@ def get_context_data(self, **kwargs): cooked_for_own = cooked_for.filter(user__in=members) association_stats[association.id] = { - 'association': association, - 'lists_claimed': dining_lists.filter( + "association": association, + "lists_claimed": dining_lists.filter( association=association ).count(), - 'cooked_for': cooked_for.count(), - 'cooked_for_own': cooked_for_own.count(), - 'weighted_eaters': 0, + "cooked_for": cooked_for.count(), + "cooked_for_own": cooked_for_own.count(), + "weighted_eaters": 0, } # Get general data for all members. Note: this is done here as the length of members is significantly longer # than the number of associations so this should be quicker users = User.objects.filter( diningentry__dining_list__in=dining_lists - ).annotate(dining_entry_count=Count('diningentry')) + ).annotate(dining_entry_count=Count("diningentry")) for user in users: memberships = UserMembership.objects.filter( @@ -276,9 +276,9 @@ def get_context_data(self, **kwargs): for membership in memberships: association_stats[membership.association_id][ - 'weighted_eaters' + "weighted_eaters" ] += user_weight - context['stats'] = association_stats + context["stats"] = association_stats return context @@ -293,8 +293,8 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Get the balance for each association - context['associations'] = Association.objects.all() - context['special_accounts'] = Account.objects.filter(special__isnull=False) + context["associations"] = Association.objects.all() + context["special_accounts"] = Account.objects.filter(special__isnull=False) return context @@ -306,18 +306,18 @@ class SiteTransactionView( This is only meant to be used for the highest boss. """ - template_name = 'accounts/site_credit_transaction.html' + template_name = "accounts/site_credit_transaction.html" form_class = SiteWideTransactionForm def get_success_url(self): return reverse( - 'association_site_credit_stats', - kwargs={'association_name': self.kwargs['association_name']}, + "association_site_credit_stats", + kwargs={"association_name": self.kwargs["association_name"]}, ) def get_form_kwargs(self): kwargs = super().get_form_kwargs() - kwargs['user'] = self.request.user + kwargs["user"] = self.request.user return kwargs def form_valid(self, form): @@ -337,20 +337,20 @@ class SiteCreditDetailView( Only allows displaying details for bookkeeping accounts. """ - template_name = 'accounts/site_credit_detail.html' + template_name = "accounts/site_credit_detail.html" model = Account - slug_field = 'special' # Finds the object from the 'slug' URL parameter + slug_field = "special" # Finds the object from the 'slug' URL parameter def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - account = context['object'] + account = context["object"] # Paginate transactions - transaction_qs = account.get_transactions().order_by('-moment') + transaction_qs = account.get_transactions().order_by("-moment") paginator = Paginator(transaction_qs, 100) - page_number = self.request.GET.get('page') + page_number = self.request.GET.get("page") page_obj = paginator.get_page(page_number) - context['page_obj'] = page_obj + context["page_obj"] = page_obj # Handle income/outcome flow # We only handle and show the form if we're on page 1 @@ -358,17 +358,17 @@ def get_context_data(self, **kwargs): qs = Transaction.objects.filter( moment__gte=self.date_start, moment__lte=self.date_end ) - influx = qs.filter(target=account).aggregate(sum=Sum('amount'))[ - 'sum' - ] or Decimal('0.00') - outflux = qs.filter(source=account).aggregate(sum=Sum('amount'))[ - 'sum' - ] or Decimal('0.00') - - context['dining_balance'] = { - 'influx': influx, - 'outflux': outflux, - 'nettoflux': influx - outflux, + influx = qs.filter(target=account).aggregate(sum=Sum("amount"))[ + "sum" + ] or Decimal("0.00") + outflux = qs.filter(source=account).aggregate(sum=Sum("amount"))[ + "sum" + ] or Decimal("0.00") + + context["dining_balance"] = { + "influx": influx, + "outflux": outflux, + "nettoflux": influx - outflux, } return context diff --git a/userdetails/views_user_settings.py b/userdetails/views_user_settings.py index 4b198651..dc2b9df7 100644 --- a/userdetails/views_user_settings.py +++ b/userdetails/views_user_settings.py @@ -13,8 +13,8 @@ def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context.update( { - 'form': UserForm(instance=self.request.user), - 'association_links_form': AssociationLinkForm(self.request.user), + "form": UserForm(instance=self.request.user), + "association_links_form": AssociationLinkForm(self.request.user), } ) return context @@ -27,14 +27,14 @@ def post(self, request, *args, **kwargs): with transaction.atomic(): user_form.save() membership_form.save() - return redirect('settings_account') + return redirect("settings_account") # A form was not valid. context = self.get_context_data() context.update( { - 'form': user_form, - 'association_links_form': membership_form, + "form": user_form, + "association_links_form": membership_form, } ) return self.render_to_response(context) diff --git a/utils/testing/__init__.py b/utils/testing/__init__.py index a504693a..c6cb405d 100644 --- a/utils/testing/__init__.py +++ b/utils/testing/__init__.py @@ -2,4 +2,4 @@ from utils.testing.patch_utils import patch, TestPatchMixin -__all__ = ['FormValidityMixin', 'TestPatchMixin', 'patch'] +__all__ = ["FormValidityMixin", "TestPatchMixin", "patch"] diff --git a/utils/testing/patch_utils.py b/utils/testing/patch_utils.py index c61f7f50..e6f8b544 100644 --- a/utils/testing/patch_utils.py +++ b/utils/testing/patch_utils.py @@ -22,7 +22,7 @@ def patch_time(dt=None): def wrapper_func(func): def inner(*args, **kwargs): - with patch('django.utils.timezone.now') as mock: + with patch("django.utils.timezone.now") as mock: mock.side_effect = mock_now(dt=dt) func(*args, **kwargs) @@ -77,7 +77,7 @@ def assert_has_call(mock: Mock, **kwargs): # Copy the dict so in the case of an error we can reconstruct the given kwargs call_kwargs = kwargs.copy() arg_dict = {} - for arg_key in filter(lambda kwarg: kwarg.startswith('arg_'), kwargs.keys()): + for arg_key in filter(lambda kwarg: kwarg.startswith("arg_"), kwargs.keys()): arg_dict[int(arg_key[4:]) - 1] = call_kwargs.pop(arg_key) # Construct a list of all valid calls @@ -94,7 +94,7 @@ def assert_has_call(mock: Mock, **kwargs): valid = False break if valid: - valid_calls.append({'args': kall[0], **kall[1]}) + valid_calls.append({"args": kall[0], **kall[1]}) if not valid_calls: raise AssertionError( diff --git a/utils/tests/test_form_validation.py b/utils/tests/test_form_validation.py index da42efdf..97773273 100644 --- a/utils/tests/test_form_validation.py +++ b/utils/tests/test_form_validation.py @@ -14,13 +14,13 @@ class TestForm(forms.Form): def clean_main_field(self): if self.cleaned_data["main_field"] == "break_field": - raise ValidationError('Test field exception', code='invalid_field') + raise ValidationError("Test field exception", code="invalid_field") return self.cleaned_data["main_field"] def clean(self): # Use get function, a fail in clean_main_field removes the entry from cleaned_data - if self.cleaned_data.get('main_field', '') == "break_form": - raise ValidationError('Test form exception', code='invalid_form') + if self.cleaned_data.get("main_field", "") == "break_form": + raise ValidationError("Test form exception", code="invalid_form") return self.cleaned_data form_class = TestForm @@ -38,64 +38,64 @@ def raises_assertion_error(self, method, *args, **kwargs): def test_assert_has_field(self): # This should not raise an error - self.assertHasField('main_field') + self.assertHasField("main_field") # This should - error = self.raises_assertion_error(self.assertHasField, 'missing_field') + error = self.raises_assertion_error(self.assertHasField, "missing_field") self.assertEqual( error.__str__(), "{field_name} was not a field in {form_class_name}".format( - field_name='missing_field', - form_class_name='TestForm', + field_name="missing_field", + form_class_name="TestForm", ), ) def test_assert_form_valid(self): # This should not raise an error - self.assertFormValid({'main_field': "ok"}) + self.assertFormValid({"main_field": "ok"}) # This should error = self.raises_assertion_error( - self.assertFormValid, {'main_field': "break_field"} + self.assertFormValid, {"main_field": "break_field"} ) self.assertEqual( error.__str__(), "The form was not valid. At least one error was encountered: '{exception_text}' in '{location}'".format( - exception_text='Test field exception', location='main_field' + exception_text="Test field exception", location="main_field" ), ) def test_assert_form_has_error_in_field(self): - self.assertFormHasError({'main_field': 'break_field'}, 'invalid_field') + self.assertFormHasError({"main_field": "break_field"}, "invalid_field") self.assertFormHasError( - {'main_field': 'break_field'}, 'invalid_field', field='main_field' + {"main_field": "break_field"}, "invalid_field", field="main_field" ) # Error is in main_field not fake_field with self.assertRaises(AssertionError): self.assertFormHasError( - {'main_field': 'break_field'}, 'invalid_form', field='fake_field' + {"main_field": "break_field"}, "invalid_form", field="fake_field" ) # This next data raises an error, just not the one with this code with self.assertRaises(AssertionError): self.assertFormHasError( - {'main_field': 'break_field'}, 'invalid_data', field='main_field' + {"main_field": "break_field"}, "invalid_data", field="main_field" ) def test_assert_form_has_error_in_form(self): - self.assertFormHasError({'main_field': 'break_form'}, 'invalid_form') + self.assertFormHasError({"main_field": "break_form"}, "invalid_form") # Should raise AssertionError because the form contains no errors. error = self.raises_assertion_error( - self.assertFormHasError, {'main_field': 'break_nothing'}, 'invalid_form' + self.assertFormHasError, {"main_field": "break_nothing"}, "invalid_form" ) self.assertEqual(error.__str__(), "The form contained no errors") # Error is not in main_field, but elsewhere error = self.raises_assertion_error( self.assertFormHasError, - {'main_field': 'break_form'}, - 'invalid_form', - field='main_field', + {"main_field": "break_form"}, + "invalid_form", + field="main_field", ) self.assertEqual( str(error), @@ -104,7 +104,7 @@ def test_assert_form_has_error_in_form(self): # Error code is not correct, but there is another error error = self.raises_assertion_error( - self.assertFormHasError, {'main_field': 'break_form'}, 'invalid_field' + self.assertFormHasError, {"main_field": "break_form"}, "invalid_field" ) self.assertEqual( str(error), From 5dad39e90d7f659c0f2e630ad9dfefa10cd45b99 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 18:39:52 +0200 Subject: [PATCH 09/14] Apply isort --- creditmanagement/csv.py | 2 +- creditmanagement/forms.py | 4 +-- creditmanagement/migrations/0001_initial.py | 5 ++-- .../migrations/0002_auto_20190203_1923.py | 2 +- .../migrations/0004_auto_20190203_2324.py | 2 +- .../migrations/0006_auto_20190213_1452.py | 2 +- .../0008_pendingdiningtransaction.py | 5 ++-- .../migrations/0011_account_transaction.py | 5 ++-- .../migrations/0015_auto_20220428_2113.py | 2 +- .../migrations/0017_remove_cancel_column.py | 2 +- creditmanagement/models.py | 4 +-- creditmanagement/receivers.py | 4 +-- creditmanagement/urls.py | 2 +- creditmanagement/views.py | 6 ++--- dining/admin.py | 6 ++--- dining/forms.py | 14 +++++----- dining/migrations/0001_initial.py | 5 ++-- dining/migrations/0003_auto_20190203_2324.py | 2 +- dining/migrations/0009_auto_20190508_0230.py | 2 +- dining/migrations/0011_auto_20190508_1905.py | 2 +- dining/migrations/0012_auto_20190728_1359.py | 1 + dining/migrations/0013_auto_20190513_1505.py | 3 ++- dining/migrations/0015_auto_20190513_2104.py | 2 +- dining/migrations/0016_auto_20190514_1317.py | 2 +- dining/migrations/0017_auto_20200824_0138.py | 4 +-- dining/migrations/0018_auto_20210319_2310.py | 2 +- .../0021_create_paymentreminderlock.py | 2 +- .../0026_diningcomment_increase_length.py | 2 +- .../migrations/0027_deletedlist_and_more.py | 5 ++-- dining/models.py | 6 ++--- dining/tests/test_forms.py | 12 ++++----- dining/tests/test_forms_diningentry.py | 6 ++--- dining/tests/test_forms_sendreminderform.py | 4 +-- dining/tests/test_models.py | 7 +++-- dining/urls.py | 2 +- dining/views.py | 26 +++++++++---------- general/mail_control.py | 2 +- general/migrations/0002_auto_20190227_1203.py | 2 +- general/templatetags/computation_tags.py | 1 - general/urls.py | 1 + general/views.py | 8 +++--- scaladining/scala_settings.py | 2 +- scaladining/urls.py | 1 - userdetails/admin.py | 2 +- userdetails/forms.py | 2 +- userdetails/migrations/0001_initial.py | 4 +-- .../migrations/0002_auto_20190203_2324.py | 2 +- .../migrations/0005_auto_20190207_1510.py | 2 +- .../migrations/0016_association_social_app.py | 2 +- .../migrations/0019_auto_20200823_1602.py | 2 +- .../migrations/0021_auto_20221224_1346.py | 1 + userdetails/models.py | 4 +-- userdetails/tests/test_externalaccounts.py | 2 +- userdetails/urls.py | 20 +++++++------- userdetails/views.py | 4 +-- userdetails/views_association.py | 12 ++++----- userdetails/views_user_settings.py | 2 +- utils/testing/__init__.py | 3 +-- utils/testing/patch_utils.py | 2 +- 59 files changed, 126 insertions(+), 121 deletions(-) diff --git a/creditmanagement/csv.py b/creditmanagement/csv.py index ac91fced..e4e45cd8 100644 --- a/creditmanagement/csv.py +++ b/creditmanagement/csv.py @@ -4,7 +4,7 @@ from django.utils.timezone import get_default_timezone -from creditmanagement.models import Transaction, Account +from creditmanagement.models import Account, Transaction def write_transactions_csv( diff --git a/creditmanagement/forms.py b/creditmanagement/forms.py index f6958f4f..128b9cb4 100644 --- a/creditmanagement/forms.py +++ b/creditmanagement/forms.py @@ -1,4 +1,4 @@ -from typing import Tuple, Any +from typing import Any, Tuple from dal_select2.widgets import ModelSelect2 from django import forms @@ -7,7 +7,7 @@ from creditmanagement.models import Account, Transaction from general.mail_control import send_templated_mail -from userdetails.models import User, Association +from userdetails.models import Association, User # Form fields which are used in transaction forms USER_FORM_FIELD = forms.ModelChoiceField( diff --git a/creditmanagement/migrations/0001_initial.py b/creditmanagement/migrations/0001_initial.py index de62b844..763bda0b 100644 --- a/creditmanagement/migrations/0001_initial.py +++ b/creditmanagement/migrations/0001_initial.py @@ -2,10 +2,11 @@ import datetime from decimal import Decimal -from django.conf import settings + import django.core.validators -from django.db import migrations, models import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0002_auto_20190203_1923.py b/creditmanagement/migrations/0002_auto_20190203_1923.py index a3ce6af7..7296cf74 100644 --- a/creditmanagement/migrations/0002_auto_20190203_1923.py +++ b/creditmanagement/migrations/0002_auto_20190203_1923.py @@ -1,8 +1,8 @@ # Generated by Django 2.1.5 on 2019-02-03 18:23 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0004_auto_20190203_2324.py b/creditmanagement/migrations/0004_auto_20190203_2324.py index dbd8d870..3a094921 100644 --- a/creditmanagement/migrations/0004_auto_20190203_2324.py +++ b/creditmanagement/migrations/0004_auto_20190203_2324.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.3 on 2019-02-03 22:24 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0006_auto_20190213_1452.py b/creditmanagement/migrations/0006_auto_20190213_1452.py index 3c8a9b3c..12a1f12b 100644 --- a/creditmanagement/migrations/0006_auto_20190213_1452.py +++ b/creditmanagement/migrations/0006_auto_20190213_1452.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.3 on 2019-02-13 13:52 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0008_pendingdiningtransaction.py b/creditmanagement/migrations/0008_pendingdiningtransaction.py index 3a4dea1e..24a0e186 100644 --- a/creditmanagement/migrations/0008_pendingdiningtransaction.py +++ b/creditmanagement/migrations/0008_pendingdiningtransaction.py @@ -1,11 +1,12 @@ # Generated by Django 2.1.3 on 2019-03-03 10:15 from decimal import Decimal -from django.conf import settings + import django.core.validators -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0011_account_transaction.py b/creditmanagement/migrations/0011_account_transaction.py index 49eb6389..397ef0cb 100644 --- a/creditmanagement/migrations/0011_account_transaction.py +++ b/creditmanagement/migrations/0011_account_transaction.py @@ -1,11 +1,12 @@ # Generated by Django 2.2.15 on 2020-08-23 20:22 from decimal import Decimal -from django.conf import settings + import django.core.validators -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0015_auto_20220428_2113.py b/creditmanagement/migrations/0015_auto_20220428_2113.py index 9c904f94..3c7ae9eb 100644 --- a/creditmanagement/migrations/0015_auto_20220428_2113.py +++ b/creditmanagement/migrations/0015_auto_20220428_2113.py @@ -1,8 +1,8 @@ # Generated by Django 3.1.7 on 2022-04-28 19:13 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/creditmanagement/migrations/0017_remove_cancel_column.py b/creditmanagement/migrations/0017_remove_cancel_column.py index 2f9722d3..9ccd9aad 100644 --- a/creditmanagement/migrations/0017_remove_cancel_column.py +++ b/creditmanagement/migrations/0017_remove_cancel_column.py @@ -1,8 +1,8 @@ # Generated by Django 4.1.4 on 2023-03-10 01:05 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/creditmanagement/models.py b/creditmanagement/models.py index b3193f29..bd187cba 100644 --- a/creditmanagement/models.py +++ b/creditmanagement/models.py @@ -1,10 +1,10 @@ from datetime import datetime from decimal import Decimal -from typing import Union, Optional +from typing import Optional, Union from django.core.validators import MinValueValidator from django.db import models -from django.db.models import QuerySet, Sum, Q +from django.db.models import Q, QuerySet, Sum from django.utils import timezone from userdetails.models import Association, User diff --git a/creditmanagement/receivers.py b/creditmanagement/receivers.py index d46e7f0f..66f743df 100644 --- a/creditmanagement/receivers.py +++ b/creditmanagement/receivers.py @@ -1,9 +1,9 @@ from django.db import DatabaseError -from django.db.models.signals import post_save, post_migrate +from django.db.models.signals import post_migrate, post_save from django.dispatch import receiver from creditmanagement.models import Account -from userdetails.models import User, Association +from userdetails.models import Association, User @receiver(post_save, sender=User) diff --git a/creditmanagement/urls.py b/creditmanagement/urls.py index cd06186c..e516af87 100644 --- a/creditmanagement/urls.py +++ b/creditmanagement/urls.py @@ -1,9 +1,9 @@ from django.urls import path from creditmanagement.views import ( - TransactionListView, TransactionAddView, TransactionCSVView, + TransactionListView, ) app_name = "credits" diff --git a/creditmanagement/views.py b/creditmanagement/views.py index 08fd079e..abb88d5d 100644 --- a/creditmanagement/views.py +++ b/creditmanagement/views.py @@ -1,13 +1,13 @@ from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin -from django.http import HttpResponseRedirect, HttpResponse +from django.http import HttpResponse, HttpResponseRedirect from django.urls import reverse_lazy -from django.views.generic import View, FormView +from django.views.generic import FormView, View from django.views.generic.list import ListView from creditmanagement.csv import write_transactions_csv from creditmanagement.forms import TransactionForm -from creditmanagement.models import Transaction, Account +from creditmanagement.models import Account, Transaction class TransactionListView(LoginRequiredMixin, ListView): diff --git a/dining/admin.py b/dining/admin.py index 58e04192..cc5b38ff 100644 --- a/dining/admin.py +++ b/dining/admin.py @@ -1,11 +1,11 @@ from django.contrib import admin from dining.models import ( - DiningDayAnnouncement, + DeletedList, DiningComment, - DiningList, + DiningDayAnnouncement, DiningEntry, - DeletedList, + DiningList, ) diff --git a/dining/forms.py b/dining/forms.py index 83fe9a6f..7fcb3165 100644 --- a/dining/forms.py +++ b/dining/forms.py @@ -1,6 +1,6 @@ from datetime import timedelta -from decimal import Decimal, ROUND_UP -from typing import List, Dict, Literal +from decimal import ROUND_UP, Decimal +from typing import Dict, List, Literal from dal_select2.widgets import ModelSelect2, ModelSelect2Multiple from django import forms @@ -10,22 +10,22 @@ from django.core.serializers import serialize from django.core.validators import MinValueValidator from django.db import transaction -from django.db.models import OuterRef, Exists, QuerySet +from django.db.models import Exists, OuterRef, QuerySet from django.forms import ValidationError from django.utils import timezone -from creditmanagement.models import Transaction, Account +from creditmanagement.models import Account, Transaction from dining.models import ( - DiningList, + DeletedList, DiningComment, DiningEntry, + DiningList, PaymentReminderLock, - DeletedList, ) from general.forms import ConcurrenflictFormMixin from general.mail_control import construct_templated_mail from general.util import SelectWithDisabled -from userdetails.models import Association, UserMembership, User +from userdetails.models import Association, User, UserMembership __all__ = [ "CreateSlotForm", diff --git a/dining/migrations/0001_initial.py b/dining/migrations/0001_initial.py index bc5f609a..4ef5fc20 100644 --- a/dining/migrations/0001_initial.py +++ b/dining/migrations/0001_initial.py @@ -2,11 +2,12 @@ import datetime from decimal import Decimal -from django.conf import settings + import django.core.validators -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/dining/migrations/0003_auto_20190203_2324.py b/dining/migrations/0003_auto_20190203_2324.py index b3e319fd..44ad47fe 100644 --- a/dining/migrations/0003_auto_20190203_2324.py +++ b/dining/migrations/0003_auto_20190203_2324.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.3 on 2019-02-03 22:24 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/dining/migrations/0009_auto_20190508_0230.py b/dining/migrations/0009_auto_20190508_0230.py index 53a91e1c..4a8d24ef 100644 --- a/dining/migrations/0009_auto_20190508_0230.py +++ b/dining/migrations/0009_auto_20190508_0230.py @@ -1,8 +1,8 @@ # Generated by Django 2.2 on 2019-05-08 00:30 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/dining/migrations/0011_auto_20190508_1905.py b/dining/migrations/0011_auto_20190508_1905.py index 98799786..15c77dcb 100644 --- a/dining/migrations/0011_auto_20190508_1905.py +++ b/dining/migrations/0011_auto_20190508_1905.py @@ -1,8 +1,8 @@ # Generated by Django 2.2 on 2019-05-08 17:05 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/dining/migrations/0012_auto_20190728_1359.py b/dining/migrations/0012_auto_20190728_1359.py index abb1e15a..fca35ac3 100644 --- a/dining/migrations/0012_auto_20190728_1359.py +++ b/dining/migrations/0012_auto_20190728_1359.py @@ -1,6 +1,7 @@ # Generated by Django 2.1.5 on 2019-07-28 11:59 from decimal import Decimal + import django.core.validators from django.db import migrations, models diff --git a/dining/migrations/0013_auto_20190513_1505.py b/dining/migrations/0013_auto_20190513_1505.py index bd53d8a7..802b220f 100644 --- a/dining/migrations/0013_auto_20190513_1505.py +++ b/dining/migrations/0013_auto_20190513_1505.py @@ -1,8 +1,9 @@ # Generated by Django 2.2 on 2019-05-13 13:05 from decimal import Decimal -from django.conf import settings + import django.core.validators +from django.conf import settings from django.db import migrations, models diff --git a/dining/migrations/0015_auto_20190513_2104.py b/dining/migrations/0015_auto_20190513_2104.py index 53d5d452..381c8ac8 100644 --- a/dining/migrations/0015_auto_20190513_2104.py +++ b/dining/migrations/0015_auto_20190513_2104.py @@ -1,8 +1,8 @@ # Generated by Django 2.2 on 2019-05-13 19:04 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/dining/migrations/0016_auto_20190514_1317.py b/dining/migrations/0016_auto_20190514_1317.py index 99738c3d..445404d7 100644 --- a/dining/migrations/0016_auto_20190514_1317.py +++ b/dining/migrations/0016_auto_20190514_1317.py @@ -1,8 +1,8 @@ # Generated by Django 2.2 on 2019-05-14 11:17 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/dining/migrations/0017_auto_20200824_0138.py b/dining/migrations/0017_auto_20200824_0138.py index e75ec5d7..1e67b920 100644 --- a/dining/migrations/0017_auto_20200824_0138.py +++ b/dining/migrations/0017_auto_20200824_0138.py @@ -1,9 +1,9 @@ # Generated by Django 2.2.15 on 2020-08-23 23:38 -from django.conf import settings -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/dining/migrations/0018_auto_20210319_2310.py b/dining/migrations/0018_auto_20210319_2310.py index 63e54199..11d79076 100644 --- a/dining/migrations/0018_auto_20210319_2310.py +++ b/dining/migrations/0018_auto_20210319_2310.py @@ -1,7 +1,7 @@ # Generated by Django 3.1.7 on 2021-03-19 22:10 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/dining/migrations/0021_create_paymentreminderlock.py b/dining/migrations/0021_create_paymentreminderlock.py index 6242e539..d4858e1d 100644 --- a/dining/migrations/0021_create_paymentreminderlock.py +++ b/dining/migrations/0021_create_paymentreminderlock.py @@ -1,7 +1,7 @@ # Generated by Django 3.2.13 on 2023-01-16 14:13 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/dining/migrations/0026_diningcomment_increase_length.py b/dining/migrations/0026_diningcomment_increase_length.py index 0269f87d..89a9a736 100644 --- a/dining/migrations/0026_diningcomment_increase_length.py +++ b/dining/migrations/0026_diningcomment_increase_length.py @@ -1,8 +1,8 @@ # Generated by Django 4.1.4 on 2023-03-07 12:17 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/dining/migrations/0027_deletedlist_and_more.py b/dining/migrations/0027_deletedlist_and_more.py index 703e3215..e84dde5e 100644 --- a/dining/migrations/0027_deletedlist_and_more.py +++ b/dining/migrations/0027_deletedlist_and_more.py @@ -1,10 +1,11 @@ # Generated by Django 4.1.4 on 2023-03-07 21:50 import datetime -from django.conf import settings -from django.db import migrations, models + import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/dining/models.py b/dining/models.py index fcb20bbf..d0944a58 100644 --- a/dining/models.py +++ b/dining/models.py @@ -1,8 +1,8 @@ -from datetime import time, datetime +from datetime import datetime, time from decimal import Decimal from django.conf import settings -from django.core.exceptions import ValidationError, MultipleObjectsReturned +from django.core.exceptions import MultipleObjectsReturned, ValidationError from django.core.validators import MinValueValidator from django.db import models from django.db.models import Sum @@ -10,7 +10,7 @@ from creditmanagement.models import Transaction from general.models import AbstractVisitTracker -from userdetails.models import User, Association +from userdetails.models import Association, User class DiningListManager(models.Manager): diff --git a/dining/tests/test_forms.py b/dining/tests/test_forms.py index 2e310600..ede66d0c 100644 --- a/dining/tests/test_forms.py +++ b/dining/tests/test_forms.py @@ -1,4 +1,4 @@ -from datetime import timedelta, datetime, time, date +from datetime import date, datetime, time, timedelta from decimal import Decimal from dal_select2.widgets import ModelSelect2, ModelSelect2Multiple @@ -13,17 +13,17 @@ from dining.forms import ( CreateSlotForm, DiningEntryDeleteForm, + DiningEntryExternalForm, + DiningEntryInternalForm, DiningInfoForm, + DiningListDeleteForm, DiningPaymentForm, SendReminderForm, - DiningListDeleteForm, - DiningEntryInternalForm, - DiningEntryExternalForm, ) -from dining.models import DiningList, DiningEntry +from dining.models import DiningEntry, DiningList from general.forms import ConcurrenflictFormMixin from userdetails.models import Association, User, UserMembership -from utils.testing import TestPatchMixin, patch, FormValidityMixin +from utils.testing import FormValidityMixin, TestPatchMixin, patch from utils.testing.patch_utils import patch_time diff --git a/dining/tests/test_forms_diningentry.py b/dining/tests/test_forms_diningentry.py index 5477582b..0c596c62 100644 --- a/dining/tests/test_forms_diningentry.py +++ b/dining/tests/test_forms_diningentry.py @@ -8,11 +8,11 @@ from creditmanagement.models import Transaction from dining.forms import ( DiningEntryDeleteForm, - DiningEntryInternalForm, DiningEntryExternalForm, + DiningEntryInternalForm, ) -from dining.models import DiningList, DiningEntry -from userdetails.models import User, Association, UserMembership +from dining.models import DiningEntry, DiningList +from userdetails.models import Association, User, UserMembership def _create_dining_list(**kwargs): diff --git a/dining/tests/test_forms_sendreminderform.py b/dining/tests/test_forms_sendreminderform.py index 65815bfd..379dec80 100644 --- a/dining/tests/test_forms_sendreminderform.py +++ b/dining/tests/test_forms_sendreminderform.py @@ -1,13 +1,13 @@ import threading from datetime import date, datetime -from django.db import transaction, connections, OperationalError +from django.db import OperationalError, connections, transaction from django.http import HttpRequest from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from django.utils import timezone from dining.forms import SendReminderForm -from dining.models import DiningList, DiningEntry +from dining.models import DiningEntry, DiningList from userdetails.models import Association, User diff --git a/dining/tests/test_models.py b/dining/tests/test_models.py index 6e54028a..46b386de 100644 --- a/dining/tests/test_models.py +++ b/dining/tests/test_models.py @@ -1,13 +1,12 @@ -from datetime import datetime, date +from datetime import date, datetime from unittest.mock import patch from django.core.exceptions import ValidationError from django.test import TestCase from django.utils import timezone -from dining.models import DiningEntry -from dining.models import DiningList -from userdetails.models import User, Association +from dining.models import DiningEntry, DiningList +from userdetails.models import Association, User class DiningListTestCase(TestCase): diff --git a/dining/urls.py b/dining/urls.py index cf7bdc91..c12cced2 100644 --- a/dining/urls.py +++ b/dining/urls.py @@ -1,4 +1,4 @@ -from django.urls import path, include +from django.urls import include, path from . import views from .views import DayView, index diff --git a/dining/views.py b/dining/views.py index 2af5dec3..529c1e0f 100644 --- a/dining/views.py +++ b/dining/views.py @@ -5,41 +5,41 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied from django.db import transaction -from django.db.models import Q, Count +from django.db.models import Count, Q from django.http import ( Http404, - HttpResponseRedirect, - HttpResponseForbidden, HttpResponse, + HttpResponseForbidden, + HttpResponseRedirect, ) -from django.shortcuts import redirect, get_object_or_404 +from django.shortcuts import get_object_or_404, redirect from django.urls import reverse from django.utils import timezone from django.utils.http import url_has_allowed_host_and_scheme -from django.views.generic import TemplateView, View, FormView +from django.views.generic import FormView, TemplateView, View from django.views.generic.detail import SingleObjectMixin from dining.datesequence import sequenced_date from dining.forms import ( CreateSlotForm, - DiningEntryDeleteForm, DiningCommentForm, + DiningEntryDeleteForm, + DiningEntryExternalForm, + DiningEntryInternalForm, DiningInfoForm, - DiningPaymentForm, DiningListDeleteForm, + DiningPaymentForm, SendReminderForm, - DiningEntryExternalForm, - DiningEntryInternalForm, ) from dining.models import ( - DiningList, - DiningDayAnnouncement, + DiningComment, DiningCommentVisitTracker, + DiningDayAnnouncement, DiningEntry, - DiningComment, + DiningList, ) from general.mail_control import send_templated_mail -from userdetails.models import User, Association +from userdetails.models import Association, User def index(request): diff --git a/general/mail_control.py b/general/mail_control.py index 97d0cf52..dfdaf2f9 100644 --- a/general/mail_control.py +++ b/general/mail_control.py @@ -2,7 +2,7 @@ from django.contrib.sites.shortcuts import get_current_site from django.core import mail -from django.core.mail import EmailMultiAlternatives, EmailMessage +from django.core.mail import EmailMessage, EmailMultiAlternatives from django.http import HttpRequest from django.template.loader import render_to_string diff --git a/general/migrations/0002_auto_20190227_1203.py b/general/migrations/0002_auto_20190227_1203.py index 4b92ad3c..692024f4 100644 --- a/general/migrations/0002_auto_20190227_1203.py +++ b/general/migrations/0002_auto_20190227_1203.py @@ -1,8 +1,8 @@ # Generated by Django 2.1.3 on 2019-02-27 11:03 +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion class Migration(migrations.Migration): diff --git a/general/templatetags/computation_tags.py b/general/templatetags/computation_tags.py index c78b0fe7..eb6c9b25 100644 --- a/general/templatetags/computation_tags.py +++ b/general/templatetags/computation_tags.py @@ -1,6 +1,5 @@ from django import template - register = template.Library() diff --git a/general/urls.py b/general/urls.py index e9a58cd6..39b4bf6c 100644 --- a/general/urls.py +++ b/general/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from . import views urlpatterns = [ diff --git a/general/views.py b/general/views.py index b2424707..c55c8c3f 100644 --- a/general/views.py +++ b/general/views.py @@ -3,14 +3,14 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import ObjectDoesNotExist -from django.http import HttpResponseForbidden, Http404 +from django.http import Http404, HttpResponseForbidden from django.shortcuts import render -from django.template.loader import get_template, TemplateDoesNotExist +from django.template.loader import TemplateDoesNotExist, get_template from django.utils import timezone -from django.views.generic import View, ListView, TemplateView +from django.views.generic import ListView, TemplateView, View from general.forms import DateRangeForm -from general.models import SiteUpdate, PageVisitTracker +from general.models import PageVisitTracker, SiteUpdate from userdetails.models import Association diff --git a/scaladining/scala_settings.py b/scaladining/scala_settings.py index 0f91209c..87919b76 100644 --- a/scaladining/scala_settings.py +++ b/scaladining/scala_settings.py @@ -1,5 +1,5 @@ -from decimal import Decimal from datetime import time, timedelta +from decimal import Decimal # Maximum number of slots on each date MAX_SLOT_NUMBER = 3 diff --git a/scaladining/urls.py b/scaladining/urls.py index 7881b7a4..80388a28 100644 --- a/scaladining/urls.py +++ b/scaladining/urls.py @@ -3,7 +3,6 @@ from django.contrib import admin from django.urls import include, path - urlpatterns = [ path("admin/", admin.site.urls), path("credit/", include("creditmanagement.urls")), diff --git a/userdetails/admin.py b/userdetails/admin.py index de39a144..e306eb6c 100644 --- a/userdetails/admin.py +++ b/userdetails/admin.py @@ -4,7 +4,7 @@ from django.contrib.auth.admin import UserAdmin from general.mail_control import send_templated_mail -from userdetails.models import User, UserMembership, Association +from userdetails.models import Association, User, UserMembership class AssociationLinks(admin.TabularInline): diff --git a/userdetails/forms.py b/userdetails/forms.py index d05bbd12..db110f92 100644 --- a/userdetails/forms.py +++ b/userdetails/forms.py @@ -5,7 +5,7 @@ from django.db.models import Q from django.forms import ModelForm -from userdetails.models import User, Association, UserMembership +from userdetails.models import Association, User, UserMembership class RegisterUserForm(UserCreationForm): diff --git a/userdetails/migrations/0001_initial.py b/userdetails/migrations/0001_initial.py index 8fdbafdd..522a9c69 100644 --- a/userdetails/migrations/0001_initial.py +++ b/userdetails/migrations/0001_initial.py @@ -1,11 +1,11 @@ # Generated by Django 2.1.5 on 2019-02-03 18:23 -from django.conf import settings import django.contrib.auth.models import django.contrib.auth.validators -from django.db import migrations, models import django.db.models.deletion import django.utils.timezone +from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/userdetails/migrations/0002_auto_20190203_2324.py b/userdetails/migrations/0002_auto_20190203_2324.py index d7fce6fb..26c6be80 100644 --- a/userdetails/migrations/0002_auto_20190203_2324.py +++ b/userdetails/migrations/0002_auto_20190203_2324.py @@ -1,8 +1,8 @@ # Generated by Django 2.1.3 on 2019-02-03 22:24 import django.contrib.auth.models -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/userdetails/migrations/0005_auto_20190207_1510.py b/userdetails/migrations/0005_auto_20190207_1510.py index d4bdd3fa..7f93baff 100644 --- a/userdetails/migrations/0005_auto_20190207_1510.py +++ b/userdetails/migrations/0005_auto_20190207_1510.py @@ -1,7 +1,7 @@ # Generated by Django 2.1.3 on 2019-02-07 14:10 -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/userdetails/migrations/0016_association_social_app.py b/userdetails/migrations/0016_association_social_app.py index 4990bd64..f1fd4d5e 100644 --- a/userdetails/migrations/0016_association_social_app.py +++ b/userdetails/migrations/0016_association_social_app.py @@ -1,7 +1,7 @@ # Generated by Django 2.2 on 2019-05-10 14:31 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/userdetails/migrations/0019_auto_20200823_1602.py b/userdetails/migrations/0019_auto_20200823_1602.py index cf79812c..dba36ea7 100644 --- a/userdetails/migrations/0019_auto_20200823_1602.py +++ b/userdetails/migrations/0019_auto_20200823_1602.py @@ -1,7 +1,7 @@ # Generated by Django 2.2.15 on 2020-08-23 14:02 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/userdetails/migrations/0021_auto_20221224_1346.py b/userdetails/migrations/0021_auto_20221224_1346.py index dfdeefd6..69dcf0f8 100644 --- a/userdetails/migrations/0021_auto_20221224_1346.py +++ b/userdetails/migrations/0021_auto_20221224_1346.py @@ -1,6 +1,7 @@ # Generated by Django 3.2.13 on 2022-12-24 12:46 from django.db import migrations, models + import userdetails.models diff --git a/userdetails/models.py b/userdetails/models.py index 0fa0601e..9c9730ed 100644 --- a/userdetails/models.py +++ b/userdetails/models.py @@ -1,7 +1,7 @@ from allauth.socialaccount.models import SocialApp from django.conf import settings -from django.contrib.auth.models import AbstractUser, Group -from django.contrib.auth.models import UserManager as DjangoUserManager, GroupManager +from django.contrib.auth.models import AbstractUser, Group, GroupManager +from django.contrib.auth.models import UserManager as DjangoUserManager from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone diff --git a/userdetails/tests/test_externalaccounts.py b/userdetails/tests/test_externalaccounts.py index b5e45ae7..84020870 100644 --- a/userdetails/tests/test_externalaccounts.py +++ b/userdetails/tests/test_externalaccounts.py @@ -3,7 +3,7 @@ from django.test import TestCase from userdetails.externalaccounts import _create_membership -from userdetails.models import User, Association, UserMembership +from userdetails.models import Association, User, UserMembership class CreateMembershipTestCase(TestCase): diff --git a/userdetails/urls.py b/userdetails/urls.py index 612041e6..a5ad9396 100644 --- a/userdetails/urls.py +++ b/userdetails/urls.py @@ -1,25 +1,25 @@ from allauth.account.views import LoginView -from django.urls import path, include +from django.urls import include, path from userdetails.views import ( - RegisterView, - DiningJoinHistoryView, DiningClaimHistoryView, + DiningJoinHistoryView, PeopleAutocompleteView, + RegisterView, ) from userdetails.views_association import ( + AssociationOverview, + AssociationSettingsView, + AssociationTransactionAddView, AssociationTransactionListView, AssociationTransactionsCSVView, - MembersOverview, + AutoCreateNegativeCreditsView, MembersEditView, - AssociationOverview, - AssociationSettingsView, - SiteDiningView, + MembersOverview, + SiteCreditDetailView, SiteCreditView, - AutoCreateNegativeCreditsView, - AssociationTransactionAddView, + SiteDiningView, SiteTransactionView, - SiteCreditDetailView, ) from userdetails.views_user_settings import SettingsProfileView diff --git a/userdetails/views.py b/userdetails/views.py index 81ca8718..b859eb83 100644 --- a/userdetails/views.py +++ b/userdetails/views.py @@ -4,9 +4,9 @@ from django.db.models import Value from django.db.models.functions import Concat from django.urls import reverse_lazy -from django.views.generic import ListView, FormView +from django.views.generic import FormView, ListView -from dining.models import DiningList, DiningEntry +from dining.models import DiningEntry, DiningList from userdetails.forms import RegisterUserForm from userdetails.models import User diff --git a/userdetails/views_association.py b/userdetails/views_association.py index 5c5d5f80..83d4c30e 100644 --- a/userdetails/views_association.py +++ b/userdetails/views_association.py @@ -4,22 +4,22 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator -from django.db.models import Q, Count, Sum -from django.http import HttpResponseRedirect, HttpResponse +from django.db.models import Count, Q, Sum +from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.urls import reverse from django.utils.http import url_has_allowed_host_and_scheme from django.views import View -from django.views.generic import ListView, TemplateView, FormView, DetailView +from django.views.generic import DetailView, FormView, ListView, TemplateView from creditmanagement.csv import write_transactions_csv from creditmanagement.forms import ClearOpenExpensesForm, SiteWideTransactionForm -from creditmanagement.models import Transaction, Account +from creditmanagement.models import Account, Transaction from creditmanagement.views import TransactionFormView -from dining.models import DiningList, DiningEntry +from dining.models import DiningEntry, DiningList from general.views import DateRangeFilterMixin from userdetails.forms import AssociationSettingsForm -from userdetails.models import UserMembership, Association, User +from userdetails.models import Association, User, UserMembership class AssociationBoardMixin: diff --git a/userdetails/views_user_settings.py b/userdetails/views_user_settings.py index dc2b9df7..825cbe14 100644 --- a/userdetails/views_user_settings.py +++ b/userdetails/views_user_settings.py @@ -3,7 +3,7 @@ from django.shortcuts import redirect from django.views.generic import TemplateView -from userdetails.forms import UserForm, AssociationLinkForm +from userdetails.forms import AssociationLinkForm, UserForm class SettingsProfileView(LoginRequiredMixin, TemplateView): diff --git a/utils/testing/__init__.py b/utils/testing/__init__.py index c6cb405d..09d339cd 100644 --- a/utils/testing/__init__.py +++ b/utils/testing/__init__.py @@ -1,5 +1,4 @@ from utils.testing.form_test_utils import FormValidityMixin -from utils.testing.patch_utils import patch, TestPatchMixin - +from utils.testing.patch_utils import TestPatchMixin, patch __all__ = ["FormValidityMixin", "TestPatchMixin", "patch"] diff --git a/utils/testing/patch_utils.py b/utils/testing/patch_utils.py index e6f8b544..12c63ab6 100644 --- a/utils/testing/patch_utils.py +++ b/utils/testing/patch_utils.py @@ -1,7 +1,7 @@ import datetime +from unittest.mock import Mock, patch from django.utils import timezone -from unittest.mock import patch, Mock __all__ = ["patch", "patch_time", "mock_now"] From 4fd44aea3127431924f00acb04738e40f4456e4a Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 18:43:43 +0200 Subject: [PATCH 10/14] Add .git-blame-ignore-revs --- .git-blame-ignore-revs | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..c6aca008 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +37079c3b2ee3fc4100c56b041d90faf5c5fbbe48 +87ddf24356f15143780b469ad006b3e95273cd7b +5dad39e90d7f659c0f2e630ad9dfefa10cd45b99 From 2b9e7c5e274de25d6bcd4ba6c3cddb0bbd4ca105 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 17:33:54 +0200 Subject: [PATCH 11/14] Switch from flake to isort for checking import sort order --- .flake8 | 3 ++- .github/workflows/ci.yml | 1 + dev-requirements.in | 1 - dev-requirements.txt | 6 ------ 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.flake8 b/.flake8 index 7b1409ca..69ae02b1 100644 --- a/.flake8 +++ b/.flake8 @@ -6,6 +6,8 @@ ignore = D413 # Makes it possible to split long (boolean) equations on multiple lines W503 + #Imports + I extend-select = W504 exclude = @@ -18,4 +20,3 @@ max-complexity = 10 max-line-length = 127 docstring-convention = google -import-order-style = pycharm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cf52f31..3a4b1d0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,3 +51,4 @@ jobs: - name: Lint with flake8 run: flake8 - uses: psf/black@stable + - run: isort --check --diff . diff --git a/dev-requirements.in b/dev-requirements.in index 9cdac5d7..404a059f 100644 --- a/dev-requirements.in +++ b/dev-requirements.in @@ -5,5 +5,4 @@ pep8-naming coverage autopep8 isort -flake8-import-order black diff --git a/dev-requirements.txt b/dev-requirements.txt index 53411f2e..bd1bce25 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -19,8 +19,6 @@ flake8==6.0.0 # pep8-naming flake8-docstrings==1.6.0 # via -r dev-requirements.in -flake8-import-order==0.18.2 - # via -r dev-requirements.in isort==5.12.0 # via -r dev-requirements.in mccabe==0.7.0 @@ -41,7 +39,6 @@ pycodestyle==2.10.0 # via # autopep8 # flake8 - # flake8-import-order pydocstyle==6.1.1 # via flake8-docstrings pyflakes==3.0.1 @@ -52,6 +49,3 @@ tomli==2.0.1 # via # autopep8 # black - -# The following packages are considered to be unsafe in a requirements file: -# setuptools From f01372340656b0065239cb59ec8349f065ae39a5 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Thu, 18 May 2023 19:56:33 +0200 Subject: [PATCH 12/14] Add pre-commit config for black --- .pre-commit-config.yaml | 17 +++++++++++++++++ dev-requirements.in | 1 + dev-requirements.txt | 23 ++++++++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..5caf86e6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-added-large-files + - id: check-ast + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks + - repo: https://github.com/psf/black + rev: 23.3.0 + hooks: + - id: black + language_version: python3.8 diff --git a/dev-requirements.in b/dev-requirements.in index 404a059f..36c0453f 100644 --- a/dev-requirements.in +++ b/dev-requirements.in @@ -6,3 +6,4 @@ coverage autopep8 isort black +pre-commit diff --git a/dev-requirements.txt b/dev-requirements.txt index bd1bce25..996f76cd 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -8,10 +8,16 @@ autopep8==2.0.2 # via -r dev-requirements.in black==23.3.0 # via -r dev-requirements.in +cfgv==3.3.1 + # via pre-commit click==8.1.3 # via black coverage==7.0.0 # via -r dev-requirements.in +distlib==0.3.6 + # via virtualenv +filelock==3.12.0 + # via virtualenv flake8==6.0.0 # via # -r dev-requirements.in @@ -19,12 +25,16 @@ flake8==6.0.0 # pep8-naming flake8-docstrings==1.6.0 # via -r dev-requirements.in +identify==2.5.24 + # via pre-commit isort==5.12.0 # via -r dev-requirements.in mccabe==0.7.0 # via flake8 mypy-extensions==1.0.0 # via black +nodeenv==1.8.0 + # via pre-commit packaging==22.0 # via # -c requirements.txt @@ -34,7 +44,11 @@ pathspec==0.11.1 pep8-naming==0.13.3 # via -r dev-requirements.in platformdirs==3.5.1 - # via black + # via + # black + # virtualenv +pre-commit==3.3.2 + # via -r dev-requirements.in pycodestyle==2.10.0 # via # autopep8 @@ -43,9 +57,16 @@ pydocstyle==6.1.1 # via flake8-docstrings pyflakes==3.0.1 # via flake8 +pyyaml==6.0 + # via pre-commit snowballstemmer==2.2.0 # via pydocstyle tomli==2.0.1 # via # autopep8 # black +virtualenv==20.23.0 + # via pre-commit + +# The following packages are considered to be unsafe in a requirements file: +# setuptools From 437f1bc494b0afa4492411d5f44edf40a3d637d4 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Mon, 12 Jun 2023 17:03:51 +0200 Subject: [PATCH 13/14] Format left over migration files --- creditmanagement/migrations/0001_initial.py | 108 ++--- .../migrations/0002_auto_20190203_1923.py | 48 +-- .../migrations/0003_merge_20190203_1925.py | 2 +- .../migrations/0004_auto_20190203_2324.py | 64 +-- .../migrations/0005_usercredit_view.py | 4 +- .../migrations/0006_auto_20190213_1452.py | 18 +- .../migrations/0007_usercredit_view_update.py | 4 +- .../0008_pendingdiningtransaction.py | 60 +-- .../migrations/0009_usercredit_view_update.py | 4 +- .../migrations/0010_auto_20200817_1230.py | 18 +- .../migrations/0011_account_transaction.py | 52 +-- .../migrations/0012_auto_20200824_0135.py | 30 +- .../migrations/0013_auto_20210319_2310.py | 8 +- .../migrations/0014_auto_20210320_0015.py | 22 +- .../migrations/0015_auto_20220428_2113.py | 20 +- .../migrations/0016_unfold_cancel_column.py | 8 +- .../migrations/0017_remove_cancel_column.py | 26 +- dining/migrations/0001_initial.py | 393 ++++++++++++++---- dining/migrations/0002_auto_20190203_2149.py | 17 +- dining/migrations/0003_auto_20190203_2324.py | 15 +- dining/migrations/0004_auto_20190211_1428.py | 17 +- dining/migrations/0005_auto_20190221_2339.py | 7 +- dining/migrations/0006_auto_20190226_1528.py | 7 +- dining/migrations/0007_auto_20190423_1651.py | 11 +- dining/migrations/0008_auto_20190507_0112.py | 11 +- dining/migrations/0009_auto_20190508_0230.py | 78 ++-- dining/migrations/0010_auto_20190508_0230.py | 10 +- dining/migrations/0011_auto_20190508_1905.py | 18 +- dining/migrations/0012_auto_20190728_1359.py | 17 +- dining/migrations/0013_auto_20190513_1505.py | 27 +- dining/migrations/0014_auto_20190513_1505.py | 7 +- dining/migrations/0015_auto_20190513_2104.py | 26 +- dining/migrations/0016_auto_20190514_1317.py | 44 +- dining/migrations/0017_auto_20200824_0138.py | 31 +- dining/migrations/0018_auto_20210319_2310.py | 16 +- dining/migrations/0019_auto_20210320_1309.py | 11 +- .../0020_delete_userdiningsettings.py | 7 +- .../0021_create_paymentreminderlock.py | 17 +- .../migrations/0022_rename_old_help_stats.py | 21 +- dining/migrations/0023_add_new_help_stats.py | 23 +- .../migrations/0024_move_help_stats_data.py | 6 +- .../0025_remove_dining_entry_subclasses.py | 10 +- .../0026_diningcomment_increase_length.py | 15 +- .../migrations/0027_deletedlist_and_more.py | 64 ++- .../0028_alter_dininglist_payment_link.py | 13 +- general/migrations/0001_initial.py | 16 +- general/migrations/0002_auto_20190227_1203.py | 20 +- .../0003_remove_siteupdate_version.py | 6 +- userdetails/migrations/0001_initial.py | 142 +++---- .../migrations/0002_auto_20190203_2324.py | 46 +- .../migrations/0003_auto_20190204_0025.py | 8 +- .../migrations/0004_user_external_link.py | 8 +- .../migrations/0005_auto_20190207_1510.py | 10 +- .../migrations/0006_auto_20190207_1744.py | 10 +- .../0007_association_is_choosable.py | 8 +- .../migrations/0008_remove_user_is_staff.py | 6 +- userdetails/migrations/0009_user_is_staff.py | 10 +- .../migrations/0010_auto_20190304_2050.py | 8 +- .../migrations/0011_auto_20190429_1303.py | 16 +- .../migrations/0012_auto_20190429_1306.py | 12 +- .../migrations/0013_auto_20190429_2232.py | 14 +- .../0014_remove_user_external_link.py | 6 +- .../migrations/0015_auto_20190508_1905.py | 14 +- .../migrations/0016_association_social_app.py | 12 +- ...association_balance_update_instructions.py | 8 +- .../0018_association_has_site_stats_access.py | 6 +- .../migrations/0019_auto_20200823_1602.py | 22 +- .../migrations/0020_auto_20210319_2310.py | 8 +- .../migrations/0021_auto_20221224_1346.py | 18 +- userdetails/migrations/0022_move_allergies.py | 10 +- 70 files changed, 1109 insertions(+), 770 deletions(-) diff --git a/creditmanagement/migrations/0001_initial.py b/creditmanagement/migrations/0001_initial.py index 763bda0b..6d0ec36b 100644 --- a/creditmanagement/migrations/0001_initial.py +++ b/creditmanagement/migrations/0001_initial.py @@ -14,171 +14,171 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0001_initial'), + ("userdetails", "0001_initial"), ] operations = [ migrations.CreateModel( - name='PendingDiningTransaction', + name="PendingDiningTransaction", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'amount', + "amount", models.DecimalField( decimal_places=2, max_digits=4, validators=[ - django.core.validators.MinValueValidator(Decimal('0.01')) + django.core.validators.MinValueValidator(Decimal("0.01")) ], - verbose_name='Money transferred', + verbose_name="Money transferred", ), ), - ('order_moment', models.DateTimeField(default=datetime.datetime.now)), - ('confirm_moment', models.DateTimeField(default=datetime.datetime.now)), + ("order_moment", models.DateTimeField(default=datetime.datetime.now)), + ("confirm_moment", models.DateTimeField(default=datetime.datetime.now)), ( - 'description', - models.CharField(blank=True, default='', max_length=50), + "description", + models.CharField(blank=True, default="", max_length=50), ), ], options={ - 'managed': False, + "managed": False, }, ), migrations.CreateModel( - name='FixedTransaction', + name="FixedTransaction", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'amount', + "amount", models.DecimalField( decimal_places=2, max_digits=4, validators=[ - django.core.validators.MinValueValidator(Decimal('0.01')) + django.core.validators.MinValueValidator(Decimal("0.01")) ], - verbose_name='Money transferred', + verbose_name="Money transferred", ), ), - ('order_moment', models.DateTimeField(default=datetime.datetime.now)), - ('confirm_moment', models.DateTimeField(default=datetime.datetime.now)), + ("order_moment", models.DateTimeField(default=datetime.datetime.now)), + ("confirm_moment", models.DateTimeField(default=datetime.datetime.now)), ( - 'description', - models.CharField(blank=True, default='', max_length=50), + "description", + models.CharField(blank=True, default="", max_length=50), ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.CreateModel( - name='PendingDiningListTracker', + name="PendingDiningListTracker", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ], ), migrations.CreateModel( - name='PendingTransaction', + name="PendingTransaction", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'amount', + "amount", models.DecimalField( decimal_places=2, max_digits=4, validators=[ - django.core.validators.MinValueValidator(Decimal('0.01')) + django.core.validators.MinValueValidator(Decimal("0.01")) ], - verbose_name='Money transferred', + verbose_name="Money transferred", ), ), - ('order_moment', models.DateTimeField(default=datetime.datetime.now)), - ('confirm_moment', models.DateTimeField(default=datetime.datetime.now)), + ("order_moment", models.DateTimeField(default=datetime.datetime.now)), + ("confirm_moment", models.DateTimeField(default=datetime.datetime.now)), ( - 'description', - models.CharField(blank=True, default='', max_length=50), + "description", + models.CharField(blank=True, default="", max_length=50), ), ( - 'source_association', + "source_association", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingtransaction_transaction_source', - to='userdetails.Association', - verbose_name='The association giving the money', + related_name="pendingtransaction_transaction_source", + to="userdetails.Association", + verbose_name="The association giving the money", ), ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.AddField( - model_name='pendingtransaction', - name='source_user', + model_name="pendingtransaction", + name="source_user", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingtransaction_transaction_source', + related_name="pendingtransaction_transaction_source", to=settings.AUTH_USER_MODEL, - verbose_name='The user giving the money', + verbose_name="The user giving the money", ), ), migrations.AddField( - model_name='pendingtransaction', - name='target_association', + model_name="pendingtransaction", + name="target_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingtransaction_transaction_target', - to='userdetails.Association', - verbose_name='The association recieving the money', + related_name="pendingtransaction_transaction_target", + to="userdetails.Association", + verbose_name="The association recieving the money", ), ), migrations.AddField( - model_name='pendingtransaction', - name='target_user', + model_name="pendingtransaction", + name="target_user", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingtransaction_transaction_target', + related_name="pendingtransaction_transaction_target", to=settings.AUTH_USER_MODEL, - verbose_name='The user receiving the money', + verbose_name="The user receiving the money", ), ), ] diff --git a/creditmanagement/migrations/0002_auto_20190203_1923.py b/creditmanagement/migrations/0002_auto_20190203_1923.py index 7296cf74..9f29f152 100644 --- a/creditmanagement/migrations/0002_auto_20190203_1923.py +++ b/creditmanagement/migrations/0002_auto_20190203_1923.py @@ -9,66 +9,66 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('dining', '0001_initial'), + ("dining", "0001_initial"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0001_initial'), - ('creditmanagement', '0001_initial'), + ("userdetails", "0001_initial"), + ("creditmanagement", "0001_initial"), ] operations = [ migrations.AddField( - model_name='pendingdininglisttracker', - name='dining_list', + model_name="pendingdininglisttracker", + name="dining_list", field=models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, to='dining.DiningList' + on_delete=django.db.models.deletion.CASCADE, to="dining.DiningList" ), ), migrations.AddField( - model_name='fixedtransaction', - name='source_association', + model_name="fixedtransaction", + name="source_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='fixedtransaction_transaction_source', - to='userdetails.Association', - verbose_name='The association giving the money', + related_name="fixedtransaction_transaction_source", + to="userdetails.Association", + verbose_name="The association giving the money", ), ), migrations.AddField( - model_name='fixedtransaction', - name='source_user', + model_name="fixedtransaction", + name="source_user", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='fixedtransaction_transaction_source', + related_name="fixedtransaction_transaction_source", to=settings.AUTH_USER_MODEL, - verbose_name='The user giving the money', + verbose_name="The user giving the money", ), ), migrations.AddField( - model_name='fixedtransaction', - name='target_association', + model_name="fixedtransaction", + name="target_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='fixedtransaction_transaction_target', - to='userdetails.Association', - verbose_name='The association recieving the money', + related_name="fixedtransaction_transaction_target", + to="userdetails.Association", + verbose_name="The association recieving the money", ), ), migrations.AddField( - model_name='fixedtransaction', - name='target_user', + model_name="fixedtransaction", + name="target_user", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='fixedtransaction_transaction_target', + related_name="fixedtransaction_transaction_target", to=settings.AUTH_USER_MODEL, - verbose_name='The user receiving the money', + verbose_name="The user receiving the money", ), ), ] diff --git a/creditmanagement/migrations/0003_merge_20190203_1925.py b/creditmanagement/migrations/0003_merge_20190203_1925.py index c0bf137a..234e0bbe 100644 --- a/creditmanagement/migrations/0003_merge_20190203_1925.py +++ b/creditmanagement/migrations/0003_merge_20190203_1925.py @@ -5,7 +5,7 @@ class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0002_auto_20190203_1923'), + ("creditmanagement", "0002_auto_20190203_1923"), ] operations = [] diff --git a/creditmanagement/migrations/0004_auto_20190203_2324.py b/creditmanagement/migrations/0004_auto_20190203_2324.py index 3a094921..086ee358 100644 --- a/creditmanagement/migrations/0004_auto_20190203_2324.py +++ b/creditmanagement/migrations/0004_auto_20190203_2324.py @@ -6,81 +6,81 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0002_auto_20190203_2324'), - ('creditmanagement', '0003_merge_20190203_1925'), + ("userdetails", "0002_auto_20190203_2324"), + ("creditmanagement", "0003_merge_20190203_1925"), ] operations = [ migrations.AlterField( - model_name='PendingTransaction', - name='source_association', + model_name="PendingTransaction", + name="source_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingtransaction_transaction_source', - to='userdetails.Association', - verbose_name='The association giving the money', + related_name="pendingtransaction_transaction_source", + to="userdetails.Association", + verbose_name="The association giving the money", ), ), migrations.AlterField( - model_name='PendingTransaction', - name='target_association', + model_name="PendingTransaction", + name="target_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingtransaction_transaction_target', - to='userdetails.Association', - verbose_name='The association recieving the money', + related_name="pendingtransaction_transaction_target", + to="userdetails.Association", + verbose_name="The association recieving the money", ), ), migrations.AlterField( - model_name='FixedTransaction', - name='source_association', + model_name="FixedTransaction", + name="source_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='fixedtransaction_transaction_source', - to='userdetails.Association', - verbose_name='The association giving the money', + related_name="fixedtransaction_transaction_source", + to="userdetails.Association", + verbose_name="The association giving the money", ), ), migrations.AlterField( - model_name='FixedTransaction', - name='target_association', + model_name="FixedTransaction", + name="target_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='fixedtransaction_transaction_target', - to='userdetails.Association', - verbose_name='The association recieving the money', + related_name="fixedtransaction_transaction_target", + to="userdetails.Association", + verbose_name="The association recieving the money", ), ), migrations.AlterField( - model_name='PendingDiningTransaction', - name='source_association', + model_name="PendingDiningTransaction", + name="source_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingdiningtransaction_transaction_source', - to='userdetails.Association', - verbose_name='The association giving the money', + related_name="pendingdiningtransaction_transaction_source", + to="userdetails.Association", + verbose_name="The association giving the money", ), ), migrations.AlterField( - model_name='PendingDiningTransaction', - name='target_association', + model_name="PendingDiningTransaction", + name="target_association", field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='pendingdiningtransaction_transaction_target', - to='userdetails.Association', - verbose_name='The association recieving the money', + related_name="pendingdiningtransaction_transaction_target", + to="userdetails.Association", + verbose_name="The association recieving the money", ), ), ] diff --git a/creditmanagement/migrations/0005_usercredit_view.py b/creditmanagement/migrations/0005_usercredit_view.py index c6fab3f5..a884e251 100644 --- a/creditmanagement/migrations/0005_usercredit_view.py +++ b/creditmanagement/migrations/0005_usercredit_view.py @@ -7,8 +7,8 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0001_initial'), - ('creditmanagement', '0004_auto_20190203_2324'), + ("userdetails", "0001_initial"), + ("creditmanagement", "0004_auto_20190203_2324"), ] operations = [] diff --git a/creditmanagement/migrations/0006_auto_20190213_1452.py b/creditmanagement/migrations/0006_auto_20190213_1452.py index 12a1f12b..9b7fefcc 100644 --- a/creditmanagement/migrations/0006_auto_20190213_1452.py +++ b/creditmanagement/migrations/0006_auto_20190213_1452.py @@ -6,28 +6,28 @@ class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0005_usercredit_view'), + ("creditmanagement", "0005_usercredit_view"), ] operations = [ migrations.AlterField( - model_name='fixedtransaction', - name='confirm_moment', + model_name="fixedtransaction", + name="confirm_moment", field=models.DateTimeField(blank=True, default=django.utils.timezone.now), ), migrations.AlterField( - model_name='fixedtransaction', - name='order_moment', + model_name="fixedtransaction", + name="order_moment", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.AlterField( - model_name='pendingtransaction', - name='confirm_moment', + model_name="pendingtransaction", + name="confirm_moment", field=models.DateTimeField(blank=True, default=django.utils.timezone.now), ), migrations.AlterField( - model_name='pendingtransaction', - name='order_moment', + model_name="pendingtransaction", + name="order_moment", field=models.DateTimeField(default=django.utils.timezone.now), ), ] diff --git a/creditmanagement/migrations/0007_usercredit_view_update.py b/creditmanagement/migrations/0007_usercredit_view_update.py index 6bda0fe7..4920a611 100644 --- a/creditmanagement/migrations/0007_usercredit_view_update.py +++ b/creditmanagement/migrations/0007_usercredit_view_update.py @@ -3,8 +3,8 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0007_association_is_choosable'), - ('creditmanagement', '0006_auto_20190213_1452'), + ("userdetails", "0007_association_is_choosable"), + ("creditmanagement", "0006_auto_20190213_1452"), ] operations = [] diff --git a/creditmanagement/migrations/0008_pendingdiningtransaction.py b/creditmanagement/migrations/0008_pendingdiningtransaction.py index 24a0e186..db13e855 100644 --- a/creditmanagement/migrations/0008_pendingdiningtransaction.py +++ b/creditmanagement/migrations/0008_pendingdiningtransaction.py @@ -12,103 +12,103 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0008_remove_user_is_staff'), - ('creditmanagement', '0007_usercredit_view_update'), + ("userdetails", "0008_remove_user_is_staff"), + ("creditmanagement", "0007_usercredit_view_update"), ] operations = [ migrations.AlterModelOptions( - name='pendingdiningtransaction', + name="pendingdiningtransaction", options={}, ), migrations.CreateModel( - name='PendingDiningTransaction', + name="PendingDiningTransaction", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'source_user', + "source_user", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, - related_name='pendingdiningtransaction_transaction_source', + related_name="pendingdiningtransaction_transaction_source", to=settings.AUTH_USER_MODEL, - verbose_name='The user giving the money', + verbose_name="The user giving the money", ), ), ( - 'source_association', + "source_association", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, - related_name='pendingdiningtransaction_transaction_source', - to='userdetails.Association', - verbose_name='The association giving the money', + related_name="pendingdiningtransaction_transaction_source", + to="userdetails.Association", + verbose_name="The association giving the money", ), ), ( - 'amount', + "amount", models.DecimalField( decimal_places=2, max_digits=4, validators=[ - django.core.validators.MinValueValidator(Decimal('0.01')) + django.core.validators.MinValueValidator(Decimal("0.01")) ], - verbose_name='Money transferred', + verbose_name="Money transferred", ), ), ( - 'target_user', + "target_user", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, - related_name='pendingdiningtransaction_transaction_target', + related_name="pendingdiningtransaction_transaction_target", to=settings.AUTH_USER_MODEL, - verbose_name='The user receiving the money', + verbose_name="The user receiving the money", ), ), ( - 'target_association', + "target_association", models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, - related_name='pendingdiningtransaction_transaction_target', - to='userdetails.Association', - verbose_name='The association recieving the money', + related_name="pendingdiningtransaction_transaction_target", + to="userdetails.Association", + verbose_name="The association recieving the money", ), ), ( - 'order_moment', + "order_moment", models.DateTimeField(default=django.utils.timezone.now), ), ( - 'confirm_moment', + "confirm_moment", models.DateTimeField(blank=True, default=django.utils.timezone.now), ), ( - 'description', - models.CharField(blank=True, default='', max_length=50), + "description", + models.CharField(blank=True, default="", max_length=50), ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.AlterModelOptions( - name='pendingdiningtransaction', + name="pendingdiningtransaction", options={ - 'managed': False, + "managed": False, }, ), ] diff --git a/creditmanagement/migrations/0009_usercredit_view_update.py b/creditmanagement/migrations/0009_usercredit_view_update.py index 16e117cf..19dfaa44 100644 --- a/creditmanagement/migrations/0009_usercredit_view_update.py +++ b/creditmanagement/migrations/0009_usercredit_view_update.py @@ -3,8 +3,8 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0017_association_balance_update_instructions'), - ('creditmanagement', '0008_pendingdiningtransaction'), + ("userdetails", "0017_association_balance_update_instructions"), + ("creditmanagement", "0008_pendingdiningtransaction"), ] operations = [] diff --git a/creditmanagement/migrations/0010_auto_20200817_1230.py b/creditmanagement/migrations/0010_auto_20200817_1230.py index c0c707d5..fc75ac60 100644 --- a/creditmanagement/migrations/0010_auto_20200817_1230.py +++ b/creditmanagement/migrations/0010_auto_20200817_1230.py @@ -8,28 +8,28 @@ class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0009_usercredit_view_update'), + ("creditmanagement", "0009_usercredit_view_update"), ] operations = [ migrations.AlterField( - model_name='fixedtransaction', - name='amount', + model_name="fixedtransaction", + name="amount", field=models.DecimalField( decimal_places=2, max_digits=5, - validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], - verbose_name='Money transferred', + validators=[django.core.validators.MinValueValidator(Decimal("0.01"))], + verbose_name="Money transferred", ), ), migrations.AlterField( - model_name='pendingtransaction', - name='amount', + model_name="pendingtransaction", + name="amount", field=models.DecimalField( decimal_places=2, max_digits=5, - validators=[django.core.validators.MinValueValidator(Decimal('0.01'))], - verbose_name='Money transferred', + validators=[django.core.validators.MinValueValidator(Decimal("0.01"))], + verbose_name="Money transferred", ), ), # This migration contained a CreateView and DeleteView for the same model. diff --git a/creditmanagement/migrations/0011_account_transaction.py b/creditmanagement/migrations/0011_account_transaction.py index 397ef0cb..9c078403 100644 --- a/creditmanagement/migrations/0011_account_transaction.py +++ b/creditmanagement/migrations/0011_account_transaction.py @@ -12,33 +12,33 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0019_auto_20200823_1602'), - ('creditmanagement', '0010_auto_20200817_1230'), + ("userdetails", "0019_auto_20200823_1602"), + ("creditmanagement", "0010_auto_20200817_1230"), ] operations = [ migrations.CreateModel( - name='Account', + name="Account", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'association', + "association", models.OneToOneField( null=True, on_delete=django.db.models.deletion.PROTECT, - to='userdetails.Association', + to="userdetails.Association", ), ), ( - 'user', + "user", models.OneToOneField( null=True, on_delete=django.db.models.deletion.PROTECT, @@ -48,61 +48,61 @@ class Migration(migrations.Migration): ], ), migrations.CreateModel( - name='Transaction', + name="Transaction", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'amount', + "amount", models.DecimalField( decimal_places=2, max_digits=8, validators=[ - django.core.validators.MinValueValidator(Decimal('0.01')) + django.core.validators.MinValueValidator(Decimal("0.01")) ], ), ), - ('moment', models.DateTimeField(default=django.utils.timezone.now)), - ('description', models.CharField(max_length=150)), - ('cancelled', models.DateTimeField(null=True)), + ("moment", models.DateTimeField(default=django.utils.timezone.now)), + ("description", models.CharField(max_length=150)), + ("cancelled", models.DateTimeField(null=True)), ( - 'cancelled_by', + "cancelled_by", models.ForeignKey( null=True, on_delete=django.db.models.deletion.PROTECT, - related_name='transaction_cancelled_set', + related_name="transaction_cancelled_set", to=settings.AUTH_USER_MODEL, ), ), ( - 'created_by', + "created_by", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, - related_name='transaction_set', + related_name="transaction_set", to=settings.AUTH_USER_MODEL, ), ), ( - 'source', + "source", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, - related_name='transaction_source_set', - to='creditmanagement.Account', + related_name="transaction_source_set", + to="creditmanagement.Account", ), ), ( - 'target', + "target", models.ForeignKey( on_delete=django.db.models.deletion.PROTECT, - related_name='transaction_target_set', - to='creditmanagement.Account', + related_name="transaction_target_set", + to="creditmanagement.Account", ), ), ], diff --git a/creditmanagement/migrations/0012_auto_20200824_0135.py b/creditmanagement/migrations/0012_auto_20200824_0135.py index 2019e6f2..1099dbc1 100644 --- a/creditmanagement/migrations/0012_auto_20200824_0135.py +++ b/creditmanagement/migrations/0012_auto_20200824_0135.py @@ -5,44 +5,44 @@ class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0011_account_transaction'), + ("creditmanagement", "0011_account_transaction"), ] operations = [ migrations.RemoveField( - model_name='pendingdiningtransaction', - name='source_association', + model_name="pendingdiningtransaction", + name="source_association", ), migrations.RemoveField( - model_name='pendingdiningtransaction', - name='source_user', + model_name="pendingdiningtransaction", + name="source_user", ), migrations.RemoveField( - model_name='pendingdiningtransaction', - name='target_association', + model_name="pendingdiningtransaction", + name="target_association", ), migrations.RemoveField( - model_name='pendingdiningtransaction', - name='target_user', + model_name="pendingdiningtransaction", + name="target_user", ), migrations.AddField( - model_name='account', - name='special', + model_name="account", + name="special", field=models.CharField(default=None, max_length=30, null=True, unique=True), ), migrations.DeleteModel( - name='PendingDiningListTracker', + name="PendingDiningListTracker", ), # Added this managed=True manually, because in migration 8 the PendingDiningTransaction table was created and # afterwards set to managed=False. Due to this, it was physically created in the database and will remain there # if this isn't set back to managed=True. migrations.AlterModelOptions( - name='PendingDiningTransaction', + name="PendingDiningTransaction", options={ - 'managed': True, + "managed": True, }, ), migrations.DeleteModel( # (This model had managed=False) - name='PendingDiningTransaction', + name="PendingDiningTransaction", ), ] diff --git a/creditmanagement/migrations/0013_auto_20210319_2310.py b/creditmanagement/migrations/0013_auto_20210319_2310.py index 591c85d2..bca99820 100644 --- a/creditmanagement/migrations/0013_auto_20210319_2310.py +++ b/creditmanagement/migrations/0013_auto_20210319_2310.py @@ -5,15 +5,15 @@ class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0012_auto_20200824_0135'), + ("creditmanagement", "0012_auto_20200824_0135"), ] operations = [ migrations.AlterField( - model_name='account', - name='special', + model_name="account", + name="special", field=models.CharField( - choices=[('kitchen_cost', 'Kitchen cost')], + choices=[("kitchen_cost", "Kitchen cost")], default=None, max_length=30, null=True, diff --git a/creditmanagement/migrations/0014_auto_20210320_0015.py b/creditmanagement/migrations/0014_auto_20210320_0015.py index e17df657..e74e525a 100644 --- a/creditmanagement/migrations/0014_auto_20210320_0015.py +++ b/creditmanagement/migrations/0014_auto_20210320_0015.py @@ -5,30 +5,30 @@ class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0013_auto_20210319_2310'), + ("creditmanagement", "0013_auto_20210319_2310"), ] operations = [ migrations.RemoveField( - model_name='pendingtransaction', - name='source_association', + model_name="pendingtransaction", + name="source_association", ), migrations.RemoveField( - model_name='pendingtransaction', - name='source_user', + model_name="pendingtransaction", + name="source_user", ), migrations.RemoveField( - model_name='pendingtransaction', - name='target_association', + model_name="pendingtransaction", + name="target_association", ), migrations.RemoveField( - model_name='pendingtransaction', - name='target_user', + model_name="pendingtransaction", + name="target_user", ), migrations.DeleteModel( - name='FixedTransaction', + name="FixedTransaction", ), migrations.DeleteModel( - name='PendingTransaction', + name="PendingTransaction", ), ] diff --git a/creditmanagement/migrations/0015_auto_20220428_2113.py b/creditmanagement/migrations/0015_auto_20220428_2113.py index 3c7ae9eb..8c2da414 100644 --- a/creditmanagement/migrations/0015_auto_20220428_2113.py +++ b/creditmanagement/migrations/0015_auto_20220428_2113.py @@ -8,26 +8,26 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0020_auto_20210319_2310'), - ('creditmanagement', '0014_auto_20210320_0015'), + ("userdetails", "0020_auto_20210319_2310"), + ("creditmanagement", "0014_auto_20210320_0015"), ] operations = [ migrations.AlterField( - model_name='account', - name='association', + model_name="account", + name="association", field=models.OneToOneField( null=True, on_delete=django.db.models.deletion.CASCADE, - to='userdetails.association', + to="userdetails.association", ), ), migrations.AlterField( - model_name='account', - name='special', + model_name="account", + name="special", field=models.CharField( blank=True, - choices=[('kitchen_cost', 'Kitchen cost')], + choices=[("kitchen_cost", "Kitchen cost")], default=None, max_length=30, null=True, @@ -35,8 +35,8 @@ class Migration(migrations.Migration): ), ), migrations.AlterField( - model_name='account', - name='user', + model_name="account", + name="user", field=models.OneToOneField( null=True, on_delete=django.db.models.deletion.CASCADE, diff --git a/creditmanagement/migrations/0016_unfold_cancel_column.py b/creditmanagement/migrations/0016_unfold_cancel_column.py index 7ada7067..5ab7b22f 100644 --- a/creditmanagement/migrations/0016_unfold_cancel_column.py +++ b/creditmanagement/migrations/0016_unfold_cancel_column.py @@ -16,9 +16,9 @@ def compute_balance(Account, Transaction): """Computes the balance for all accounts and returns a list.""" qs = Transaction.objects.filter(cancelled=None) return [ - (qs.filter(target=a).aggregate(sum=Sum('amount'))['sum'] or Decimal('0.00')) - - (qs.filter(source=a).aggregate(sum=Sum('amount'))['sum'] or Decimal('0.00')) - for a in Account.objects.order_by('id') + (qs.filter(target=a).aggregate(sum=Sum("amount"))["sum"] or Decimal("0.00")) + - (qs.filter(source=a).aggregate(sum=Sum("amount"))["sum"] or Decimal("0.00")) + for a in Account.objects.order_by("id") ] @@ -78,7 +78,7 @@ def forward(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('creditmanagement', '0015_auto_20220428_2113'), + ("creditmanagement", "0015_auto_20220428_2113"), ] operations = [ diff --git a/creditmanagement/migrations/0017_remove_cancel_column.py b/creditmanagement/migrations/0017_remove_cancel_column.py index 9ccd9aad..9ac9e552 100644 --- a/creditmanagement/migrations/0017_remove_cancel_column.py +++ b/creditmanagement/migrations/0017_remove_cancel_column.py @@ -8,32 +8,32 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0022_move_allergies'), - ('creditmanagement', '0016_unfold_cancel_column'), + ("userdetails", "0022_move_allergies"), + ("creditmanagement", "0016_unfold_cancel_column"), ] operations = [ migrations.RemoveField( - model_name='transaction', - name='cancelled', + model_name="transaction", + name="cancelled", ), migrations.RemoveField( - model_name='transaction', - name='cancelled_by', + model_name="transaction", + name="cancelled_by", ), migrations.AlterField( - model_name='account', - name='association', + model_name="account", + name="association", field=models.OneToOneField( blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, - to='userdetails.association', + to="userdetails.association", ), ), migrations.AlterField( - model_name='account', - name='user', + model_name="account", + name="user", field=models.OneToOneField( blank=True, null=True, @@ -42,8 +42,8 @@ class Migration(migrations.Migration): ), ), migrations.AlterField( - model_name='transaction', - name='description', + model_name="transaction", + name="description", field=models.CharField(max_length=1000), ), ] diff --git a/dining/migrations/0001_initial.py b/dining/migrations/0001_initial.py index 4ef5fc20..9ae34be8 100644 --- a/dining/migrations/0001_initial.py +++ b/dining/migrations/0001_initial.py @@ -11,152 +11,375 @@ class Migration(migrations.Migration): - initial = True dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('userdetails', '0001_initial'), + ("userdetails", "0001_initial"), ] operations = [ migrations.CreateModel( - name='DiningComment', + name="DiningComment", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('message', models.TextField()), - ('pinned_to_top', models.BooleanField(default=False)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ("message", models.TextField()), + ("pinned_to_top", models.BooleanField(default=False)), ], ), migrations.CreateModel( - name='DiningCommentView', + name="DiningCommentView", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('timestamp', models.DateTimeField(auto_now_add=True)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), ], ), migrations.CreateModel( - name='DiningDayAnnouncements', + name="DiningDayAnnouncements", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateField()), - ('title', models.CharField(max_length=15)), - ('text', models.CharField(max_length=240)), - ('slots_occupy', models.IntegerField(default=0, help_text='The amount of slots this occupies')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField()), + ("title", models.CharField(max_length=15)), + ("text", models.CharField(max_length=240)), + ( + "slots_occupy", + models.IntegerField( + default=0, help_text="The amount of slots this occupies" + ), + ), ], ), migrations.CreateModel( - name='DiningEntry', + name="DiningEntry", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('has_paid', models.BooleanField(default=False)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("has_paid", models.BooleanField(default=False)), ], ), migrations.CreateModel( - name='DiningList', + name="DiningList", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateField(default=django.utils.timezone.now)), - ('sign_up_deadline', models.DateTimeField(help_text='The time before users need to sign up.')), - ('serve_time', models.TimeField(default=datetime.time(18, 0))), - ('dish', models.CharField(blank=True, default='', help_text='The dish made', max_length=30)), - ('adjustable_duration', models.DurationField(default=datetime.timedelta(2), help_text='The amount of time the dining list can be adjusted after its date')), - ('limit_signups_to_association_only', models.BooleanField(default=False, help_text='Whether only members of the given association can sign up')), - ('kitchen_cost', models.DecimalField(decimal_places=2, default=Decimal('0.50'), max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='kitchen cost per person')), - ('dinner_cost_total', models.DecimalField(decimal_places=2, default=0, max_digits=10, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='total dinner costs')), - ('dinner_cost_single', models.DecimalField(blank=True, decimal_places=2, default=0, max_digits=5, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='dinner cost per person')), - ('dinner_cost_keep_single_constant', models.BooleanField(default=False, verbose_name='Define costs from single price')), - ('auto_pay', models.BooleanField(default=False)), - ('tikkie_link', models.CharField(blank=True, max_length=50, null=True, verbose_name='tikkie hyperlink')), - ('min_diners', models.IntegerField(default=4, validators=[django.core.validators.MaxValueValidator(6)])), - ('max_diners', models.IntegerField(default=20, validators=[django.core.validators.MinValueValidator(12)])), - ('association', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='userdetails.Association', unique_for_date='date')), - ('claimed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dininglist_claimer', to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("date", models.DateField(default=django.utils.timezone.now)), + ( + "sign_up_deadline", + models.DateTimeField( + help_text="The time before users need to sign up." + ), + ), + ("serve_time", models.TimeField(default=datetime.time(18, 0))), + ( + "dish", + models.CharField( + blank=True, default="", help_text="The dish made", max_length=30 + ), + ), + ( + "adjustable_duration", + models.DurationField( + default=datetime.timedelta(2), + help_text="The amount of time the dining list can be adjusted after its date", + ), + ), + ( + "limit_signups_to_association_only", + models.BooleanField( + default=False, + help_text="Whether only members of the given association can sign up", + ), + ), + ( + "kitchen_cost", + models.DecimalField( + decimal_places=2, + default=Decimal("0.50"), + max_digits=10, + validators=[ + django.core.validators.MinValueValidator(Decimal("0.00")) + ], + verbose_name="kitchen cost per person", + ), + ), + ( + "dinner_cost_total", + models.DecimalField( + decimal_places=2, + default=0, + max_digits=10, + validators=[ + django.core.validators.MinValueValidator(Decimal("0.00")) + ], + verbose_name="total dinner costs", + ), + ), + ( + "dinner_cost_single", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=5, + null=True, + validators=[ + django.core.validators.MinValueValidator(Decimal("0.00")) + ], + verbose_name="dinner cost per person", + ), + ), + ( + "dinner_cost_keep_single_constant", + models.BooleanField( + default=False, verbose_name="Define costs from single price" + ), + ), + ("auto_pay", models.BooleanField(default=False)), + ( + "tikkie_link", + models.CharField( + blank=True, + max_length=50, + null=True, + verbose_name="tikkie hyperlink", + ), + ), + ( + "min_diners", + models.IntegerField( + default=4, + validators=[django.core.validators.MaxValueValidator(6)], + ), + ), + ( + "max_diners", + models.IntegerField( + default=20, + validators=[django.core.validators.MinValueValidator(12)], + ), + ), + ( + "association", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="userdetails.Association", + unique_for_date="date", + ), + ), + ( + "claimed_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="dininglist_claimer", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='DiningWork', + name="DiningWork", fields=[ - ('w_id', models.AutoField(primary_key=True, serialize=False)), - ('has_shopped', models.BooleanField(default=False)), - ('has_cooked', models.BooleanField(default=False)), - ('has_cleaned', models.BooleanField(default=False)), + ("w_id", models.AutoField(primary_key=True, serialize=False)), + ("has_shopped", models.BooleanField(default=False)), + ("has_cooked", models.BooleanField(default=False)), + ("has_cleaned", models.BooleanField(default=False)), ], ), migrations.CreateModel( - name='UserDiningSettings', + name="UserDiningSettings", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('allergies', models.CharField(blank=True, help_text='Leave empty if not applicable', max_length=100, verbose_name='Allergies or dietary restrictions')), - ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "allergies", + models.CharField( + blank=True, + help_text="Leave empty if not applicable", + max_length=100, + verbose_name="Allergies or dietary restrictions", + ), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( - name='DiningEntryExternal', + name="DiningEntryExternal", fields=[ - ('diningentry_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='dining.DiningEntry')), - ('name', models.CharField(max_length=40)), + ( + "diningentry_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="dining.DiningEntry", + ), + ), + ("name", models.CharField(max_length=40)), ], - bases=('dining.diningentry',), + bases=("dining.diningentry",), ), migrations.CreateModel( - name='DiningEntryUser', + name="DiningEntryUser", fields=[ - ('diningwork_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, to='dining.DiningWork')), - ('diningentry_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='dining.DiningEntry')), - ('added_by', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='added_entry_on_dining', to=settings.AUTH_USER_MODEL)), + ( + "diningwork_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + to="dining.DiningWork", + ), + ), + ( + "diningentry_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="dining.DiningEntry", + ), + ), + ( + "added_by", + models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_DEFAULT, + related_name="added_entry_on_dining", + to=settings.AUTH_USER_MODEL, + ), + ), ], - bases=('dining.diningentry', 'dining.diningwork'), + bases=("dining.diningentry", "dining.diningwork"), ), migrations.AddField( - model_name='dininglist', - name='diners', - field=models.ManyToManyField(through='dining.DiningEntry', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="diners", + field=models.ManyToManyField( + through="dining.DiningEntry", to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='dininglist', - name='purchaser', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='dininglist_purchaser', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="purchaser", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="dininglist_purchaser", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AddField( - model_name='diningentry', - name='dining_list', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dining_entries', to='dining.DiningList'), + model_name="diningentry", + name="dining_list", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="dining_entries", + to="dining.DiningList", + ), ), migrations.AddField( - model_name='diningentry', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="diningentry", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='diningcommentview', - name='dining_list', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dining.DiningList'), + model_name="diningcommentview", + name="dining_list", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="dining.DiningList" + ), ), migrations.AddField( - model_name='diningcommentview', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="diningcommentview", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), migrations.AddField( - model_name='diningcomment', - name='dining_list', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dining.DiningList'), + model_name="diningcomment", + name="dining_list", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="dining.DiningList" + ), ), migrations.AddField( - model_name='diningcomment', - name='poster', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + model_name="diningcomment", + name="poster", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL + ), ), migrations.CreateModel( - name='DiningListComment', - fields=[ - ], + name="DiningListComment", + fields=[], options={ - 'proxy': True, - 'indexes': [], + "proxy": True, + "indexes": [], }, - bases=('dining.dininglist',), + bases=("dining.dininglist",), ), ] diff --git a/dining/migrations/0002_auto_20190203_2149.py b/dining/migrations/0002_auto_20190203_2149.py index 8a1423cd..a29f3b02 100644 --- a/dining/migrations/0002_auto_20190203_2149.py +++ b/dining/migrations/0002_auto_20190203_2149.py @@ -4,19 +4,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0001_initial'), + ("dining", "0001_initial"), ] operations = [ migrations.RemoveField( - model_name='dininglist', - name='tikkie_link', + model_name="dininglist", + name="tikkie_link", ), migrations.AddField( - model_name='dininglist', - name='payment_link', - field=models.CharField(blank=True, help_text='Link for payment, e.g. a Tikkie link.', max_length=100), + model_name="dininglist", + name="payment_link", + field=models.CharField( + blank=True, + help_text="Link for payment, e.g. a Tikkie link.", + max_length=100, + ), ), ] diff --git a/dining/migrations/0003_auto_20190203_2324.py b/dining/migrations/0003_auto_20190203_2324.py index 44ad47fe..953f2621 100644 --- a/dining/migrations/0003_auto_20190203_2324.py +++ b/dining/migrations/0003_auto_20190203_2324.py @@ -5,16 +5,19 @@ class Migration(migrations.Migration): - dependencies = [ - ('userdetails', '0002_auto_20190203_2324'), - ('dining', '0002_auto_20190203_2149'), + ("userdetails", "0002_auto_20190203_2324"), + ("dining", "0002_auto_20190203_2149"), ] operations = [ migrations.AlterField( - model_name='DiningList', - name='association', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='userdetails.Association', unique_for_date='date'), + model_name="DiningList", + name="association", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="userdetails.Association", + unique_for_date="date", + ), ), ] diff --git a/dining/migrations/0004_auto_20190211_1428.py b/dining/migrations/0004_auto_20190211_1428.py index 08595f7d..9479c47d 100644 --- a/dining/migrations/0004_auto_20190211_1428.py +++ b/dining/migrations/0004_auto_20190211_1428.py @@ -4,23 +4,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0003_auto_20190203_2324'), + ("dining", "0003_auto_20190203_2324"), ] operations = [ migrations.RemoveField( - model_name='dininglist', - name='dinner_cost_keep_single_constant', + model_name="dininglist", + name="dinner_cost_keep_single_constant", ), migrations.RemoveField( - model_name='dininglist', - name='dinner_cost_total', + model_name="dininglist", + name="dinner_cost_total", ), migrations.RenameField( - model_name='dininglist', - old_name='dinner_cost_single', - new_name='dining_cost', + model_name="dininglist", + old_name="dinner_cost_single", + new_name="dining_cost", ), ] diff --git a/dining/migrations/0005_auto_20190221_2339.py b/dining/migrations/0005_auto_20190221_2339.py index d8e7f00c..0bb67939 100644 --- a/dining/migrations/0005_auto_20190221_2339.py +++ b/dining/migrations/0005_auto_20190221_2339.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0004_auto_20190211_1428'), + ("dining", "0004_auto_20190211_1428"), ] operations = [ migrations.AlterField( - model_name='diningcomment', - name='message', + model_name="diningcomment", + name="message", field=models.CharField(max_length=256), ), ] diff --git a/dining/migrations/0006_auto_20190226_1528.py b/dining/migrations/0006_auto_20190226_1528.py index 2fa46704..18d23f60 100644 --- a/dining/migrations/0006_auto_20190226_1528.py +++ b/dining/migrations/0006_auto_20190226_1528.py @@ -5,15 +5,14 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dining', '0005_auto_20190221_2339'), + ("dining", "0005_auto_20190221_2339"), ] operations = [ migrations.RenameModel( - old_name='DiningCommentView', - new_name='DiningCommentVisitTracker', + old_name="DiningCommentView", + new_name="DiningCommentVisitTracker", ), ] diff --git a/dining/migrations/0007_auto_20190423_1651.py b/dining/migrations/0007_auto_20190423_1651.py index 4a8c3266..f185f594 100644 --- a/dining/migrations/0007_auto_20190423_1651.py +++ b/dining/migrations/0007_auto_20190423_1651.py @@ -4,15 +4,16 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0006_auto_20190226_1528'), + ("dining", "0006_auto_20190226_1528"), ] operations = [ migrations.AlterField( - model_name='dininglist', - name='dish', - field=models.CharField(blank=True, default='', help_text='The dish made', max_length=100), + model_name="dininglist", + name="dish", + field=models.CharField( + blank=True, default="", help_text="The dish made", max_length=100 + ), ), ] diff --git a/dining/migrations/0008_auto_20190507_0112.py b/dining/migrations/0008_auto_20190507_0112.py index 728765dd..afb291b4 100644 --- a/dining/migrations/0008_auto_20190507_0112.py +++ b/dining/migrations/0008_auto_20190507_0112.py @@ -4,19 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0007_auto_20190423_1651'), + ("dining", "0007_auto_20190423_1651"), ] operations = [ migrations.RenameModel( - old_name='DiningDayAnnouncements', - new_name='DiningDayAnnouncement', + old_name="DiningDayAnnouncements", + new_name="DiningDayAnnouncement", ), migrations.AlterField( - model_name='dininglist', - name='date', + model_name="dininglist", + name="date", field=models.DateField(), ), ] diff --git a/dining/migrations/0009_auto_20190508_0230.py b/dining/migrations/0009_auto_20190508_0230.py index 4a8d24ef..70339301 100644 --- a/dining/migrations/0009_auto_20190508_0230.py +++ b/dining/migrations/0009_auto_20190508_0230.py @@ -6,58 +6,84 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dining', '0008_auto_20190507_0112'), + ("dining", "0008_auto_20190507_0112"), ] operations = [ migrations.AddField( - model_name='diningentry', - name='created_by', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='created_dining_entries', to=settings.AUTH_USER_MODEL, null=True), + model_name="diningentry", + name="created_by", + field=models.ForeignKey( + default=None, + on_delete=django.db.models.deletion.PROTECT, + related_name="created_dining_entries", + to=settings.AUTH_USER_MODEL, + null=True, + ), preserve_default=False, ), migrations.AlterField( - model_name='diningdayannouncement', - name='text', + model_name="diningdayannouncement", + name="text", field=models.TextField(), ), migrations.AlterField( - model_name='diningdayannouncement', - name='title', + model_name="diningdayannouncement", + name="title", field=models.CharField(max_length=100), ), migrations.AlterField( - model_name='diningentry', - name='dining_list', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='dining_entries', to='dining.DiningList'), + model_name="diningentry", + name="dining_list", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="dining_entries", + to="dining.DiningList", + ), ), migrations.AlterField( - model_name='diningentry', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="diningentry", + name="user", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), migrations.AlterField( - model_name='diningentryexternal', - name='name', + model_name="diningentryexternal", + name="name", field=models.CharField(max_length=100), ), migrations.AlterField( - model_name='diningentryuser', - name='added_by', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='added_entry_on_dining', to=settings.AUTH_USER_MODEL), + model_name="diningentryuser", + name="added_by", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="added_entry_on_dining", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='dininglist', - name='association', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='userdetails.Association'), + model_name="dininglist", + name="association", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="userdetails.Association", + ), ), migrations.AlterField( - model_name='dininglist', - name='claimed_by', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='dininglist_claimer', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="claimed_by", + field=models.ForeignKey( + default=None, + on_delete=django.db.models.deletion.PROTECT, + related_name="dininglist_claimer", + to=settings.AUTH_USER_MODEL, + ), preserve_default=False, ), ] diff --git a/dining/migrations/0010_auto_20190508_0230.py b/dining/migrations/0010_auto_20190508_0230.py index e2c46e92..b6d20541 100644 --- a/dining/migrations/0010_auto_20190508_0230.py +++ b/dining/migrations/0010_auto_20190508_0230.py @@ -4,8 +4,8 @@ def move_added_by(apps, schema_editor): - DiningEntryUser = apps.get_model('dining', 'DiningEntryUser') - DiningEntryExternal = apps.get_model('dining', 'DiningEntryExternal') + DiningEntryUser = apps.get_model("dining", "DiningEntryUser") + DiningEntryExternal = apps.get_model("dining", "DiningEntryExternal") for entry in DiningEntryUser.objects.all(): entry.created_by = entry.added_by if entry.added_by else entry.user @@ -17,7 +17,7 @@ def move_added_by(apps, schema_editor): def reverse_added_by(apps, schema_editor): - DiningEntryUser = apps.get_model('dining', 'DiningEntryUser') + DiningEntryUser = apps.get_model("dining", "DiningEntryUser") for entry in DiningEntryUser.objects.all(): entry.added_by = entry.created_by @@ -26,8 +26,8 @@ def reverse_added_by(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('dining', '0009_auto_20190508_0230'), - ('userdetails', '0014_remove_user_external_link'), + ("dining", "0009_auto_20190508_0230"), + ("userdetails", "0014_remove_user_external_link"), ] operations = [ diff --git a/dining/migrations/0011_auto_20190508_1905.py b/dining/migrations/0011_auto_20190508_1905.py index 15c77dcb..9eb34b6a 100644 --- a/dining/migrations/0011_auto_20190508_1905.py +++ b/dining/migrations/0011_auto_20190508_1905.py @@ -6,20 +6,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0010_auto_20190508_0230'), + ("dining", "0010_auto_20190508_0230"), ] operations = [ migrations.RemoveField( - model_name='diningentryuser', - name='added_by', + model_name="diningentryuser", + name="added_by", ), migrations.AlterField( - model_name='diningentry', - name='created_by', - field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='created_dining_entries', to=settings.AUTH_USER_MODEL), + model_name="diningentry", + name="created_by", + field=models.ForeignKey( + default=None, + on_delete=django.db.models.deletion.PROTECT, + related_name="created_dining_entries", + to=settings.AUTH_USER_MODEL, + ), preserve_default=False, ), ] diff --git a/dining/migrations/0012_auto_20190728_1359.py b/dining/migrations/0012_auto_20190728_1359.py index fca35ac3..0df982af 100644 --- a/dining/migrations/0012_auto_20190728_1359.py +++ b/dining/migrations/0012_auto_20190728_1359.py @@ -7,15 +7,22 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0011_auto_20190508_1905'), + ("dining", "0011_auto_20190508_1905"), ] operations = [ migrations.AlterField( - model_name='dininglist', - name='dining_cost', - field=models.DecimalField(blank=True, decimal_places=2, default=None, max_digits=5, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='dinner cost per person'), + model_name="dininglist", + name="dining_cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=None, + max_digits=5, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal("0.00"))], + verbose_name="dinner cost per person", + ), ), ] diff --git a/dining/migrations/0013_auto_20190513_1505.py b/dining/migrations/0013_auto_20190513_1505.py index 802b220f..68065a6a 100644 --- a/dining/migrations/0013_auto_20190513_1505.py +++ b/dining/migrations/0013_auto_20190513_1505.py @@ -8,21 +8,32 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dining', '0012_auto_20190728_1359'), + ("dining", "0012_auto_20190728_1359"), ] operations = [ migrations.AddField( - model_name='dininglist', - name='owners', - field=models.ManyToManyField(help_text='These users can manage the dining list', related_name='owned_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="owners", + field=models.ManyToManyField( + help_text="These users can manage the dining list", + related_name="owned_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='dininglist', - name='dining_cost', - field=models.DecimalField(blank=True, decimal_places=2, default=None, max_digits=5, null=True, validators=[django.core.validators.MinValueValidator(Decimal('0.00'))], verbose_name='dinner cost per person'), + model_name="dininglist", + name="dining_cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=None, + max_digits=5, + null=True, + validators=[django.core.validators.MinValueValidator(Decimal("0.00"))], + verbose_name="dinner cost per person", + ), ), ] diff --git a/dining/migrations/0014_auto_20190513_1505.py b/dining/migrations/0014_auto_20190513_1505.py index e707169c..abeb7b03 100644 --- a/dining/migrations/0014_auto_20190513_1505.py +++ b/dining/migrations/0014_auto_20190513_1505.py @@ -4,7 +4,7 @@ def move_claimant_purchaser(apps, schema_editor): - DiningList = apps.get_model('dining', 'DiningList') + DiningList = apps.get_model("dining", "DiningList") for dl in DiningList.objects.all(): dl.owners.add(dl.claimed_by) if dl.purchaser: @@ -12,15 +12,14 @@ def move_claimant_purchaser(apps, schema_editor): def move_back(apps, schema_editor): - DiningList = apps.get_model('dining', 'DiningList') + DiningList = apps.get_model("dining", "DiningList") for dl in DiningList.objects.all(): dl.claimed_by = dl.owners.first() class Migration(migrations.Migration): - dependencies = [ - ('dining', '0013_auto_20190513_1505'), + ("dining", "0013_auto_20190513_1505"), ] operations = [ diff --git a/dining/migrations/0015_auto_20190513_2104.py b/dining/migrations/0015_auto_20190513_2104.py index 381c8ac8..b67f24dc 100644 --- a/dining/migrations/0015_auto_20190513_2104.py +++ b/dining/migrations/0015_auto_20190513_2104.py @@ -6,21 +6,31 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dining', '0014_auto_20190513_1505'), + ("dining", "0014_auto_20190513_1505"), ] operations = [ migrations.AddField( - model_name='dininglist', - name='main_contact', - field=models.ForeignKey(blank=True, help_text='Is shown on the dining list. If not specified, all owners are shown.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='main_contact_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="main_contact", + field=models.ForeignKey( + blank=True, + help_text="Is shown on the dining list. If not specified, all owners are shown.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="main_contact_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='dininglist', - name='owners', - field=models.ManyToManyField(help_text='Owners can manage the dining list. Board members can always manage the dining list even if they are not an owner.', related_name='owned_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="owners", + field=models.ManyToManyField( + help_text="Owners can manage the dining list. Board members can always manage the dining list even if they are not an owner.", + related_name="owned_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/dining/migrations/0016_auto_20190514_1317.py b/dining/migrations/0016_auto_20190514_1317.py index 445404d7..f784439d 100644 --- a/dining/migrations/0016_auto_20190514_1317.py +++ b/dining/migrations/0016_auto_20190514_1317.py @@ -6,29 +6,47 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0015_auto_20190513_2104'), + ("dining", "0015_auto_20190513_2104"), ] operations = [ migrations.RemoveField( - model_name='dininglist', - name='claimed_by', + model_name="dininglist", + name="claimed_by", ), migrations.AlterField( - model_name='dininglist', - name='main_contact', - field=models.ForeignKey(blank=True, help_text='If specified, is shown on the dining list as the main contact. The owners are always shown.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='main_contact_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="main_contact", + field=models.ForeignKey( + blank=True, + help_text="If specified, is shown on the dining list as the main contact. The owners are always shown.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="main_contact_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='dininglist', - name='owners', - field=models.ManyToManyField(blank=True, help_text='Owners can manage the dining list.', related_name='owned_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="owners", + field=models.ManyToManyField( + blank=True, + help_text="Owners can manage the dining list.", + related_name="owned_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), migrations.AlterField( - model_name='dininglist', - name='purchaser', - field=models.ForeignKey(blank=True, help_text='If specified, is shown on the dining list as the user who should receive the grocery shopping payments.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchaser_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="purchaser", + field=models.ForeignKey( + blank=True, + help_text="If specified, is shown on the dining list as the user who should receive the grocery shopping payments.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="purchaser_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/dining/migrations/0017_auto_20200824_0138.py b/dining/migrations/0017_auto_20200824_0138.py index 1e67b920..2cf6599a 100644 --- a/dining/migrations/0017_auto_20200824_0138.py +++ b/dining/migrations/0017_auto_20200824_0138.py @@ -7,29 +7,36 @@ class Migration(migrations.Migration): - dependencies = [ - ('creditmanagement', '0012_auto_20200824_0135'), - ('dining', '0016_auto_20190514_1317'), + ("creditmanagement", "0012_auto_20200824_0135"), + ("dining", "0016_auto_20190514_1317"), ] operations = [ migrations.DeleteModel( - name='DiningListComment', + name="DiningListComment", ), migrations.AddField( - model_name='diningentry', - name='transaction', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='creditmanagement.Transaction'), + model_name="diningentry", + name="transaction", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="creditmanagement.Transaction", + ), ), migrations.AlterField( - model_name='diningcomment', - name='timestamp', + model_name="diningcomment", + name="timestamp", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.AlterField( - model_name='dininglist', - name='owners', - field=models.ManyToManyField(help_text='Owners can manage the dining list.', related_name='owned_dining_lists', to=settings.AUTH_USER_MODEL), + model_name="dininglist", + name="owners", + field=models.ManyToManyField( + help_text="Owners can manage the dining list.", + related_name="owned_dining_lists", + to=settings.AUTH_USER_MODEL, + ), ), ] diff --git a/dining/migrations/0018_auto_20210319_2310.py b/dining/migrations/0018_auto_20210319_2310.py index 11d79076..ba55360a 100644 --- a/dining/migrations/0018_auto_20210319_2310.py +++ b/dining/migrations/0018_auto_20210319_2310.py @@ -5,16 +5,20 @@ class Migration(migrations.Migration): - dependencies = [ - ('creditmanagement', '0013_auto_20210319_2310'), - ('dining', '0017_auto_20200824_0138'), + ("creditmanagement", "0013_auto_20210319_2310"), + ("dining", "0017_auto_20200824_0138"), ] operations = [ migrations.AlterField( - model_name='diningentry', - name='transaction', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='creditmanagement.transaction'), + model_name="diningentry", + name="transaction", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="creditmanagement.transaction", + ), ), ] diff --git a/dining/migrations/0019_auto_20210320_1309.py b/dining/migrations/0019_auto_20210320_1309.py index bdf1d73b..ffe3d143 100644 --- a/dining/migrations/0019_auto_20210320_1309.py +++ b/dining/migrations/0019_auto_20210320_1309.py @@ -4,18 +4,17 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0018_auto_20210319_2310'), + ("dining", "0018_auto_20210319_2310"), ] operations = [ migrations.RemoveField( - model_name='dininglist', - name='main_contact', + model_name="dininglist", + name="main_contact", ), migrations.RemoveField( - model_name='dininglist', - name='purchaser', + model_name="dininglist", + name="purchaser", ), ] diff --git a/dining/migrations/0020_delete_userdiningsettings.py b/dining/migrations/0020_delete_userdiningsettings.py index 9c885a37..c0fe62ee 100644 --- a/dining/migrations/0020_delete_userdiningsettings.py +++ b/dining/migrations/0020_delete_userdiningsettings.py @@ -4,15 +4,14 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0019_auto_20210320_1309'), + ("dining", "0019_auto_20210320_1309"), # We need to make sure that the allergies have been copied over before we delete the UserDiningSettings model. - ('userdetails', '0022_move_allergies'), + ("userdetails", "0022_move_allergies"), ] operations = [ migrations.DeleteModel( - name='UserDiningSettings', + name="UserDiningSettings", ), ] diff --git a/dining/migrations/0021_create_paymentreminderlock.py b/dining/migrations/0021_create_paymentreminderlock.py index d4858e1d..f0ae0c2d 100644 --- a/dining/migrations/0021_create_paymentreminderlock.py +++ b/dining/migrations/0021_create_paymentreminderlock.py @@ -5,17 +5,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0020_delete_userdiningsettings'), + ("dining", "0020_delete_userdiningsettings"), ] operations = [ migrations.CreateModel( - name='PaymentReminderLock', + name="PaymentReminderLock", fields=[ - ('dining_list', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='dining.dininglist')), - ('sent', models.DateTimeField(null=True)), + ( + "dining_list", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + primary_key=True, + serialize=False, + to="dining.dininglist", + ), + ), + ("sent", models.DateTimeField(null=True)), ], ), ] diff --git a/dining/migrations/0022_rename_old_help_stats.py b/dining/migrations/0022_rename_old_help_stats.py index 52b06d81..5d067f54 100644 --- a/dining/migrations/0022_rename_old_help_stats.py +++ b/dining/migrations/0022_rename_old_help_stats.py @@ -4,25 +4,24 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0021_create_paymentreminderlock'), + ("dining", "0021_create_paymentreminderlock"), ] operations = [ migrations.RenameField( - model_name='diningwork', - old_name='has_cleaned', - new_name='has_cleaned_old', + model_name="diningwork", + old_name="has_cleaned", + new_name="has_cleaned_old", ), migrations.RenameField( - model_name='diningwork', - old_name='has_cooked', - new_name='has_cooked_old', + model_name="diningwork", + old_name="has_cooked", + new_name="has_cooked_old", ), migrations.RenameField( - model_name='diningwork', - old_name='has_shopped', - new_name='has_shopped_old', + model_name="diningwork", + old_name="has_shopped", + new_name="has_shopped_old", ), ] diff --git a/dining/migrations/0023_add_new_help_stats.py b/dining/migrations/0023_add_new_help_stats.py index d554fb7f..3872d830 100644 --- a/dining/migrations/0023_add_new_help_stats.py +++ b/dining/migrations/0023_add_new_help_stats.py @@ -4,34 +4,33 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0022_rename_old_help_stats'), + ("dining", "0022_rename_old_help_stats"), ] operations = [ migrations.AlterModelOptions( - name='diningentry', - options={'verbose_name_plural': 'dining entries'}, + name="diningentry", + options={"verbose_name_plural": "dining entries"}, ), migrations.AddField( - model_name='diningentry', - name='external_name', + model_name="diningentry", + name="external_name", field=models.CharField(blank=True, max_length=100), ), migrations.AddField( - model_name='diningentry', - name='has_cleaned', + model_name="diningentry", + name="has_cleaned", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='diningentry', - name='has_cooked', + model_name="diningentry", + name="has_cooked", field=models.BooleanField(default=False), ), migrations.AddField( - model_name='diningentry', - name='has_shopped', + model_name="diningentry", + name="has_shopped", field=models.BooleanField(default=False), ), ] diff --git a/dining/migrations/0024_move_help_stats_data.py b/dining/migrations/0024_move_help_stats_data.py index 65d3134a..55b76740 100644 --- a/dining/migrations/0024_move_help_stats_data.py +++ b/dining/migrations/0024_move_help_stats_data.py @@ -53,9 +53,7 @@ def backwards(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('dining', '0023_add_new_help_stats'), + ("dining", "0023_add_new_help_stats"), ] - operations = [ - migrations.RunPython(forwards, backwards, elidable=True) - ] + operations = [migrations.RunPython(forwards, backwards, elidable=True)] diff --git a/dining/migrations/0025_remove_dining_entry_subclasses.py b/dining/migrations/0025_remove_dining_entry_subclasses.py index 83e4ea5b..825e77bc 100644 --- a/dining/migrations/0025_remove_dining_entry_subclasses.py +++ b/dining/migrations/0025_remove_dining_entry_subclasses.py @@ -4,15 +4,13 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0024_move_help_stats_data'), + ("dining", "0024_move_help_stats_data"), ] operations = [ # For some reason these RemoveField didn't work properly. # But I think that they are not necessary. - # migrations.RemoveField( # model_name='diningentryuser', # name='diningentry_ptr', @@ -22,12 +20,12 @@ class Migration(migrations.Migration): # name='diningwork_ptr', # ), migrations.DeleteModel( - name='DiningEntryExternal', + name="DiningEntryExternal", ), migrations.DeleteModel( - name='DiningEntryUser', + name="DiningEntryUser", ), migrations.DeleteModel( - name='DiningWork', + name="DiningWork", ), ] diff --git a/dining/migrations/0026_diningcomment_increase_length.py b/dining/migrations/0026_diningcomment_increase_length.py index 89a9a736..b5ca9c78 100644 --- a/dining/migrations/0026_diningcomment_increase_length.py +++ b/dining/migrations/0026_diningcomment_increase_length.py @@ -6,21 +6,22 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dining', '0025_remove_dining_entry_subclasses'), + ("dining", "0025_remove_dining_entry_subclasses"), ] operations = [ migrations.AlterField( - model_name='diningcomment', - name='message', + model_name="diningcomment", + name="message", field=models.TextField(), ), migrations.AlterField( - model_name='diningcomment', - name='poster', - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + model_name="diningcomment", + name="poster", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL + ), ), ] diff --git a/dining/migrations/0027_deletedlist_and_more.py b/dining/migrations/0027_deletedlist_and_more.py index e84dde5e..7fa08bbf 100644 --- a/dining/migrations/0027_deletedlist_and_more.py +++ b/dining/migrations/0027_deletedlist_and_more.py @@ -9,41 +9,65 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dining', '0026_diningcomment_increase_length'), + ("dining", "0026_diningcomment_increase_length"), ] operations = [ migrations.RemoveField( - model_name='dininglist', - name='min_diners', + model_name="dininglist", + name="min_diners", ), migrations.AlterField( - model_name='dininglist', - name='adjustable_duration', - field=models.DurationField(default=datetime.timedelta(days=2), help_text='How long the dining list can be adjusted after its date.'), + model_name="dininglist", + name="adjustable_duration", + field=models.DurationField( + default=datetime.timedelta(days=2), + help_text="How long the dining list can be adjusted after its date.", + ), ), migrations.AlterField( - model_name='dininglist', - name='dish', - field=models.CharField(blank=True, default='', max_length=100), + model_name="dininglist", + name="dish", + field=models.CharField(blank=True, default="", max_length=100), ), migrations.AlterField( - model_name='dininglist', - name='limit_signups_to_association_only', - field=models.BooleanField(default=False, help_text='Whether only members of the given association can sign up.'), + model_name="dininglist", + name="limit_signups_to_association_only", + field=models.BooleanField( + default=False, + help_text="Whether only members of the given association can sign up.", + ), ), migrations.CreateModel( - name='DeletedList', + name="DeletedList", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='deletion date')), - ('reason', models.TextField()), - ('json_list', models.TextField(verbose_name='JSON dining list')), - ('json_diners', models.TextField(verbose_name='JSON dining entries')), - ('deleted_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "date", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="deletion date" + ), + ), + ("reason", models.TextField()), + ("json_list", models.TextField(verbose_name="JSON dining list")), + ("json_diners", models.TextField(verbose_name="JSON dining entries")), + ( + "deleted_by", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.AUTH_USER_MODEL, + ), + ), ], ), ] diff --git a/dining/migrations/0028_alter_dininglist_payment_link.py b/dining/migrations/0028_alter_dininglist_payment_link.py index ec357089..ae8e4011 100644 --- a/dining/migrations/0028_alter_dininglist_payment_link.py +++ b/dining/migrations/0028_alter_dininglist_payment_link.py @@ -4,15 +4,18 @@ class Migration(migrations.Migration): - dependencies = [ - ('dining', '0027_deletedlist_and_more'), + ("dining", "0027_deletedlist_and_more"), ] operations = [ migrations.AlterField( - model_name='dininglist', - name='payment_link', - field=models.URLField(blank=True, help_text='Link for payment, e.g. a Tikkie link.', max_length=2000), + model_name="dininglist", + name="payment_link", + field=models.URLField( + blank=True, + help_text="Link for payment, e.g. a Tikkie link.", + max_length=2000, + ), ), ] diff --git a/general/migrations/0001_initial.py b/general/migrations/0001_initial.py index b7989c45..48e0e2a2 100644 --- a/general/migrations/0001_initial.py +++ b/general/migrations/0001_initial.py @@ -10,26 +10,26 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='SiteUpdate', + name="SiteUpdate", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), - ('date', models.DateField(auto_now_add=True, unique=True)), + ("date", models.DateField(auto_now_add=True, unique=True)), ( - 'version', + "version", models.CharField( - help_text='The current version', max_length=16, unique=True + help_text="The current version", max_length=16, unique=True ), ), - ('title', models.CharField(max_length=140, unique=True)), - ('message', models.TextField()), + ("title", models.CharField(max_length=140, unique=True)), + ("message", models.TextField()), ], ), ] diff --git a/general/migrations/0002_auto_20190227_1203.py b/general/migrations/0002_auto_20190227_1203.py index 692024f4..6e32a8eb 100644 --- a/general/migrations/0002_auto_20190227_1203.py +++ b/general/migrations/0002_auto_20190227_1203.py @@ -8,26 +8,26 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('general', '0001_initial'), + ("general", "0001_initial"), ] operations = [ migrations.CreateModel( - name='PageVisitTracker', + name="PageVisitTracker", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), - ('timestamp', models.DateTimeField(auto_now_add=True)), - ('page', models.IntegerField()), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ("page", models.IntegerField()), ( - 'user', + "user", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, @@ -35,12 +35,12 @@ class Migration(migrations.Migration): ), ], options={ - 'abstract': False, + "abstract": False, }, ), migrations.AlterField( - model_name='siteupdate', - name='date', + model_name="siteupdate", + name="date", field=models.DateTimeField(auto_now_add=True, unique=True), ), ] diff --git a/general/migrations/0003_remove_siteupdate_version.py b/general/migrations/0003_remove_siteupdate_version.py index 1b202733..7579bd2b 100644 --- a/general/migrations/0003_remove_siteupdate_version.py +++ b/general/migrations/0003_remove_siteupdate_version.py @@ -5,12 +5,12 @@ class Migration(migrations.Migration): dependencies = [ - ('general', '0002_auto_20190227_1203'), + ("general", "0002_auto_20190227_1203"), ] operations = [ migrations.RemoveField( - model_name='siteupdate', - name='version', + model_name="siteupdate", + name="version", ), ] diff --git a/userdetails/migrations/0001_initial.py b/userdetails/migrations/0001_initial.py index 522a9c69..a7d18bca 100644 --- a/userdetails/migrations/0001_initial.py +++ b/userdetails/migrations/0001_initial.py @@ -12,201 +12,201 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('auth', '0009_alter_user_last_name_max_length'), + ("auth", "0009_alter_user_last_name_max_length"), ] operations = [ migrations.CreateModel( - name='UserMembership', + name="UserMembership", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), ( - 'created_on', + "created_on", models.DateField(auto_created=True, blank=True, null=True), ), - ('is_verified', models.BooleanField(default=False)), - ('verified_on', models.DateField(blank=True, default=None, null=True)), + ("is_verified", models.BooleanField(default=False)), + ("verified_on", models.DateField(blank=True, default=None, null=True)), ], ), migrations.CreateModel( - name='User', + name="User", fields=[ ( - 'id', + "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, - verbose_name='ID', + verbose_name="ID", ), ), - ('password', models.CharField(max_length=128, verbose_name='password')), + ("password", models.CharField(max_length=128, verbose_name="password")), ( - 'last_login', + "last_login", models.DateTimeField( - blank=True, null=True, verbose_name='last login' + blank=True, null=True, verbose_name="last login" ), ), ( - 'is_superuser', + "is_superuser", models.BooleanField( default=False, - help_text='Designates that this user has all permissions without explicitly assigning them.', - verbose_name='superuser status', + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", ), ), ( - 'username', + "username", models.CharField( error_messages={ - 'unique': 'A user with that username already exists.' + "unique": "A user with that username already exists." }, - help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", max_length=150, unique=True, validators=[ django.contrib.auth.validators.UnicodeUsernameValidator() ], - verbose_name='username', + verbose_name="username", ), ), ( - 'first_name', + "first_name", models.CharField( - blank=True, max_length=30, verbose_name='first name' + blank=True, max_length=30, verbose_name="first name" ), ), ( - 'last_name', + "last_name", models.CharField( - blank=True, max_length=150, verbose_name='last name' + blank=True, max_length=150, verbose_name="last name" ), ), ( - 'email', + "email", models.EmailField( - blank=True, max_length=254, verbose_name='email address' + blank=True, max_length=254, verbose_name="email address" ), ), ( - 'is_staff', + "is_staff", models.BooleanField( default=False, - help_text='Designates whether the user can log into this admin site.', - verbose_name='staff status', + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", ), ), ( - 'is_active', + "is_active", models.BooleanField( default=True, - help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', - verbose_name='active', + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", ), ), ( - 'date_joined', + "date_joined", models.DateTimeField( - default=django.utils.timezone.now, verbose_name='date joined' + default=django.utils.timezone.now, verbose_name="date joined" ), ), ], options={ - 'verbose_name': 'user', - 'verbose_name_plural': 'users', - 'abstract': False, + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, }, managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ("objects", django.contrib.auth.models.UserManager()), ], ), migrations.CreateModel( - name='Association', + name="Association", fields=[], options={ - 'proxy': True, - 'indexes': [], + "proxy": True, + "indexes": [], }, - bases=('auth.group',), + bases=("auth.group",), managers=[ - ('objects', django.contrib.auth.models.GroupManager()), + ("objects", django.contrib.auth.models.GroupManager()), ], ), migrations.CreateModel( - name='AssociationDetails', + name="AssociationDetails", fields=[ ( - 'association', + "association", models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, - to='userdetails.Association', + to="userdetails.Association", ), ), - ('image', models.ImageField(upload_to='')), - ('shorthand', models.SlugField(max_length=10)), + ("image", models.ImageField(upload_to="")), + ("shorthand", models.SlugField(max_length=10)), ], ), migrations.CreateModel( - name='UserDetail', + name="UserDetail", fields=[ ( - 'related_user', + "related_user", models.OneToOneField( on_delete=django.db.models.deletion.CASCADE, primary_key=True, - related_name='details', + related_name="details", serialize=False, to=settings.AUTH_USER_MODEL, ), ), - ('phone_number', models.CharField(blank=True, max_length=15)), + ("phone_number", models.CharField(blank=True, max_length=15)), ], ), migrations.AddField( - model_name='user', - name='groups', + model_name="user", + name="groups", field=models.ManyToManyField( blank=True, - help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', - related_name='user_set', - related_query_name='user', - to='auth.Group', - verbose_name='groups', + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", ), ), migrations.AddField( - model_name='user', - name='user_permissions', + model_name="user", + name="user_permissions", field=models.ManyToManyField( blank=True, - help_text='Specific permissions for this user.', - related_name='user_set', - related_query_name='user', - to='auth.Permission', - verbose_name='user permissions', + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.Permission", + verbose_name="user permissions", ), ), migrations.AddField( - model_name='usermembership', - name='association', + model_name="usermembership", + name="association", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to='userdetails.Association', + to="userdetails.Association", ), ), migrations.AddField( - model_name='usermembership', - name='related_user', + model_name="usermembership", + name="related_user", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL ), diff --git a/userdetails/migrations/0002_auto_20190203_2324.py b/userdetails/migrations/0002_auto_20190203_2324.py index 26c6be80..088abd8e 100644 --- a/userdetails/migrations/0002_auto_20190203_2324.py +++ b/userdetails/migrations/0002_auto_20190203_2324.py @@ -7,61 +7,61 @@ class Migration(migrations.Migration): dependencies = [ - ('auth', '0009_alter_user_last_name_max_length'), - ('userdetails', '0001_initial'), + ("auth", "0009_alter_user_last_name_max_length"), + ("userdetails", "0001_initial"), ] operations = [ migrations.RemoveField( - model_name='associationdetails', - name='association', + model_name="associationdetails", + name="association", ), migrations.DeleteModel( - name='Association', + name="Association", ), migrations.CreateModel( - name='Association', + name="Association", fields=[ ( - 'group_ptr', + "group_ptr", models.OneToOneField( auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, - to='auth.Group', + to="auth.Group", ), ), - ('slug', models.SlugField(max_length=10)), - ('image', models.ImageField(blank=True, upload_to='')), + ("slug", models.SlugField(max_length=10)), + ("image", models.ImageField(blank=True, upload_to="")), ], - bases=('auth.group',), + bases=("auth.group",), managers=[ - ('objects', django.contrib.auth.models.GroupManager()), + ("objects", django.contrib.auth.models.GroupManager()), ], ), migrations.DeleteModel( - name='AssociationDetails', + name="AssociationDetails", ), migrations.AlterField( - model_name='User', - name='groups', + model_name="User", + name="groups", field=models.ManyToManyField( blank=True, - help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', - related_name='user_set', - related_query_name='user', - to='auth.Group', - verbose_name='groups', + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.Group", + verbose_name="groups", ), ), migrations.AlterField( - model_name='UserMembership', - name='association', + model_name="UserMembership", + name="association", field=models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to='userdetails.Association', + to="userdetails.Association", ), ), ] diff --git a/userdetails/migrations/0003_auto_20190204_0025.py b/userdetails/migrations/0003_auto_20190204_0025.py index f95c4495..a6021d4d 100644 --- a/userdetails/migrations/0003_auto_20190204_0025.py +++ b/userdetails/migrations/0003_auto_20190204_0025.py @@ -5,15 +5,15 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0002_auto_20190203_2324'), + ("userdetails", "0002_auto_20190203_2324"), ] operations = [ migrations.RemoveField( - model_name='userdetail', - name='related_user', + model_name="userdetail", + name="related_user", ), migrations.DeleteModel( - name='UserDetail', + name="UserDetail", ), ] diff --git a/userdetails/migrations/0004_user_external_link.py b/userdetails/migrations/0004_user_external_link.py index 7594b1cf..ef3e2750 100644 --- a/userdetails/migrations/0004_user_external_link.py +++ b/userdetails/migrations/0004_user_external_link.py @@ -5,13 +5,13 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0003_auto_20190204_0025'), + ("userdetails", "0003_auto_20190204_0025"), ] operations = [ migrations.AddField( - model_name='user', - name='external_link', - field=models.CharField(default='', editable=False, max_length=150), + model_name="user", + name="external_link", + field=models.CharField(default="", editable=False, max_length=150), ), ] diff --git a/userdetails/migrations/0005_auto_20190207_1510.py b/userdetails/migrations/0005_auto_20190207_1510.py index 7f93baff..3003dcf7 100644 --- a/userdetails/migrations/0005_auto_20190207_1510.py +++ b/userdetails/migrations/0005_auto_20190207_1510.py @@ -6,18 +6,18 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0004_user_external_link'), + ("userdetails", "0004_user_external_link"), ] operations = [ migrations.AlterField( - model_name='usermembership', - name='created_on', + model_name="usermembership", + name="created_on", field=models.DateTimeField(default=django.utils.timezone.now), ), migrations.AlterField( - model_name='usermembership', - name='verified_on', + model_name="usermembership", + name="verified_on", field=models.DateTimeField(blank=True, default=None, null=True), ), ] diff --git a/userdetails/migrations/0006_auto_20190207_1744.py b/userdetails/migrations/0006_auto_20190207_1744.py index c0f064a5..283aab4f 100644 --- a/userdetails/migrations/0006_auto_20190207_1744.py +++ b/userdetails/migrations/0006_auto_20190207_1744.py @@ -5,17 +5,17 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0005_auto_20190207_1510'), + ("userdetails", "0005_auto_20190207_1510"), ] operations = [ migrations.AlterField( - model_name='user', - name='external_link', + model_name="user", + name="external_link", field=models.CharField( - default='', + default="", editable=False, - help_text='When this is set, the account is linked to an external system.', + help_text="When this is set, the account is linked to an external system.", max_length=150, ), ), diff --git a/userdetails/migrations/0007_association_is_choosable.py b/userdetails/migrations/0007_association_is_choosable.py index 3738be24..919cceaf 100644 --- a/userdetails/migrations/0007_association_is_choosable.py +++ b/userdetails/migrations/0007_association_is_choosable.py @@ -5,16 +5,16 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0006_auto_20190207_1744'), + ("userdetails", "0006_auto_20190207_1744"), ] operations = [ migrations.AddField( - model_name='association', - name='is_choosable', + model_name="association", + name="is_choosable", field=models.BooleanField( default=True, - verbose_name='Whether this association can be chosen as membership by users', + verbose_name="Whether this association can be chosen as membership by users", ), ), ] diff --git a/userdetails/migrations/0008_remove_user_is_staff.py b/userdetails/migrations/0008_remove_user_is_staff.py index d100fa82..d81ce279 100644 --- a/userdetails/migrations/0008_remove_user_is_staff.py +++ b/userdetails/migrations/0008_remove_user_is_staff.py @@ -5,12 +5,12 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0007_association_is_choosable'), + ("userdetails", "0007_association_is_choosable"), ] operations = [ migrations.RemoveField( - model_name='user', - name='is_staff', + model_name="user", + name="is_staff", ), ] diff --git a/userdetails/migrations/0009_user_is_staff.py b/userdetails/migrations/0009_user_is_staff.py index 4a70be19..badd8755 100644 --- a/userdetails/migrations/0009_user_is_staff.py +++ b/userdetails/migrations/0009_user_is_staff.py @@ -5,17 +5,17 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0008_remove_user_is_staff'), + ("userdetails", "0008_remove_user_is_staff"), ] operations = [ migrations.AddField( - model_name='user', - name='is_staff', + model_name="user", + name="is_staff", field=models.BooleanField( default=False, - help_text='Designates whether the user can log into this admin site.', - verbose_name='staff status', + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", ), ), ] diff --git a/userdetails/migrations/0010_auto_20190304_2050.py b/userdetails/migrations/0010_auto_20190304_2050.py index 9bded12b..7cce28db 100644 --- a/userdetails/migrations/0010_auto_20190304_2050.py +++ b/userdetails/migrations/0010_auto_20190304_2050.py @@ -5,15 +5,15 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0009_user_is_staff'), + ("userdetails", "0009_user_is_staff"), ] operations = [ migrations.AlterField( - model_name='user', - name='email', + model_name="user", + name="email", field=models.EmailField( - max_length=254, unique=True, verbose_name='email address' + max_length=254, unique=True, verbose_name="email address" ), ), ] diff --git a/userdetails/migrations/0011_auto_20190429_1303.py b/userdetails/migrations/0011_auto_20190429_1303.py index df2cb819..595d451b 100644 --- a/userdetails/migrations/0011_auto_20190429_1303.py +++ b/userdetails/migrations/0011_auto_20190429_1303.py @@ -6,25 +6,25 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0010_auto_20190304_2050'), + ("userdetails", "0010_auto_20190304_2050"), ] operations = [ migrations.CreateModel( - name='UserOverview', + name="UserOverview", fields=[], options={ - 'proxy': True, - 'indexes': [], + "proxy": True, + "indexes": [], }, - bases=('userdetails.user',), + bases=("userdetails.user",), managers=[ - ('objects', django.contrib.auth.models.UserManager()), + ("objects", django.contrib.auth.models.UserManager()), ], ), migrations.AddField( - model_name='association', - name='is_min_exception', + model_name="association", + name="is_min_exception", field=models.BooleanField(default=False), ), ] diff --git a/userdetails/migrations/0012_auto_20190429_1306.py b/userdetails/migrations/0012_auto_20190429_1306.py index dba9f5f4..7c668e8f 100644 --- a/userdetails/migrations/0012_auto_20190429_1306.py +++ b/userdetails/migrations/0012_auto_20190429_1306.py @@ -5,20 +5,20 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0011_auto_20190429_1303'), + ("userdetails", "0011_auto_20190429_1303"), ] operations = [ migrations.RemoveField( - model_name='association', - name='is_min_exception', + model_name="association", + name="is_min_exception", ), migrations.AddField( - model_name='association', - name='has_min_exception', + model_name="association", + name="has_min_exception", field=models.BooleanField( default=False, - verbose_name='Whether this association has an exception to the minimum balance', + verbose_name="Whether this association has an exception to the minimum balance", ), ), ] diff --git a/userdetails/migrations/0013_auto_20190429_2232.py b/userdetails/migrations/0013_auto_20190429_2232.py index 85098057..58d00794 100644 --- a/userdetails/migrations/0013_auto_20190429_2232.py +++ b/userdetails/migrations/0013_auto_20190429_2232.py @@ -5,18 +5,18 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0012_auto_20190429_1306'), + ("userdetails", "0012_auto_20190429_1306"), ] operations = [ migrations.AddField( - model_name='association', - name='icon_image', - field=models.ImageField(blank=True, null=True, upload_to=''), + model_name="association", + name="icon_image", + field=models.ImageField(blank=True, null=True, upload_to=""), ), migrations.AlterField( - model_name='association', - name='image', - field=models.ImageField(blank=True, null=True, upload_to=''), + model_name="association", + name="image", + field=models.ImageField(blank=True, null=True, upload_to=""), ), ] diff --git a/userdetails/migrations/0014_remove_user_external_link.py b/userdetails/migrations/0014_remove_user_external_link.py index 75c71e1f..d530ed6d 100644 --- a/userdetails/migrations/0014_remove_user_external_link.py +++ b/userdetails/migrations/0014_remove_user_external_link.py @@ -5,12 +5,12 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0013_auto_20190429_2232'), + ("userdetails", "0013_auto_20190429_2232"), ] operations = [ migrations.RemoveField( - model_name='user', - name='external_link', + model_name="user", + name="external_link", ), ] diff --git a/userdetails/migrations/0015_auto_20190508_1905.py b/userdetails/migrations/0015_auto_20190508_1905.py index b71d31da..2697ea30 100644 --- a/userdetails/migrations/0015_auto_20190508_1905.py +++ b/userdetails/migrations/0015_auto_20190508_1905.py @@ -5,24 +5,24 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0014_remove_user_external_link'), + ("userdetails", "0014_remove_user_external_link"), ] operations = [ migrations.AlterField( - model_name='association', - name='has_min_exception', + model_name="association", + name="has_min_exception", field=models.BooleanField( default=False, - help_text='If checked, this association has an exception to the minimum balance', + help_text="If checked, this association has an exception to the minimum balance", ), ), migrations.AlterField( - model_name='association', - name='is_choosable', + model_name="association", + name="is_choosable", field=models.BooleanField( default=True, - help_text='If checked, this association can be chosen as membership by users', + help_text="If checked, this association can be chosen as membership by users", ), ), ] diff --git a/userdetails/migrations/0016_association_social_app.py b/userdetails/migrations/0016_association_social_app.py index f1fd4d5e..783d3f59 100644 --- a/userdetails/migrations/0016_association_social_app.py +++ b/userdetails/migrations/0016_association_social_app.py @@ -6,20 +6,20 @@ class Migration(migrations.Migration): dependencies = [ - ('socialaccount', '0003_extra_data_default_dict'), - ('userdetails', '0015_auto_20190508_1905'), + ("socialaccount", "0003_extra_data_default_dict"), + ("userdetails", "0015_auto_20190508_1905"), ] operations = [ migrations.AddField( - model_name='association', - name='social_app', + model_name="association", + name="social_app", field=models.ForeignKey( blank=True, - help_text='A user automatically becomes member of the association if she signs up using this social app', + help_text="A user automatically becomes member of the association if she signs up using this social app", null=True, on_delete=django.db.models.deletion.PROTECT, - to='socialaccount.SocialApp', + to="socialaccount.SocialApp", ), ), ] diff --git a/userdetails/migrations/0017_association_balance_update_instructions.py b/userdetails/migrations/0017_association_balance_update_instructions.py index 2a1e3d6b..611ef760 100644 --- a/userdetails/migrations/0017_association_balance_update_instructions.py +++ b/userdetails/migrations/0017_association_balance_update_instructions.py @@ -5,13 +5,13 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0016_association_social_app'), + ("userdetails", "0016_association_social_app"), ] operations = [ migrations.AddField( - model_name='association', - name='balance_update_instructions', - field=models.TextField(default='to be defined', max_length=512), + model_name="association", + name="balance_update_instructions", + field=models.TextField(default="to be defined", max_length=512), ), ] diff --git a/userdetails/migrations/0018_association_has_site_stats_access.py b/userdetails/migrations/0018_association_has_site_stats_access.py index bcbd8852..df32e0c0 100644 --- a/userdetails/migrations/0018_association_has_site_stats_access.py +++ b/userdetails/migrations/0018_association_has_site_stats_access.py @@ -5,13 +5,13 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0017_association_balance_update_instructions'), + ("userdetails", "0017_association_balance_update_instructions"), ] operations = [ migrations.AddField( - model_name='association', - name='has_site_stats_access', + model_name="association", + name="has_site_stats_access", field=models.BooleanField(default=False), ), ] diff --git a/userdetails/migrations/0019_auto_20200823_1602.py b/userdetails/migrations/0019_auto_20200823_1602.py index dba36ea7..90f81289 100644 --- a/userdetails/migrations/0019_auto_20200823_1602.py +++ b/userdetails/migrations/0019_auto_20200823_1602.py @@ -6,35 +6,35 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0018_association_has_site_stats_access'), + ("userdetails", "0018_association_has_site_stats_access"), ] operations = [ migrations.AlterField( - model_name='association', - name='has_min_exception', + model_name="association", + name="has_min_exception", field=models.BooleanField( default=False, - help_text='If checked, this association has an exception to the minimum balance.', + help_text="If checked, this association has an exception to the minimum balance.", ), ), migrations.AlterField( - model_name='association', - name='is_choosable', + model_name="association", + name="is_choosable", field=models.BooleanField( default=True, - help_text='If checked, this association can be chosen as membership by users.', + help_text="If checked, this association can be chosen as membership by users.", ), ), migrations.AlterField( - model_name='association', - name='social_app', + model_name="association", + name="social_app", field=models.ForeignKey( blank=True, - help_text='A user automatically becomes member of the association if they sign up using this social app.', + help_text="A user automatically becomes member of the association if they sign up using this social app.", null=True, on_delete=django.db.models.deletion.PROTECT, - to='socialaccount.SocialApp', + to="socialaccount.SocialApp", ), ), ] diff --git a/userdetails/migrations/0020_auto_20210319_2310.py b/userdetails/migrations/0020_auto_20210319_2310.py index fbb17e64..691feb58 100644 --- a/userdetails/migrations/0020_auto_20210319_2310.py +++ b/userdetails/migrations/0020_auto_20210319_2310.py @@ -5,15 +5,15 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0019_auto_20200823_1602'), + ("userdetails", "0019_auto_20200823_1602"), ] operations = [ migrations.AlterField( - model_name='user', - name='first_name', + model_name="user", + name="first_name", field=models.CharField( - blank=True, max_length=150, verbose_name='first name' + blank=True, max_length=150, verbose_name="first name" ), ), ] diff --git a/userdetails/migrations/0021_auto_20221224_1346.py b/userdetails/migrations/0021_auto_20221224_1346.py index 69dcf0f8..ef1ff52e 100644 --- a/userdetails/migrations/0021_auto_20221224_1346.py +++ b/userdetails/migrations/0021_auto_20221224_1346.py @@ -7,30 +7,30 @@ class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0020_auto_20210319_2310'), + ("userdetails", "0020_auto_20210319_2310"), ] operations = [ migrations.AlterModelManagers( - name='association', + name="association", managers=[ - ('objects', userdetails.models.AssociationManager()), + ("objects", userdetails.models.AssociationManager()), ], ), migrations.AlterModelManagers( - name='user', + name="user", managers=[ - ('objects', userdetails.models.UserManager()), + ("objects", userdetails.models.UserManager()), ], ), migrations.AddField( - model_name='user', - name='allergies', + model_name="user", + name="allergies", field=models.CharField( blank=True, - help_text='E.g. gluten or vegetarian. Leave empty if not applicable.', + help_text="E.g. gluten or vegetarian. Leave empty if not applicable.", max_length=100, - verbose_name='food allergies or preferences', + verbose_name="food allergies or preferences", ), ), ] diff --git a/userdetails/migrations/0022_move_allergies.py b/userdetails/migrations/0022_move_allergies.py index 1c8cbd10..5d777b77 100644 --- a/userdetails/migrations/0022_move_allergies.py +++ b/userdetails/migrations/0022_move_allergies.py @@ -4,7 +4,7 @@ def forwards(apps, schema_editor): - UserDiningSettings = apps.get_model('dining', 'UserDiningSettings') + UserDiningSettings = apps.get_model("dining", "UserDiningSettings") for obj in UserDiningSettings.objects.all(): user = obj.user @@ -13,8 +13,8 @@ def forwards(apps, schema_editor): def backwards(apps, schema_editor): - UserDiningSettings = apps.get_model('dining', 'UserDiningSettings') - User = apps.get_model('userdetails', 'User') + UserDiningSettings = apps.get_model("dining", "UserDiningSettings") + User = apps.get_model("userdetails", "User") for obj in User.objects.all(): UserDiningSettings.objects.create(user=obj, allergies=obj.allergies) @@ -22,9 +22,9 @@ def backwards(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('userdetails', '0021_auto_20221224_1346'), + ("userdetails", "0021_auto_20221224_1346"), # We need to make sure that the UserDiningSettings model exists, we depend on it. - ('dining', '0019_auto_20210320_1309'), + ("dining", "0019_auto_20210320_1309"), ] operations = [ From e3939110d8a09b1fb3e05dee5afafb114471a5b7 Mon Sep 17 00:00:00 2001 From: Lena Wildervanck Date: Mon, 12 Jun 2023 17:09:32 +0200 Subject: [PATCH 14/14] Add migrations to the checked folders of the formatter and the previous commit to the git blame ignore list --- .git-blame-ignore-revs | 1 + pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index c6aca008..9ef81814 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -1,3 +1,4 @@ 37079c3b2ee3fc4100c56b041d90faf5c5fbbe48 87ddf24356f15143780b469ad006b3e95273cd7b 5dad39e90d7f659c0f2e630ad9dfefa10cd45b99 +437f1bc494b0afa4492411d5f44edf40a3d637d4 diff --git a/pyproject.toml b/pyproject.toml index 29a2c240..45f063bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,8 @@ [tool.black] line-length = 88 -extend-exclude = ".*/migrations/.*.py" +# extend-exclude = ".*/migrations/.*.py" [tool.isort] profile = "black" line_length = 88 -extend_skip_glob = ["*/migrations/*.py"] +# extend_skip_glob = ["*/migrations/*.py"]