misc: add and apply pre-commit hooks (#58764)
This commit is contained in:
parent
63aafcdb1f
commit
b4475d710f
|
@ -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']
|
|
@ -15,9 +15,9 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
|
|
|
@ -14,17 +14,16 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
import eo_gestion.admin
|
||||
|
||||
from . import models
|
||||
|
|
|
@ -15,14 +15,12 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -15,29 +15,28 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
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.'
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
|
|
@ -15,21 +15,21 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
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}
|
||||
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
import logging
|
||||
|
||||
import django
|
||||
|
||||
from uwsgidecorators import timer
|
||||
|
||||
|
||||
django.setup()
|
||||
|
||||
logger = logging.getLogger('django.server')
|
||||
|
|
|
@ -14,14 +14,13 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 = ""
|
||||
|
|
|
@ -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': [
|
||||
|
|
|
@ -15,14 +15,12 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
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()
|
||||
|
|
|
@ -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 = '<div class="drag js-reorder-{1}" order="{0}">' \
|
||||
' </div>'.format(getattr(item, this.default_order_field), item.pk)
|
||||
html = '<div class="drag js-reorder-{1}" order="{0}">' ' </div>'.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):
|
||||
|
|
|
@ -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()
|
||||
|
|
12
setup.py
12
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(
|
||||
|
|
|
@ -15,11 +15,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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"]
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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')
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
import django_webtest
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
import os
|
||||
|
||||
|
||||
ALLOWED_HOSTS = ["localhost"]
|
||||
|
||||
DATABASES = {
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<EFBFBD>h<EFBFBD>', 'Header-Ok': 'ok',},
|
||||
'http.response.headers': {
|
||||
'Header Pourri': 'H<EFBFBD>h<EFBFBD>',
|
||||
'Header-Ok': 'ok',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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']],
|
||||
],
|
||||
|
|
|
@ -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'}
|
||||
|
|
9
tox.ini
9
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
|
||||
|
|
Loading…
Reference in New Issue