diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ce247d6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + args: ['--target-version', 'py37', '--skip-string-normalization', '--line-length', '110'] +- repo: https://github.com/PyCQA/isort + rev: 5.7.0 + hooks: + - id: isort + args: ['--profile', 'black', '--line-length', '110'] +- repo: https://github.com/asottile/pyupgrade + rev: v2.20.0 + hooks: + - id: pyupgrade + args: ['--keep-percent-format', '--py37-plus'] diff --git a/eo_gestion/actions.py b/eo_gestion/actions.py index 9621567..29b1dfb 100644 --- a/eo_gestion/actions.py +++ b/eo_gestion/actions.py @@ -15,9 +15,9 @@ # along with this program. If not, see . import csv -from io import BytesIO import os import zipfile +from io import BytesIO from django.core.exceptions import PermissionDenied from django.http import HttpResponse @@ -65,7 +65,7 @@ def export_references_as_fodt(modeladmin, request, queryset): output_filename = 'references.fodt' mimetype = 'application/vnd.oasis.opendocument.text' - t = Template(open(template, 'r').read()) + t = Template(open(template).read()) export = t.render(Context(context)).encode('utf-8') response = HttpResponse(content=export, content_type=mimetype) diff --git a/eo_gestion/admin.py b/eo_gestion/admin.py index f3149c0..3ff9f41 100644 --- a/eo_gestion/admin.py +++ b/eo_gestion/admin.py @@ -14,17 +14,16 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import taggit.admin from django.conf import settings from django.contrib import admin from django.contrib.auth import REDIRECT_FIELD_NAME -from django.contrib.auth.models import User, Group -from django.contrib.auth.admin import UserAdmin, GroupAdmin +from django.contrib.auth.admin import GroupAdmin, UserAdmin +from django.contrib.auth.models import Group, User from django.http import HttpResponseRedirect from django.utils.http import urlencode from django.views.decorators.cache import never_cache -import taggit.admin - class EOGestionAdminSite(admin.AdminSite): @never_cache @@ -32,18 +31,18 @@ class EOGestionAdminSite(admin.AdminSite): if "mellon" in settings.INSTALLED_APPS: next_url = request.GET.get(REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL or "/") query = urlencode({REDIRECT_FIELD_NAME: next_url}) - url = "/accounts/mellon/login/?{0}".format(query) + url = f"/accounts/mellon/login/?{query}" return HttpResponseRedirect(url) - return super(EOGestionAdminSite, self).login(request, extra_context=extra_context) + return super().login(request, extra_context=extra_context) @never_cache def logout(self, request, extra_context=None): if "mellon" in settings.INSTALLED_APPS: next_url = request.GET.get(REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL or "/") query = urlencode({REDIRECT_FIELD_NAME: next_url}) - url = "/accounts/mellon/logout/?{0}".format(query) + url = f"/accounts/mellon/logout/?{query}" return HttpResponseRedirect(url) - return super(EOGestionAdminSite, self).logout(request, extra_context=extra_context) + return super().logout(request, extra_context=extra_context) site = EOGestionAdminSite() diff --git a/eo_gestion/chorus/admin.py b/eo_gestion/chorus/admin.py index 6002a1e..7c66bfc 100644 --- a/eo_gestion/chorus/admin.py +++ b/eo_gestion/chorus/admin.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from django.contrib import admin + import eo_gestion.admin from . import models diff --git a/eo_gestion/chorus/annuaire.py b/eo_gestion/chorus/annuaire.py index 2e6e38a..5a3eee7 100644 --- a/eo_gestion/chorus/annuaire.py +++ b/eo_gestion/chorus/annuaire.py @@ -15,14 +15,12 @@ # along with this program. If not, see . import itertools -import zipfile - -from xml.dom import pulldom import xml.etree.ElementTree as ET - -from django.core.files.storage import default_storage +import zipfile +from xml.dom import pulldom import requests +from django.core.files.storage import default_storage from . import chorus @@ -34,7 +32,7 @@ def grouper(iterable, n, fillvalue=None): return itertools.zip_longest(*args, fillvalue=fillvalue) -class AnnuaireManager(object): +class AnnuaireManager: STRUCTURE_UNITAIRE_TAG_NAME = 'CPPStructurePartenaireUnitaire' def _update_annuaire(self): @@ -48,7 +46,7 @@ class AnnuaireManager(object): structures = [struct for struct in structures if struct] # ignore None inserts = [] updates = [] - identifiers = set(structure.full_identifier for structure in structures) + identifiers = {structure.full_identifier for structure in structures} known.update(identifiers) known_structures = { struct.full_identifier: struct diff --git a/eo_gestion/chorus/chorus.py b/eo_gestion/chorus/chorus.py index 087d44a..8170765 100644 --- a/eo_gestion/chorus/chorus.py +++ b/eo_gestion/chorus/chorus.py @@ -17,11 +17,10 @@ import base64 import logging +import requests from django.conf import settings from django.utils.encoding import force_text -import requests - from eo_gestion.decorators import cache logger = logging.getLogger(__name__) diff --git a/eo_gestion/chorus/migrations/0001_initial.py b/eo_gestion/chorus/migrations/0001_initial.py index 81326c9..a6faac2 100644 --- a/eo_gestion/chorus/migrations/0001_initial.py +++ b/eo_gestion/chorus/migrations/0001_initial.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.9 on 2020-02-04 09:08 from django.db import migrations, models + import eo_gestion.chorus.validators @@ -31,7 +32,9 @@ class Migration(migrations.Migration): ('email', models.EmailField(max_length=254, null=True)), ('engagement_obligatoire', models.BooleanField()), ], - options={'ordering': ('name', 'service_name'),}, + options={ + 'ordering': ('name', 'service_name'), + }, ), migrations.CreateModel( name='Update', @@ -46,6 +49,7 @@ class Migration(migrations.Migration): ], ), migrations.AddIndex( - model_name='structure', index=models.Index(fields=['name', 'service_name'], name='name_idx'), + model_name='structure', + index=models.Index(fields=['name', 'service_name'], name='name_idx'), ), ] diff --git a/eo_gestion/chorus/models.py b/eo_gestion/chorus/models.py index 8fbafcb..f96e9d9 100644 --- a/eo_gestion/chorus/models.py +++ b/eo_gestion/chorus/models.py @@ -20,22 +20,30 @@ from .validators import validate_siret class Update(models.Model): - timestamp = models.DateTimeField(auto_now_add=True,) - success = models.BooleanField(default=False,) + timestamp = models.DateTimeField( + auto_now_add=True, + ) + success = models.BooleanField( + default=False, + ) message = models.TextField() class Structure(models.Model): name = models.CharField(max_length=80) full_identifier = models.CharField(max_length=len('29202001300010') + 64, unique=True) - siret = models.CharField(max_length=len('29202001300010'), validators=[validate_siret], db_index=True,) + siret = models.CharField( + max_length=len('29202001300010'), + validators=[validate_siret], + db_index=True, + ) service_code = models.CharField(max_length=64, default='', blank=True) service_name = models.CharField(max_length=80, default='', blank=True) email = models.EmailField(null=True) engagement_obligatoire = models.BooleanField() def __init__(self, *args, **kwargs): - super(Structure, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if not self.full_identifier: self.full_identifier = (self.siret + self.service_code)[: len('29202001300010') + 64] diff --git a/eo_gestion/eo_banque/admin.py b/eo_gestion/eo_banque/admin.py index 3dfbdd5..c26f585 100644 --- a/eo_gestion/eo_banque/admin.py +++ b/eo_gestion/eo_banque/admin.py @@ -17,11 +17,12 @@ from django.contrib import admin from django.contrib.contenttypes.admin import GenericStackedInline -from django.db.models import Sum, F, Q +from django.db.models import F, Q, Sum +from .. import actions +from .. import admin as eo_gestion_admin from ..eo_facture.admin import PaymentInline from ..eo_facture.templatetags.eo_facture import amountformat -from .. import actions, admin as eo_gestion_admin from . import models @@ -83,7 +84,7 @@ class LigneBanquePopAdmin(admin.ModelAdmin): return True def get_queryset(self, request): - qs = super(LigneBanquePopAdmin, self).get_queryset(request) + qs = super().get_queryset(request) qs = qs.prefetch_related("payments") return qs diff --git a/eo_gestion/eo_banque/management/commands/load-csv-banquepop.py b/eo_gestion/eo_banque/management/commands/load-csv-banquepop.py index 468eef7..a7efabd 100644 --- a/eo_gestion/eo_banque/management/commands/load-csv-banquepop.py +++ b/eo_gestion/eo_banque/management/commands/load-csv-banquepop.py @@ -8,10 +8,10 @@ from eo_gestion.eo_banque.models import LigneBanquePop class Command(BaseCommand): - ''' - Charge un fichier CSV exporté depuis notre banque en ligne Banque - Populaire - ''' + """ + Charge un fichier CSV exporté depuis notre banque en ligne Banque + Populaire + """ can_import_django_settings = True output_transaction = True @@ -39,7 +39,7 @@ class Command(BaseCommand): def load_one_file(self, csv_file_path): for delimiter in [";", "\t"]: - csv_file = open(csv_file_path, 'r', encoding='latin1') + csv_file = open(csv_file_path, encoding='latin1') csv_lines = csv.reader(csv_file, delimiter=delimiter) first_line = next(csv_lines) if first_line == self.HEADER: diff --git a/eo_gestion/eo_banque/management/commands/nouveau-solde.py b/eo_gestion/eo_banque/management/commands/nouveau-solde.py index 438d7f6..9f21b82 100644 --- a/eo_gestion/eo_banque/management/commands/nouveau-solde.py +++ b/eo_gestion/eo_banque/management/commands/nouveau-solde.py @@ -7,9 +7,9 @@ from eo_gestion.eo_banque.models import SoldeBanquePop class Command(BaseCommand): - ''' - Charge un fichier CSV exporté depuis notre banque en ligne Baneque Populaire - ''' + """ + Charge un fichier CSV exporté depuis notre banque en ligne Baneque Populaire + """ can_import_django_settings = True output_transaction = True diff --git a/eo_gestion/eo_banque/management/commands/solde.py b/eo_gestion/eo_banque/management/commands/solde.py index 16902db..7c159b5 100644 --- a/eo_gestion/eo_banque/management/commands/solde.py +++ b/eo_gestion/eo_banque/management/commands/solde.py @@ -1,4 +1,4 @@ -from datetime import timedelta, date, datetime +from datetime import date, datetime, timedelta from django.core.management.base import BaseCommand from django.db import transaction @@ -7,9 +7,9 @@ from eo_gestion.eo_banque.models import solde class Command(BaseCommand): - ''' - Charge un fichier CSV exporté depuis notre banque en ligne Banque Populaire - ''' + """ + Charge un fichier CSV exporté depuis notre banque en ligne Banque Populaire + """ can_import_django_settings = True output_transaction = True diff --git a/eo_gestion/eo_banque/migrations/0001_initial.py b/eo_gestion/eo_banque/migrations/0001_initial.py index 819d2ba..bf72023 100644 --- a/eo_gestion/eo_banque/migrations/0001_initial.py +++ b/eo_gestion/eo_banque/migrations/0001_initial.py @@ -1,4 +1,4 @@ -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): @@ -57,8 +57,8 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='lignebanquepop', - unique_together=set( - [('compte', 'date_comptabilisation', 'date_operation', 'libelle', 'reference', 'montant')] - ), + unique_together={ + ('compte', 'date_comptabilisation', 'date_operation', 'libelle', 'reference', 'montant') + }, ), ] diff --git a/eo_gestion/eo_banque/models.py b/eo_gestion/eo_banque/models.py index 6c7ade8..972f03c 100644 --- a/eo_gestion/eo_banque/models.py +++ b/eo_gestion/eo_banque/models.py @@ -16,9 +16,9 @@ from datetime import date -from django.db import models from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType +from django.db import models from django.utils import six @@ -44,7 +44,6 @@ def solde(t=None): return m -@six.python_2_unicode_compatible class Commentaire(models.Model): contenu = models.TextField() content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) @@ -56,11 +55,10 @@ class Commentaire(models.Model): return "Commentaire créé le %s" % self.creation -@six.python_2_unicode_compatible class LigneBanquePop(models.Model): - ''' - Une ligne de notre relevé de compte Banque Populaire - ''' + """ + Une ligne de notre relevé de compte Banque Populaire + """ compte = models.CharField(max_length=20) date_comptabilisation = models.DateField() @@ -79,13 +77,13 @@ class LigneBanquePop(models.Model): return "%(date_valeur)s %(libelle)s %(montant)s" % self.__dict__ def montant_non_affecte(self): - return self.montant - sum([x.montant_affecte for x in self.payments.all()]) + return self.montant - sum(x.montant_affecte for x in self.payments.all()) class SoldeBanquePop(models.Model): - ''' - Le solde à un temps T, permet de calculer notre solde à toute date - ''' + """ + Le solde à un temps T, permet de calculer notre solde à toute date + """ compte = models.CharField(max_length=20) date = models.DateField(auto_now_add=True) diff --git a/eo_gestion/eo_banque/templatetags/eo_banque.py b/eo_gestion/eo_banque/templatetags/eo_banque.py index aff824e..08c866d 100644 --- a/eo_gestion/eo_banque/templatetags/eo_banque.py +++ b/eo_gestion/eo_banque/templatetags/eo_banque.py @@ -21,8 +21,8 @@ from django import template from django.db.models import Sum from django.urls import reverse -from ..models import LigneBanquePop, solde from ...decorators import cache +from ..models import LigneBanquePop, solde register = template.Library() @@ -84,8 +84,8 @@ def finances(month=3): def total(context): qs = context["cl"].get_queryset(context["request"]) ls = [ligne[0] for ligne in qs.values_list("montant")] - credit = sum([montant for montant in ls if montant > 0]) - debit = sum([montant for montant in ls if montant < 0]) + credit = sum(montant for montant in ls if montant > 0) + debit = sum(montant for montant in ls if montant < 0) total = sum(ls) return {'credit': credit, 'debit': debit, 'total': total} diff --git a/eo_gestion/eo_facture/admin.py b/eo_gestion/eo_facture/admin.py index f82ae2a..abfe49d 100644 --- a/eo_gestion/eo_facture/admin.py +++ b/eo_gestion/eo_facture/admin.py @@ -17,35 +17,35 @@ import datetime as dt -from django.contrib import admin +import django.http as http +from adminsortable2.admin import SortableInlineAdminMixin from django.conf.urls import url +from django.contrib import admin +from django.contrib.admin.options import BaseModelAdmin +from django.contrib.humanize.templatetags.humanize import ordinal +from django.db.models import TextField +from django.forms import Textarea +from django.forms.models import BaseInlineFormSet from django.shortcuts import render from django.urls import reverse -from django.contrib.admin.options import BaseModelAdmin -from django.db.models import TextField -import django.http as http -from django.contrib.humanize.templatetags.humanize import ordinal -from django.forms.models import BaseInlineFormSet -from django.forms import Textarea from django.utils.html import format_html from django.utils.six import BytesIO -from adminsortable2.admin import SortableInlineAdminMixin - -from .. import ods, actions -from . import forms, models, views -from .templatetags.eo_facture import amountformat import eo_gestion.admin +from .. import actions, ods +from . import forms, models, views +from .templatetags.eo_facture import amountformat -class LookupAllowed(object): + +class LookupAllowed: def lookup_allowed(self, *args, **kwargs): return True -class SelectRelatedMixin(object): +class SelectRelatedMixin: def get_queryset(self, request): - qs = super(SelectRelatedMixin, self).get_queryset(request) + qs = super().get_queryset(request) return qs.select_related() @@ -55,7 +55,7 @@ class CommonPaymentInline(BaseModelAdmin): kwargs['queryset'] = models.Facture.objects.avec_solde() if db_field.name == 'ligne_banque_pop' and request.path.endswith('/add/'): kwargs['queryset'] = models.encaissements_avec_solde_non_affecte() - return super(CommonPaymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs) + return super().formfield_for_foreignkey(db_field, request, **kwargs) class PrestationInline(SelectRelatedMixin, admin.TabularInline): @@ -70,7 +70,7 @@ class FactureInline(SelectRelatedMixin, admin.TabularInline): class PaymentInlineFormset(BaseInlineFormSet): def __init__(self, *args, **kwargs): - super(PaymentInlineFormset, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) for form in self.forms: if form.instance.id is None: field = form.fields["ligne_banque_pop"] @@ -135,7 +135,7 @@ class ClientAdmin(admin.ModelAdmin): raw_id_fields = ['chorus_structure'] def get_readonly_fields(self, request, obj=None): - readonly_fields = super(ClientAdmin, self).get_readonly_fields(request, obj=obj) + readonly_fields = super().get_readonly_fields(request, obj=obj) if obj and obj.chorus_structure: readonly_fields += ('siret', 'service_code') return readonly_fields @@ -186,7 +186,7 @@ class ContratAdmin(LookupAllowed, admin.ModelAdmin): column_montant.short_description = 'Montant' def get_queryset(self, request): - qs = super(ContratAdmin, self).get_queryset(request) + qs = super().get_queryset(request) return qs.prefetch_related('prestations', 'factures__lignes', 'tags') def tag_list(self, obj): @@ -223,7 +223,7 @@ class ContratAdmin(LookupAllowed, admin.ModelAdmin): return render(request, "admin/eo_facture/contrat/duplicate.html", context=context) def get_urls(self): - urls = super(ContratAdmin, self).get_urls() + urls = super().get_urls() duplicate_view = self.admin_site.admin_view(self.duplicate) my_urls = [url(r'^(.+)/duplicate/$', duplicate_view, name='eo_facture_contrat_duplicate')] return my_urls + urls @@ -295,7 +295,7 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin): def get_queryset(self, request): from django.db import connection - qs = super(FactureAdmin, self).get_queryset(request) + qs = super().get_queryset(request) qs = qs.prefetch_related("lignes__facture__client") qs = qs.prefetch_related("payments__ligne_banque_pop") qs = qs.prefetch_related("contrat__factures") @@ -359,7 +359,7 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin): return response def get_urls(self): - urls = super(FactureAdmin, self).get_urls() + urls = super().get_urls() my_urls = [ url(r'^view/([^/]*)', views.facture), url( @@ -378,7 +378,11 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin): self.admin_site.admin_view(views.send_to_chorus), name='eo_facture_facture_send_to_chorus', ), - url(r"^sheet/$", self.admin_site.admin_view(self.sheet), name="eo_facture_facture_sheet",), + url( + r"^sheet/$", + self.admin_site.admin_view(self.sheet), + name="eo_facture_facture_sheet", + ), ] return my_urls + urls diff --git a/eo_gestion/eo_facture/facturx.py b/eo_gestion/eo_facture/facturx.py index 7865617..34da9c7 100644 --- a/eo_gestion/eo_facture/facturx.py +++ b/eo_gestion/eo_facture/facturx.py @@ -19,9 +19,8 @@ import os import subprocess import tempfile -from django.template.loader import render_to_string - import facturx +from django.template.loader import render_to_string logger = logging.getLogger(__name__) @@ -50,9 +49,7 @@ def to_pdfa(pdf_bytes: bytes, icc_profile: str = DEFAULT_ICC_PROFILE): input_fd.name, ] logger.debug('converting to PDF/A, calling %s', args) - completed = subprocess.run( - args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, check=False - ) + completed = subprocess.run(args, capture_output=True, text=True, check=False) logger.debug('ghostscript result %s', completed) if completed.returncode != 0: logger.error('ghostcript call failed %s', completed) diff --git a/eo_gestion/eo_facture/fields.py b/eo_gestion/eo_facture/fields.py index 70df4c7..4bd78ae 100644 --- a/eo_gestion/eo_facture/fields.py +++ b/eo_gestion/eo_facture/fields.py @@ -17,17 +17,17 @@ from decimal import Decimal, InvalidOperation -from django.core.exceptions import ValidationError -from django.core import validators -from django.db import models from django import forms -from django.utils.translation import ugettext_lazy as _ +from django.core import validators +from django.core.exceptions import ValidationError +from django.db import models from django.utils.six import text_type +from django.utils.translation import ugettext_lazy as _ class PercentagePerYear(list): def __init__(self, sequence): - super(PercentagePerYear, self).__init__(sequence) + super().__init__(sequence) def __str__(self): return ','.join(map(lambda p: ':'.join(map(str, [p[0], int(100 * p[1])])), self)) @@ -37,7 +37,7 @@ class EuroField(models.DecimalField): def __init__(self, *args, **kwargs): kwargs["max_digits"] = 8 kwargs["decimal_places"] = 2 - super(EuroField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def assertion_error_to_validation_error(fun): @@ -105,7 +105,7 @@ class PercentagePerYearField(models.Field): def __init__(self, *args, **kwargs): kwargs["max_length"] = 64 - super(PercentagePerYearField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def from_db_value(self, value, expression, connection, context): return self.to_python(value) @@ -143,4 +143,4 @@ class PercentagePerYearField(models.Field): def formfield(self, **kwargs): defaults = {"form_class": PercentagePerYearFormField} defaults.update(kwargs) - return super(PercentagePerYearField, self).formfield(**kwargs) + return super().formfield(**kwargs) diff --git a/eo_gestion/eo_facture/forms.py b/eo_gestion/eo_facture/forms.py index 7da5253..f1527a2 100644 --- a/eo_gestion/eo_facture/forms.py +++ b/eo_gestion/eo_facture/forms.py @@ -19,9 +19,9 @@ import datetime from decimal import Decimal from django import forms -from django.db.transaction import atomic -from django.core.exceptions import ValidationError from django.contrib.admin import widgets +from django.core.exceptions import ValidationError +from django.db.transaction import atomic from . import models @@ -37,7 +37,7 @@ class RapidFactureForm(forms.Form): def __init__(self, request=None, *args, **kwargs): self.request = request - super(RapidFactureForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @atomic def clean(self): @@ -89,7 +89,7 @@ class DuplicateContractForm(forms.Form): def __init__(self, request=None, *args, **kwargs): self.request = request - super(DuplicateContractForm, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @atomic def clean(self): diff --git a/eo_gestion/eo_facture/migrations/0001_initial.py b/eo_gestion/eo_facture/migrations/0001_initial.py index e517c34..93993fd 100644 --- a/eo_gestion/eo_facture/migrations/0001_initial.py +++ b/eo_gestion/eo_facture/migrations/0001_initial.py @@ -1,10 +1,12 @@ -from django.db import models, migrations import datetime -import eo_gestion.eo_facture.models -import eo_gestion.eo_facture.fields from decimal import Decimal -from django.conf import settings + import django.core.validators +from django.conf import settings +from django.db import migrations, models + +import eo_gestion.eo_facture.fields +import eo_gestion.eo_facture.models class Migration(migrations.Migration): @@ -46,11 +48,16 @@ class Migration(migrations.Migration): ( 'tva', models.DecimalField( - default=Decimal('20'), verbose_name='TVA par défaut', max_digits=8, decimal_places=2, + default=Decimal('20'), + verbose_name='TVA par défaut', + max_digits=8, + decimal_places=2, ), ), ], - options={'ordering': ('nom',),}, + options={ + 'ordering': ('nom',), + }, bases=(models.Model,), ), migrations.CreateModel( @@ -87,7 +94,9 @@ class Migration(migrations.Migration): ), ), ], - options={'ordering': ('-id',),}, + options={ + 'ordering': ('-id',), + }, bases=(models.Model,), ), migrations.CreateModel( @@ -168,7 +177,9 @@ class Migration(migrations.Migration): ), ), ], - options={'ordering': ('-id',),}, + options={ + 'ordering': ('-id',), + }, bases=(models.Model,), ), migrations.CreateModel( @@ -193,7 +204,9 @@ class Migration(migrations.Migration): ), ), ], - options={'ordering': ('order',),}, + options={ + 'ordering': ('order',), + }, bases=(models.Model,), ), migrations.CreateModel( @@ -230,7 +243,10 @@ class Migration(migrations.Migration): ), ), ], - options={'ordering': ('-ligne_banque_pop__date_valeur',), 'verbose_name': 'Paiement',}, + options={ + 'ordering': ('-ligne_banque_pop__date_valeur',), + 'verbose_name': 'Paiement', + }, bases=(models.Model,), ), migrations.CreateModel( @@ -251,7 +267,9 @@ class Migration(migrations.Migration): ), ), ], - options={'ordering': ('contrat', 'id'),}, + options={ + 'ordering': ('contrat', 'id'), + }, bases=(models.Model,), ), ] diff --git a/eo_gestion/eo_facture/migrations/0002_facture_account_on_previous_period.py b/eo_gestion/eo_facture/migrations/0002_facture_account_on_previous_period.py index 1314b3a..de8a8b8 100644 --- a/eo_gestion/eo_facture/migrations/0002_facture_account_on_previous_period.py +++ b/eo_gestion/eo_facture/migrations/0002_facture_account_on_previous_period.py @@ -1,4 +1,4 @@ -from django.db import models, migrations +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/eo_gestion/eo_facture/migrations/0006_client_contacts.py b/eo_gestion/eo_facture/migrations/0006_client_contacts.py index 3e97487..97514af 100644 --- a/eo_gestion/eo_facture/migrations/0006_client_contacts.py +++ b/eo_gestion/eo_facture/migrations/0006_client_contacts.py @@ -11,6 +11,8 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( - model_name='client', name='contacts', field=models.TextField(blank=True, verbose_name='Contacts'), + model_name='client', + name='contacts', + field=models.TextField(blank=True, verbose_name='Contacts'), ), ] diff --git a/eo_gestion/eo_facture/migrations/0007_auto_20191009_1335.py b/eo_gestion/eo_facture/migrations/0007_auto_20191009_1335.py index b622bdc..9cc7aaa 100644 --- a/eo_gestion/eo_facture/migrations/0007_auto_20191009_1335.py +++ b/eo_gestion/eo_facture/migrations/0007_auto_20191009_1335.py @@ -16,6 +16,7 @@ from decimal import Decimal + from django.db import migrations, models diff --git a/eo_gestion/eo_facture/migrations/0008_auto_20200201_1559.py b/eo_gestion/eo_facture/migrations/0008_auto_20200201_1559.py index 8e124a5..df93bab 100644 --- a/eo_gestion/eo_facture/migrations/0008_auto_20200201_1559.py +++ b/eo_gestion/eo_facture/migrations/0008_auto_20200201_1559.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.9 on 2020-02-01 14:59 from django.db import migrations, models + import eo_gestion.eo_facture.validators diff --git a/eo_gestion/eo_facture/migrations/0009_client_chorus_structure.py b/eo_gestion/eo_facture/migrations/0009_client_chorus_structure.py index 2619459..8165c72 100644 --- a/eo_gestion/eo_facture/migrations/0009_client_chorus_structure.py +++ b/eo_gestion/eo_facture/migrations/0009_client_chorus_structure.py @@ -1,7 +1,7 @@ # Generated by Django 2.2.9 on 2020-02-01 15:00 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/eo_gestion/eo_facture/migrations/0011_contrat_url.py b/eo_gestion/eo_facture/migrations/0011_contrat_url.py index 2ad6523..9271f55 100644 --- a/eo_gestion/eo_facture/migrations/0011_contrat_url.py +++ b/eo_gestion/eo_facture/migrations/0011_contrat_url.py @@ -11,6 +11,8 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( - model_name='contrat', name='url', field=models.URLField(blank=True, verbose_name='URL'), + model_name='contrat', + name='url', + field=models.URLField(blank=True, verbose_name='URL'), ), ] diff --git a/eo_gestion/eo_facture/migrations/0012_client_active.py b/eo_gestion/eo_facture/migrations/0012_client_active.py index 30100b6..d096662 100644 --- a/eo_gestion/eo_facture/migrations/0012_client_active.py +++ b/eo_gestion/eo_facture/migrations/0012_client_active.py @@ -11,6 +11,8 @@ class Migration(migrations.Migration): operations = [ migrations.AddField( - model_name='client', name='active', field=models.BooleanField(default=True, verbose_name='Actif'), + model_name='client', + name='active', + field=models.BooleanField(default=True, verbose_name='Actif'), ), ] diff --git a/eo_gestion/eo_facture/migrations/0013_contrat_tags.py b/eo_gestion/eo_facture/migrations/0013_contrat_tags.py index 78526c7..969d4af 100644 --- a/eo_gestion/eo_facture/migrations/0013_contrat_tags.py +++ b/eo_gestion/eo_facture/migrations/0013_contrat_tags.py @@ -1,6 +1,7 @@ # Generated by Django 2.2.9 on 2020-07-04 14:55 from django.db import migrations + import eo_gestion.eo_facture.taggit diff --git a/eo_gestion/eo_facture/models.py b/eo_gestion/eo_facture/models.py index d5d5288..2f75f51 100644 --- a/eo_gestion/eo_facture/models.py +++ b/eo_gestion/eo_facture/models.py @@ -15,29 +15,28 @@ # along with this program. If not, see . -from decimal import Decimal, ROUND_HALF_UP import datetime from collections import defaultdict +from decimal import ROUND_HALF_UP, Decimal +from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError +from django.core.validators import RegexValidator, validate_email from django.db import models -from django.db.models import Sum, Q, F -from django.core.validators import validate_email, RegexValidator -from django.conf import settings -from django.db.models.signals import post_save, post_delete +from django.db.models import F, Q, Sum from django.db.models.query import QuerySet +from django.db.models.signals import post_delete, post_save from django.template.loader import get_template -from django.utils.translation import ugettext_lazy as _ from django.utils import six from django.utils.timezone import now - +from django.utils.translation import ugettext_lazy as _ from weasyprint import HTML +from eo_gestion.utils import percentage_str from ..eo_banque import models as banque_models -from . import fields, validators, facturx, taggit -from eo_gestion.utils import percentage_str +from . import facturx, fields, taggit, validators validate_telephone = RegexValidator(r"[. 0-9]*") @@ -52,7 +51,6 @@ def today(): return now().date() -@six.python_2_unicode_compatible class Client(models.Model): nom = models.CharField(max_length=255, unique=True) adresse = models.TextField() @@ -73,7 +71,12 @@ class Client(models.Model): blank=True, default='', ) - service_code = models.CharField(max_length=128, db_index=True, blank=True, default='',) + service_code = models.CharField( + max_length=128, + db_index=True, + blank=True, + default='', + ) active = models.BooleanField(verbose_name='Actif', default=True) chorus_structure = models.ForeignKey( @@ -101,7 +104,6 @@ def one_hundred_percent_this_year(): return "%s:100" % now().date().year -@six.python_2_unicode_compatible class Contrat(models.Model): client = models.ForeignKey(Client, related_name="contrats", on_delete=models.CASCADE) intitule = models.CharField(max_length=150) @@ -123,7 +125,7 @@ class Contrat(models.Model): tags = taggit.TaggableManager(blank=True) def montant_facture(self): - return sum([facture.montant for facture in self.factures.non_proforma()]) + return sum(facture.montant for facture in self.factures.non_proforma()) def pourcentage_facture(self): return percentage_str(self.montant_facture(), self.montant()) @@ -132,11 +134,11 @@ class Contrat(models.Model): def montant(self): """ - Montant total d'un contrat, y compris les prestations - optionnelles. Si pas de prestation définie, montant des factures émises. + Montant total d'un contrat, y compris les prestations + optionnelles. Si pas de prestation définie, montant des factures émises. """ if self.prestations.exists(): - return Decimal(sum([p.montant() for p in self.prestations.all()])) + return Decimal(sum(p.montant() for p in self.prestations.all())) else: return self.montant_facture() @@ -145,7 +147,7 @@ class Contrat(models.Model): def nom_client(self): """ - Le nom du client qui a signé ce contrat avec EO. + Le nom du client qui a signé ce contrat avec EO. """ return self.client.nom @@ -159,7 +161,6 @@ class Contrat(models.Model): ordering = ('-id',) -@six.python_2_unicode_compatible class Prestation(models.Model): contrat = models.ForeignKey(Contrat, related_name='prestations', on_delete=models.CASCADE) intitule = models.CharField(max_length=255, verbose_name='Intitulé') @@ -213,7 +214,6 @@ class FactureQuerySet(QuerySet): ) -@six.python_2_unicode_compatible class Facture(models.Model): proforma = models.BooleanField(default=True, verbose_name='Facture proforma', db_index=True) ordre = models.IntegerField( @@ -274,7 +274,7 @@ class Facture(models.Model): def save(self): if self.ordre is None and not self.proforma: self.ordre = self.last_ordre_plus_one() - super(Facture, self).save() + super().save() def clean(self): if not (self.contrat or self.client): @@ -333,7 +333,7 @@ class Facture(models.Model): @property def montant(self): - return sum([ligne.montant for ligne in self.lignes.all()]) + return sum(ligne.montant for ligne in self.lignes.all()) def solde(self): payments = self.payments.all() @@ -395,7 +395,6 @@ class Facture(models.Model): ordering = ("-id",) -@six.python_2_unicode_compatible class Ligne(models.Model): facture = models.ForeignKey(Facture, related_name="lignes", on_delete=models.CASCADE) intitule = models.TextField(blank=True) @@ -428,8 +427,8 @@ class Ligne(models.Model): if self.facture.contrat and not self.facture.proforma: facture = self.facture contrat = facture.contrat - deja_facture = sum([f.montant for f in contrat.factures.exclude(id=facture.id)]) - deja_facture += sum([l.montant for l in facture.lignes.all() if l != self]) + deja_facture = sum(f.montant for f in contrat.factures.exclude(id=facture.id)) + deja_facture += sum(l.montant for l in facture.lignes.all() if l != self) if deja_facture + self.montant > contrat.montant(): errors.append( 'Cette ligne fait dépasser le montant initial du contrat de %.2f %s' @@ -442,7 +441,11 @@ class Ligne(models.Model): ordering = ("order",) def __str__(self): - return "%s pour %s %s" % (self.intitule, self.montant, self.facture.client.monnaie,) + return "%s pour %s %s" % ( + self.intitule, + self.montant, + self.facture.client.monnaie, + ) def encaissements_avec_solde_non_affecte(): @@ -455,7 +458,6 @@ def encaissements_avec_solde_non_affecte(): ) -@six.python_2_unicode_compatible class Payment(models.Model): facture = models.ForeignKey(Facture, related_name='payments', on_delete=models.CASCADE) ligne_banque_pop = models.ForeignKey( @@ -502,7 +504,10 @@ class Payment(models.Model): pass else: deja_affecte = other_payments.aggregate(aggregate).get("montant_affecte") or 0 - if self.montant_affecte is not None and deja_affecte + self.montant_affecte > self.facture.montant_ttc: + if ( + self.montant_affecte is not None + and deja_affecte + self.montant_affecte > self.facture.montant_ttc + ): raise ValidationError( 'Le montant affecté aux différentes factures ' 'est supérieur au montant de l\'encaissement.' diff --git a/eo_gestion/eo_facture/taggit.py b/eo_gestion/eo_facture/taggit.py index d5cf128..f04fffb 100644 --- a/eo_gestion/eo_facture/taggit.py +++ b/eo_gestion/eo_facture/taggit.py @@ -15,7 +15,6 @@ # along with this program. If not, see . from django.forms import ModelMultipleChoiceField, SelectMultiple - from taggit.managers import TaggableManager from taggit.models import Tag @@ -25,6 +24,7 @@ class TagWidget(SelectMultiple): class Media: '''Enable use of Select2 in template''' + js = [ 'xstatic/jquery.js', 'xstatic/jquery-ui.js', @@ -42,9 +42,7 @@ class TagField(ModelMultipleChoiceField): if value is None: return value # TagManager returns Trough model and not the target model, we must adapt - if (hasattr(value, '__iter__') - and not isinstance(value, str) - and not hasattr(value, '_meta')): + if hasattr(value, '__iter__') and not isinstance(value, str) and not hasattr(value, '_meta'): if value and hasattr(value[0], '_meta'): value = list(value.select_related('tag').values_list('tag__name', flat=True)) return value diff --git a/eo_gestion/eo_facture/templatetags/eo_facture.py b/eo_gestion/eo_facture/templatetags/eo_facture.py index d9f76e0..5a5367d 100644 --- a/eo_gestion/eo_facture/templatetags/eo_facture.py +++ b/eo_gestion/eo_facture/templatetags/eo_facture.py @@ -15,21 +15,21 @@ # along with this program. If not, see . -from datetime import date, timedelta, datetime -from decimal import Decimal, InvalidOperation from collections import defaultdict +from datetime import date, datetime, timedelta +from decimal import Decimal, InvalidOperation from django import template -from django.dispatch import receiver from django.db import transaction -from django.db.models.signals import post_save, post_delete -from django.utils.formats import number_format -from django.utils.timesince import timesince -from django.utils.six import text_type +from django.db.models.signals import post_delete, post_save +from django.dispatch import receiver from django.urls import reverse +from django.utils.formats import number_format +from django.utils.six import text_type +from django.utils.timesince import timesince -from eo_gestion.eo_facture.models import Contrat, Facture, DELAI_PAIEMENT, Payment from eo_gestion.eo_banque.models import LigneBanquePop +from eo_gestion.eo_facture.models import DELAI_PAIEMENT, Contrat, Facture, Payment from eo_gestion.utils import percentage from ...decorators import cache @@ -107,7 +107,8 @@ def income(): invoiced_clients_by_year = {} for year in invoiced_by_year_and_client: clients = sorted( - invoiced_by_year_and_client[year].keys(), key=lambda x: -invoiced_by_year_and_client[year][x], + invoiced_by_year_and_client[year].keys(), + key=lambda x: -invoiced_by_year_and_client[year][x], ) invoiced_clients_by_year[year] = clients contracted_by_year = defaultdict(lambda: Decimal(0)) @@ -153,7 +154,7 @@ class StringWithHref(text_type): return text_type.__new__(cls, v) def __init__(self, v, href=None): - super(StringWithHref, self).__init__() + super().__init__() self.href = href @@ -213,7 +214,7 @@ def income_by_clients(year=None): total = sum(total_by_clients.values()) total_invoiced = sum(invoiced_by_clients.values()) total_contracted = sum(contracted_by_clients.values()) - percent_by_clients = dict([(i, Decimal(100) * v / total) for i, v in total_by_clients.items()]) + percent_by_clients = {i: Decimal(100) * v / total for i, v in total_by_clients.items()} clients = sorted(total_by_clients.keys(), key=lambda x: -total_by_clients[x]) running_total = Decimal(0) # compute pareto index @@ -290,7 +291,7 @@ def a_facturer(): } ) contrats_a_facturer.sort(key=lambda x: -x['depuis']) - montant = sum([x['montant'] for x in contrats_a_facturer]) + montant = sum(x['montant'] for x in contrats_a_facturer) return {'a_facturer': contrats_a_facturer, 'montant': montant} diff --git a/eo_gestion/eo_facture/views.py b/eo_gestion/eo_facture/views.py index d8f56bb..d877433 100644 --- a/eo_gestion/eo_facture/views.py +++ b/eo_gestion/eo_facture/views.py @@ -20,13 +20,14 @@ import logging import os.path from django import http -from django.shortcuts import get_object_or_404, redirect from django.conf import settings from django.contrib import messages from django.contrib.admin.models import LogEntry from django.contrib.contenttypes.models import ContentType +from django.shortcuts import get_object_or_404, redirect from eo_gestion.chorus.chorus import push_to_chorus + from .models import Contrat, Facture logger = logging.getLogger(__name__) @@ -46,7 +47,8 @@ def facture_pdf(request, facture_id): pdf = facture.pdf(base_uri=request.build_absolute_uri('/')) if hasattr(settings, 'FACTURE_DIR'): filename = os.path.join( - settings.FACTURE_DIR, '%s-%s.pdf' % (facture.code(), facture.contrat.client.nom.encode('utf8')), + settings.FACTURE_DIR, + '%s-%s.pdf' % (facture.code(), facture.contrat.client.nom.encode('utf8')), ) with open(filename, 'wb') as fd: fd.write(pdf) @@ -90,11 +92,11 @@ def send_to_chorus(request, facture_id): description = '' if 'numeroFluxDepot' in response: - msg = 'Facture {facture} envoyée à ChorusPro'.format(facture=facture.code()) + msg = f'Facture {facture.code()} envoyée à ChorusPro' for key, value in response.items(): - description += ' | {key} - {value}'.format(key=key, value=value) + description += f' | {key} - {value}' else: - msg = 'Échec d\'envoi de la facture {facture} à ChorusPro'.format(facture=facture.code()) + msg = f'Échec d\'envoi de la facture {facture.code()} à ChorusPro' msg += description LogEntry.objects.create( diff --git a/eo_gestion/mule.py b/eo_gestion/mule.py index 8a0f16b..82f8aee 100644 --- a/eo_gestion/mule.py +++ b/eo_gestion/mule.py @@ -18,10 +18,8 @@ import logging import django - from uwsgidecorators import timer - django.setup() logger = logging.getLogger('django.server') diff --git a/eo_gestion/ods.py b/eo_gestion/ods.py index 77e70f8..ac7cb76 100644 --- a/eo_gestion/ods.py +++ b/eo_gestion/ods.py @@ -14,14 +14,13 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see . -from datetime import date, datetime import re import xml.etree.ElementTree as ET import zipfile +from datetime import date, datetime from django.utils.encoding import force_text - NS = { "fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0", "office": "urn:oasis:names:tc:opendocument:xmlns:office:1.0", @@ -46,7 +45,7 @@ def is_number(value): return True -class Workbook(object): +class Workbook: def __init__(self): self.sheets = [] @@ -158,7 +157,7 @@ class Workbook(object): z.close() -class WorkSheet(object): +class WorkSheet: def __init__(self, workbook, name): self.cells = {} self.name = name @@ -184,7 +183,7 @@ class WorkSheet(object): return root -class WorkCell(object): +class WorkCell: def __init__(self, worksheet, value): if value is None: value = "" diff --git a/eo_gestion/settings.py b/eo_gestion/settings.py index d4104c5..0777b6a 100644 --- a/eo_gestion/settings.py +++ b/eo_gestion/settings.py @@ -16,9 +16,8 @@ import os.path -from django.conf import global_settings - import facturx +from django.conf import global_settings # Django settings for facturation project. @@ -40,7 +39,12 @@ DATABASES = { } } -CACHES = {'default': {'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'cache',}} +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', + 'LOCATION': 'cache', + } +} LOGGING = { 'version': 1, @@ -51,10 +55,23 @@ LOGGING = { 'datefmt': '%Y-%m-%d %a %H:%M:%S', }, }, - 'handlers': {'console': {'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'verbose',}}, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + } + }, 'loggers': { - '': {'handlers': ['console'], 'level': 'INFO',}, - 'factur-x': {'level': 'WARNING', 'handlers': [], 'propagate': False,}, + '': { + 'handlers': ['console'], + 'level': 'INFO', + }, + 'factur-x': { + 'level': 'WARNING', + 'handlers': [], + 'propagate': False, + }, }, } @@ -116,7 +133,9 @@ ROOT_URLCONF = "eo_gestion.urls" TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(os.path.dirname(__file__), 'templates'),], + 'DIRS': [ + os.path.join(os.path.dirname(__file__), 'templates'), + ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ diff --git a/eo_gestion/urls.py b/eo_gestion/urls.py index e1df7c5..ca746be 100644 --- a/eo_gestion/urls.py +++ b/eo_gestion/urls.py @@ -15,14 +15,12 @@ # along with this program. If not, see . -from django.conf.urls import include, url - -from django.conf import settings import django.contrib.admin +from django.conf import settings +from django.conf.urls import include, url from django.views.generic.base import RedirectView from . import admin - from .eo_facture.views import api_references django.contrib.admin.autodiscover() diff --git a/eo_gestion/vendor/adminsortable2/admin.py b/eo_gestion/vendor/adminsortable2/admin.py index bb4afa2..4d17d63 100644 --- a/eo_gestion/vendor/adminsortable2/admin.py +++ b/eo_gestion/vendor/adminsortable2/admin.py @@ -1,5 +1,5 @@ -import os import json +import os from itertools import chain from types import MethodType @@ -19,13 +19,10 @@ from django.db.models.functions import Coalesce from django.db.models.signals import post_save, pre_save from django.forms import widgets from django.forms.models import BaseInlineFormSet -from django.http import ( - HttpResponse, HttpResponseBadRequest, - HttpResponseNotAllowed, HttpResponseForbidden -) +from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, HttpResponseNotAllowed +from django.urls import path, reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ -from django.urls import path, reverse __all__ = ['SortableAdminMixin', 'SortableInlineAdminMixin'] @@ -55,12 +52,10 @@ class MovePageActionForm(admin.helpers.ActionForm): required=False, initial=1, widget=widgets.NumberInput(attrs={'id': 'changelist-form-step'}), - label=False + label=False, ) page = forms.IntegerField( - required=False, - widget=widgets.NumberInput(attrs={'id': 'changelist-form-page'}), - label=False + required=False, widget=widgets.NumberInput(attrs={'id': 'changelist-form-page'}), label=False ) @@ -91,7 +86,7 @@ class SortableAdminMixin(SortableAdminBase): return [ os.path.join('adminsortable2', app_label, opts.model_name, 'change_list.html'), os.path.join('adminsortable2', app_label, 'change_list.html'), - 'adminsortable2/change_list.html' + 'adminsortable2/change_list.html', ] def __init__(self, model, admin_site): @@ -111,31 +106,22 @@ class SortableAdminMixin(SortableAdminBase): # Insert the magic field into the same position as the first occurrence # of the default_order_field, or, if not present, at the start try: - self.default_order_field_index = self.list_display.index( - self.default_order_field - ) + self.default_order_field_index = self.list_display.index(self.default_order_field) except ValueError: self.default_order_field_index = 0 self.list_display.insert(self.default_order_field_index, '_reorder') # Remove *all* occurrences of the field from `list_display` if self.list_display and self.default_order_field in self.list_display: - self.list_display = [ - f for f in self.list_display if f != self.default_order_field - ] + self.list_display = [f for f in self.list_display if f != self.default_order_field] # Remove *all* occurrences of the field from `list_display_links` if self.list_display_links and self.default_order_field in self.list_display_links: - self.list_display_links = [ - f for f in self.list_display_links if - f != self.default_order_field - ] + self.list_display_links = [f for f in self.list_display_links if f != self.default_order_field] # Remove *all* occurrences of the field from `ordering` if self.ordering and self.default_order_field in self.ordering: - self.ordering = [ - f for f in self.ordering if f != self.default_order_field - ] + self.ordering = [f for f in self.ordering if f != self.default_order_field] rev_field = '-' + self.default_order_field if self.ordering and rev_field in self.ordering: self.ordering = [f for f in self.ordering if f != rev_field] @@ -148,7 +134,7 @@ class SortableAdminMixin(SortableAdminBase): path( 'adminsortable2_update/', self.admin_site.admin_view(self.update_order), - name=self._get_update_url_name() + name=self._get_update_url_name(), ), ] return my_urls + super().get_urls() @@ -209,10 +195,12 @@ class SortableAdminMixin(SortableAdminBase): def media(self): m = super().media if self.enable_sorting: - m = m + widgets.Media(js=[ - 'adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js', - 'adminsortable2/js/list-sortable.js', - ]) + m = m + widgets.Media( + js=[ + 'adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js', + 'adminsortable2/js/list-sortable.js', + ] + ) return m def _add_reorder_method(self): @@ -223,11 +211,13 @@ class SortableAdminMixin(SortableAdminBase): This can only be done using a function, since it is not possible to add dynamic attributes to bound methods. """ + def func(this, item): html = '' if this.enable_sorting: - html = '
' \ - ' 
'.format(getattr(item, this.default_order_field), item.pk) + html = '
' ' 
'.format( + getattr(item, this.default_order_field), item.pk + ) return mark_safe(html) setattr(func, 'allow_tags', True) @@ -255,36 +245,37 @@ class SortableAdminMixin(SortableAdminBase): endorder = int(request.POST.get('endorder', 0)) moved_items = list(self._move_item(request, startorder, endorder)) return HttpResponse( - json.dumps(moved_items, cls=DjangoJSONEncoder), - content_type='application/json;charset=UTF-8' + json.dumps(moved_items, cls=DjangoJSONEncoder), content_type='application/json;charset=UTF-8' ) def save_model(self, request, obj, form, change): if not change: - setattr( - obj, self.default_order_field, - self.get_max_order(request, obj) + 1 - ) + setattr(obj, self.default_order_field, self.get_max_order(request, obj) + 1) super().save_model(request, obj, form, change) def move_to_exact_page(self, request, queryset): self._bulk_move(request, queryset, self.EXACT) + move_to_exact_page.short_description = _('Move selected to specific page') def move_to_back_page(self, request, queryset): self._bulk_move(request, queryset, self.BACK) + move_to_back_page.short_description = _('Move selected ... pages back') def move_to_forward_page(self, request, queryset): self._bulk_move(request, queryset, self.FORWARD) + move_to_forward_page.short_description = _('Move selected ... pages forward') def move_to_first_page(self, request, queryset): self._bulk_move(request, queryset, self.FIRST) + move_to_first_page.short_description = _('Move selected to first page') def move_to_last_page(self, request, queryset): self._bulk_move(request, queryset, self.LAST) + move_to_last_page.short_description = _('Move selected to last page') def _move_item(self, request, startorder, endorder): @@ -332,10 +323,7 @@ class SortableAdminMixin(SortableAdminBase): move_qs = model.objects.filter(**move_filter).order_by(order_by) move_objs = list(move_qs) for instance in move_objs: - setattr( - instance, rank_field, - getattr(instance, rank_field) + move_delta - ) + setattr(instance, rank_field, getattr(instance, rank_field) + move_delta) # Do not run `instance.save()`, because it will be updated # later in bulk by `move_qs.update`. pre_save.send( @@ -359,10 +347,10 @@ class SortableAdminMixin(SortableAdminBase): setattr(obj, rank_field, endorder) obj.save(update_fields=[rank_field]) - return [{ - 'pk': instance.pk, - 'order': getattr(instance, rank_field) - } for instance in chain(move_objs, [obj])] + return [ + {'pk': instance.pk, 'order': getattr(instance, rank_field)} + for instance in chain(move_objs, [obj]) + ] @staticmethod def get_extra_model_filters(request): @@ -372,9 +360,7 @@ class SortableAdminMixin(SortableAdminBase): return {} def get_max_order(self, request, obj=None): - return self.model.objects.aggregate( - max_order=Coalesce(Max(self.default_order_field), 0) - )['max_order'] + return self.model.objects.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] def _bulk_move(self, request, queryset, method): if not self.enable_sorting: @@ -416,15 +402,9 @@ class SortableAdminMixin(SortableAdminBase): self.message_user(request, msg, level=messages.ERROR) return - endorders_start = getattr( - objects[page.start_index() - 1], self.default_order_field - ) + endorders_start = getattr(objects[page.start_index() - 1], self.default_order_field) endorders_step = -1 if self.order_by.startswith('-') else 1 - endorders = range( - endorders_start, - endorders_start + endorders_step * queryset_size, - endorders_step - ) + endorders = range(endorders_start, endorders_start + endorders_step * queryset_size, endorders_step) if page.number > current_page_number: # Move forward (like drag down) queryset = queryset.reverse() @@ -455,10 +435,11 @@ class PolymorphicSortableAdminMixin(SortableAdminMixin): rather than ``admin.ModelAdmin``, then additionally inherit from ``PolymorphicSortableAdminMixin`` rather than ``SortableAdminMixin``. """ + def get_max_order(self, request, obj=None): - return self.base_model.objects.aggregate( - max_order=Coalesce(Max(self.default_order_field), 0) - )['max_order'] + return self.base_model.objects.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))[ + 'max_order' + ] class CustomInlineFormSetMixin: @@ -466,8 +447,9 @@ class CustomInlineFormSetMixin: self.default_order_direction, self.default_order_field = _get_default_ordering(self.model, self) if self.default_order_field not in self.form.base_fields: - self.form.base_fields[self.default_order_field] = \ - self.model._meta.get_field(self.default_order_field).formfield() + self.form.base_fields[self.default_order_field] = self.model._meta.get_field( + self.default_order_field + ).formfield() self.form.base_fields[self.default_order_field].is_hidden = True self.form.base_fields[self.default_order_field].required = False @@ -476,12 +458,8 @@ class CustomInlineFormSetMixin: super().__init__(*args, **kwargs) def get_max_order(self): - query_set = self.model.objects.filter( - **{self.fk.get_attname(): self.instance.pk} - ) - return query_set.aggregate( - max_order=Coalesce(Max(self.default_order_field), 0) - )['max_order'] + query_set = self.model.objects.filter(**{self.fk.get_attname(): self.instance.pk}) + return query_set.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] def save_new(self, form, commit=True): """ @@ -548,19 +526,18 @@ class SortableInlineAdminMixin(SortableAdminBase): @property def media(self): - shared = ( - super().media + widgets.Media( - js=('adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js', - 'adminsortable2/js/inline-sortable.js'))) + shared = super().media + widgets.Media( + js=('adminsortable2/js/libs/jquery.ui.sortable-1.11.4.js', 'adminsortable2/js/inline-sortable.js') + ) if isinstance(self, admin.StackedInline): return shared + widgets.Media( - js=('adminsortable2/js/inline-sortable.js', - 'adminsortable2/js/inline-stacked.js')) + js=('adminsortable2/js/inline-sortable.js', 'adminsortable2/js/inline-stacked.js') + ) else: # assume TabularInline (don't return None in any case) return shared + widgets.Media( - js=('adminsortable2/js/inline-sortable.js', - 'adminsortable2/js/inline-tabular.js')) + js=('adminsortable2/js/inline-sortable.js', 'adminsortable2/js/inline-tabular.js') + ) @property def template(self): @@ -580,14 +557,11 @@ class CustomGenericInlineFormSet(CustomInlineFormSetMixin, BaseGenericInlineForm **{ self.ct_fk_field.name: self.instance.pk, self.ct_field.name: ContentType.objects.get_for_model( - self.instance, - for_concrete_model=self.for_concrete_model - ) + self.instance, for_concrete_model=self.for_concrete_model + ), } ) - return query_set.aggregate( - max_order=Coalesce(Max(self.default_order_field), 0) - )['max_order'] + return query_set.aggregate(max_order=Coalesce(Max(self.default_order_field), 0))['max_order'] class SortableGenericInlineAdminMixin(SortableInlineAdminMixin): diff --git a/gestion.wsgi b/gestion.wsgi index 96e9b75..5be6b5b 100644 --- a/gestion.wsgi +++ b/gestion.wsgi @@ -4,4 +4,5 @@ import sys sys.path.append(os.path.dirname(__file__)) os.environ['DJANGO_SETTINGS_MODULE'] = 'eo_gestion.settings' import django.core.handlers.wsgi + application = django.core.handlers.wsgi.WSGIHandler() diff --git a/setup.py b/setup.py index 38d2574..2e883f8 100644 --- a/setup.py +++ b/setup.py @@ -3,11 +3,11 @@ import os import subprocess - from distutils.cmd import Command from distutils.command.build import build as _build from distutils.command.sdist import sdist -from setuptools import setup, find_packages + +from setuptools import find_packages, setup from setuptools.command.install_lib import install_lib as _install_lib install_requires = [ @@ -23,11 +23,11 @@ install_requires = [ def get_version(): - '''Use the VERSION, if absent generates a version with git describe, if not - tag exists, take 0.0- and add the length of the commit log. - ''' + """Use the VERSION, if absent generates a version with git describe, if not + tag exists, take 0.0- and add the length of the commit log. + """ if os.path.exists('VERSION'): - with open('VERSION', 'r') as v: + with open('VERSION') as v: return v.read() if os.path.exists('.git'): p = subprocess.Popen( diff --git a/tests/base/conftest.py b/tests/base/conftest.py index cf68317..3b5d0ee 100644 --- a/tests/base/conftest.py +++ b/tests/base/conftest.py @@ -15,11 +15,8 @@ # along with this program. If not, see . import pytest - -from django.core.management import call_command - from django.contrib.auth.models import User - +from django.core.management import call_command DATA = ["tests/fixture.json"] diff --git a/tests/base/test_api.py b/tests/base/test_api.py index 34d9cd8..4f2d57c 100644 --- a/tests/base/test_api.py +++ b/tests/base/test_api.py @@ -14,9 +14,10 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from eo_gestion.eo_facture.models import Contrat from taggit.models import Tag +from eo_gestion.eo_facture.models import Contrat + def test_references(app): gru = Tag.objects.create(name='GRU') diff --git a/tests/conftest.py b/tests/conftest.py index 17f4bea..942c417 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,9 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import pytest - import django_webtest +import pytest @pytest.fixture diff --git a/tests/settings.py b/tests/settings.py index e169ac8..6bb635b 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -16,7 +16,6 @@ import os - ALLOWED_HOSTS = ["localhost"] DATABASES = { diff --git a/tests/test_chorus.py b/tests/test_chorus.py index 1ec0323..75e060b 100644 --- a/tests/test_chorus.py +++ b/tests/test_chorus.py @@ -14,9 +14,8 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import pytest import httmock - +import pytest import requests from eo_gestion.chorus import chorus @@ -47,7 +46,12 @@ def chorus_connection_error(url, request): @httmock.urlmatch() def chorus_error_500(url, request): return httmock.response( - 500, b'Pas content \xe9', headers={'Header Pourri': 'Héhé'.encode('latin1'), 'Header-Ok': 'ok',} + 500, + b'Pas content \xe9', + headers={ + 'Header Pourri': 'Héhé'.encode('latin1'), + 'Header-Ok': 'ok', + }, ) @@ -69,5 +73,8 @@ def test_push_to_chorus_error_500(): assert result == { 'http.response.status-code': 500, 'http.response.body': "b'Pas content \\xe9'", - 'http.response.headers': {'Header Pourri': 'H�h�', 'Header-Ok': 'ok',}, + 'http.response.headers': { + 'Header Pourri': 'H�h�', + 'Header-Ok': 'ok', + }, } diff --git a/tests/test_facturx.py b/tests/test_facturx.py index 8ec5227..d3e0c29 100644 --- a/tests/test_facturx.py +++ b/tests/test_facturx.py @@ -16,12 +16,12 @@ import datetime import io -import pytest import xml.etree.ElementTree as ET import facturx +import pytest -from eo_gestion.eo_facture.facturx import to_pdfa, add_facturx_from_bytes +from eo_gestion.eo_facture.facturx import add_facturx_from_bytes, to_pdfa @pytest.fixture @@ -88,7 +88,14 @@ def test_add_facturx_from_bytes(fake_invoice_bytes): ['PostalTradeAddress', ['CountryID', 'FR']], ['SpecifiedTaxRegistration', ['ID', 'FR09491081899']], ], - ['BuyerTradeParty', ['Name', 'RGFIPD'], ['SpecifiedLegalOrganization', ['ID', '1234'],],], + [ + 'BuyerTradeParty', + ['Name', 'RGFIPD'], + [ + 'SpecifiedLegalOrganization', + ['ID', '1234'], + ], + ], ['BuyerOrderReferencedDocument', ['IssuerAssignedID', '5678']], ['ContractReferencedDocument', ['IssuerAssignedID', 'ABCD']], ], diff --git a/tests/test_forms.py b/tests/test_forms.py index fad5221..eef0ca7 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -17,6 +17,7 @@ from datetime import date, timedelta from django.contrib.auth.models import User + from eo_gestion.eo_facture.forms import FactureForm from eo_gestion.eo_facture.models import Client, Contrat @@ -86,4 +87,4 @@ def test_facture_form(db, freezer): data['initial-emission'] = '2019-01-03' form = FactureForm(data=dict(data, proforma='true', emission='2019-01-01'), instance=facture) assert not form.is_valid(), form.errors - assert set(form.errors) == set(['proforma', 'emission']) + assert set(form.errors) == {'proforma', 'emission'} diff --git a/tox.ini b/tox.ini index efd2db5..c35de06 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/barbacompta/{env:BRANCH_NAME:} -envlist = py3-pylint +envlist = py3-pylint,code-style [testenv] setenv = @@ -29,6 +29,13 @@ setenv = commands = ./manage.py {posargs:--help} +[testenv:code-style] +skip_install = true +deps = + pre-commit +commands = + pre-commit run --all-files --show-diff-on-failure + [pytest] filterwarnings= ignore:Using or importing the ABCs from