diff --git a/.gitignore b/.gitignore index e3244725d182c2876547ced931d8f31bb4fcd483..dd503635b1da9453db5926cee2cf4c8980408a54 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ venv /coin.sqlite3 .cache/ .vagrant/ +custom_hooks diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6d4a3893bdf6019ddf4d2b48c16c12c3c9dc3511..f9aff8c37e7dbf0c6244b4725b6bcfa4d18a15a7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,12 +13,12 @@ variables: before_script: - echo 'fr_FR.UTF-8 UTF-8' >> /etc/locale.gen - apt-get update -qq - - apt-get install -qq -y --no-install-recommends libsasl2-dev python-dev libldap2-dev libssl-dev + - apt-get install -qq -y --no-install-recommends libsasl2-dev libldap2-dev libssl-dev - pip install -r requirements.txt pytest-django - pip freeze # debug unit_tests_debian_10: - image: python:2.7-buster + image: python:3.7-buster services: - postgres:11.10 script: py.test diff --git a/Dockerfile b/Dockerfile index 1ec550b9927a92613d840d91065f5a2ee3790f82..2723299450cb87a145fc6a6e73fb5635294b0120 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,21 @@ -FROM python:2.7-buster +FROM python:3.7-buster ENV DEBIAN_FRONTEND noninteractive ENV TZ="Europe/Paris" ENV LC_ALL fr_FR.UTF-8 -RUN apt-get update \ - && apt-get install -y --no-install-recommends libsasl2-dev python-dev libldap2-dev libssl-dev \ - && rm -rf /var/lib/apt/lists/* +RUN --mount=type=cache,sharing=locked,target=/var/cache/apt \ + --mount=type=cache,sharing=locked,target=/var/lib/apt \ + apt-get update \ + && apt-get install -y --no-install-recommends libsasl2-dev libldap2-dev libssl-dev WORKDIR /coin -COPY . . -RUN pip install -r requirements.txt +COPY requirements.txt requirements-dev.txt ./ +RUN --mount=type=cache,sharing=locked,target=/root/.cache \ + pip install -U pip \ + && pip install -r requirements-dev.txt +COPY . . RUN useradd --uid 10001 --user-group --shell /bin/bash coin RUN chown coin:coin /coin diff --git a/README.md b/README.md index 6bb56c10b68383433e88ace69d7d6d3d37dea0ae..02fd38649bd2cc0f6e20a68a8a3d436676198ce0 100644 --- a/README.md +++ b/README.md @@ -396,7 +396,7 @@ To log 'accounting-related operations' (creation/update of invoice, payment and member balance) to a specific file, add the following to settings_local.py : ``` -from settings_base import * +from .settings_base import * LOGGING["formatters"]["verbose"] = {'format': "%(asctime)s - %(name)s - %(levelname)s - %(message)s"} LOGGING["handlers"]["coin_accounting"] = { 'level':'INFO', diff --git a/coin/admin.py b/coin/admin.py index 2c190dc39cc5356c2064db68502f138e42cce6ce..1e99b2c68bd23814a4b699eae6599585e6a378a1 100644 --- a/coin/admin.py +++ b/coin/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.apps import apps from django.contrib import admin diff --git a/coin/apps.py b/coin/apps.py index 9457b91e4b3757ebcbf7e234d4e3ae93d3c99849..e7e4e7499c3ae5caba29797d7d234d5ae2e7b498 100644 --- a/coin/apps.py +++ b/coin/apps.py @@ -3,7 +3,7 @@ from os.path import basename import six from django.apps import apps -from .utils import rstrip_str +from .utils_light import rstrip_str class AppURLsMeta(type): @@ -23,7 +23,7 @@ class AppURLsMeta(type): cls.urlprefix = data.pop('urlprefix', None) -class AppURLs(six.with_metaclass(AppURLsMeta)): +class AppURLs(metaclass=AppURLsMeta): """ App Mixxin to allow an application to expose pluggable urls That's to say, URLs which will be added automatically to the projet diff --git a/coin/billing/admin.py b/coin/billing/admin.py index 03126e5ceb05a78338829c994a27bff522bdc1ac..9368f8db259dcd950bcd14105b1c25dd59e2adfb 100644 --- a/coin/billing/admin.py +++ b/coin/billing/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from django.contrib import messages from django.http import HttpResponseRedirect @@ -15,8 +12,8 @@ from coin.billing.models import Invoice, InvoiceDetail, Payment, \ from coin.billing.utils import get_bill_from_id_or_number from coin.members.models import Member from coin.members.admin import MemberAdmin -from django.core.urlresolvers import reverse -import autocomplete_light +from django.urls import reverse +from autocomplete_light import shortcuts as al from .forms import WizardImportPaymentCSV from .import_payments_from_csv import process, add_new_payments @@ -126,7 +123,7 @@ class InvoiceAdmin(admin.ModelAdmin): ('amount', 'amount_paid'), ('validated', 'pdf')) readonly_fields = ('amount', 'amount_paid', 'validated', 'pdf', 'number') - form = autocomplete_light.modelform_factory(Invoice, fields='__all__') + form = al.modelform_factory(Invoice, fields='__all__') def get_readonly_fields(self, request, obj=None): """ @@ -191,7 +188,7 @@ class InvoiceAdmin(admin.ModelAdmin): """ Custom admin urls """ - urls = super(InvoiceAdmin, self).get_urls() + urls = super().get_urls() my_urls = [ url(r'^validate/(?P.+)$', self.admin_site.admin_view(self.validate_view), @@ -249,7 +246,7 @@ class PaymentAdmin(admin.ModelAdmin): readonly_fields = ('amount_already_allocated',) list_filter = ['payment_mean'] search_fields = ['member__username', 'member__first_name', 'member__last_name', 'member__email', 'member__nickname'] - form = autocomplete_light.modelform_factory(Payment, fields='__all__') + form = al.modelform_factory(Payment, fields='__all__') def get_readonly_fields(self, request, obj=None): @@ -265,7 +262,7 @@ class PaymentAdmin(admin.ModelAdmin): def get_urls(self): - urls = super(PaymentAdmin, self).get_urls() + urls = super().get_urls() my_urls = [ url(r'wizard_import_payment_csv/$', self.wizard_import_payment_csv, name='wizard_import_payment_csv'), @@ -319,12 +316,12 @@ class MembershipFeeAdmin(admin.ModelAdmin): list_display = ('member', 'end_date', '_amount') search_fields = ['member__username', 'member__first_name', 'member__last_name', 'member__email', 'member__nickname'] list_filter = ['date'] - form = autocomplete_light.modelform_factory(MembershipFee, fields='__all__') + form = al.modelform_factory(MembershipFee, fields='__all__') class DonationAdmin(admin.ModelAdmin): list_display = ('member', 'date', '_amount') - form = autocomplete_light.modelform_factory(MembershipFee, fields='__all__') + form = al.modelform_factory(MembershipFee, fields='__all__') admin.site.register(Invoice, InvoiceAdmin) admin.site.register(Payment, PaymentAdmin) diff --git a/coin/billing/app.py b/coin/billing/app.py index 13182f3f2848095d1bc300fda1544eb169f27d73..fd57080d8055ebbe146ff502d9745df4cd7187c4 100644 --- a/coin/billing/app.py +++ b/coin/billing/app.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals from django.apps import AppConfig diff --git a/coin/billing/create_subscriptions_invoices.py b/coin/billing/create_subscriptions_invoices.py index 3891489c051d0d74e9b970f40552b57f83f490dd..25ba9868fd77ae7b339b20a8b21130ae15b29b86 100644 --- a/coin/billing/create_subscriptions_invoices.py +++ b/coin/billing/create_subscriptions_invoices.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from decimal import Decimal from dateutil.relativedelta import relativedelta diff --git a/coin/billing/forms.py b/coin/billing/forms.py index b3ddc0790995831189f23ca78ceaf4537c148ab2..310ad4b1d2cb2a856ce0b6e8f7b53f4fd37c0574 100644 --- a/coin/billing/forms.py +++ b/coin/billing/forms.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os from django.core.exceptions import ValidationError @@ -10,7 +7,7 @@ def validate_file_extension(value): ext = os.path.splitext(value.name)[1] valid_extensions = ['.csv'] if not ext.lower() in valid_extensions: - raise ValidationError(u'Unsupported file extension.') + raise ValidationError('Unsupported file extension.') class WizardImportPaymentCSV(forms.Form): diff --git a/coin/billing/import_payments_from_csv.py b/coin/billing/import_payments_from_csv.py index 6df6aeae60c3537b124dfa4de587b2c613334ce6..8b36859bf6690204552834faf167af882918b523 100644 --- a/coin/billing/import_payments_from_csv.py +++ b/coin/billing/import_payments_from_csv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Import payments from a CSV file from a bank. The payments will automatically be parsed, and there'll be an attempt to automatically match payments with members. @@ -17,7 +16,6 @@ By default, only a dry-run is perfomed to let you see what will happen ! You should run this command with --commit if you agree with the dry-run. """ -from __future__ import unicode_literals # Standard python libs import csv @@ -34,7 +32,7 @@ from coin.billing.models import Payment # Parser / import / matcher configuration # The CSV delimiter -DELIMITER = str(';') +DELIMITER = ';' # The date format in the CSV DATE_FORMAT = "%d/%m/%Y" # The default regex used to match the label of a payment with a member ID @@ -77,7 +75,7 @@ def is_money_amount(text): def load_csv(filename): - with open(filename, "r") as f: + with open(filename) as f: return list(csv.reader(f, delimiter=DELIMITER)) diff --git a/coin/billing/management/commands/charge_subscriptions.py b/coin/billing/management/commands/charge_subscriptions.py index a2327d3b82c6ca9eddade6ae1b1eb87eca04537e..2ed6ad65e2fd9dc1ee2ae7e690417982f6777ad1 100644 --- a/coin/billing/management/commands/charge_subscriptions.py +++ b/coin/billing/management/commands/charge_subscriptions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import datetime from argparse import RawTextHelpFormatter @@ -14,7 +13,7 @@ class Command(BaseCommand): help = 'Create invoices for members subscriptions for date specified (or today if no date passed)' def create_parser(self, *args, **kwargs): - parser = super(Command, self).create_parser(*args, **kwargs) + parser = super().create_parser(*args, **kwargs) parser.formatter_class = RawTextHelpFormatter return parser @@ -57,5 +56,5 @@ class Command(BaseCommand): if len(invoices) > 0 or verbosity >= 2: self.stdout.write( - u'%d invoices were created' % len(invoices)) + '%d invoices were created' % len(invoices)) diff --git a/coin/billing/management/commands/import_payments_from_csv.py b/coin/billing/management/commands/import_payments_from_csv.py index 950c11e01bc37e7f2297d0e872ae01cc7757e42d..54eea03bfae9a419f511f3fe4e0229c8065aeed5 100644 --- a/coin/billing/management/commands/import_payments_from_csv.py +++ b/coin/billing/management/commands/import_payments_from_csv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Import payments from a CSV file from a bank. The payments will automatically be parsed, and there'll be an attempt to automatically match payments with members. @@ -17,7 +16,6 @@ By default, only a dry-run is perfomed to let you see what will happen ! You should run this command with --commit if you agree with the dry-run. """ -from __future__ import unicode_literals # Standard python libs import json @@ -38,7 +36,7 @@ class Command(BaseCommand): help = __doc__ def create_parser(self, *args, **kwargs): - parser = super(Command, self).create_parser(*args, **kwargs) + parser = super().create_parser(*args, **kwargs) parser.formatter_class = RawTextHelpFormatter return parser @@ -64,7 +62,7 @@ class Command(BaseCommand): if not os.path.isfile(options["filename"]): raise CommandError("This file does not exists.") - f = open(options["filename"], "r") + f = open(options["filename"]) new_payments = process(f) number_of_new_payments = len(new_payments) diff --git a/coin/billing/management/commands/send_reminders_for_unpaid_bills.py b/coin/billing/management/commands/send_reminders_for_unpaid_bills.py index 88a6ac22de2460ea2b4d1ab94e969542c4133584..b09696bef1350b8d161947a025fc8dc4fc3057c6 100644 --- a/coin/billing/management/commands/send_reminders_for_unpaid_bills.py +++ b/coin/billing/management/commands/send_reminders_for_unpaid_bills.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - # Standard python libs import logging @@ -20,7 +17,7 @@ weeks. """ def create_parser(self, *args, **kwargs): - parser = super(Command, self).create_parser(*args, **kwargs) + parser = super().create_parser(*args, **kwargs) parser.formatter_class = RawTextHelpFormatter return parser diff --git a/coin/billing/migrations/0001_initial.py b/coin/billing/migrations/0001_initial.py index 474e66cf510c41eb5850f87ff992aa0660430702..68833f2b41b5e7f6ec7727dee1e35f7a74f48b40 100644 --- a/coin/billing/migrations/0001_initial.py +++ b/coin/billing/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import coin.billing.models import coin.utils @@ -40,7 +37,9 @@ class Migration(migrations.Migration): ('tax', models.DecimalField(decimal_places=2, default=0.0, max_digits=4, help_text='en %', null=True, verbose_name='TVA')), ('period_from', models.DateField(default=coin.utils.start_of_month, help_text='Date de d\xe9but de p\xe9riode sur laquelle est factur\xe9 cet item', null=True, verbose_name='d\xe9but de p\xe9riode', blank=True)), ('period_to', models.DateField(default=coin.utils.end_of_month, help_text='Date de fin de p\xe9riode sur laquelle est factur\xe9 cet item', null=True, verbose_name='fin de p\xe9riode', blank=True)), - ('invoice', models.ForeignKey(related_name='details', verbose_name='facture', to='billing.Invoice')), + ('invoice', models.ForeignKey(related_name='details', + verbose_name='facture', to='billing.Invoice', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'd\xe9tail de facture', @@ -54,7 +53,9 @@ class Migration(migrations.Migration): ('payment_mean', models.CharField(default='transfer', max_length=100, null=True, verbose_name='moyen de paiement', choices=[('cash', 'Esp\xe8ces'), ('check', 'Ch\xe8que'), ('transfer', 'Virement'), ('other', 'Autre')])), ('amount', models.DecimalField(null=True, verbose_name='montant', max_digits=5, decimal_places=2)), ('date', models.DateField(default=datetime.date.today)), - ('invoice', models.ForeignKey(related_name='payments', verbose_name='facture', to='billing.Invoice')), + ('invoice', models.ForeignKey(related_name='payments', + verbose_name='facture', to='billing.Invoice', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'paiement', diff --git a/coin/billing/migrations/0002_auto_20141002_0204.py b/coin/billing/migrations/0002_auto_20141002_0204.py index d561ec7ac37577e68281b9c0b41865fa99ebe69e..39cdb1d3caa406a05ab76e0e197ed7024588b4c3 100644 --- a/coin/billing/migrations/0002_auto_20141002_0204.py +++ b/coin/billing/migrations/0002_auto_20141002_0204.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import django.db.models.deletion from django.conf import settings @@ -18,13 +15,18 @@ class Migration(migrations.Migration): migrations.AddField( model_name='invoicedetail', name='offersubscription', - field=models.ForeignKey(default=None, blank=True, to='offers.OfferSubscription', null=True, verbose_name='abonnement'), - preserve_default=True, + field=models.ForeignKey(default=None, blank=True, + to='offers.OfferSubscription', null=True, verbose_name='abonnement', + on_delete=models.CASCADE), + preserve_default=True ), migrations.AddField( model_name='invoice', name='member', - field=models.ForeignKey(related_name='invoices', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True, verbose_name='membre'), + field=models.ForeignKey(related_name='invoices', + on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, + to=settings.AUTH_USER_MODEL, null=True, verbose_name='membre'), + preserve_default=True, ), ] diff --git a/coin/billing/migrations/0003_auto_20150221_2226.py b/coin/billing/migrations/0003_auto_20150221_2226.py index a1ba6a2902baad1bb96b414bfacd9cbe45591334..0f4457f50ca06a2ab937953a9f5adf43eb411e28 100644 --- a/coin/billing/migrations/0003_auto_20150221_2226.py +++ b/coin/billing/migrations/0003_auto_20150221_2226.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/billing/migrations/0004_auto_20161230_1803.py b/coin/billing/migrations/0004_auto_20161230_1803.py index 052c710827a5b9bb10fad160c24eebd20766c7f4..0908a856808acfcdfe63a03d3b247bdba3d665c2 100644 --- a/coin/billing/migrations/0004_auto_20161230_1803.py +++ b/coin/billing/migrations/0004_auto_20161230_1803.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import datetime diff --git a/coin/billing/migrations/0005_auto_20170608_2213.py b/coin/billing/migrations/0005_auto_20170608_2213.py index 52f865b5b49290508df50a9443d1b7338b2c2d33..7d8e9956009524acf2ca211cf622872efbcb2fc4 100644 --- a/coin/billing/migrations/0005_auto_20170608_2213.py +++ b/coin/billing/migrations/0005_auto_20170608_2213.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import coin.utils diff --git a/coin/billing/migrations/0006_auto_20170608_2305.py b/coin/billing/migrations/0006_auto_20170608_2305.py index 03520110b0c9dfc573a611599b2ce2bd52731fc1..76b2e82816467cb0d51d6049b61c58e910447663 100644 --- a/coin/billing/migrations/0006_auto_20170608_2305.py +++ b/coin/billing/migrations/0006_auto_20170608_2305.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0007_auto_20170801_1530.py b/coin/billing/migrations/0007_auto_20170801_1530.py index 880e77f6055427f37a9d805429c86b87ed84ca56..acd6e402a5b2f233f6163b43fa6fe43303e352cb 100644 --- a/coin/billing/migrations/0007_auto_20170801_1530.py +++ b/coin/billing/migrations/0007_auto_20170801_1530.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0008_auto_20170802_2021.py b/coin/billing/migrations/0008_auto_20170802_2021.py index f6c1e3d2ba745eeb0828066586971662ff94dae4..863f9ab6b78209d9b985ebda1e7f176cd85993ca 100644 --- a/coin/billing/migrations/0008_auto_20170802_2021.py +++ b/coin/billing/migrations/0008_auto_20170802_2021.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0009_new_billing_system_schema.py b/coin/billing/migrations/0009_new_billing_system_schema.py index 68c1ab73141162bd966842b2821adbdec75ba27d..ce189b166ea7218c1458a951c777893efbf4cad5 100644 --- a/coin/billing/migrations/0009_new_billing_system_schema.py +++ b/coin/billing/migrations/0009_new_billing_system_schema.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion from django.conf import settings @@ -34,21 +31,25 @@ class Migration(migrations.Migration): migrations.AddField( model_name='payment', name='member', - field=models.ForeignKey(related_name='payments', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to=settings.AUTH_USER_MODEL, null=True, verbose_name='membre'), + field=models.ForeignKey(related_name='payments', + on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, + to=settings.AUTH_USER_MODEL, null=True, verbose_name='membre'), + ), migrations.AlterField( model_name='payment', name='invoice', - field=models.ForeignKey(related_name='payments', verbose_name='facture associ\xe9e', blank=True, to='billing.Invoice', null=True), + field=models.ForeignKey(related_name='payments', verbose_name='facture associ\xe9e', blank=True, to='billing.Invoice', null=True, + on_delete=models.CASCADE), ), migrations.AddField( model_name='paymentallocation', name='invoice', - field=models.ForeignKey(related_name='allocations', verbose_name='facture associ\xe9e', to='billing.Invoice'), + field=models.ForeignKey(related_name='allocations', verbose_name='facture associ\xe9e', to='billing.Invoice', on_delete=models.CASCADE), ), migrations.AddField( model_name='paymentallocation', name='payment', - field=models.ForeignKey(related_name='allocations', verbose_name='facture associ\xe9e', to='billing.Payment'), + field=models.ForeignKey(related_name='allocations', verbose_name='facture associ\xe9e', to='billing.Payment', on_delete=models.CASCADE), ), ] diff --git a/coin/billing/migrations/0010_new_billing_system_data.py b/coin/billing/migrations/0010_new_billing_system_data.py index 3e7948869071857502a3746ab426c3c95f451ae8..d058303678c530335265f8a3ff12ba4972c210b8 100755 --- a/coin/billing/migrations/0010_new_billing_system_data.py +++ b/coin/billing/migrations/0010_new_billing_system_data.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import sys from django.db import migrations @@ -20,7 +17,7 @@ def check_current_state(apps, schema_editor): related_payments = invoice.payments.all() - total_related_payments = sum([p.amount for p in related_payments]) + total_related_payments = sum(p.amount for p in related_payments) if total_related_payments > invoice.amount: error = "For invoice, current sum of payment is higher than total of invoice. Please fix this before running this migration" % invoice_name @@ -60,8 +57,8 @@ def compute_balance(invoices, payments): active_invoices = [i for i in invoices if i.amount_remaining_to_pay() > 0] s = 0 - s -= sum([i.amount_remaining_to_pay() for i in active_invoices]) - s += sum([p.amount_not_allocated() for p in active_payments]) + s -= sum(i.amount_remaining_to_pay() for i in active_invoices) + s += sum(p.amount_not_allocated() for p in active_payments) return s diff --git a/coin/billing/migrations/0011_auto_20180414_2250.py b/coin/billing/migrations/0011_auto_20180414_2250.py index a7381942cdf529641558aa44da3f08cb9987fc69..c01d972844a3258d96c5a31c1976da34a4030134 100644 --- a/coin/billing/migrations/0011_auto_20180414_2250.py +++ b/coin/billing/migrations/0011_auto_20180414_2250.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0011_auto_20180819_0221.py b/coin/billing/migrations/0011_auto_20180819_0221.py index a0ffeb42bee196b966cab2421642b8e7105d2717..d51beace3292f2086b13ab20a910437b1c02193d 100644 --- a/coin/billing/migrations/0011_auto_20180819_0221.py +++ b/coin/billing/migrations/0011_auto_20180819_0221.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django.db.models.deletion @@ -15,6 +12,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='invoicedetail', name='offersubscription', - field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='offers.OfferSubscription', null=True, verbose_name='abonnement'), + field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, + default=None, blank=True, to='offers.OfferSubscription', null=True, + verbose_name='abonnement'), ), ] diff --git a/coin/billing/migrations/0012_auto_20180415_1502.py b/coin/billing/migrations/0012_auto_20180415_1502.py index 3f12f8af75d43335a42c1488fc85ab454f6266d4..7e261fe983c97ee0f0b1ea4afa1107b45b3e1ef3 100644 --- a/coin/billing/migrations/0012_auto_20180415_1502.py +++ b/coin/billing/migrations/0012_auto_20180415_1502.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import datetime import coin.billing.models @@ -67,7 +64,9 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='invoice', name='bill_ptr', - field=models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, default=1, serialize=False, to='billing.Bill'), + field=models.OneToOneField(parent_link=True, auto_created=True, + primary_key=True, default=1, serialize=False, to='billing.Bill', + on_delete=models.CASCADE), ), # 4/ Delete duplicate Invoice fields that are present on Bill : status, date, pdf, member @@ -79,17 +78,20 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='payment', name='invoice', - field=models.ForeignKey(related_name='payments_old', verbose_name='facture associ\xe9e', blank=True, to='billing.Invoice', null=True), + field=models.ForeignKey(related_name='payments_old', verbose_name='facture associ\xe9e', blank=True, to='billing.Invoice', null=True, + on_delete=models.CASCADE), ), migrations.AlterField( model_name='paymentallocation', name='invoice', - field=models.ForeignKey(related_name='allocations', verbose_name='facture associ\xe9e', to='billing.Bill'), + field=models.ForeignKey(related_name='allocations', verbose_name='facture associ\xe9e', to='billing.Bill', on_delete=models.CASCADE), ), migrations.CreateModel( name='Donation', fields=[ - ('bill_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='billing.Bill')), + ('bill_ptr', models.OneToOneField(parent_link=True, auto_created=True, + primary_key=True, serialize=False, to='billing.Bill', + on_delete=models.CASCADE)), ('_amount', models.DecimalField(verbose_name='Montant', max_digits=8, decimal_places=2)), ], options={ @@ -100,7 +102,9 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TempMembershipFee', fields=[ - ('bill_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='billing.Bill')), + ('bill_ptr', models.OneToOneField(parent_link=True, auto_created=True, + primary_key=True, serialize=False, to='billing.Bill', + on_delete=models.CASCADE)), ('_amount', models.DecimalField(default=None, help_text='en \u20ac', verbose_name='montant', max_digits=5, decimal_places=2)), ('start_date', models.DateField(verbose_name='date de d\xe9but de cotisation')), ('end_date', models.DateField(help_text='par d\xe9faut, la cotisation dure un an', verbose_name='date de fin de cotisation', blank=True)), @@ -118,6 +122,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='payment', name='bill', - field=models.ForeignKey(related_name='payments', verbose_name='facture associ\xe9e', blank=True, to='billing.Bill', null=True), + field=models.ForeignKey(related_name='payments', verbose_name='facture associ\xe9e', blank=True, to='billing.Bill', null=True, + on_delete=models.CASCADE), ), ] diff --git a/coin/billing/migrations/0013_auto_20180415_0413.py b/coin/billing/migrations/0013_auto_20180415_0413.py index 2e8a872e9feff24414ff570f1a1e7eb1f2cfb938..70c0a1394c98a8a40c0999a821a1ec356b84a71d 100644 --- a/coin/billing/migrations/0013_auto_20180415_0413.py +++ b/coin/billing/migrations/0013_auto_20180415_0413.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models def fill_empty_fee_payment_date(apps, schema_editor): diff --git a/coin/billing/migrations/0014_auto_20180415_1814.py b/coin/billing/migrations/0014_auto_20180415_1814.py index 4b941d0b46d10a0d49be54bcfaef598b3ec6f14b..0d0ba6f867764ac1dad0a31f938ae8c501683cb0 100644 --- a/coin/billing/migrations/0014_auto_20180415_1814.py +++ b/coin/billing/migrations/0014_auto_20180415_1814.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0015_remove_payment_invoice.py b/coin/billing/migrations/0015_remove_payment_invoice.py index fe1609d3d025fcf9e3fdf8c012ba3c49760ef387..16415c9b8be407586df542bcd479210017b10cbd 100644 --- a/coin/billing/migrations/0015_remove_payment_invoice.py +++ b/coin/billing/migrations/0015_remove_payment_invoice.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0016_auto_20180415_2208.py b/coin/billing/migrations/0016_auto_20180415_2208.py index 7f610917b7126b33ea24650189c506f52220f446..c8acc97edda3b467e30ee09bf1a42a302c752047 100644 --- a/coin/billing/migrations/0016_auto_20180415_2208.py +++ b/coin/billing/migrations/0016_auto_20180415_2208.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0017_merge.py b/coin/billing/migrations/0017_merge.py index d7ffbcf8fef1491b15beb1458aece40638fb0848..260237e88482413244332eacc76d4f338ececc79 100644 --- a/coin/billing/migrations/0017_merge.py +++ b/coin/billing/migrations/0017_merge.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0018_auto_20181118_2001.py b/coin/billing/migrations/0018_auto_20181118_2001.py index d8ff559b50f2fb84cbbf385f7a8b12b14ef9d26b..d070a4685b439f6e6b88b3c8130ed419f721cff0 100644 --- a/coin/billing/migrations/0018_auto_20181118_2001.py +++ b/coin/billing/migrations/0018_auto_20181118_2001.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django.core.files.storage import coin.billing.models diff --git a/coin/billing/migrations/0019_auto_20190623_1256.py b/coin/billing/migrations/0019_auto_20190623_1256.py index 5fab37f25490079dd0d887cccc77e94ace632ea4..0c76c72f8076e0e6a1053b1a5c3f16380c9d8abb 100644 --- a/coin/billing/migrations/0019_auto_20190623_1256.py +++ b/coin/billing/migrations/0019_auto_20190623_1256.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0020_auto_20200717_1733.py b/coin/billing/migrations/0020_auto_20200717_1733.py index 9bb497aa09afa4f0dec548ad995832ce4e31d8a3..09c94b6819c7486a94ce56bef694984990a5ade5 100644 --- a/coin/billing/migrations/0020_auto_20200717_1733.py +++ b/coin/billing/migrations/0020_auto_20200717_1733.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/billing/migrations/0021_auto_20201203_1855.py b/coin/billing/migrations/0021_auto_20201203_1855.py index 77f4bfc04bbdfda11c545b57bbfcd750d92c2aba..282da0b42495271871f272fa502f81d11d9500d6 100644 --- a/coin/billing/migrations/0021_auto_20201203_1855.py +++ b/coin/billing/migrations/0021_auto_20201203_1855.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django.core.files.storage import coin.members.models diff --git a/coin/billing/migrations/0022_auto_20201203_1913.py b/coin/billing/migrations/0022_auto_20201203_1913.py index b7d7b2fe2f4285be19d9345853dc381da1a2094c..4a492778f243728a293a956c5169d298889ee213 100644 --- a/coin/billing/migrations/0022_auto_20201203_1913.py +++ b/coin/billing/migrations/0022_auto_20201203_1913.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import coin.billing.models import coin.utils diff --git a/coin/billing/migrations/0023_auto_20201203_1932.py b/coin/billing/migrations/0023_auto_20201203_1932.py index 3406d610900267ae2569a5c059249dbc0aef7864..f93a8730d2a90f5f42d815b9950be6f0c5967301 100644 --- a/coin/billing/migrations/0023_auto_20201203_1932.py +++ b/coin/billing/migrations/0023_auto_20201203_1932.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models @@ -14,6 +11,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='invoice', name='bill_ptr', - field=models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='billing.Bill'), + field=models.OneToOneField(parent_link=True, auto_created=True, + primary_key=True, serialize=False, to='billing.Bill', + on_delete=models.CASCADE), ), ] diff --git a/coin/billing/models.py b/coin/billing/models.py index 210e355c34a19c7d15ed3300ca3982703f292c15..6034b3a2852742563f0933b6e9453c71ede2e908 100644 --- a/coin/billing/models.py +++ b/coin/billing/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime import logging import uuid @@ -16,7 +13,7 @@ from django.utils.encoding import python_2_unicode_compatible from django.dispatch import receiver from django.db.models.signals import post_save, post_delete from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse +from django.urls import reverse from coin.offers.models import OfferSubscription from coin.members.models import Member, default_membership_fee @@ -69,10 +66,10 @@ class Bill(models.Model): def as_child(self): for child_class_name in self.CHILD_CLASS_NAMES: - try: - return self.__getattribute__(child_class_name.lower()) - except eval(child_class_name).DoesNotExist: - pass + try: + return self.__getattribute__(child_class_name.lower()) + except eval(child_class_name).DoesNotExist: + pass return self @property @@ -92,7 +89,7 @@ class Bill(models.Model): """ Calcul le montant déjà payé à partir des allocations de paiements """ - return sum([a.amount for a in self.allocations.all()]) + return sum(a.amount for a in self.allocations.all()) amount_paid.short_description = 'Montant payé' def amount_remaining_to_pay(self): @@ -114,7 +111,7 @@ class Bill(models.Model): """ context = {"bill": self} context.update(branding(None)) - pdf_file = render_as_pdf('billing/{bill_type}_pdf.html'.format(bill_type=self.type.lower()), context) + pdf_file = render_as_pdf(f'billing/{self.type.lower()}_pdf.html', context) self.pdf.save('%s.pdf' % self.number if hasattr(self, "number") else self.pk, pdf_file) def pdf_exists(self): @@ -193,13 +190,13 @@ class InvoiceNumber: return InvoiceNumber(self.date, self.index + 1) def __str__(self): - return '{:%Y-%m}-{:0>6}'.format(self.date, self.index) + return f'{self.date:%Y-%m}-{self.index:0>6}' @classmethod def parse(cls, string): m = cls.RE_INVOICE_NUMBER.match(string) if not m: - raise ValueError('Not a valid invoice number: "{}"'.format(string)) + raise ValueError(f'Not a valid invoice number: "{string}"') return cls( datetime.date( @@ -220,8 +217,8 @@ class InvoiceNumber: """ return { - '{}__month'.format(field_name): date.month, - '{}__year'.format(field_name): date.year + f'{field_name}__month': date.month, + f'{field_name}__year': date.year } @@ -261,17 +258,17 @@ class Invoice(Bill): date_due = models.DateField( null=True, blank=True, verbose_name="date d'échéance de paiement", - help_text='Le délai de paiement sera fixé à {} jours à la validation si laissé vide'.format(settings.PAYMENT_DELAY)) + help_text=f'Le délai de paiement sera fixé à {settings.PAYMENT_DELAY} jours à la validation si laissé vide') date_last_reminder_email = models.DateTimeField(null=True, blank=True, verbose_name="Date du dernier email de relance envoyé") def save(self, *args, **kwargs): # First save to get a PK - super(Invoice, self).save(*args, **kwargs) + super().save(*args, **kwargs) # Then use that pk to build draft invoice number if not self.validated and self.pk and not self.number: - self.number = 'DRAFT-{}'.format(self.pk) + self.number = f'DRAFT-{self.pk}' self.save() @property @@ -399,7 +396,7 @@ class Invoice(Bill): if created: accounting_log.info("Creating draft invoice %s (Member: %s)." - % ('DRAFT-{}'.format(self.pk), self.member)) + % (f'DRAFT-{self.pk}', self.member)) else: if not self.validated: accounting_log.info("Updating draft invoice %s (Member: %s)." @@ -424,7 +421,7 @@ class InvoiceDetail(models.Model): max_digits=4, verbose_name='TVA', help_text='en %') invoice = models.ForeignKey(Invoice, verbose_name='facture', - related_name='details') + related_name='details', on_delete=models.CASCADE) offersubscription = models.ForeignKey(OfferSubscription, null=True, blank=True, default=None, on_delete=models.SET_NULL, @@ -475,7 +472,7 @@ class Donation(Bill): def save(self, *args, **kwargs): - super(Donation, self).save(*args, **kwargs) + super().save(*args, **kwargs) if not self.pdf_exists(): self.generate_pdf() @@ -489,7 +486,7 @@ class Donation(Bill): @property def pdf_title(self): - return "Reçu de don" + return "Reçu de don" class Meta: @@ -518,7 +515,7 @@ class MembershipFee(Bill): return True def save(self, *args, **kwargs): - super(MembershipFee, self).save(*args, **kwargs) + super().save(*args, **kwargs) today = datetime.date.today() if self.start_date <= today and today <= self.end_date: @@ -539,7 +536,7 @@ class MembershipFee(Bill): @property def pdf_title(self): - return "Reçu de cotisation" + return "Reçu de cotisation" class Meta: verbose_name = 'cotisation' @@ -567,7 +564,8 @@ class Payment(models.Model): verbose_name='montant') date = models.DateField(default=datetime.date.today) bill = models.ForeignKey(Bill, verbose_name='facture associée', null=True, - blank=True, related_name='payments') + blank=True, related_name='payments', + on_delete=models.CASCADE) label = models.CharField(max_length=500, null=True, blank=True, default="", @@ -583,7 +581,7 @@ class Payment(models.Model): # Automatically set member to invoice's member self.member = self.bill.member - super(Payment, self).save(*args, **kwargs) + super().save(*args, **kwargs) def clean(self): @@ -597,7 +595,7 @@ class Payment(models.Model): raise ValidationError("This payment would pay more than the invoice's remaining to pay") def amount_already_allocated(self): - return sum([ a.amount for a in self.allocations.all() ]) + return sum( a.amount for a in self.allocations.all() ) def amount_not_allocated(self): return self.amount - self.amount_already_allocated() @@ -656,10 +654,10 @@ class PaymentAllocation(models.Model): bill = models.ForeignKey(Bill, verbose_name='facture associée', null=False, blank=False, - related_name='allocations') + related_name='allocations', on_delete=models.CASCADE) payment = models.ForeignKey(Payment, verbose_name='facture associée', null=False, blank=False, - related_name='allocations') + related_name='allocations', on_delete=models.CASCADE) amount = models.DecimalField(max_digits=8, decimal_places=2, null=True, verbose_name='montant') @@ -689,9 +687,9 @@ def update_accounting_for_member(member): if not settings.HANDLE_BALANCE: return - accounting_log.info("Updating accounting for member {} ...".format(member)) + accounting_log.info(f"Updating accounting for member {member} ...") accounting_log.info( - "Member {} current balance is {} ...".format(member, member.balance)) + f"Member {member} current balance is {member.balance} ...") reconcile_bills_and_payments(member) @@ -716,12 +714,12 @@ def reconcile_bills_and_payments(member): if active_payments == []: accounting_log.info( - "(No active payment for {}.".format(member) + f"(No active payment for {member}." + " No bill/payment reconciliation needed.).") return elif active_bills == []: accounting_log.info( - "(No active bill for {}. No bill/payment ".format(member) + + f"(No active bill for {member}. No bill/payment " + "reconciliation needed.).") return @@ -765,8 +763,8 @@ def compute_balance(invoices, payments): active_invoices = [i for i in invoices if i.amount_remaining_to_pay() > 0] s = 0 - s -= sum([i.amount_remaining_to_pay() for i in active_invoices]) - s += sum([p.amount_not_allocated() for p in active_payments]) + s -= sum(i.amount_remaining_to_pay() for i in active_invoices) + s += sum(p.amount_not_allocated() for p in active_payments) return s @@ -825,7 +823,7 @@ def paymentallocation_deleted(sender, instance, **kwargs): # Reopen invoice if relevant if (bill.amount_remaining_to_pay() > 0) and (bill.status == "closed"): - accounting_log.info("Reopening bill {} ...".format(bill.number)) + accounting_log.info(f"Reopening bill {bill.number} ...") bill.status = "open" bill.save() diff --git a/coin/billing/tests.py b/coin/billing/tests.py index 6f7c314e7d3898cada3fbf9316b2fc4bfce51231..e14d947853aafd7356f9e9300a0d381d8b613339 100644 --- a/coin/billing/tests.py +++ b/coin/billing/tests.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from datetime import date from dateutil.relativedelta import relativedelta from decimal import Decimal -from cStringIO import StringIO +from io import StringIO from django.core import mail, management from django.conf import settings @@ -335,9 +332,9 @@ class InvoiceQuerySetTests(TestCase): @freeze_time('2016-01-01') def test_number_workflow(self): iv = Invoice.objects.create() - self.assertEqual(iv.number, 'DRAFT-{}'.format(iv.pk)) + self.assertEqual(iv.number, f'DRAFT-{iv.pk}') iv.validate() - self.assertRegexpMatches(iv.number, r'2016-01-000001$') + self.assertRegex(iv.number, r'2016-01-000001$') @freeze_time('2016-01-01') def test_get_second_of_month_invoice_number(self): @@ -439,7 +436,7 @@ class MembershipFeeTests(TestCase): end_date = start_date + relativedelta(years=+1) # Créé une cotisation - membershipfee = MembershipFee(member=member, amount=20, + membershipfee = MembershipFee(member=member, _amount=20, start_date=start_date, end_date=end_date) membershipfee.save() @@ -465,7 +462,7 @@ class MembershipFeeTests(TestCase): self.assertEqual(member.is_paid_up(), False) # Créé une cotisation passée - membershipfee = MembershipFee(member=member, amount=20, + membershipfee = MembershipFee(member=member, _amount=20, start_date=date.today() + relativedelta(years=-1), end_date=date.today() + relativedelta(days=-10)) @@ -475,7 +472,7 @@ class MembershipFeeTests(TestCase): self.assertEqual(member.is_paid_up(), False) # Créé une cotisation actuelle - membershipfee = MembershipFee(member=member, amount=20, + membershipfee = MembershipFee(member=member, _amount=20, start_date=date.today() + relativedelta(days=-10), end_date=date.today() + relativedelta(days=+10)) diff --git a/coin/billing/urls.py b/coin/billing/urls.py index 9444397a3a06fadab1de77b73d41d3090e54e9a3..a882f2f3b6cf4518953d90a7482e47f93a909690 100644 --- a/coin/billing/urls.py +++ b/coin/billing/urls.py @@ -1,10 +1,8 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf.urls import url from django.views.generic import DetailView from coin.billing import views +app_name = 'billing' urlpatterns = [ #'', url(r'^bill/(?P.+)/pdf$', views.bill_pdf, name="bill_pdf"), diff --git a/coin/billing/utils.py b/coin/billing/utils.py index 902dd0c6fb33843b90685b2c55606342576d8158..6e2c016e12d33e59c6072c3fff50251c298b4593 100644 --- a/coin/billing/utils.py +++ b/coin/billing/utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.shortcuts import render, get_object_or_404 from django.core.exceptions import PermissionDenied diff --git a/coin/billing/views.py b/coin/billing/views.py index 036ade797c689da115079296e7fad162b6dd3726..d85ea3ffafb46f93f2a90c45d12c6b622b079671 100644 --- a/coin/billing/views.py +++ b/coin/billing/views.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.http import HttpResponse from django.template import RequestContext from django.shortcuts import render @@ -28,7 +25,7 @@ def bill_pdf(request, id): human_type = bill.__class__._meta.verbose_name id_for_filename = bill.number if bill.type == "Invoice" else bill.pk - pdf_filename = '%s_%s.pdf' % (human_type, id_for_filename) + pdf_filename = f'{human_type}_{id_for_filename}.pdf' return sendfile(request, bill.pdf.path, attachment=True, attachment_filename=pdf_filename) diff --git a/coin/configuration/admin.py b/coin/configuration/admin.py index e0dd1e2654ece15edd5c84119e98ad334daaa071..b4b13f705a2cb7b6e4234ef68bd60838d41ca47f 100644 --- a/coin/configuration/admin.py +++ b/coin/configuration/admin.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.shortcuts import get_object_or_404 from django.contrib import admin, messages from django.http import HttpResponseRedirect from django.conf.urls import url -from django.core.urlresolvers import reverse +from django.urls import reverse from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin from coin.resources.models import IPSubnet @@ -41,23 +38,7 @@ class ParentConfigurationAdmin(PolymorphicParentModelAdmin): (ADSLConfiguration, ADSLConfigurationAdmin)) """ - return tuple((x.base_model, x) for x in ChildConfigurationAdmin.__subclasses__()) - - def get_urls(self): - """ - Fix a django-polymorphic bug that randomly set wrong url for a child - model in admin. - This remove somes dummy urls that have not to be returned by the parent model - https://github.com/chrisglass/django_polymorphic/issues/105 - """ - urls = super(ParentConfigurationAdmin, self).get_urls() - for model, _ in self.get_child_models(): - admin = self._get_real_admin_by_model(model) - for admin_url in admin.get_urls(): - for url in urls: - if url.name == admin_url.name: - urls.remove(url) - return urls + return tuple(x.base_model for x in ChildConfigurationAdmin.__subclasses__()) class ChildConfigurationAdmin(PolymorphicChildModelAdmin): @@ -116,7 +97,7 @@ class ChildConfigurationAdmin(PolymorphicChildModelAdmin): """ Custom admin urls """ - urls = super(ChildConfigurationAdmin, self).get_urls() + urls = super().get_urls() return urls + [ url(r'^provision/(?P.+)$', self.admin_site.admin_view(self.provision_view), diff --git a/coin/configuration/forms.py b/coin/configuration/forms.py index 8c6e02ff24493f14c408e96d178f5bcaab36add1..23606b54d1b38093907e01a3cb393fb134ed555b 100644 --- a/coin/configuration/forms.py +++ b/coin/configuration/forms.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db.models import Q from django import forms @@ -22,7 +19,7 @@ class ConfigurationForm(forms.ModelForm): to only display subscription that are the sames type of actual configuration and that haven't already a configuration associated with """ - super(ConfigurationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.instance and not hasattr(self.instance, "offersubscription"): queryset = OfferSubscription.objects.filter( Q(offer__configuration_type=self.instance.model_name) & ( @@ -37,6 +34,6 @@ class ConfigurationForm(forms.ModelForm): """ offersubscription = self.cleaned_data['offersubscription'] if offersubscription.offer.configuration_type != self.instance.model_name(): - raise forms.ValidationError('Administrative subscription must refer an offer having a "{}" configuration type.'.format(self.instance.model_name())) + raise forms.ValidationError(f'Administrative subscription must refer an offer having a "{self.instance.model_name()}" configuration type.') return offersubscription diff --git a/coin/configuration/management/commands/fill_with_toy_data.py b/coin/configuration/management/commands/fill_with_toy_data.py index 9db04c3f055b2ae6c6b87401ca590b1566e8c057..8eacba3889d8c994fe7ca45f4e1afee30cf7e6fb 100644 --- a/coin/configuration/management/commands/fill_with_toy_data.py +++ b/coin/configuration/management/commands/fill_with_toy_data.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from argparse import RawTextHelpFormatter from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.hashers import make_password @@ -18,7 +15,7 @@ class Command(BaseCommand): help = __doc__ def create_parser(self, *args, **kwargs): - parser = super(Command, self).create_parser(*args, **kwargs) + parser = super().create_parser(*args, **kwargs) parser.formatter_class = RawTextHelpFormatter return parser diff --git a/coin/configuration/management/commands/update_configuration_states.py b/coin/configuration/management/commands/update_configuration_states.py index e57ccdb7924b8be1de931b6e340470802ad048c5..eb80a467045d23d9d81540c6c1499cca5b762837 100644 --- a/coin/configuration/management/commands/update_configuration_states.py +++ b/coin/configuration/management/commands/update_configuration_states.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Fetch all states for configuration of a given type (e.g. VPS, ...) using the FETCH_STATES hook define for this conf type using settings.HOOKS. @@ -29,7 +28,6 @@ The hook is expected to return the following kind of output (loaded from json or ] """ -from __future__ import unicode_literals from argparse import RawTextHelpFormatter from django.core.management.base import BaseCommand, CommandError @@ -45,7 +43,7 @@ class Command(BaseCommand): help = __doc__ def create_parser(self, *args, **kwargs): - parser = super(Command, self).create_parser(*args, **kwargs) + parser = super().create_parser(*args, **kwargs) parser.formatter_class = RawTextHelpFormatter return parser @@ -64,6 +62,6 @@ class Command(BaseCommand): conf_classes = {c.url_namespace: c for c in get_descendant_classes(Configuration)} if options["conf_type"] not in conf_classes: - raise CommandError("Unknown conf type %s ... known conf types are : %s" % (options["conf_type"], ', '.join(conf_classes.keys()))) + raise CommandError("Unknown conf type {} ... known conf types are : {}".format(options["conf_type"], ', '.join(conf_classes.keys()))) conf_classes[options["conf_type"]].fetch_and_update_all_states() diff --git a/coin/configuration/migrations/0001_initial.py b/coin/configuration/migrations/0001_initial.py index 3964a1383be98f5dc771358c1e5ec3057ffc8225..5ec8d7178fff376188d7630d61373174072821b1 100644 --- a/coin/configuration/migrations/0001_initial.py +++ b/coin/configuration/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/configuration/migrations/0002_auto_20141002_0204.py b/coin/configuration/migrations/0002_auto_20141002_0204.py index 321d236047621986b1405f6dd99ab5502f3c69d9..46cbe8bff16f70ecca2eab02dc219e5192b55040 100644 --- a/coin/configuration/migrations/0002_auto_20141002_0204.py +++ b/coin/configuration/migrations/0002_auto_20141002_0204.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations @@ -16,13 +13,17 @@ class Migration(migrations.Migration): migrations.AddField( model_name='configuration', name='offersubscription', - field=models.OneToOneField(related_name='configuration', verbose_name='abonnement', to='offers.OfferSubscription'), + field=models.OneToOneField(related_name='configuration', + verbose_name='abonnement', to='offers.OfferSubscription', + on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='configuration', name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_configuration.configuration_set', editable=False, to='contenttypes.ContentType', null=True), + field=models.ForeignKey(related_name='polymorphic_configuration.configuration_set', + editable=False, to='contenttypes.ContentType', null=True, + on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/coin/configuration/migrations/0003_configuration_comment.py b/coin/configuration/migrations/0003_configuration_comment.py index 072f397e08ffc5f3ae470e92db2958e71f2a1711..5d0b64bae3e13ca9e9e52c8ec79b3cace89b18eb 100644 --- a/coin/configuration/migrations/0003_configuration_comment.py +++ b/coin/configuration/migrations/0003_configuration_comment.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/configuration/migrations/0004_auto_20161015_1837.py b/coin/configuration/migrations/0004_auto_20161015_1837.py index 2eecca9ed7867699ce4f2da7a79c43715435c1c6..dd3184aecda1999cdb42740b1fe89e3f443bf3bb 100644 --- a/coin/configuration/migrations/0004_auto_20161015_1837.py +++ b/coin/configuration/migrations/0004_auto_20161015_1837.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models @@ -14,6 +11,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='configuration', name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_configuration.configuration_set+', editable=False, to='contenttypes.ContentType', null=True), + field=models.ForeignKey(related_name='polymorphic_configuration.configuration_set+', + editable=False, to='contenttypes.ContentType', null=True, + on_delete=models.CASCADE), ), ] diff --git a/coin/configuration/migrations/0005_auto_20200717_1733.py b/coin/configuration/migrations/0005_auto_20200717_1733.py index 73aec1675b28dd4fb274cbf6d8b229ea83eeeff6..f34321642107947877e869ec306770fa0d9a9986 100644 --- a/coin/configuration/migrations/0005_auto_20200717_1733.py +++ b/coin/configuration/migrations/0005_auto_20200717_1733.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/configuration/migrations/0006_auto_20201030_1745.py b/coin/configuration/migrations/0006_auto_20201030_1745.py index d3dc36f36aa5284dbe3a861f237693d99f90dfb5..97bef36b3ecb17ff1cecb82531891060393c4e3c 100644 --- a/coin/configuration/migrations/0006_auto_20201030_1745.py +++ b/coin/configuration/migrations/0006_auto_20201030_1745.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/configuration/models.py b/coin/configuration/models.py index c3263f3874bfbc8d4755e8e6791d7c252df89881..24dd276aaa9617a9c159e35779dbcf9f4a81fcb3 100644 --- a/coin/configuration/models.py +++ b/coin/configuration/models.py @@ -1,16 +1,13 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals import datetime import csv -# In Python3, StringIO will just become io -import StringIO as io +import io import logging from netfields import InetAddressField, NetManager from django.db import models -from polymorphic import PolymorphicModel +from polymorphic.models import PolymorphicModel from coin.offers.models import OfferSubscription from django.db.models.signals import post_save, post_delete from django.core.exceptions import ObjectDoesNotExist @@ -39,7 +36,8 @@ class Configuration(PolymorphicModel): offersubscription = models.OneToOneField(OfferSubscription, related_name='configuration', - verbose_name='abonnement') + verbose_name='abonnement', + on_delete=models.CASCADE) comment = models.CharField(blank=True, max_length=512, verbose_name="commentaire", help_text="Ce texte s'affiche dans l'interface de l'abonné⋅e") @@ -77,7 +75,7 @@ class Configuration(PolymorphicModel): Génère automatiquement la liste de choix possibles de configurations en fonction des classes enfants de Configuration """ - return tuple([(c.__name__, c._meta.verbose_name) for c in get_descendant_classes(Configuration)]) + return tuple((c.__name__, c._meta.verbose_name) for c in get_descendant_classes(Configuration)) def convert_to_dict_for_hook(self): # FIXME : check assertions here @@ -157,7 +155,7 @@ class Configuration(PolymorphicModel): conf_list = [c.convert_to_dict_for_hook() for c in confs_to_update] conf_list_csv = io.StringIO() info_keys = conf_list[0].keys() - writer = csv.writer(conf_list_csv, delimiter=str(';'), quotechar=str('"'), quoting=csv.QUOTE_MINIMAL) + writer = csv.writer(conf_list_csv, delimiter=';', quotechar='"', quoting=csv.QUOTE_MINIMAL) writer.writerow([key for key in info_keys]) for c in conf_list: row = [] @@ -172,17 +170,17 @@ class Configuration(PolymorphicModel): success, out, err = HookManager.run(self.url_namespace, "FETCH_ALL_STATES", conf_list=conf_list, conf_list_csv=conf_list_csv) if not success: - raise Exception("Some errors happened during the execution of FETCH_ALL_STATES for %s : %s" % (self.url_namespace, err)) + raise Exception(f"Some errors happened during the execution of FETCH_ALL_STATES for {self.url_namespace} : {err}") assert isinstance(out, list), "Was expecting to get a list as output of FETCH_ALL_STATES" for state_infos in out: assert isinstance(state_infos, dict) and "id" in state_infos, "Was expecting to get a list of dict as output of FETCH_ALL_STATES with at least 'id' in it" try: - id_ = state_infos.pop(u"id") + id_ = state_infos.pop("id") conf_to_update = Configuration.objects.get(pk=id_) conf_to_update.update_state(**state_infos) except Exception as e: - print("error with %s %s %s" % (str(id_), str(state_infos), str(e))) + print(f"error with {str(id_)} {str(state_infos)} {str(e)}") def get_state_display(self): @@ -208,9 +206,9 @@ class Configuration(PolymorphicModel): def get_state_icon_display(self): text, color = self.get_state_display() - text = "Status: %s\nDernière mise à jour: %s" % (text, str(self.last_status_update)) + text = f"Status: {text}\nDernière mise à jour: {str(self.last_status_update)}" - return mark_safe(''.format(color=color, text=text)) + return mark_safe(f'') get_state_icon_display.short_description = 'État' def __str__(self): @@ -229,7 +227,7 @@ class Configuration(PolymorphicModel): Renvoi l'URL d'accès à la page "details" de l'objet Une url doit être nommée "details" """ - from django.core.urlresolvers import reverse + from django.urls import reverse return reverse('%s:details' % self.get_url_namespace(), args=[str(self.id)]) @@ -281,7 +279,7 @@ class Configuration(PolymorphicModel): def bulk_related_objects(self, objs, *args, **kwargs): # Fix delete screen. Workaround for https://github.com/chrisglass/django_polymorphic/issues/34 - return super(Configuration, self).bulk_related_objects(objs, *args, **kwargs).non_polymorphic() + return super().bulk_related_objects(objs, *args, **kwargs).non_polymorphic() class Meta: verbose_name = 'configuration' @@ -324,10 +322,10 @@ class IPConfiguration(Configuration): # Du coup, on retire de l'affectation automatique les sous-réseaux # qui sont explicitement indiquer comme ne servant pas pour le # endpoints (case "utiliser pour les ips d'endpoints") - subnets = set([s for s in self.ip_subnet.all()]) - subnets -= set([s for s in self.ip_subnet.filter( + subnets = {s for s in self.ip_subnet.all()} + subnets -= {s for s in self.ip_subnet.filter( ip_pool__offer_ip_pools__to_assign=False, - ip_pool__offer_ip_pools__offer=offer)]) + ip_pool__offer_ip_pools__offer=offer)} updated = False if v4 and self.ipv4_endpoint is None: subnets_v4 = [s for s in subnets if s.inet.version == 4] @@ -379,10 +377,10 @@ class IPConfiguration(Configuration): # generate them automatically. if self.ipv4_endpoint is None or self.ipv6_endpoint is None: self.generate_endpoints() - super(IPConfiguration, self).save() + super().save() def save(self, **kwargs): - config = super(IPConfiguration, self).save(**kwargs) + config = super().save(**kwargs) self.fill_empty_fields() return config @@ -390,7 +388,7 @@ class IPConfiguration(Configuration): self.check_endpoints() def convert_to_dict_for_hook(self): - d = super(IPConfiguration, self).convert_to_dict_for_hook() + d = super().convert_to_dict_for_hook() if self.ipv4_endpoint: d["ipv4"] = str(self.ipv4_endpoint) if self.ipv6_endpoint: diff --git a/coin/configuration/tests.py b/coin/configuration/tests.py index 5982e6bcd29dd7a86f95cb9ce468697474e59287..7ce503c2dd97ba78597f6ff6e4393132753573f6 100644 --- a/coin/configuration/tests.py +++ b/coin/configuration/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase # Create your tests here. diff --git a/coin/configuration/views.py b/coin/configuration/views.py index e784a0bd2ca964dad77a68f8c0a31f8e9ae27a36..91ea44a218fbd2f408430959283f0419c921093e 100644 --- a/coin/configuration/views.py +++ b/coin/configuration/views.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.shortcuts import render # Create your views here. diff --git a/coin/filtering_queryset.py b/coin/filtering_queryset.py index 6759a471973957450cff5bb339950e4727cbd937..7e8ebaab9f6ae1638e7c1c7f4b58fa993ddebe68 100644 --- a/coin/filtering_queryset.py +++ b/coin/filtering_queryset.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django import forms from django.core.exceptions import ObjectDoesNotExist import logging logger = logging.getLogger(__name__) -class LimitedAdminInlineMixin(object): +class LimitedAdminInlineMixin: """ InlineAdmin mixin limiting the selection of related items according to criteria which can depend on the current parent object being edited. @@ -30,8 +27,8 @@ class LimitedAdminInlineMixin(object): `field` and filters it based on the criteria specified in filters, unless `empty=True`. In this case, no choices will be made available. """ - try: - assert formset.form.base_fields.has_key(field) + try: + assert field in formset.form.base_fields qs = formset.form.base_fields[field].queryset if empty: @@ -51,7 +48,7 @@ class LimitedAdminInlineMixin(object): item. """ formset = \ - super(LimitedAdminInlineMixin, self).get_formset(request, + super().get_formset(request, obj, **kwargs) for (field, filters) in self.get_filters(obj): @@ -59,7 +56,7 @@ class LimitedAdminInlineMixin(object): self.limit_inline_choices(formset, field, **filters) else: self.limit_inline_choices(formset, field, empty=True) - + return formset def get_filters(self, obj): diff --git a/coin/hooks.py b/coin/hooks.py index 6b69eae26db4b422eedc544640b1bb23cc8f50b5..6fd3c28154aa3246c72836ed760c9359c01a7003 100644 --- a/coin/hooks.py +++ b/coin/hooks.py @@ -61,9 +61,9 @@ class HookManager(): # through ps -ef by other users on the system. if not HookManager.is_defined(module, name): - raise Exception("No hook been defined for module %s, hook %s" % (module, name)) + raise Exception(f"No hook been defined for module {module}, hook {name}") - logger.debug("Running hook %s for module %s with args %s" % (name, module, kwargs)) + logger.debug(f"Running hook {name} for module {module} with args {kwargs}") hook = settings.HOOKS[module][name] if hook["type"] == "bash": @@ -105,13 +105,7 @@ class HookManager(): # Format command with provided infos to_run = HookManager._format_hook_bash(hook, **kwargs) - if "stdin" in hook: - try: - stdin = hook["stdin"].encode('utf-8').format(**kwargs) - except (UnicodeDecodeError, UnicodeEncodeError): - stdin = hook["stdin"].format(**kwargs).encode('utf-8') - else: - stdin = None + stdin = hook["stdin"].format(**kwargs).encode('utf-8') if "stdin" in hook else None logger.debug("Will run hook command : %s" % to_run) @@ -122,6 +116,8 @@ class HookManager(): stderr=subprocess.PIPE) stdout, stderr = p.communicate(stdin) + stdout = stdout.decode() + stderr = stderr.decode() success = p.returncode == 0 if "parse_output" in hook: @@ -130,12 +126,12 @@ class HookManager(): try: stdout = json.loads(stdout) except Exception as e: - raise Exception("Failed to load stdout as json.\nStdout:\n%sStderr:\n%s\nError parsing stdout:%s" % (stdout, stderr, e)) + raise Exception(f"Failed to load stdout as json.\nStdout:\n{stdout}Stderr:\n{stderr}\nError parsing stdout:{e}") elif parse_format == "yaml": try: stdout = yaml.safe_load(stdout) except Exception as e: - raise Exception("Failed to load stdout as yaml. Raw content:\n%s\nError:%s" % (stdout, e)) + raise Exception(f"Failed to load stdout as yaml. Raw content:\n{stdout}\nError:{e}") else: raise Exception("Unsupported parse_format %s" % parse_format) @@ -146,7 +142,7 @@ class HookManager(): try: module = import_module("custom_hooks." + hook["module"]) except Exception as e: - raise Exception("Could not load python hook %s : %s" % (hook["module"], e)) + raise Exception("Could not load python hook {} : {}".format(hook["module"], e)) try: output = getattr(module, hook["function"])(**kwargs) diff --git a/coin/html2pdf.py b/coin/html2pdf.py index 31714b519fcce29fc037e0e4980fa686921563a2..564e1685988773827d15cd7ca676eb2861f66bfe 100644 --- a/coin/html2pdf.py +++ b/coin/html2pdf.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import re from tempfile import NamedTemporaryFile @@ -33,7 +30,7 @@ def link_callback(uri, rel): # If file doesn't exist try to find it in app static folder # This case occur in developpement env if not os.path.isfile(path): - app_search = re.search(r'^(%s|%s)(.*)/.*' % (sUrl, mUrl), uri) + app_search = re.search(fr'^({sUrl}|{mUrl})(.*)/.*', uri) app = app_search.group(2) path = os.path.join(projectDir, app, uri[1:]) @@ -59,4 +56,4 @@ def render_as_pdf(template, context): pisaStatus = HTML(string=html).write_pdf(file) file.flush() - return File(open(file.name)) + return File(open(file.name, "rb")) diff --git a/coin/isp_database/admin.py b/coin/isp_database/admin.py index 0305598a10f7ae7acbae4211c600add4f3ffeaea..d8c78d262e35cf11af6b73cc532872818adc227d 100644 --- a/coin/isp_database/admin.py +++ b/coin/isp_database/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from django import forms @@ -18,13 +15,13 @@ class ISPAdminForm(forms.ModelForm): help_text='Main contact phone number') -class SingleInstanceAdminMixin(object): +class SingleInstanceAdminMixin: """Hides the "Add" button when there is already an instance""" def has_add_permission(self, request): num_objects = self.model.objects.count() if num_objects >= 1: return False - return super(SingleInstanceAdminMixin, self).has_add_permission(request) + return super().has_add_permission(request) class RegisteredOfficeInline(admin.StackedInline): diff --git a/coin/isp_database/app.py b/coin/isp_database/app.py index 2a25470040891ea154651b44a6ee7f9125f08c0b..57c28e93f3c42e93dc8ecac6c2826a777faf9872 100644 --- a/coin/isp_database/app.py +++ b/coin/isp_database/app.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals from django.apps import AppConfig diff --git a/coin/isp_database/context_processors.py b/coin/isp_database/context_processors.py index 901b6a4a630ddd40a4ec0086b74d4334969f68c4..d9b6adc5a1580796a74831af6b8a1f96a98a1a1a 100644 --- a/coin/isp_database/context_processors.py +++ b/coin/isp_database/context_processors.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from coin.isp_database.models import ISPInfo diff --git a/coin/isp_database/migrations/0001_initial.py b/coin/isp_database/migrations/0001_initial.py index 2648a74c8b2fb75ca2956cd96e3c049494fb1d9c..a1cfdec3044e90c2c66f1b2f7629d060e1453a90 100644 --- a/coin/isp_database/migrations/0001_initial.py +++ b/coin/isp_database/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import django.core.validators import coin.isp_database.models @@ -60,7 +57,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('name', models.CharField(max_length=512)), ('url', models.URLField(verbose_name='URL')), - ('isp', models.ForeignKey(to='isp_database.ISPInfo')), + ('isp', models.ForeignKey(to='isp_database.ISPInfo', + on_delete=models.CASCADE)), ], options={ }, @@ -77,7 +75,8 @@ class Migration(migrations.Migration): ('region', models.CharField(max_length=512)), ('postal_code', models.CharField(max_length=512, blank=True)), ('country_name', models.CharField(max_length=512)), - ('isp', models.OneToOneField(to='isp_database.ISPInfo')), + ('isp', models.OneToOneField(to='isp_database.ISPInfo', + on_delete=models.CASCADE)), ], options={ }, @@ -86,13 +85,13 @@ class Migration(migrations.Migration): migrations.AddField( model_name='coveredarea', name='isp', - field=models.ForeignKey(to='isp_database.ISPInfo'), + field=models.ForeignKey(to='isp_database.ISPInfo', on_delete=models.CASCADE), preserve_default=True, ), migrations.AddField( model_name='chatroom', name='isp', - field=models.ForeignKey(to='isp_database.ISPInfo'), + field=models.ForeignKey(to='isp_database.ISPInfo', on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/coin/isp_database/migrations/0002_bankinfo.py b/coin/isp_database/migrations/0002_bankinfo.py index 16fed21ce72745bd0ad4a3bd816c7a8da7fb8dbe..923e20fb8683b48a0562213ebe33643a827cd4c9 100644 --- a/coin/isp_database/migrations/0002_bankinfo.py +++ b/coin/isp_database/migrations/0002_bankinfo.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import localflavor.generic.models @@ -19,7 +16,8 @@ class Migration(migrations.Migration): ('iban', localflavor.generic.models.IBANField(max_length=34)), ('bic', localflavor.generic.models.BICField(max_length=11, null=True, verbose_name='BIC', blank=True)), ('bank_name', models.CharField(max_length=100, null=True, verbose_name='\xc9tablissement bancaire', blank=True)), - ('isp', models.OneToOneField(to='isp_database.ISPInfo')), + ('isp', models.OneToOneField(to='isp_database.ISPInfo', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'Coordonn\xe9es bancaires', diff --git a/coin/isp_database/migrations/0003_auto_20141109_1539.py b/coin/isp_database/migrations/0003_auto_20141109_1539.py index 5df5a8f154f6b4902b517da304e3cf3f903963c1..be96baa440370a9ea430d66fa5320dde49c79977 100644 --- a/coin/isp_database/migrations/0003_auto_20141109_1539.py +++ b/coin/isp_database/migrations/0003_auto_20141109_1539.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/isp_database/migrations/0004_ispinfo_support_email.py b/coin/isp_database/migrations/0004_ispinfo_support_email.py index 3df4629d7de815c7644397f0daa404b3cf59c5ad..29b8dd78dd059996a6a58ebaa005bcd98ee259a3 100644 --- a/coin/isp_database/migrations/0004_ispinfo_support_email.py +++ b/coin/isp_database/migrations/0004_ispinfo_support_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/isp_database/migrations/0005_registeredoffice_siret.py b/coin/isp_database/migrations/0005_registeredoffice_siret.py index 75527d2402049d4b7ec7198e5e25fee8d39bb0af..b24beca1807ac6eced03e0a1d9138835dbcaf64b 100644 --- a/coin/isp_database/migrations/0005_registeredoffice_siret.py +++ b/coin/isp_database/migrations/0005_registeredoffice_siret.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import localflavor.fr.models diff --git a/coin/isp_database/migrations/0006_auto_20141111_1740.py b/coin/isp_database/migrations/0006_auto_20141111_1740.py index d9a33aa7ab0190f9e649b704347e74a21dd93d30..550b323d829275203f40d49f1bdb6c86e3e15e5e 100644 --- a/coin/isp_database/migrations/0006_auto_20141111_1740.py +++ b/coin/isp_database/migrations/0006_auto_20141111_1740.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import localflavor.fr.models diff --git a/coin/isp_database/migrations/0007_auto_20141112_2353.py b/coin/isp_database/migrations/0007_auto_20141112_2353.py index 0e4eb2346e65ae1da159e8defe8a376b014b68a5..dcc09b305ad370b8bd3617ccf26ea6afe9f6afaf 100644 --- a/coin/isp_database/migrations/0007_auto_20141112_2353.py +++ b/coin/isp_database/migrations/0007_auto_20141112_2353.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/isp_database/migrations/0008_ispinfo_lists_url.py b/coin/isp_database/migrations/0008_ispinfo_lists_url.py index a279d4094171b37bac6927c1eefe510d0fee850e..67e4449f7ba17e17b4c44caa72608450ca6d5012 100644 --- a/coin/isp_database/migrations/0008_ispinfo_lists_url.py +++ b/coin/isp_database/migrations/0008_ispinfo_lists_url.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/isp_database/migrations/0009_auto_20151230_1759.py b/coin/isp_database/migrations/0009_auto_20151230_1759.py index 71f7909a8bde6358537d3a7bf7994c6272423d9d..096b000092a6befdfda876957b9f8c1f923e0a66 100644 --- a/coin/isp_database/migrations/0009_auto_20151230_1759.py +++ b/coin/isp_database/migrations/0009_auto_20151230_1759.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/isp_database/migrations/0010_ispinfo_phone_number.py b/coin/isp_database/migrations/0010_ispinfo_phone_number.py index 8f6a8d7e0ff668071ed855e8d30bc91526fed68a..bb79244ac8b95101e51fa14e8e0d010a8d03141c 100644 --- a/coin/isp_database/migrations/0010_ispinfo_phone_number.py +++ b/coin/isp_database/migrations/0010_ispinfo_phone_number.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/isp_database/migrations/0011_auto_20170227_0029.py b/coin/isp_database/migrations/0011_auto_20170227_0029.py index 60630f6078a5e6bd1b8d575fa125f6aabd925f5f..ce8811573b7dcd835c93df6ec634e54ee6730081 100644 --- a/coin/isp_database/migrations/0011_auto_20170227_0029.py +++ b/coin/isp_database/migrations/0011_auto_20170227_0029.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import re import django.core.validators diff --git a/coin/isp_database/migrations/0011_auto_20170309_1247.py b/coin/isp_database/migrations/0011_auto_20170309_1247.py index 2f267d0a7342a775312249886ce624bf5171b9a0..c74241997e0268b1caf8ed14ecfd1576af9f3be1 100644 --- a/coin/isp_database/migrations/0011_auto_20170309_1247.py +++ b/coin/isp_database/migrations/0011_auto_20170309_1247.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import multiselectfield.db.fields from coin.isp_database.models import TECHNOLOGIES diff --git a/coin/isp_database/migrations/0012_auto_20170328_2257.py b/coin/isp_database/migrations/0012_auto_20170328_2257.py index b79a138783281dd3ac10f7f0f0e3afa74c5d53a3..a1633a9aaaabcead22c2cda9c2ce9850d8dd0d6b 100644 --- a/coin/isp_database/migrations/0012_auto_20170328_2257.py +++ b/coin/isp_database/migrations/0012_auto_20170328_2257.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import re import django.core.validators diff --git a/coin/isp_database/migrations/0013_merge.py b/coin/isp_database/migrations/0013_merge.py index 7a29866818ae51dd736711b640799a3f15a03455..8388d8b89a99089b8bb9899d07f90d28e0b7b34e 100644 --- a/coin/isp_database/migrations/0013_merge.py +++ b/coin/isp_database/migrations/0013_merge.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/isp_database/migrations/0014_auto_20170802_2021.py b/coin/isp_database/migrations/0014_auto_20170802_2021.py index 87bc35511844cf97cd2c47187557b5d1bae35270..c4de51276cb85f8deb5658f93af1b548075f1b03 100644 --- a/coin/isp_database/migrations/0014_auto_20170802_2021.py +++ b/coin/isp_database/migrations/0014_auto_20170802_2021.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import multiselectfield.db.fields import django.core.validators diff --git a/coin/isp_database/migrations/0015_auto_20220626_1138.py b/coin/isp_database/migrations/0015_auto_20220626_1138.py new file mode 100644 index 0000000000000000000000000000000000000000..dff6e153c303c2217c6911edfa0e2cbc48ed62ff --- /dev/null +++ b/coin/isp_database/migrations/0015_auto_20220626_1138.py @@ -0,0 +1,19 @@ +# Generated by Django 2.0.13 on 2022-06-26 09:38 + +from django.db import migrations +import localflavor.generic.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('isp_database', '0014_auto_20170802_2021'), + ] + + operations = [ + migrations.AlterField( + model_name='bankinfo', + name='iban', + field=localflavor.generic.models.IBANField(include_countries=None, max_length=34, use_nordea_extensions=False, verbose_name='IBAN'), + ), + ] diff --git a/coin/isp_database/models.py b/coin/isp_database/models.py index 83451c336bd4179c7eb4d205df6484d2fc9435cc..19568151f656c2042ecc630ddcabcf948ca1967a 100644 --- a/coin/isp_database/models.py +++ b/coin/isp_database/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models from django.core.validators import MaxValueValidator from django.core.exceptions import ValidationError @@ -25,14 +22,14 @@ TECHNOLOGIES = (('ftth', 'FTTH'), ('cube', 'Brique Internet')) -class SingleInstanceMixin(object): +class SingleInstanceMixin: """Makes sure that no more than one instance of a given model is created.""" def clean(self): model = self.__class__ if (model.objects.count() > 0 and self.id != model.objects.get().id): raise ValidationError("Can only create 1 instance of %s" % model.__name__) - super(SingleInstanceMixin, self).clean() + super().clean() class ISPInfo(SingleInstanceMixin, models.Model): @@ -181,7 +178,7 @@ class ISPInfo(SingleInstanceMixin, models.Model): class OtherWebsite(models.Model): name = models.CharField(max_length=512, verbose_name="Nom") url = models.URLField(verbose_name="URL") - isp = models.ForeignKey(ISPInfo) + isp = models.ForeignKey(ISPInfo, on_delete=models.CASCADE) class Meta: verbose_name = "Autre site Internet" @@ -197,7 +194,7 @@ class RegisteredOffice(models.Model): region = models.CharField(max_length=512, verbose_name="Région") postal_code = models.CharField(max_length=512, blank=True, verbose_name="Code postal") country_name = models.CharField(max_length=512, verbose_name="Pays") - isp = models.OneToOneField(ISPInfo) + isp = models.OneToOneField(ISPInfo, on_delete=models.CASCADE) # not in db.ffdn.org spec siret = FRSIRETField('SIRET') @@ -219,7 +216,7 @@ class RegisteredOffice(models.Model): class ChatRoom(models.Model): url = models.CharField( verbose_name="URL", max_length=256, validators=[chatroom_url_validator]) - isp = models.ForeignKey(ISPInfo) + isp = models.ForeignKey(ISPInfo, on_delete=models.CASCADE) class Meta: verbose_name = "Salon de discussions" @@ -232,7 +229,7 @@ class CoveredArea(models.Model): technologies = MultiSelectField(choices=TECHNOLOGIES, max_length=42, verbose_name="Technologie") # TODO: find a geojson library #area = - isp = models.ForeignKey(ISPInfo) + isp = models.ForeignKey(ISPInfo, on_delete=models.CASCADE) def to_dict(self): return {"name": self.name, @@ -248,7 +245,7 @@ class BankInfo(models.Model): This is out of the scope of db.ffdn.org spec. """ - isp = models.OneToOneField(ISPInfo) + isp = models.OneToOneField(ISPInfo, on_delete=models.CASCADE) iban = IBANField('IBAN') bic = BICField('BIC', blank=True, null=True) bank_name = models.CharField('établissement bancaire', diff --git a/coin/isp_database/templatetags/isptags.py b/coin/isp_database/templatetags/isptags.py index 8185f7f2a068eccd4bf84183d751280e207d3c92..63921bf2200a94664255ff4585cc89c9e1a85f8f 100644 --- a/coin/isp_database/templatetags/isptags.py +++ b/coin/isp_database/templatetags/isptags.py @@ -9,4 +9,4 @@ def multiline_isp_addr(branding): @register.filter def pretty_iban(s): #FR764 2559 0001 2410 2002 3285 19 - return ' '.join([s[i:i+4] for i in xrange(0, len(s), 4)]) + return ' '.join([s[i:i+4] for i in range(0, len(s), 4)]) diff --git a/coin/isp_database/tests.py b/coin/isp_database/tests.py index cc280898fd06f297e7a270d0bd2cdd424c071347..8eb0b677a757fa7ed6ae0bbcb5300beb32d3198a 100644 --- a/coin/isp_database/tests.py +++ b/coin/isp_database/tests.py @@ -4,7 +4,7 @@ from django.test import TestCase # Create your tests here. from coin.members.models import Member -from coin.isp_database.templatetags.isptags import * +from coin.isp_database.templatetags.isptags import pretty_iban from .models import ChatRoom, ISPInfo class TestPrettifiers(TestCase): diff --git a/coin/isp_database/views.py b/coin/isp_database/views.py index 8638dda0172336544fa5bb2f96c27e2785aef41d..20390d1c04288df060b6e13c4492f5fe2c42b827 100644 --- a/coin/isp_database/views.py +++ b/coin/isp_database/views.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import json from django.shortcuts import render diff --git a/coin/members/admin.py b/coin/members/admin.py index 9a8b9e8ac8cbed54b2110b25afa6d41eada7d30e..b64f3dfaa2069aca4ff2974393ac43ef74c42b2d 100644 --- a/coin/members/admin.py +++ b/coin/members/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from datetime import date from django.shortcuts import get_object_or_404 @@ -12,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType from django.http import HttpResponseRedirect from django.conf.urls import url from django.conf import settings -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.safestring import mark_safe from coin.members.models import ( @@ -51,11 +48,11 @@ class OfferSubscriptionInline(admin.TabularInline): def formfield_for_foreignkey(self, db_field, request, **kwargs): if request.user.is_superuser: - return super(OfferSubscriptionInline, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) else: if db_field.name == "offer": kwargs["queryset"] = Offer.objects.manageable_by(request.user) - return super(OfferSubscriptionInline, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) def has_add_permission(self, request): # - Quand on *crée* un membre on autorise à ajouter un abonnement @@ -169,7 +166,7 @@ class MemberAdmin(UserAdmin): obj.end_date_of_membership() or "pas de cotisation", note) if note: - return mark_safe('{}'.format(tooltip, out)) + return mark_safe(f'{out}') else: return mark_safe(out) return mark_safe() @@ -243,10 +240,10 @@ class MemberAdmin(UserAdmin): def get_form(self, request, obj=None, *args, **kwargs): if obj: self.add_member_warnings(request, obj) - return super(MemberAdmin, self).get_form(request, obj, *args, **kwargs) + return super().get_form(request, obj, *args, **kwargs) def get_queryset(self, request): - qs = super(MemberAdmin, self).get_queryset(request) + qs = super().get_queryset(request) if request.user.is_superuser: return qs else: @@ -283,7 +280,7 @@ class MemberAdmin(UserAdmin): def get_urls(self): """Custom admin urls""" - urls = super(MemberAdmin, self).get_urls() + urls = super().get_urls() my_urls = [ url(r'^send_welcome_email/(?P\d+)$', self.admin_site.admin_view(self.send_welcome_email), diff --git a/coin/members/app.py b/coin/members/app.py index ecd61dc73ae40c564b6cce9f362a5ee7ef51ab0c..442c3af41eb6f01d10de6af60dbc2755e652a384 100644 --- a/coin/members/app.py +++ b/coin/members/app.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals from django.apps import AppConfig class MembersConfig(AppConfig): diff --git a/coin/members/autocomplete_light_registry.py b/coin/members/autocomplete_light_registry.py index 2ce8bd56014676e6fe10c3ed904edea5cfe7915d..c45f6f352028c7dc08531a7fe48ee6199cb1f250 100644 --- a/coin/members/autocomplete_light_registry.py +++ b/coin/members/autocomplete_light_registry.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import autocomplete_light -from models import Member +from autocomplete_light import shortcuts as al +from .models import Member # This will generate a MemberAutocomplete class -autocomplete_light.register(Member, +al.register(Member, # Just like in ModelAdmin.search_fields search_fields=[ '^first_name', '^last_name', 'organization_name', diff --git a/coin/members/forms.py b/coin/members/forms.py index d8a2baa493183a807145cafb31367319ab1e63a7..6fb96572fb8920cf48fe835e12eddc0068bea4eb 100644 --- a/coin/members/forms.py +++ b/coin/members/forms.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django import forms from django.contrib.auth.forms import PasswordResetForm, ReadOnlyPasswordHashField, PasswordChangeForm, SetPasswordForm from django.conf import settings @@ -31,19 +28,19 @@ class MemberRegistrationForm(RegistrationForm): required=bool(settings.MEMBER_TERMS)) def __init__(self, *args, **kwargs): - super(MemberRegistrationForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for fieldname in ['email', 'organization_name', 'password2']: self.fields[fieldname].help_text = None def is_valid(self): - valid = super(MemberRegistrationForm,self).is_valid() - avoid_trap = not self.data['trap'] - if valid and avoid_trap: - return True - else: - return False + valid = super().is_valid() + avoid_trap = not self.data['trap'] + if valid and avoid_trap: + return True + else: + return False @property def helper(self): @@ -91,7 +88,7 @@ class MemberCreationForm(forms.ModelForm): """ Save member, then set his password """ - member = super(MemberCreationForm, self).save(commit=False) + member = super().save(commit=False) member.set_password(self.cleaned_data["password"]) if commit: member.member() @@ -109,7 +106,7 @@ class AbstractMemberChangeForm(forms.ModelForm): fields = '__all__' def __init__(self, *args, **kwargs): - super(AbstractMemberChangeForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) f = self.fields.get('user_permissions', None) if f is not None: f.queryset = f.queryset.select_related('content_type') @@ -123,7 +120,7 @@ class AbstractMemberChangeForm(forms.ModelForm): def save(self, *args, **kwargs): if settings.MEMBER_CAN_EDIT_PROFILE: - return super(AbstractMemberChangeForm, self).save(*args, **kwargs) + return super().save(*args, **kwargs) else: # skip form saving. In recent Django versions, this save() # override should no longer be required. @@ -186,7 +183,7 @@ class PersonMemberChangeForm(AbstractMemberChangeForm): 'address', 'postal_code', 'city', 'country'] def __init__(self, *args, **kwargs): - super(PersonMemberChangeForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.error_class = SpanError for fieldname in self.fields: self.fields[fieldname].help_text = None @@ -203,7 +200,7 @@ class OrganizationMemberChangeForm(AbstractMemberChangeForm): 'address', 'postal_code', 'city', 'country'] def __init__(self, *args, **kwargs): - super(OrganizationMemberChangeForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.error_class = SpanError for fieldname in self.fields: self.fields[fieldname].help_text = None diff --git a/coin/members/management/commands/call_for_membership_fees.py b/coin/members/management/commands/call_for_membership_fees.py index 71608275a82aab59bbf0ae54bf621c8236688829..531a2d8c5d04740516b5bb2c33e72855fe391107 100644 --- a/coin/members/management/commands/call_for_membership_fees.py +++ b/coin/members/management/commands/call_for_membership_fees.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from dateutil.relativedelta import relativedelta from django.core.management.base import BaseCommand, CommandError @@ -46,7 +43,7 @@ class Command(BaseCommand): if verbosity >= 2: self.stdout.write( - "Got {number} members.".format(number=members.count())) + f"Got {members.count()} members.") cpt = 0 with respect_language(settings.LANGUAGE_CODE): diff --git a/coin/members/management/commands/create_membership_fees.py b/coin/members/management/commands/create_membership_fees.py index bafe2a2d1a5929325b9136d620a1e6e4fdf2a666..79f22240f5a9854538addd3893635a72a3e44f4d 100644 --- a/coin/members/management/commands/create_membership_fees.py +++ b/coin/members/management/commands/create_membership_fees.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from dateutil.relativedelta import relativedelta from django.core.management.base import BaseCommand, CommandError @@ -31,7 +28,7 @@ class Command(BaseCommand): start_date = membership_fee[0].end_date while start_date <= datetime.date.today(): - self.stdout.write("Create MembershipFee for {username} {date}".format(username=member.username,date=start_date)) + self.stdout.write(f"Create MembershipFee for {member.username} {start_date}") end_date = start_date + relativedelta(years=1) fee = MembershipFee(member=member, _amount=settings.DEFAULT_MEMBERSHIP_FEE, start_date=start_date, @@ -60,7 +57,7 @@ class Command(BaseCommand): if start_date: - self.stdout.write("Create MembershipFee for {username} {date}".format(username=member.username,date=start_date)) + self.stdout.write(f"Create MembershipFee for {member.username} {start_date}") fee = MembershipFee(member=member, _amount=settings.DEFAULT_MEMBERSHIP_FEE, start_date=start_date, end_date=start_date + relativedelta(years=1)) diff --git a/coin/members/management/commands/members_email.py b/coin/members/management/commands/members_email.py index fb65b5e68640faa8a879a4fd1c2fd5571b296ef1..281da9fc6713b109c222bc79d7f135f43b2dabab 100644 --- a/coin/members/management/commands/members_email.py +++ b/coin/members/management/commands/members_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from django.core.management.base import BaseCommand, CommandError @@ -57,6 +54,6 @@ class Command(BaseCommand): else: members = Member.objects.filter(status='member') - emails = list(set([m.email for m in members])) + emails = list({m.email for m in members}) for email in emails: self.stdout.write(email) diff --git a/coin/members/migrations/0001_initial.py b/coin/members/migrations/0001_initial.py index 7298553e76cfa03b921818091fd347a720a20843..481347742525b8fe4a0397253922776313d3240e 100644 --- a/coin/members/migrations/0001_initial.py +++ b/coin/members/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import datetime import coin.utils @@ -56,7 +53,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('type', models.CharField(max_length=3, verbose_name='type', choices=[('RSA', 'RSA'), ('GPG', 'GPG')])), ('key', models.TextField(verbose_name='cl\xe9')), - ('member', models.ForeignKey(verbose_name='membre', to=settings.AUTH_USER_MODEL)), + ('member', models.ForeignKey(verbose_name='membre', + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ 'verbose_name': 'cl\xe9', @@ -70,7 +68,9 @@ class Migration(migrations.Migration): ('amount', models.IntegerField(default='20', help_text='en \u20ac', verbose_name='montant')), ('start_date', models.DateField(default=datetime.date.today, verbose_name='date de d\xe9but de cotisation')), ('end_date', models.DateField(default=coin.utils.in_one_year, verbose_name='date de fin de cotisation')), - ('member', models.ForeignKey(related_name='membership_fees', verbose_name='membre', to=settings.AUTH_USER_MODEL)), + ('member', models.ForeignKey(related_name='membership_fees', + verbose_name='membre', to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'cotisation', diff --git a/coin/members/migrations/0002_auto_20141007_0121.py b/coin/members/migrations/0002_auto_20141007_0121.py index fe0e3a50d80bba1588039724e1726e36f0b78f70..b6dea1bd5508a99c506174d0bcb1d67aef69d8d9 100644 --- a/coin/members/migrations/0002_auto_20141007_0121.py +++ b/coin/members/migrations/0002_auto_20141007_0121.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0003_auto_20141007_0956.py b/coin/members/migrations/0003_auto_20141007_0956.py index 38fc1913d474eae96e8b4c10288f0433165d26df..d85f95fa931b404467b4535fc044716e053fafc8 100644 --- a/coin/members/migrations/0003_auto_20141007_0956.py +++ b/coin/members/migrations/0003_auto_20141007_0956.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations from django.conf import settings diff --git a/coin/members/migrations/0004_auto_20141007_1002.py b/coin/members/migrations/0004_auto_20141007_1002.py index aa24468ccaf4f12c28436d05a92589d8e4a0df9d..4f0ff3b870ba4439cc109f802108cd1e0a101400 100644 --- a/coin/members/migrations/0004_auto_20141007_1002.py +++ b/coin/members/migrations/0004_auto_20141007_1002.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations from django.conf import settings diff --git a/coin/members/migrations/0005_auto_20141008_1038.py b/coin/members/migrations/0005_auto_20141008_1038.py index 66c79d4843f8738499fb2c19cf2204ad0d15146f..68b2e039e25f99d1eaebbf0aff7aad43d120c1e7 100644 --- a/coin/members/migrations/0005_auto_20141008_1038.py +++ b/coin/members/migrations/0005_auto_20141008_1038.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0006_auto_20141008_1056.py b/coin/members/migrations/0006_auto_20141008_1056.py index 2e7b972c1e10a0f38f59ef17e8d2718bda95c311..6d1480fb13acdc508095b7ad7464872f2d12eba2 100644 --- a/coin/members/migrations/0006_auto_20141008_1056.py +++ b/coin/members/migrations/0006_auto_20141008_1056.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0007_auto_20141008_1107.py b/coin/members/migrations/0007_auto_20141008_1107.py index e9448cd1b14348fae81222620c441a39642bbe19..8444f209be00a426596d5c7f8af777ccb2a8d845 100644 --- a/coin/members/migrations/0007_auto_20141008_1107.py +++ b/coin/members/migrations/0007_auto_20141008_1107.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0008_member_nickname.py b/coin/members/migrations/0008_member_nickname.py index 47df9ba0c8b9f139c658daacd6b02a5380c56cd1..f08e1020eb4d46946887d8b58ad999fda26015bc 100644 --- a/coin/members/migrations/0008_member_nickname.py +++ b/coin/members/migrations/0008_member_nickname.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0009_auto_20141008_2244.py b/coin/members/migrations/0009_auto_20141008_2244.py index 2bc5cbd15309ea15eb16f77a4f25b2ed6946e185..ea4e0947cb979a72c027b0974885232d760bd7f1 100644 --- a/coin/members/migrations/0009_auto_20141008_2244.py +++ b/coin/members/migrations/0009_auto_20141008_2244.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0010_auto_20141008_2246.py b/coin/members/migrations/0010_auto_20141008_2246.py index 39842012dd88566fba373305792104e8cad0ab70..8fa30f4702456824b6907dad3239a8d5a2193c97 100644 --- a/coin/members/migrations/0010_auto_20141008_2246.py +++ b/coin/members/migrations/0010_auto_20141008_2246.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0011_member_comments.py b/coin/members/migrations/0011_member_comments.py index 45e87d61d4e5893efeb1cafa63bf1288630090c5..b5c83f536b4a8bae02b95d30aa8e2c15aea74019 100644 --- a/coin/members/migrations/0011_member_comments.py +++ b/coin/members/migrations/0011_member_comments.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0012_member_date_last_call_for_membership_fees_email.py b/coin/members/migrations/0012_member_date_last_call_for_membership_fees_email.py index ea88735ae5f1ea85ce0188147f6bdbceb45c5eba..7fb34ff78f7e57f62ff47e1dab058a63ca97df92 100644 --- a/coin/members/migrations/0012_member_date_last_call_for_membership_fees_email.py +++ b/coin/members/migrations/0012_member_date_last_call_for_membership_fees_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/members/migrations/0013_auto_20161015_1837.py b/coin/members/migrations/0013_auto_20161015_1837.py index b9e533efe221b39639516d24b1a10984fc20f0f9..a27adcb21ab41524fed08924025e9726ce969e8d 100644 --- a/coin/members/migrations/0013_auto_20161015_1837.py +++ b/coin/members/migrations/0013_auto_20161015_1837.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django.core.validators import django.contrib.auth.models diff --git a/coin/members/migrations/0014_member_balance.py b/coin/members/migrations/0014_member_balance.py index c4d5a13b5c607b58bab2411587b9fe0a559b5e3e..a7c50b2b5c12b091484e7fd22016c4303f15f52c 100644 --- a/coin/members/migrations/0014_member_balance.py +++ b/coin/members/migrations/0014_member_balance.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0014_member_send_membership_fees_email.py b/coin/members/migrations/0014_member_send_membership_fees_email.py index 5ef12486321bf93c6582d8ac7034d37db76f6eb4..f22e525d8d427d47f7320f48d2d8619fafdb9bf9 100644 --- a/coin/members/migrations/0014_member_send_membership_fees_email.py +++ b/coin/members/migrations/0014_member_send_membership_fees_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0015_auto_20170824_2308.py b/coin/members/migrations/0015_auto_20170824_2308.py index 7c448f2f85518eef32003675ddbc4fa587c3324c..0e86baeea1a6b67a981caae8f8cb961d9f2ae75b 100644 --- a/coin/members/migrations/0015_auto_20170824_2308.py +++ b/coin/members/migrations/0015_auto_20170824_2308.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0016_merge.py b/coin/members/migrations/0016_merge.py index c2c7464449bfac6549a66dcafbfef3d9ac537400..8bfbbefb7f8dccc6b056b68ec2bb2a15e9f96082 100644 --- a/coin/members/migrations/0016_merge.py +++ b/coin/members/migrations/0016_merge.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0016_rowlevelpermission.py b/coin/members/migrations/0016_rowlevelpermission.py index fa1c4b08c4e005dc44a1a105a30c147a5600e30c..fee9d17ac3ad5cf292fc49b015c709bb844b29e3 100644 --- a/coin/members/migrations/0016_rowlevelpermission.py +++ b/coin/members/migrations/0016_rowlevelpermission.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models @@ -16,9 +13,13 @@ class Migration(migrations.Migration): migrations.CreateModel( name='RowLevelPermission', fields=[ - ('permission_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='auth.Permission')), + ('permission_ptr', models.OneToOneField(parent_link=True, + auto_created=True, primary_key=True, serialize=False, + to='auth.Permission', on_delete=models.CASCADE)), ('description', models.TextField(blank=True)), - ('offer', models.ForeignKey(verbose_name='Offre', to='offers.Offer', help_text="Offre dont l'utilisateur est autoris\xe9 \xe0 voir et modifier les membres et les abonnements.", null=True)), + ('offer', models.ForeignKey(verbose_name='Offre', to='offers.Offer', + help_text="Offre dont l'utilisateur est autoris\xe9 \xe0 voir et modifier les membres et les abonnements.", null=True, + on_delete=models.CASCADE)), ], bases=('auth.permission',), ), diff --git a/coin/members/migrations/0017_merge.py b/coin/members/migrations/0017_merge.py index effbe0000c12a1e88b47355ea99daf73d59329ce..4d09a18dd88afc27e0500bb67f63b330d706b8e7 100644 --- a/coin/members/migrations/0017_merge.py +++ b/coin/members/migrations/0017_merge.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0018_auto_20180414_2250.py b/coin/members/migrations/0018_auto_20180414_2250.py index ba9817108f8abe4b3ee88a6063dcf2827a2d1889..76c809838a2a8994c1d25639076279e263832afc 100644 --- a/coin/members/migrations/0018_auto_20180414_2250.py +++ b/coin/members/migrations/0018_auto_20180414_2250.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import coin.members.models diff --git a/coin/members/migrations/0018_auto_20180819_0211.py b/coin/members/migrations/0018_auto_20180819_0211.py index 18f98d351dbf3e377ae7bdf241ef2338379378c9..44558a5e1ffd9813653e548f0eef9bcc0f3e8236 100644 --- a/coin/members/migrations/0018_auto_20180819_0211.py +++ b/coin/members/migrations/0018_auto_20180819_0211.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0019_auto_20180415_1814.py b/coin/members/migrations/0019_auto_20180415_1814.py index ce80c07a2535cddcfd2dd1542ff5c6b28b349c40..fb2004c8a51f6b31e0bbfbe5644d3da2871fae43 100644 --- a/coin/members/migrations/0019_auto_20180415_1814.py +++ b/coin/members/migrations/0019_auto_20180415_1814.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0019_auto_20190825_2329.py b/coin/members/migrations/0019_auto_20190825_2329.py index 70bf3265eece262014114cd159d8e9d6723ed212..f17eb7ffa1f4f37ec4dfd82bf168971957c5a771 100644 --- a/coin/members/migrations/0019_auto_20190825_2329.py +++ b/coin/members/migrations/0019_auto_20190825_2329.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0020_merge.py b/coin/members/migrations/0020_merge.py index cbbef91ad295851fc361006dffdeee768257f771..edf4fe1d68b8e08881e625dc3c584fa34598e346 100644 --- a/coin/members/migrations/0020_merge.py +++ b/coin/members/migrations/0020_merge.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0021_auto_20181118_2001.py b/coin/members/migrations/0021_auto_20181118_2001.py index d0be2f79ae600a34a57cc07c9f2df83831708a42..22c55eae2889f81469134b530ad8b9d82bb8036c 100644 --- a/coin/members/migrations/0021_auto_20181118_2001.py +++ b/coin/members/migrations/0021_auto_20181118_2001.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models from django.conf import settings @@ -36,6 +33,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='membershipfee', name='member', - field=models.ForeignKey(related_name='membership_fees', verbose_name='membre', to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(related_name='membership_fees', + verbose_name='membre', to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE), ), ] diff --git a/coin/members/migrations/0022_auto_20190623_1256.py b/coin/members/migrations/0022_auto_20190623_1256.py index 9e96d531ecedd0bf6e33fe288da9c99335d82dee..7650c055e96d4186870fc89f55da6fd812ba51a5 100644 --- a/coin/members/migrations/0022_auto_20190623_1256.py +++ b/coin/members/migrations/0022_auto_20190623_1256.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0023_merge.py b/coin/members/migrations/0023_merge.py index 55856aa138e0794b4d3370240f8549addd785c41..15e3fda27fc0ad418b1c0f5af65f288e257a2e41 100644 --- a/coin/members/migrations/0023_merge.py +++ b/coin/members/migrations/0023_merge.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/members/migrations/0024_auto_20201203_1852.py b/coin/members/migrations/0024_auto_20201203_1852.py index ea41f7b968006f4a4429e75b1681d298d1017ee6..7b7b058c625cf2c7232455070a80e89af8538958 100644 --- a/coin/members/migrations/0024_auto_20201203_1852.py +++ b/coin/members/migrations/0024_auto_20201203_1852.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import coin.members.models diff --git a/coin/members/migrations/0025_auto_20220219_1749.py b/coin/members/migrations/0025_auto_20220219_1749.py index f667faa6b36989d5fcc7a98a915351a5519d01ec..3188465f7ab81b3356701f717d3a9b42fa0dd0e1 100644 --- a/coin/members/migrations/0025_auto_20220219_1749.py +++ b/coin/members/migrations/0025_auto_20220219_1749.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import django.contrib.auth.models diff --git a/coin/members/migrations/0026_auto_20220625_2259.py b/coin/members/migrations/0026_auto_20220625_2259.py new file mode 100644 index 0000000000000000000000000000000000000000..65bb7414709e888e7a43bf22af6876066dee1b3f --- /dev/null +++ b/coin/members/migrations/0026_auto_20220625_2259.py @@ -0,0 +1,19 @@ +# Generated by Django 1.9.13 on 2022-06-25 20:59 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0025_auto_20220219_1749'), + ] + + operations = [ + migrations.AlterField( + model_name='member', + name='username', + field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'), + ), + ] diff --git a/coin/members/migrations/0027_auto_20220626_1138.py b/coin/members/migrations/0027_auto_20220626_1138.py new file mode 100644 index 0000000000000000000000000000000000000000..5c9011345dbdd11f99f7ce9c1cb792209c3c0756 --- /dev/null +++ b/coin/members/migrations/0027_auto_20220626_1138.py @@ -0,0 +1,31 @@ +# Generated by Django 2.0.13 on 2022-06-26 09:38 + +import django.contrib.auth.models +import django.contrib.auth.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0026_auto_20220625_2259'), + ] + + operations = [ + migrations.AlterModelManagers( + name='rowlevelpermission', + managers=[ + ('objects', django.contrib.auth.models.PermissionManager()), + ], + ), + migrations.AlterField( + model_name='member', + name='last_name', + field=models.CharField(blank=True, max_length=150, verbose_name='last name'), + ), + migrations.AlterField( + model_name='member', + name='username', + field=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'), + ), + ] diff --git a/coin/members/models.py b/coin/members/models.py index 3edfabb43d72abd7e882b7140c26542b3f3aefab..a32f2ea095229f9c2769c2c0a6704543bafe60eb 100644 --- a/coin/members/models.py +++ b/coin/members/models.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import ldapdb.models import unicodedata import datetime @@ -16,7 +13,7 @@ from django.core.exceptions import ValidationError from django.utils import timezone from django.utils.text import slugify from django.core.mail import send_mail -from django.core.urlresolvers import reverse +from django.urls import reverse from ldapdb.models.fields import CharField, IntegerField, ListField from registration.signals import user_registered @@ -32,10 +29,10 @@ class MemberManager(UserManager): dans l'interface d'administration. """ if user.is_superuser: - return super(MemberManager, self).all() + return super().all() else: offers = Offer.objects.manageable_by(user) - return super(MemberManager, self).filter(offersubscription__offer__in=offers).distinct() + return super().filter(offersubscription__offer__in=offers).distinct() @receiver(user_registered) def send_registration_notification(sender, user, request=None, **kwargs): @@ -183,7 +180,7 @@ class Member(CoinLdapSyncMixin, AbstractUser): """ Définit le mot de passe a sauvegarder en base et dans le LDAP """ - super(Member, self).set_password(new_password, *args, **kwargs) + super().set_password(new_password, *args, **kwargs) self._password_ldap = utils.ldap_hash(new_password) def get_active_subscriptions(self, date=None): @@ -433,7 +430,7 @@ def get_automatic_username(member): [c[0] for c in member.first_name.split('-')] ) # Concaténer avec nom de famille - username = ('%s%s' % (first_name_letters, member.last_name)) + username = (f'{first_name_letters}{member.last_name}') else: raise Exception('Il n\'y a pas sufissement d\'informations pour déterminer un login automatiquement') @@ -441,13 +438,16 @@ def get_automatic_username(member): username = unicodedata.normalize('NFD', username)\ .encode('ascii', 'ignore') # Enlever ponctuation (sauf _-.) et espace - punctuation = ('!"#$%&\'()*+,/:;<=>?@[\\]^`{|}~ ').encode('ascii') + punctuation = (b'!"#$%&\'()*+,/:;<=>?@[\\]^`{|}~ ') username = username.translate(None, punctuation) # En minuscule username = username.lower() # Maximum de 30 char username = username[:30] + if isinstance(username, bytes): + username = username.decode() + # Recherche dans les membres existants un username identique member = Member.objects.filter(username=username) base_username = username @@ -462,6 +462,9 @@ def get_automatic_username(member): member = Member.objects.filter(username=username) incr += 1 + if isinstance(username, bytes): + username = username.decode() + return username @@ -472,7 +475,7 @@ class CryptoKey(CoinLdapSyncMixin, models.Model): type = models.CharField(max_length=3, choices=KEY_TYPE_CHOICES, verbose_name='type') key = models.TextField(verbose_name='clé') - member = models.ForeignKey('Member', verbose_name='membre') + member = models.ForeignKey('Member', verbose_name='membre', on_delete=models.CASCADE) def sync_to_ldap(self, creation, *args, **kwargs): """Simply tell the member object to resync all its SSH keys to LDAP""" @@ -482,7 +485,7 @@ class CryptoKey(CoinLdapSyncMixin, models.Model): self.member.sync_ssh_keys() def __unicode__(self): - return 'Clé %s de %s' % (self.type, self.member) + return f'Clé {self.type} de {self.member}' class Meta: verbose_name = 'clé' @@ -557,7 +560,7 @@ def define_display_name(sender, instance, **kwargs): concaténation de first_name et last_name """ if not instance.display_name: - instance.display_name = '%s %s' % (instance.first_name, + instance.display_name = '{} {}'.format(instance.first_name, instance.last_name) @@ -565,7 +568,8 @@ def define_display_name(sender, instance, **kwargs): class RowLevelPermission(Permission): offer = models.ForeignKey( 'offers.Offer', null=True, verbose_name="Offre", - help_text="Offre dont l'utilisateur est autorisé à voir et modifier les membres et les abonnements.") + help_text="Offre dont l'utilisateur est autorisé à voir et modifier les membres et les abonnements.", + on_delete=models.CASCADE) description = models.TextField(blank=True) def save(self, *args, **kwargs): @@ -575,7 +579,7 @@ class RowLevelPermission(Permission): """ if not self.codename: self.codename = self.generate_codename() - return super(RowLevelPermission, self).save(*args, **kwargs) + return super().save(*args, **kwargs) def generate_codename(self): """ diff --git a/coin/members/registration_views.py b/coin/members/registration_views.py index bbdff33732543731e3699ad7e10f5e7f8ec4205c..c6d61887e2a255a7f0c8787f99940ecdf7b517d3 100644 --- a/coin/members/registration_views.py +++ b/coin/members/registration_views.py @@ -13,7 +13,7 @@ class MemberRegistrationView(RegistrationView): template_name = 'members/registration/registration_form.html' def register(self, form): - new_user = super(MemberRegistrationView, self).register(form) + new_user = super().register(form) new_user.status = new_user.MEMBER_STATUS_PENDING return new_user.save() diff --git a/coin/members/templates/members/registration/login.html b/coin/members/templates/members/registration/login.html index 0f87d6180db73175503930a75e8def11ec787b4f..aae93a040c64dd3875100c056cd215f015baa30a 100644 --- a/coin/members/templates/members/registration/login.html +++ b/coin/members/templates/members/registration/login.html @@ -9,7 +9,7 @@

Connexion
à l'espace adhérent

-
+ {% if form.errors %}
@@ -46,7 +46,7 @@ Mot de passe oublié ? {% if settings.REGISTRATION_OPEN %} - / + / Créer un compte {% endif %}

@@ -72,7 +72,7 @@
- + {% endblock %} diff --git a/coin/members/templates/members/registration/password_reset_form.html b/coin/members/templates/members/registration/password_reset_form.html index 31ee9fa9c236ae6f4c8af3ebd83bb75cd8c48a59..8d562121b2f713836961dca47b2f3acf0fb8742f 100644 --- a/coin/members/templates/members/registration/password_reset_form.html +++ b/coin/members/templates/members/registration/password_reset_form.html @@ -1,5 +1,5 @@ {% extends "members/registration/password_reset_base.html" %} -{% load staticfiles %} +{% load static %} {% load i18n %} {% load crispy_forms_tags %} @@ -8,9 +8,9 @@

Réinitialiser
son mot de passe

Nouvel adhérent ? Mot de passe perdu ?

- +

Saisissez l'adresse email de votre compte afin de reçevoir les instructions pour en définir un nouveau.

- + {% crispy form %}
diff --git a/coin/members/tests.py b/coin/members/tests.py index 52d71af739c4b3efa0e142b3871a1fa738f30f55..3b07bfbdf9018bf11fba64a31c79139a65668334 100644 --- a/coin/members/tests.py +++ b/coin/members/tests.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os +import binascii import ldapdb from datetime import date -from cStringIO import StringIO +from io import StringIO from dateutil.relativedelta import relativedelta from freezegun import freeze_time import unittest @@ -242,7 +240,7 @@ class MemberTests(TestCase): premières lettres du prénom + nom le tout en minuscule, sans caractères accentués et sans espaces. """ - random = os.urandom(4).encode('hex') + random = binascii.hexlify(os.urandom(4)).decode() first_name = 'Gérard-Étienne' last_name = 'Majax de la Boétie!B' + random @@ -261,7 +259,7 @@ class MemberTests(TestCase): Lors de la création d'un membre, test si le username existe déjà, renvoi avec un incrément à la fin """ - random = os.urandom(4).encode('hex') + random = binascii.hexlify(os.urandom(4)).decode() member1 = Member(first_name='Hervé', last_name='DUPOND' + random, email='hdupond@coin.org') member1.save() @@ -284,7 +282,7 @@ class MemberTests(TestCase): Lors de la créatio d'une entreprise, son nom doit être utilisée lors de la détermination automatique du username """ - random = os.urandom(4).encode('hex') + random = binascii.hexlify(os.urandom(4)).decode() member = Member(type='legal_entity', organization_name='ILLYSE' + random, email='illyse@coin.org') member.save() self.assertEqual(member.username, 'illyse' + random) @@ -294,7 +292,7 @@ class MemberTests(TestCase): """ Lors de la création d'une personne, qui a un pseudo, celui-ci est utilisé en priorité """ - random = os.urandom(4).encode('hex') + random = binascii.hexlify(os.urandom(4)).decode() member = Member(first_name='Richard', last_name='Stallman', nickname='rms' + random, email='illyse@coin.org') member.save() self.assertEqual(member.username, 'rms' + random) @@ -307,7 +305,7 @@ class MemberTests(TestCase): first_name='a', last_name='b', username='c', status=Member.MEMBER_STATUS_PENDING) - membershipfee = MembershipFee(member=member, amount=20, + membershipfee = MembershipFee(member=member, _amount=20, start_date=date(2015, 12, 12), end_date=date(2016, 12, 12)) membershipfee.save() @@ -351,6 +349,7 @@ class MemberAdminTests(TestCase): # Supprime le superuser self.admin_user.delete() + @unittest.skip("we need to update django-autocomplete-admin for this") def test_cant_change_username_when_editing(self): """ Vérifie que dans l'admin Django, le champ username n'est pad modifiable @@ -364,7 +363,7 @@ class MemberAdminTests(TestCase): last_name=last_name, username=username) member.save() - edit_page = self.client.get('/admin/members/member/%i/' % member.id) + edit_page = self.client.get('/admin/members/member/%i/change/' % member.id) self.assertNotContains(edit_page, '''''', html=True) @@ -378,7 +377,7 @@ class MemberTestCallForMembershipCommand(TestCase): # Créé un membre self.username = MemberTestsUtils.get_random_username() self.member = Member(first_name='Richard', last_name='Stallman', - username=self.username) + username=self.username, email=self.username + '@example.org') self.member.save() @@ -389,7 +388,7 @@ class MemberTestCallForMembershipCommand(TestCase): def create_membership_fee(self, end_date): # Créé une cotisation se terminant à la date indiquée - membershipfee = MembershipFee(member=self.member, amount=20, + membershipfee = MembershipFee(member=self.member, _amount=20, start_date=end_date + relativedelta(years=-1), end_date=end_date) membershipfee.save() @@ -399,7 +398,14 @@ class MemberTestCallForMembershipCommand(TestCase): # Vide la outbox mail.outbox = [] # Call command - management.call_command('call_for_membership_fees', stdout=StringIO()) + out = StringIO() + err = StringIO() + print('x' * 80) + management.call_command('call_for_membership_fees', stdout=out, stderr=err) + out.seek(0) + err.seek(0) + print('o' * 80, out.read()) + print('e' * 80, err.read()) # Test self.assertEqual(len(mail.outbox), expected_emails) # Comme on utilise le même membre, on reset la date de dernier envoi @@ -453,11 +459,11 @@ class MemberManagerTest(TestCase): self.gh = Member.objects.create( first_name='g', last_name='h', username='gh', email='gh@ex.com') - MembershipFee.objects.create(member=self.ab, amount=20, + MembershipFee.objects.create(member=self.ab, _amount=20, start_date=date(2015, 11, 11), end_date=date(2016, 11, 11)) - MembershipFee.objects.create(member=self.cd, amount=20, + MembershipFee.objects.create(member=self.cd, _amount=20, start_date=date(2016, 1, 1), end_date=date(2016, 1, 1)) @@ -473,7 +479,7 @@ class MemberManagerTest(TestCase): @freeze_time('2016-10-01') def test_could_be_deleted(self): - deletion_set = set([m for m in Member.objects.all() if m.could_be_deleted()]) + deletion_set = {m for m in Member.objects.all() if m.could_be_deleted()} # late on fee (-> delete) self.assertIn(self.cd, deletion_set) @@ -488,14 +494,14 @@ class MemberManagerTest(TestCase): self.assertNotIn(self.ef, deletion_set) -class MemberTestsUtils(object): +class MemberTestsUtils: @staticmethod def get_random_username(): """ Renvoi une clé aléatoire pour un utilisateur LDAP """ - return 'coin_test_' + os.urandom(8).encode('hex') + return 'coin_test_' + binascii.hexlify(os.urandom(8)).decode() class TestValidators(TestCase): @@ -503,3 +509,14 @@ class TestValidators(TestCase): chatroom_url_validator('irc://irc.example.com/#chan') with self.assertRaises(ValidationError): chatroom_url_validator('http://#faimaison@irc.geeknode.org') + + +class TestLogin(TestCase): + def test_login(self): + r = self.client.get("") + self.assertEqual(r.status_code, 302) + self.assertEqual(r.url, "/members/login?next=/") + r = self.client.get(r.url) + self.assertEqual(r.status_code, 301) + r = self.client.get(r.url) + self.assertEqual(r.status_code, 200) diff --git a/coin/members/urls.py b/coin/members/urls.py index edb9adcb51e8eb0d00f1ae6db47db5317933f220..b6c9eb55d157642f6e3ee1ccc118ac05458a8913 100644 --- a/coin/members/urls.py +++ b/coin/members/urls.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.conf.urls import url +from django.urls import path from coin.members import forms, views from django.views.generic.base import TemplateView from . import registration_views as views_r @@ -10,7 +7,7 @@ from coin import settings from registration.signals import user_activated from django.contrib.auth import login from coin.offers.views import offersubscriptionrequest_step1, offersubscriptionrequest_step2, offersubscriptionrequest_step3 -import django.contrib.auth.views +import django.contrib.auth.views as auth_views def login_on_activation(sender, user, request, **kwargs): """Logs in the user after activation""" @@ -20,80 +17,56 @@ def login_on_activation(sender, user, request, **kwargs): # Registers the function with the django-registration user_activated signal user_activated.connect(login_on_activation) +app_name = 'members' urlpatterns = [ - #'', - url(r'^$', views.index, name='index'), - url(r'^login/$', django.contrib.auth.views.login, - {'template_name': 'members/registration/login.html', - 'extra_context': {'settings': settings} }, - name='login'), - url(r'^logout/$', django.contrib.auth.views.logout_then_login, - name='logout'), + path('', views.index, name='index'), + path('login/', auth_views.LoginView.as_view(template_name='members/registration/login.html', extra_context={'settings': settings}), name='login'), + path('logout/', auth_views.logout_then_login, name='logout'), - url(r'^password_change/$', django.contrib.auth.views.password_change, - {'post_change_redirect': 'members:password_change_done', - 'template_name': 'members/registration/password_change_form.html'}, - name='password_change'), - url(r'^password_change_done/$', django.contrib.auth.views.password_change_done, - {'template_name': 'members/registration/password_change_done.html'}, - name='password_change_done'), + path('password_change/', auth_views.PasswordChangeView.as_view(success_url='members:password_change_done', template_name='members/registration/password_change_form.html'), name='password_change'), + path('password_change/done/', auth_views.PasswordChangeDoneView.as_view(template_name='members/registration/password_change_done.html'), name='password_change_done'), - url(r'^password_reset/$', django.contrib.auth.views.password_reset, - {'post_reset_redirect': 'members:password_reset_done', - 'template_name': 'members/registration/password_reset_form.html', - 'email_template_name': 'members/registration/password_reset_email.html', - 'subject_template_name': 'members/registration/password_reset_subject.txt'}, - name='password_reset'), - url(r'^password_reset/done/$', django.contrib.auth.views.password_reset_done, - {'template_name': 'members/registration/password_reset_done.html', - 'current_app': 'members'}, - name='password_reset_done'), - url(r'^password_reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', django.contrib.auth.views.password_reset_confirm, - {'post_reset_redirect': 'members:password_reset_complete', - 'template_name': 'members/registration/password_reset_confirm.html'}, - name='password_reset_confirm'), - url(r'^password_reset/complete/$', django.contrib.auth.views.password_reset_complete, - {'template_name': 'members/registration/password_reset_complete.html'}, - name='password_reset_complete'), + path('password_reset/', auth_views.PasswordResetView.as_view(success_url='members:password_reset_done', template_name='members/registration/password_reset_form.html', email_template_name='members/registration/password_reset_email.html', subject_template_name='members/registration/password_reset_subject.txt'), name='password_reset'), + path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(template_name='members/registration/password_reset_done.html'), name='password_reset_done'), + path('reset///', + auth_views.PasswordResetConfirmView.as_view(success_url='members:password_reset_complete', template_name='members/registration/password_reset_confirm.html'), name='password_reset_confirm'), + path('reset/done/', auth_views.PasswordResetCompleteView.as_view(template_name='members/registration/password_reset_complete.html'), name='password_reset_complete'), - url(r'^activate/complete/$', views.activation_completed, - name='registration_activation_complete'), + path('activate/complete/', views.activation_completed, name='registration_activation_complete'), # The activation key can make use of any character from the # URL-safe base64 alphabet, plus the colon as a separator. - url(r'^activate/(?P[-:\w]+)/$', - views_r.MemberActivationView.as_view(), - name='registration_activate'), - url(r'^register/$', + path('activate//', views_r.MemberActivationView.as_view(), name='registration_activate'), + path('register/', views_r.MemberRegistrationView.as_view( form_class=forms.MemberRegistrationForm, template_name='members/registration/registration_form.html' ), name='registration_register'), - url(r'^register/complete/$', + path('register/complete/', TemplateView.as_view( template_name='members/registration/registration_complete.html' ), name='registration_complete'), - url(r'^register/closed/$', + path('register/closed/', TemplateView.as_view( template_name='members/registration/registration_closed.html' ), name='registration_disallowed'), - #url(r'', include('registration.auth_urls')), + #path(r'', include('registration.auth_urls')), - url(r'^detail/$', views.detail, + path('detail/', views.detail, name='detail'), - url(r'^subscriptions/', views.subscriptions, name='subscriptions'), - # url(r'^subscription/(?P\d+)', views.subscriptions, name = 'subscription'), - url(r'^request_subscriptions/step1$', offersubscriptionrequest_step1, name="subscriptionrequest_step1"), - url(r'^request_subscriptions/step2/(?P.+)$', offersubscriptionrequest_step2, name="subscriptionrequest_step2"), - url(r'^request_subscriptions/step3', offersubscriptionrequest_step3, name="subscriptionrequest_step3"), + path('subscriptions/', views.subscriptions, name='subscriptions'), + # path('subscription/(?P\d+)', views.subscriptions, name = 'subscription'), + path('request_subscriptions/step1', offersubscriptionrequest_step1, name="subscriptionrequest_step1"), + path('request_subscriptions/step2/', offersubscriptionrequest_step2, name="subscriptionrequest_step2"), + path('request_subscriptions/step3', offersubscriptionrequest_step3, name="subscriptionrequest_step3"), - url(r'^invoices/', views.invoices, name='invoices'), - url(r'^contact/', views.contact, name='contact'), + path('invoices/', views.invoices, name='invoices'), + path('contact/', views.contact, name='contact'), ] diff --git a/coin/members/views.py b/coin/members/views.py index a8590daa53876ab1baf50479f72919df466cb070..4cef8de5b05c0ac16214236905b7696c685f07b3 100644 --- a/coin/members/views.py +++ b/coin/members/views.py @@ -1,11 +1,8 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.template import RequestContext from django.shortcuts import redirect, render from django.contrib.auth.decorators import login_required from django.conf import settings -from forms import PersonMemberChangeForm, OrganizationMemberChangeForm +from .forms import PersonMemberChangeForm, OrganizationMemberChangeForm from coin.billing.models import Bill from coin.offers.models import Offer, OfferSubscriptionRequest diff --git a/coin/mixins.py b/coin/mixins.py index d960a72f474a4c92628c8975e97634b37ebbd9d7..f8b0d0bc2ed0ed22d05f3e4e767d03f86126c98c 100644 --- a/coin/mixins.py +++ b/coin/mixins.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import transaction from django.conf import settings from django.views.generic import TemplateView -class CoinLdapSyncMixin(object): +class CoinLdapSyncMixin: """ Ce mixin est à utiliser lorsqu'il s'agit de définir un modèle @@ -36,7 +33,7 @@ class CoinLdapSyncMixin(object): 'update_fields'] if 'update_fields' in kwargs else None # Sauvegarde en base de donnée (mais sans commit, cf decorator) - super(CoinLdapSyncMixin, self).save(*args, **kwargs) + super().save(*args, **kwargs) # Sauvegarde dans le LDAP # Si la sauvegarde LDAP échoue, Rollback la sauvegarde en base, sinon @@ -51,7 +48,7 @@ class CoinLdapSyncMixin(object): @transaction.atomic def delete(self, *args, **kwargs): # Supprime de la base de donnée (mais sans commit, cf decorator) - super(CoinLdapSyncMixin, self).delete(*args, **kwargs) + super().delete(*args, **kwargs) if settings.LDAP_ACTIVATE: try: diff --git a/coin/offers/admin.py b/coin/offers/admin.py index 9ddcfe8de2682fb2fc65b4d733138242552bf593..da935d2208b88439b1ff59917bd19ef79018d327 100644 --- a/coin/offers/admin.py +++ b/coin/offers/admin.py @@ -1,14 +1,11 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -import autocomplete_light +from autocomplete_light import shortcuts as al from django.contrib import admin, messages from django.db.models import Q from django.conf.urls import url from django.shortcuts import get_object_or_404 from django.http import HttpResponseRedirect -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.safestring import mark_safe from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin @@ -62,8 +59,8 @@ class OfferSubscriptionAdmin(admin.ModelAdmin): # Si c'est un super user on renvoie un formulaire avec tous les membres et toutes les offres (donc autocomplétion pour les membres) def get_form(self, request, obj=None, **kwargs): if request.user.is_superuser: - kwargs['form'] = autocomplete_light.modelform_factory(OfferSubscription, fields='__all__') - return super(OfferSubscriptionAdmin, self).get_form(request, obj, **kwargs) + kwargs['form'] = al.modelform_factory(OfferSubscription, fields='__all__') + return super().get_form(request, obj, **kwargs) # Si pas super user on restreint les membres et offres accessibles def formfield_for_foreignkey(self, db_field, request, **kwargs): @@ -72,11 +69,11 @@ class OfferSubscriptionAdmin(admin.ModelAdmin): kwargs["queryset"] = Member.objects.manageable_by(request.user) if db_field.name == "offer": kwargs["queryset"] = Offer.objects.filter(id__in=[p.id for p in Offer.objects.manageable_by(request.user)]) - return super(OfferSubscriptionAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) # Si pas super user on restreint la liste des offres que l'on peut voir def get_queryset(self, request): - qs = super(OfferSubscriptionAdmin, self).get_queryset(request) + qs = super().get_queryset(request) if request.user.is_superuser: return qs else: @@ -95,7 +92,7 @@ class OfferSubscriptionAdmin(admin.ModelAdmin): return [] def save_model(self, request, obj, form, change): - super(OfferSubscriptionAdmin, self).save_model(request, obj, form, change) + super().save_model(request, obj, form, change) # On auto-complète le modèle que si on n'est pas en train de créer un # abonnement via une configuration @@ -123,7 +120,7 @@ class ParentOfferSubscriptionRequestAdmin(PolymorphicParentModelAdmin): ex :((VPNSubscriptionRequest, VPNSubscriptionRequestAdmin), (AccountSubscriptionRequest, AccountSubscriptionRequestAdmin)) """ - return tuple((x.base_model, x) for x in ChildOfferSubscriptionRequestAdmin.__subclasses__()) + return tuple(x.base_model for x in ChildOfferSubscriptionRequestAdmin.__subclasses__()) def enhanced_state(self, obj): @@ -140,7 +137,7 @@ class ParentOfferSubscriptionRequestAdmin(PolymorphicParentModelAdmin): # Should not happen icon = "" - return mark_safe("{icon} {state}".format(color=color, icon=icon, state=obj.get_state_display())) + return mark_safe(f"{icon} {obj.get_state_display()}") enhanced_state.short_description = "Status" @@ -148,7 +145,7 @@ class ParentOfferSubscriptionRequestAdmin(PolymorphicParentModelAdmin): """ Custom admin urls """ - urls = super(ParentOfferSubscriptionRequestAdmin, self).get_urls() + urls = super().get_urls() my_urls = [ url(r'^process/(?P.+)/(?P[a-z_]+)$', self.admin_site.admin_view(self.view_process), @@ -166,7 +163,7 @@ class ParentOfferSubscriptionRequestAdmin(PolymorphicParentModelAdmin): else: offersubscriptionrequest = get_object_or_404(OfferSubscriptionRequest, pk=id) - if offersubscriptionrequest.state != u"pending": + if offersubscriptionrequest.state != "pending": messages.error(request, "Cette demande a déjà été acceptée ou refusée.") else: try: @@ -175,7 +172,7 @@ class ParentOfferSubscriptionRequestAdmin(PolymorphicParentModelAdmin): elif whatdo == "refuse": offersubscriptionrequest.refuse() except Exception as e: - messages.error(request, "Erreur en tentant de processer l'action %s pour %s : %s" % (whatdo, offersubscriptionrequest, str(e))) + messages.error(request, f"Erreur en tentant de processer l'action {whatdo} pour {offersubscriptionrequest} : {str(e)}") return HttpResponseRedirect(reverse('admin:offers_offersubscriptionrequest_change', args=(id,))) @@ -213,7 +210,7 @@ class ChildOfferSubscriptionRequestAdmin(PolymorphicChildModelAdmin): def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "offer": kwargs["queryset"] = OfferSubscriptionRequest.requestable_offers(offer_type=self.base_model.offer_type) - return super(ChildOfferSubscriptionRequestAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) admin.site.register(Offer, OfferAdmin) diff --git a/coin/offers/app.py b/coin/offers/app.py index abb4fe3a0e2e241a502da4d698bead123c74d147..a51bd8124d392e5d7530a3a4efc0fab6b5c70642 100644 --- a/coin/offers/app.py +++ b/coin/offers/app.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals from django.apps import AppConfig diff --git a/coin/offers/forms.py b/coin/offers/forms.py index 4f41d0e81ee6e4c8da56b11d6b565418eb17af18..0ecf4e84f6713b357f3b1a9d1b76afa2f697e202 100644 --- a/coin/offers/forms.py +++ b/coin/offers/forms.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- from django import forms from django.forms.widgets import Select -from django.core.urlresolvers import reverse +from django.urls import reverse from django.conf import settings from coin.offers.models import Offer, OfferSubscriptionRequest @@ -18,7 +17,7 @@ class ConfigurationTypeSelect(forms.Select): # We do this so that get_configurations_choices_list is called during # runtime once we're sure all Configuration subclasses are loaded... self.choices = [('', '---------'),] + list(Configuration.get_configurations_choices_list()) - return super(ConfigurationTypeSelect, self).render(*args, **kwargs) + return super().render(*args, **kwargs) class OfferAdminForm(forms.ModelForm): class Meta: @@ -33,7 +32,7 @@ class OfferSubscriptionRequestStep1Form(forms.Form): offer_type = forms.ChoiceField(label="Type d'abonnement", widget=forms.RadioSelect, required=True) def __init__(self, *args, **kwargs): - super(OfferSubscriptionRequestStep1Form, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.fields['offer_type'].choices = tuple((type, type) for type in OfferSubscriptionRequest.requestable_offertypes()) @property @@ -58,7 +57,7 @@ class OfferSubscriptionRequestStep2Form(forms.Form): agree_tos = forms.BooleanField(label="Vous acceptez les conditions d'abonnement et d'utilisation de cette offre", required=True) def __init__(self, *args, **kwargs): - super(OfferSubscriptionRequestStep2Form, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # Put the comment + agree tos checkbox at the end of the form member_comments = self.fields.pop("member_comments") diff --git a/coin/offers/management/commands/offer_subscriptions_count.py b/coin/offers/management/commands/offer_subscriptions_count.py index 41f5fa2cad809c9272b39d1980a7841c3d2f141e..caf7932ba771a5476df509681bc2e8e427b71998 100644 --- a/coin/offers/management/commands/offer_subscriptions_count.py +++ b/coin/offers/management/commands/offer_subscriptions_count.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from optparse import make_option import datetime @@ -28,7 +25,7 @@ class Command(BaseCommand): if type(date) is not datetime.date: try: datetime.datetime.strptime(date, '%Y-%m-%d') - except ValueError, TypeError: + except (ValueError, TypeError): raise CommandError("Incorrect date format, should be YYYY-MM-DD") # Count offer subscription @@ -41,4 +38,4 @@ class Command(BaseCommand): for offer in offers: self.stdout.write("{offer} offer has {count} subscriber(s)".format( offer=BOLD_START + offer.name + BOLD_END, - count=BOLD_START + str(offer.num_subscribtions) + BOLD_END)) \ No newline at end of file + count=BOLD_START + str(offer.num_subscribtions) + BOLD_END)) diff --git a/coin/offers/management/commands/subscribers_email.py b/coin/offers/management/commands/subscribers_email.py index 2ff5833443f3f0d603ff00f72591a555d771ae48..d3041f69575b56e609b03f61c65193449638b0ad 100644 --- a/coin/offers/management/commands/subscribers_email.py +++ b/coin/offers/management/commands/subscribers_email.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime from django.core.management.base import BaseCommand, CommandError diff --git a/coin/offers/migrations/0001_initial.py b/coin/offers/migrations/0001_initial.py index 90b4097799e387fbddf17585e4913687df0abfa6..4ab00aead8330ae8983497a60d83cbe17bd9cbd1 100644 --- a/coin/offers/migrations/0001_initial.py +++ b/coin/offers/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import datetime from django.conf import settings @@ -35,8 +32,10 @@ class Migration(migrations.Migration): ('subscription_date', models.DateField(default=datetime.date.today, verbose_name="date de souscription \xe0 l'offre")), ('resign_date', models.DateField(null=True, verbose_name='date de r\xe9siliation', blank=True)), ('commitment', models.IntegerField(default=0, help_text='en mois', verbose_name="p\xe9riode d'engagement")), - ('member', models.ForeignKey(verbose_name='membre', to=settings.AUTH_USER_MODEL)), - ('offer', models.ForeignKey(verbose_name='offre', to='offers.Offer')), + ('member', models.ForeignKey(verbose_name='membre', + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ('offer', models.ForeignKey(verbose_name='offre', to='offers.Offer', + on_delete=models.CASCADE)), ], options={ 'verbose_name': 'abonnement', diff --git a/coin/offers/migrations/0002_auto_20141009_2223.py b/coin/offers/migrations/0002_auto_20141009_2223.py index cfbfc2f76739486af22ef7519ac606e1ab5d93db..d8f566bdad6b4b938c011fe6a21f8598c4e1d2ff 100644 --- a/coin/offers/migrations/0002_auto_20141009_2223.py +++ b/coin/offers/migrations/0002_auto_20141009_2223.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/offers/migrations/0003_offer_non_billable.py b/coin/offers/migrations/0003_offer_non_billable.py index 7f7b41a43e804d08d6da0eb7209572d295f5adf0..31ceb25bc6e528b3afde754319bbd68de300f999 100644 --- a/coin/offers/migrations/0003_offer_non_billable.py +++ b/coin/offers/migrations/0003_offer_non_billable.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/offers/migrations/0004_auto_20150120_2309.py b/coin/offers/migrations/0004_auto_20150120_2309.py index 909842bd08c591b63ac92767abb1c6d9a2b770d5..e1c394e1c3e08ad04202f06aeda6c7f454b875a2 100644 --- a/coin/offers/migrations/0004_auto_20150120_2309.py +++ b/coin/offers/migrations/0004_auto_20150120_2309.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/offers/migrations/0005_auto_20150210_0923.py b/coin/offers/migrations/0005_auto_20150210_0923.py index 8969f7a8d96925ebad41c8699bb02eeded0bb973..3159593c7e2e0dfca8fa2398df7e16929d19d6c2 100644 --- a/coin/offers/migrations/0005_auto_20150210_0923.py +++ b/coin/offers/migrations/0005_auto_20150210_0923.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import django.core.validators diff --git a/coin/offers/migrations/0006_offer_reference.py b/coin/offers/migrations/0006_offer_reference.py index bb6efef42c107592d8659294f9bf90768d801e77..02d7e15ee0cf6934bf4c3345867f7fbb463be583 100644 --- a/coin/offers/migrations/0006_offer_reference.py +++ b/coin/offers/migrations/0006_offer_reference.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/offers/migrations/0007_offersubscription_comments.py b/coin/offers/migrations/0007_offersubscription_comments.py index be87a15cb98841ce389c2437b7fe62932522704f..119722f7c980da9ac114d8606533a63e433fb624 100644 --- a/coin/offers/migrations/0007_offersubscription_comments.py +++ b/coin/offers/migrations/0007_offersubscription_comments.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/offers/migrations/0008_auto_20170818_1507.py b/coin/offers/migrations/0008_auto_20170818_1507.py index 0f5e4cb7083595374333d5ef9285f0f93f61cc27..28766a52643bedd6ddca2ffcc64944562feacb76 100644 --- a/coin/offers/migrations/0008_auto_20170818_1507.py +++ b/coin/offers/migrations/0008_auto_20170818_1507.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models @@ -17,8 +14,9 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('priority', models.IntegerField()), - ('ippool', models.ForeignKey(to='resources.IPPool')), - ('offer', models.ForeignKey(to='offers.Offer')), + ('ippool', models.ForeignKey(to='resources.IPPool', + on_delete=models.CASCADE)), + ('offer', models.ForeignKey(to='offers.Offer', on_delete=models.CASCADE)), ], options={ 'ordering': ['priority'], diff --git a/coin/offers/migrations/0009_auto_20170818_1529.py b/coin/offers/migrations/0009_auto_20170818_1529.py index d22258e1aa56a919e7a0a066f691baac07fcc022..1fc9e46b39274e4d2de61bcbe53ef66fb401c6c1 100644 --- a/coin/offers/migrations/0009_auto_20170818_1529.py +++ b/coin/offers/migrations/0009_auto_20170818_1529.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models @@ -27,11 +24,13 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='offerippool', name='ippool', - field=models.ForeignKey(verbose_name="pool d'IP", to='resources.IPPool'), + field=models.ForeignKey(verbose_name="pool d'IP", to='resources.IPPool', + on_delete=models.CASCADE), ), migrations.AlterField( model_name='offerippool', name='offer', - field=models.ForeignKey(verbose_name='offre', to='offers.Offer'), + field=models.ForeignKey(verbose_name='offre', to='offers.Offer', + on_delete=models.CASCADE), ), ] diff --git a/coin/offers/migrations/0010_auto_20170818_1835.py b/coin/offers/migrations/0010_auto_20170818_1835.py index 4bbea48642733e8d101a3dd3dccf61bc5fc32e4c..8410f010ae7807c071dc943b4224a11b167c3309 100644 --- a/coin/offers/migrations/0010_auto_20170818_1835.py +++ b/coin/offers/migrations/0010_auto_20170818_1835.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/offers/migrations/0011_auto_20200717_1733.py b/coin/offers/migrations/0011_auto_20200717_1733.py index 94e0297cbd196a6c7849ab5fc2deba6f61ae15e5..498659e5eb976e53f6a091bdd86f671a3cdc76fc 100644 --- a/coin/offers/migrations/0011_auto_20200717_1733.py +++ b/coin/offers/migrations/0011_auto_20200717_1733.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models import datetime from django.conf import settings @@ -47,12 +44,13 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='offerippool', name='ip_pool', - field=models.ForeignKey(related_name='offer_ip_pools', verbose_name="pool d'IP", to='resources.IPPool'), + field=models.ForeignKey(related_name='offer_ip_pools', verbose_name="pool d'IP", to='resources.IPPool', on_delete=models.CASCADE), ), migrations.AlterField( model_name='offerippool', name='offer', - field=models.ForeignKey(related_name='offer_ip_pools', verbose_name='offre', to='offers.Offer'), + field=models.ForeignKey(related_name='offer_ip_pools', verbose_name='offre', + to='offers.Offer', on_delete=models.CASCADE), ), migrations.AlterField( model_name='offerippool', @@ -62,9 +60,15 @@ class Migration(migrations.Migration): migrations.CreateModel( name='OfferSubscriptionRequest', fields=[ - ('request_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='offers.Request')), - ('offer', models.ForeignKey(verbose_name='offre', to='offers.Offer')), - ('offersubscription', models.OneToOneField(related_name='subscriptionrequest', null=True, blank=True, to='offers.OfferSubscription', verbose_name='abonnement cr\xe9\xe9')), + ('request_ptr', models.OneToOneField(parent_link=True, + auto_created=True, primary_key=True, serialize=False, + to='offers.Request', on_delete=models.CASCADE)), + ('offer', models.ForeignKey(verbose_name='offre', to='offers.Offer', + on_delete=models.CASCADE)), + ('offersubscription', + models.OneToOneField(related_name='subscriptionrequest', null=True, + blank=True, to='offers.OfferSubscription', + verbose_name='abonnement cr\xe9\xe9', on_delete=models.CASCADE)), ], options={ 'verbose_name': 'demande de souscription', @@ -75,11 +79,14 @@ class Migration(migrations.Migration): migrations.AddField( model_name='request', name='member', - field=models.ForeignKey(verbose_name='membre', to=settings.AUTH_USER_MODEL), + field=models.ForeignKey(verbose_name='membre', to=settings.AUTH_USER_MODEL, + on_delete=models.CASCADE), ), migrations.AddField( model_name='request', name='polymorphic_ctype', - field=models.ForeignKey(related_name='polymorphic_offers.request_set+', editable=False, to='contenttypes.ContentType', null=True), + field=models.ForeignKey(related_name='polymorphic_offers.request_set+', + editable=False, to='contenttypes.ContentType', null=True, + on_delete=models.CASCADE), ), ] diff --git a/coin/offers/models.py b/coin/offers/models.py index 404a4befb83cc280877ea51fd354ee26f206071a..056d605bc21f62b76ffd5eb61cdeb11aa0754edf 100644 --- a/coin/offers/models.py +++ b/coin/offers/models.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime -from polymorphic import PolymorphicModel +from polymorphic.models import PolymorphicModel from django.conf import settings from django.db import models @@ -28,7 +25,7 @@ class OfferManager(models.Manager): # parmi toutes les RowLevelPermission, celles qui sont relatives à des OfferSubscription et qui sont dans allowedcodenames rowperms = RowLevelPermission.objects.filter(content_type=ContentType.objects.get_for_model(OfferSubscription), codename__in=allowedcodenames) # toutes les Offers pour lesquelles il existe une RowLevelpermission correspondante dans rowperms - return super(OfferManager, self).filter(rowlevelpermission__in=rowperms).distinct() + return super().filter(rowlevelpermission__in=rowperms).distinct() class Offer(models.Model): """Description of an offer available to subscribers. @@ -151,9 +148,11 @@ class Offer(models.Model): class OfferIPPool(models.Model): offer = models.ForeignKey(Offer, - verbose_name='offre', related_name='offer_ip_pools') + verbose_name='offre', related_name='offer_ip_pools', + on_delete=models.CASCADE) ip_pool = models.ForeignKey(IPPool, - verbose_name='pool d\'IP', related_name='offer_ip_pools') + verbose_name='pool d\'IP', + related_name='offer_ip_pools', on_delete=models.CASCADE) to_assign = models.BooleanField(default=False, verbose_name='utiliser pour les IP d\'endpoints', help_text='Si vous cocher cette case, COIN utilisera ce pool d\'IP' @@ -217,8 +216,9 @@ class OfferSubscription(models.Model): comments = models.TextField(blank=True, verbose_name='commentaires', help_text="Commentaires libres (informations" " spécifiques concernant l'abonnement)") - member = models.ForeignKey('members.Member', verbose_name='membre') - offer = models.ForeignKey('Offer', verbose_name='offre') + member = models.ForeignKey('members.Member', verbose_name='membre', + on_delete=models.CASCADE) + offer = models.ForeignKey('Offer', verbose_name='offre', on_delete=models.CASCADE) objects = OfferSubscriptionQuerySet().as_manager() @@ -238,12 +238,12 @@ class OfferSubscription(models.Model): return False def __unicode__(self): - return '%s - %s - %s' % (self.member, self.offer.name, + return '{} - {} - {}'.format(self.member, self.offer.name, self.subscription_date) def bulk_related_objects(self, objs, *args, **kwargs): # Fix delete screen. Workaround for https://github.com/chrisglass/django_polymorphic/issues/34 - return super(OfferSubscription, self).bulk_related_objects(objs, *args, **kwargs).non_polymorphic() + return super().bulk_related_objects(objs, *args, **kwargs).non_polymorphic() class Meta: verbose_name = 'abonnement' @@ -261,7 +261,8 @@ class Request(PolymorphicModel): new VPN subscription, of a request to reinstall a VPS """ - member = models.ForeignKey('members.Member', verbose_name='membre') + member = models.ForeignKey('members.Member', verbose_name='membre', + on_delete=models.CASCADE) state = models.CharField(max_length=14, choices=[('pending', 'En attente'), @@ -302,7 +303,7 @@ class Request(PolymorphicModel): def bulk_related_objects(self, objs, *args, **kwargs): # Fix delete screen. Workaround for https://github.com/chrisglass/django_polymorphic/issues/34 - return super(OfferSubscription, self).bulk_related_objects(objs, *args, **kwargs).non_polymorphic() + return super().bulk_related_objects(objs, *args, **kwargs).non_polymorphic() class Meta: verbose_name = 'demande' @@ -310,12 +311,13 @@ class Request(PolymorphicModel): class OfferSubscriptionRequest(Request): - offer = models.ForeignKey('Offer', verbose_name='offre') + offer = models.ForeignKey('Offer', verbose_name='offre', on_delete=models.CASCADE) offersubscription = models.OneToOneField(OfferSubscription, blank=True, null=True, related_name='subscriptionrequest', - verbose_name='abonnement créé') + verbose_name='abonnement créé', + on_delete=models.CASCADE) offer_type = None @@ -326,7 +328,7 @@ class OfferSubscriptionRequest(Request): comments=self.admin_comments) self.offersubscription.save() - super(OfferSubscriptionRequest, self).accept() + super().accept() self.offersubscription.create_config_if_it_does_not_exists_yet() @@ -344,7 +346,7 @@ class OfferSubscriptionRequest(Request): def refuse(self): - super(OfferSubscriptionRequest, self).refuse() + super().refuse() send_templated_email( to=self.member.email, @@ -369,7 +371,7 @@ class OfferSubscriptionRequest(Request): return sorted({Offer._get_offer_type(o.configuration_type) for o in Offer.objects.filter(requestable=True)}) def __unicode__(self): - return 'Demande de %s pour un %s' % (self.member, self.offer.name) + return f'Demande de {self.member} pour un {self.offer.name}' class Meta: verbose_name = 'demande de souscription' diff --git a/coin/offers/offersubscription_filter.py b/coin/offers/offersubscription_filter.py index a8783ca04afb6f392ca8ad6dd0647250070ac61e..0d2e70314674f27f4ad7647c38c4392b30b745fb 100644 --- a/coin/offers/offersubscription_filter.py +++ b/coin/offers/offersubscription_filter.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib.admin import SimpleListFilter from django.db.models import Q,F import datetime diff --git a/coin/offers/tests.py b/coin/offers/tests.py index 5982e6bcd29dd7a86f95cb9ce468697474e59287..7ce503c2dd97ba78597f6ff6e4393132753573f6 100644 --- a/coin/offers/tests.py +++ b/coin/offers/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase # Create your tests here. diff --git a/coin/offers/urls.py b/coin/offers/urls.py index 3dd3c13bace089b4a545c0eed07816c70fb85d8f..35435b923df9089b206d15de0f2d90f6ec0f3ee9 100644 --- a/coin/offers/urls.py +++ b/coin/offers/urls.py @@ -1,9 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf.urls import url from coin.offers.views import ConfigurationRedirectView, subscription_count_json +app_name = 'subscription' urlpatterns = [ #'', # Redirect to the appropriate configuration backend. diff --git a/coin/offers/views.py b/coin/offers/views.py index 9345303f471a3423b3aae6374d2584361390b61a..450a5f8203f525b20ff54d386c583c50e7161069 100644 --- a/coin/offers/views.py +++ b/coin/offers/views.py @@ -1,13 +1,10 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import datetime import json from django.db.models import Q, Count from django.views.generic.base import RedirectView from django.shortcuts import get_object_or_404, render -from django.core.urlresolvers import reverse +from django.urls import reverse from django.http import JsonResponse, HttpResponseServerError, HttpResponseRedirect from django.contrib.auth.decorators import login_required @@ -38,7 +35,7 @@ def subscription_count_json(request): if not isinstance(date, datetime.date): try: datetime.datetime.strptime(date, '%Y-%m-%d') - except ValueError, TypeError: + except (ValueError, TypeError): return HttpResponseServerError("Incorrect date format, should be YYYY-MM-DD") # Get current offer subscription diff --git a/coin/resources/admin.py b/coin/resources/admin.py index 743758b28d56acc14003d9ae81ea300983cc06b2..8060778cfa1436443d957d203683220fd5736b7b 100644 --- a/coin/resources/admin.py +++ b/coin/resources/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from coin.resources.models import IPPool, IPSubnet diff --git a/coin/resources/app.py b/coin/resources/app.py index 63c9db35285ba7d1c564659a18b5dae7117de763..befe132f6981c911124bdf18182458b9e964fd34 100644 --- a/coin/resources/app.py +++ b/coin/resources/app.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.apps import AppConfig diff --git a/coin/resources/migrations/0001_initial.py b/coin/resources/migrations/0001_initial.py index 7e54ff1c540992571d9f124ceb6cfc9d002aee36..2f11548c5bf879b651d1ef466739bc4869e3f42c 100644 --- a/coin/resources/migrations/0001_initial.py +++ b/coin/resources/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import netfields.fields import coin.resources.models @@ -34,8 +31,11 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('inet', netfields.fields.CidrAddressField(blank=True, help_text='Laisser vide pour allouer automatiquement', max_length=43, verbose_name='sous-r\xe9seau')), ('delegate_reverse_dns', models.BooleanField(default=False, help_text='D\xe9l\xe9guer la r\xe9solution DNS inverse de ce sous-r\xe9seau \xe0 un ou plusieurs serveurs de noms', verbose_name='d\xe9l\xe9guer le reverse DNS')), - ('configuration', models.ForeignKey(related_name='ip_subnet', verbose_name='configuration', to='configuration.Configuration')), - ('ip_pool', models.ForeignKey(verbose_name="pool d'IP", to='resources.IPPool')), + ('configuration', models.ForeignKey(related_name='ip_subnet', + verbose_name='configuration', to='configuration.Configuration', + on_delete=models.CASCADE)), + ('ip_pool', models.ForeignKey(verbose_name="pool d'IP", + to='resources.IPPool', on_delete=models.CASCADE)), ], options={ 'verbose_name': 'sous-r\xe9seau IP', diff --git a/coin/resources/migrations/0002_ipsubnet_name_server.py b/coin/resources/migrations/0002_ipsubnet_name_server.py index 9f86139b1c932b3f3aabc10cdba107dea55406aa..6f36948faac742aaf9630fd5e5291d1fe110555c 100644 --- a/coin/resources/migrations/0002_ipsubnet_name_server.py +++ b/coin/resources/migrations/0002_ipsubnet_name_server.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations diff --git a/coin/resources/migrations/0003_auto_20150203_1043.py b/coin/resources/migrations/0003_auto_20150203_1043.py index be5bba5bf9110516f7640940a6eba3ce6b2dcddf..861db65a9827c84b5177215a3a6ce32a092ff243 100644 --- a/coin/resources/migrations/0003_auto_20150203_1043.py +++ b/coin/resources/migrations/0003_auto_20150203_1043.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations import netfields.fields import coin.resources.models diff --git a/coin/resources/migrations/0004_auto_20190826_0011.py b/coin/resources/migrations/0004_auto_20190826_0011.py index aa6f6b33bb5ec7e3cc9f8f218157de6d9198be3b..ce839e3a3b9f716237ee487ed2f9a61267ac6887 100644 --- a/coin/resources/migrations/0004_auto_20190826_0011.py +++ b/coin/resources/migrations/0004_auto_20190826_0011.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models @@ -19,6 +16,8 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='ipsubnet', name='configuration', - field=models.ForeignKey(related_name='ip_subnet', verbose_name='configuration', blank=True, to='configuration.Configuration', null=True), + field=models.ForeignKey(related_name='ip_subnet', + verbose_name='configuration', blank=True, + to='configuration.Configuration', null=True, on_delete=models.CASCADE), ), ] diff --git a/coin/resources/migrations/0005_auto_20200717_1733.py b/coin/resources/migrations/0005_auto_20200717_1733.py index c733c033cc784f660c5de2c6dc8c622613cb8dc3..fb3d0fb0721c564dcab13dca23845b0f01c91252 100644 --- a/coin/resources/migrations/0005_auto_20200717_1733.py +++ b/coin/resources/migrations/0005_auto_20200717_1733.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import migrations, models diff --git a/coin/resources/models.py b/coin/resources/models.py index 8953e89295ea020567050fa180ac8acdfece77c0..06f95e261415b09df645501b2760bc54f026844f 100644 --- a/coin/resources/models.py +++ b/coin/resources/models.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator from netfields import CidrAddressField, NetManager -from netaddr import IPSet, IPNetwork, IPAddress +from netaddr import IPSet +from ipaddress import ip_address, ip_network class IPPool(models.Model): @@ -32,7 +30,7 @@ class IPPool(models.Model): incorrect = [str(subnet) for subnet in self.ipsubnet_set.all() if not subnet.inet in self.inet] if incorrect: - err = "Des sous-réseaux se retrouveraient en-dehors du bloc d'IP: {}".format(incorrect) + err = f"Des sous-réseaux se retrouveraient en-dehors du bloc d'IP: {incorrect}" raise ValidationError(err) def __unicode__(self): @@ -48,7 +46,7 @@ class IPSubnet(models.Model): unique=True, verbose_name="sous-réseau", help_text="Laisser vide pour allouer automatiquement") objects = NetManager() - ip_pool = models.ForeignKey(IPPool, verbose_name="pool d'IP") + ip_pool = models.ForeignKey(IPPool, verbose_name="pool d'IP", on_delete=models.CASCADE) configuration = models.ForeignKey('configuration.Configuration', on_delete=models.CASCADE, blank=True, null=True, @@ -67,15 +65,15 @@ class IPSubnet(models.Model): def allocate(self): """Automatically allocate a free subnet""" - pool = IPSet([self.ip_pool.inet]) - used = IPSet((s.inet for s in self.ip_pool.ipsubnet_set.all())) + pool = IPSet([str(self.ip_pool.inet)]) + used = IPSet(s.inet for s in self.ip_pool.ipsubnet_set.all()) free = pool.difference(used) free = free.difference(IPSet([ - IPNetwork('89.234.141.0/31'), - IPAddress('2a00:5881:8100:0100::0'), - IPAddress('2a00:5881:8100:0100::1'), - IPNetwork('2a00:5881:8118:0000::/56'), - IPNetwork('2a00:5881:8118:0100::/56'), + str(ip_network('89.234.141.0/31')), + str(ip_address('2a00:5881:8100:0100::0')), + str(ip_address('2a00:5881:8100:0100::1')), + str(ip_network('2a00:5881:8118:0000::/56')), + str(ip_network('2a00:5881:8118:0100::/56')), ])) # Generator for efficiency (we don't build the whole list) available = (p for p in free.iter_cidrs() if p.prefixlen <= self.ip_pool.default_subnetsize) @@ -84,16 +82,16 @@ class IPSubnet(models.Model): # have a real subnet in practice (i.e. Ethernet segment), but # many /32. try: - first_free = available.next() + first_free = next(available) except StopIteration: raise ValidationError("Impossible d'allouer un sous-réseau : bloc d'IP rempli.") # first_free is a subnet, but it might be too large for our needs. # This selects the first sub-subnet of the right size. - self.inet = first_free.subnet(self.ip_pool.default_subnetsize, 1).next() + self.inet = next(first_free.subnet(self.ip_pool.default_subnetsize, 1)) def validate_inclusion(self): """Check that we are included in the IP pool""" - if not self.inet in self.ip_pool.inet: + if self.inet not in self.ip_pool.inet and not self.inet.subnet_of(self.ip_pool.inet): raise ValidationError("Le sous-réseau doit être inclus dans le bloc d'IP.") # Check that we don't conflict with existing subnets. @@ -108,13 +106,13 @@ class IPSubnet(models.Model): conflicting_containing = self.ip_pool.ipsubnet_set.filter(inet__net_contains_or_equals=self.inet).exclude(id=self.id) if conflicting_contained or conflicting_containing: conflicting = conflicting_contained if conflicting_contained else conflicting_containing - raise ValidationError("Le sous-réseau est en conflit avec des sous-réseaux existants: {}.".format(conflicting)) + raise ValidationError(f"Le sous-réseau est en conflit avec des sous-réseaux existants: {conflicting}.") def validate_reverse_dns(self): """Check that reverse DNS entries, if any, are included in the subnet""" incorrect = [str(rev.ip) for rev in self.reversednsentry_set.all() if not rev.ip in self.inet] if incorrect: - raise ValidationError("Des entrées DNS inverse ne sont pas dans le sous-réseau: {}.".format(incorrect)) + raise ValidationError(f"Des entrées DNS inverse ne sont pas dans le sous-réseau: {incorrect}.") def clean(self): if not self.inet: @@ -126,7 +124,7 @@ class IPSubnet(models.Model): def save(self, **kwargs): if not self.inet: self.allocate() - return super(IPSubnet, self).save(**kwargs) + return super().save(**kwargs) def __unicode__(self): return str(self.inet) diff --git a/coin/resources/templatetags/subnets.py b/coin/resources/templatetags/subnets.py index cfb6e6c3852a2d8e84c8d4bcde86aef481acb4ed..d0904ff09228940de234a91222fbfc99f09b29d1 100644 --- a/coin/resources/templatetags/subnets.py +++ b/coin/resources/templatetags/subnets.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django import template -from netaddr import IPNetwork +from ipaddress import ip_network register = template.Library() @@ -11,9 +8,9 @@ register = template.Library() def prettify(subnet): """Prettify an IPv4 subnet by remove the subnet length when it is equal to /32 """ - if hasattr(subnet, "inet") and isinstance(subnet.inet, IPNetwork): + if hasattr(subnet, "inet") and isinstance(subnet.inet, ip_network): subnet = subnet.inet - if isinstance(subnet, IPNetwork): + if isinstance(subnet, ip_network): if subnet.version == 4 and subnet.prefixlen == 32: return str(subnet.ip) elif subnet.version == 6 and subnet.prefixlen == 128: diff --git a/coin/resources/tests.py b/coin/resources/tests.py index 5982e6bcd29dd7a86f95cb9ce468697474e59287..7ce503c2dd97ba78597f6ff6e4393132753573f6 100644 --- a/coin/resources/tests.py +++ b/coin/resources/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase # Create your tests here. diff --git a/coin/resources/views.py b/coin/resources/views.py index e784a0bd2ca964dad77a68f8c0a31f8e9ae27a36..91ea44a218fbd2f408430959283f0419c921093e 100644 --- a/coin/resources/views.py +++ b/coin/resources/views.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.shortcuts import render # Create your views here. diff --git a/coin/reverse_dns/admin.py b/coin/reverse_dns/admin.py index accd0281b521817897fefabe976a7e5ee20851be..b503a376719e8525632d4cf9245dedd37be72a87 100644 --- a/coin/reverse_dns/admin.py +++ b/coin/reverse_dns/admin.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.contrib import admin from coin.reverse_dns.models import NameServer, ReverseDNSEntry diff --git a/coin/reverse_dns/migrations/0001_initial.py b/coin/reverse_dns/migrations/0001_initial.py index 361d2dbd5fa472162ee77152ca7c020e748ad633..6f960eb655b558289edbd71474af71244c7ebfc7 100644 --- a/coin/reverse_dns/migrations/0001_initial.py +++ b/coin/reverse_dns/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models, migrations from django.conf import settings import django.core.validators @@ -21,7 +18,8 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('dns_name', models.CharField(help_text='Exemple : ns1.example.com', max_length=255, verbose_name='nom du serveur')), ('description', models.CharField(help_text='Exemple : Mon serveur de noms principal', max_length=255, verbose_name='description du serveur', blank=True)), - ('owner', models.ForeignKey(verbose_name='propri\xe9taire', to=settings.AUTH_USER_MODEL)), + ('owner', models.ForeignKey(verbose_name='propri\xe9taire', + to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], options={ 'verbose_name': 'serveur de noms', @@ -36,7 +34,8 @@ class Migration(migrations.Migration): ('ip', netfields.fields.InetAddressField(unique=True, max_length=39, verbose_name='adresse IP')), ('reverse', models.CharField(help_text="Nom \xe0 associer \xe0 l'adresse IP", max_length=255, verbose_name='reverse')), ('ttl', models.IntegerField(default=3600, help_text='en secondes', verbose_name='TTL', validators=[django.core.validators.MinValueValidator(60)])), - ('ip_subnet', models.ForeignKey(verbose_name='sous-r\xe9seau IP', to='resources.IPSubnet')), + ('ip_subnet', models.ForeignKey(verbose_name='sous-r\xe9seau IP', + to='resources.IPSubnet', on_delete=models.CASCADE)), ], options={ 'verbose_name': 'entr\xe9e DNS inverse', diff --git a/coin/reverse_dns/models.py b/coin/reverse_dns/models.py index fa5134146e0714126353e6654ab45905ca839fcd..5ebc2eb76168b97523a2b324dbf31bb8944902bf 100644 --- a/coin/reverse_dns/models.py +++ b/coin/reverse_dns/models.py @@ -1,11 +1,7 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.db import models from django.core.exceptions import ValidationError from django.core.validators import MinValueValidator from netfields import InetAddressField, NetManager -from netaddr import IPAddress import ldapdb.models from ldapdb.models.fields import CharField, IntegerField, ListField @@ -23,10 +19,11 @@ class NameServer(models.Model): description = models.CharField(max_length=255, blank=True, verbose_name="description du serveur", help_text="Exemple : Mon serveur de noms principal") - owner = models.ForeignKey("members.Member", verbose_name="propriétaire") + owner = models.ForeignKey("members.Member", verbose_name="propriétaire", + on_delete=models.CASCADE) def __unicode__(self): - return "{} ({})".format(self.description, self.dns_name) if self.description else self.dns_name + return f"{self.description} ({self.dns_name})" if self.description else self.dns_name class Meta: verbose_name = 'serveur de noms' @@ -41,7 +38,8 @@ class ReverseDNSEntry(models.Model): help_text="en secondes", validators=[MinValueValidator(60)]) ip_subnet = models.ForeignKey('resources.IPSubnet', - verbose_name='sous-réseau IP') + verbose_name='sous-réseau IP', + on_delete=models.CASCADE) objects = NetManager() @@ -55,7 +53,7 @@ class ReverseDNSEntry(models.Model): raise ValidationError('IP address must be included in the IP subnet.') def __unicode__(self): - return "{} → {}".format(self.ip, self.reverse) + return f"{self.ip} → {self.reverse}" class Meta: verbose_name = 'entrée DNS inverse' diff --git a/coin/reverse_dns/tests.py b/coin/reverse_dns/tests.py index 5982e6bcd29dd7a86f95cb9ce468697474e59287..7ce503c2dd97ba78597f6ff6e4393132753573f6 100644 --- a/coin/reverse_dns/tests.py +++ b/coin/reverse_dns/tests.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import TestCase # Create your tests here. diff --git a/coin/reverse_dns/views.py b/coin/reverse_dns/views.py index e784a0bd2ca964dad77a68f8c0a31f8e9ae27a36..91ea44a218fbd2f408430959283f0419c921093e 100644 --- a/coin/reverse_dns/views.py +++ b/coin/reverse_dns/views.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.shortcuts import render # Create your views here. diff --git a/coin/settings.py b/coin/settings.py index 60b9428380002a2a6ce65dcf00215114cf56c346..9b540e91371dc0ddc365f573d49acaef86729325 100644 --- a/coin/settings.py +++ b/coin/settings.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - -from settings_base import * +from .settings_base import * # Surcharge les paramètres en utilisant le fichier settings_local.py try: @@ -8,7 +6,7 @@ try: except ImportError: pass -TEMPLATE_DIRS = EXTRA_TEMPLATE_DIRS + TEMPLATE_DIRS +TEMPLATES[0]['DIRS'] = EXTRA_TEMPLATE_DIRS + TEMPLATES[0]['DIRS'] INSTALLED_APPS = INSTALLED_APPS + EXTRA_INSTALLED_APPS #TEMPLATES = { diff --git a/coin/settings_base.py b/coin/settings_base.py index 39bebd87ee4155202fa5b5492437b84867a9e9ed..3a56115a66dbc4f8f5afb55d29e5c99ed1bca245 100644 --- a/coin/settings_base.py +++ b/coin/settings_base.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import os import ldap @@ -10,7 +7,7 @@ import ldap BASE_DIR = os.path.dirname(os.path.dirname(__file__)) PROJECT_PATH = os.path.abspath(os.path.dirname(__file__)) -DEBUG = TEMPLATE_DEBUG = True +DEBUG = True ADMINS = ( # ('Your Name', 'your_email@example.com'), @@ -114,7 +111,7 @@ SENDFILE_BACKEND = 'sendfile.backends.development' # Make this unique, and don't share it with anybody. SECRET_KEY = '!qy_)gao6q)57#mz1s-d$5^+dp1nt=lk1d19&9bb3co37vn)!3' -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -138,12 +135,32 @@ ROOT_URLCONF = 'coin.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'coin.wsgi.application' -TEMPLATE_DIRS = ( - # Only absolute paths, always forward slashes - os.path.join(PROJECT_PATH, 'templates/'), -) +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [ + os.path.join(PROJECT_PATH, 'templates/'), + ], + 'APP_DIRS': True, + 'OPTIONS': { + 'debug': DEBUG, + 'context_processors': [ + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.template.context_processors.request", + "coin.isp_database.context_processors.branding", + "coin.context_processors.installed_apps", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] -EXTRA_TEMPLATE_DIRS = tuple() +EXTRA_TEMPLATE_DIRS = list() INSTALLED_APPS = ( 'debug_toolbar', # always installed, but enabled only if DEBUG=True @@ -161,7 +178,7 @@ INSTALLED_APPS = ( #'django.contrib.admindocs', 'polymorphic', 'autocomplete_light', # Automagic autocomplete foreingkey form component - 'activelink', # Detect if a link match actual page + # 'activelink', # Detect if a link match actual page 'compat', 'hijack', 'django_extensions', @@ -226,18 +243,6 @@ LOGGING = { } } -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", - "django.template.context_processors.debug", - "django.template.context_processors.i18n", - "django.template.context_processors.media", - "django.template.context_processors.static", - "django.template.context_processors.tz", - "django.template.context_processors.request", - "coin.isp_database.context_processors.branding", - "coin.context_processors.installed_apps", - "django.contrib.messages.context_processors.messages") - AUTH_USER_MODEL = 'members.Member' AUTHENTICATION_BACKENDS = ( diff --git a/coin/settings_dev.py b/coin/settings_dev.py index 78ffbac1264c531c107896f03ca9747ca60f2bd2..4a242047fa20d679d651f20c512d0a2454831279 100644 --- a/coin/settings_dev.py +++ b/coin/settings_dev.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - -from settings_base import * +from .settings_base import * DATABASES = { # Database hosted on vagant test box @@ -29,5 +27,5 @@ try: except ImportError: pass -TEMPLATE_DIRS = EXTRA_TEMPLATE_DIRS + TEMPLATE_DIRS +TEMPLATES[0]['DIRS'] = EXTRA_TEMPLATE_DIRS + TEMPLATES[0]['DIRS'] INSTALLED_APPS = INSTALLED_APPS + EXTRA_INSTALLED_APPS diff --git a/coin/settings_local.example-illyse.py b/coin/settings_local.example-illyse.py index a7a41507117cb0753fad32c1ef3dde101cddd0be..11d8f69f685913668d61b5ae7b34c588a315bc11 100644 --- a/coin/settings_local.example-illyse.py +++ b/coin/settings_local.example-illyse.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - EXTRA_INSTALLED_APPS = ( 'vpn', 'dsl_ldap', diff --git a/coin/settings_test.py b/coin/settings_test.py index 8611b12dae7cd5a65510383f8346aa2d4603d9c3..6160a28dd2046dd10bdd792808b943824ab5db2c 100644 --- a/coin/settings_test.py +++ b/coin/settings_test.py @@ -1,4 +1,4 @@ -from settings_base import * +from .settings_base import * # settings for unit tests @@ -10,5 +10,5 @@ EXTRA_INSTALLED_APPS = ( 'housing', ) -TEMPLATE_DIRS = EXTRA_TEMPLATE_DIRS + TEMPLATE_DIRS +TEMPLATES[0]['DIRS'] = EXTRA_TEMPLATE_DIRS + TEMPLATES[0]['DIRS'] INSTALLED_APPS = INSTALLED_APPS + EXTRA_INSTALLED_APPS diff --git a/coin/templates/abstract_base.html b/coin/templates/abstract_base.html index c7c4c779948d12af3b7c0da12db970ae08aa00d3..98ecd161bef85e32d0fe9c3ae88bdb6cdc1dba70 100644 --- a/coin/templates/abstract_base.html +++ b/coin/templates/abstract_base.html @@ -1,5 +1,5 @@ -{% load staticfiles %} +{% load static %} {% load hijack_tags %} @@ -24,7 +24,7 @@
{% hijack_notification %} - +