migrate to python3 (#38507)
* add basic tests and big anonymized fixture * add missing copyright notices
This commit is contained in:
parent
2c85151c22
commit
e49e51a9e5
|
@ -1,6 +1,24 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 csv
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse
|
||||
from django.utils.six import text_type
|
||||
|
||||
|
||||
def export_as_csv(modeladmin, request, queryset):
|
||||
|
@ -10,8 +28,8 @@ def export_as_csv(modeladmin, request, queryset):
|
|||
if not request.user.is_staff:
|
||||
raise PermissionDenied
|
||||
opts = modeladmin.model._meta
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename=%s.csv' % unicode(opts).replace('.', '_')
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = "attachment; filename=%s.csv" % text_type(opts).replace(".", "_")
|
||||
writer = csv.writer(response)
|
||||
field_names = [field.name for field in opts.fields]
|
||||
m2m_field_names = [m2m_field.name for m2m_field in opts.many_to_many]
|
||||
|
@ -19,12 +37,12 @@ def export_as_csv(modeladmin, request, queryset):
|
|||
writer.writerow(field_names + m2m_field_names)
|
||||
# Write data rows
|
||||
for obj in queryset:
|
||||
values = [unicode(getattr(obj, field)) for field in field_names]
|
||||
values = [text_type(getattr(obj, field)) for field in field_names]
|
||||
for m2m_field in m2m_field_names:
|
||||
value = getattr(obj, m2m_field)
|
||||
value = u','.join(map(unicode, value.all()))
|
||||
values.append(unicode(value))
|
||||
writer.writerow(map(lambda x: unicode.encode(x, 'utf8'), values))
|
||||
value = u",".join(map(text_type, value.all()))
|
||||
values.append(text_type(value))
|
||||
writer.writerow(map(lambda x: text_type.encode(x, "utf8"), values))
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
@ -1,8 +1,25 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.contrib.auth.models import *
|
||||
from django.contrib.auth.admin import *
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.admin import UserAdmin, GroupAdmin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.http import urlencode
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
||||
|
@ -10,19 +27,19 @@ from django.views.decorators.cache import never_cache
|
|||
class EOGestionAdminSite(admin.AdminSite):
|
||||
@never_cache
|
||||
def login(self, request, extra_context=None):
|
||||
if settings.MELLON_IDENTITY_PROVIDERS:
|
||||
next_url = request.GET.get(REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL or '/')
|
||||
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 = "/accounts/mellon/login/?{0}".format(query)
|
||||
return HttpResponseRedirect(url)
|
||||
return super(EOGestionAdminSite, self).login(request, extra_context=extra_context)
|
||||
|
||||
@never_cache
|
||||
def logout(self, request, extra_context=None):
|
||||
if settings.MELLON_IDENTITY_PROVIDERS:
|
||||
next_url = request.GET.get(REDIRECT_FIELD_NAME, settings.LOGIN_REDIRECT_URL or '/')
|
||||
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 = "/accounts/mellon/logout/?{0}".format(query)
|
||||
return HttpResponseRedirect(url)
|
||||
return super(EOGestionAdminSite, self).logout(request, extra_context=extra_context)
|
||||
|
||||
|
|
|
@ -1,13 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 django.contrib import admin
|
||||
from django.contrib.contenttypes.admin import GenericStackedInline
|
||||
|
||||
import models
|
||||
import eo_gestion.admin
|
||||
from .. import actions
|
||||
from eo_gestion.eo_facture.admin import PaymentInline
|
||||
from eo_gestion.eo_facture.templatetags.eo_facture import amountformat
|
||||
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
|
||||
|
||||
|
||||
class CommentaireInlineAdmin(GenericStackedInline):
|
||||
|
@ -49,13 +65,13 @@ class LigneBanquePopAdmin(admin.ModelAdmin):
|
|||
|
||||
def get_queryset(self, request):
|
||||
qs = super(LigneBanquePopAdmin, self).get_queryset(request)
|
||||
qs = qs.prefetch_related('payments')
|
||||
qs = qs.prefetch_related("payments")
|
||||
return qs
|
||||
|
||||
|
||||
admin.site.register(models.LigneBanquePop, LigneBanquePopAdmin)
|
||||
admin.site.register(models.SoldeBanquePop, list_display=['compte', 'date', 'montant'])
|
||||
|
||||
eo_gestion.admin.site.register(models.LigneBanquePop, LigneBanquePopAdmin)
|
||||
eo_gestion.admin.site.register(models.SoldeBanquePop, list_display=['compte', 'date', 'montant'])
|
||||
eo_gestion.admin.site.register(models.Commentaire)
|
||||
eo_gestion_admin.site.register(models.LigneBanquePop, LigneBanquePopAdmin)
|
||||
eo_gestion_admin.site.register(models.SoldeBanquePop, list_display=['compte', 'date', 'montant'])
|
||||
eo_gestion_admin.site.register(models.Commentaire)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from optparse import make_option
|
||||
import sys
|
||||
import csv
|
||||
from datetime import datetime, date
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
# import xml.etree.ElementTree as etree
|
||||
import csv
|
||||
from datetime import datetime
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from eo_gestion.eo_banque.models import *
|
||||
from eo_gestion.eo_banque.models import LigneBanquePop
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -24,36 +22,36 @@ class Command(BaseCommand):
|
|||
args = '<csv_file> <csv_file>...'
|
||||
help = 'Charge les fichiers CSVs'
|
||||
HEADER = [
|
||||
u"Compte",
|
||||
u"Date de comptabilisation",
|
||||
u"Date opération",
|
||||
u"Libellé",
|
||||
u"Référence",
|
||||
u"Date valeur",
|
||||
u"Montant",
|
||||
'Compte',
|
||||
'Date de comptabilisation',
|
||||
'Date opération',
|
||||
'Libellé',
|
||||
'Référence',
|
||||
'Date valeur',
|
||||
'Montant',
|
||||
]
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('args', nargs='+')
|
||||
parser.add_argument("args", nargs="+")
|
||||
|
||||
def to_date(self, str):
|
||||
try:
|
||||
return datetime.strptime(str, '%Y/%m/%d').date()
|
||||
except:
|
||||
except Exception:
|
||||
return datetime.strptime(str, '%d/%m/%Y').date()
|
||||
|
||||
def to_utf8(self, iter):
|
||||
return map(lambda x: x.decode('latin1'), iter)
|
||||
|
||||
def load_one_file(self, csv_file_path):
|
||||
for delimiter in [';', '\t']:
|
||||
csv_file = file(csv_file_path)
|
||||
for delimiter in [";", "\t"]:
|
||||
csv_file = open(csv_file_path)
|
||||
csv_lines = csv.reader(csv_file, delimiter=delimiter)
|
||||
first_line = self.to_utf8(csv_lines.next())
|
||||
if first_line == self.HEADER:
|
||||
break
|
||||
else:
|
||||
raise CommandError('Invalid CSV file header')
|
||||
raise CommandError("Invalid CSV file header")
|
||||
counter = 0
|
||||
loaded = 0
|
||||
for line in csv_lines:
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from optparse import make_option
|
||||
import sys
|
||||
import csv
|
||||
from datetime import datetime, date
|
||||
# coding: utf-8
|
||||
|
||||
# import xml.etree.ElementTree as etree
|
||||
from datetime import date
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from eo_gestion.eo_banque.models import *
|
||||
from eo_gestion.eo_banque.models import SoldeBanquePop
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -20,8 +16,8 @@ class Command(BaseCommand):
|
|||
can_import_django_settings = True
|
||||
output_transaction = True
|
||||
requires_system_checks = True
|
||||
args = '<csv_file> <csv_file>...'
|
||||
help = 'Charge le solde courant'
|
||||
args = "<csv_file> <csv_file>..."
|
||||
help = "Charge le solde courant"
|
||||
|
||||
@transaction.atomic
|
||||
def handle(self, compte, montant, **options):
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import print_function
|
||||
|
||||
from optparse import make_option
|
||||
import sys
|
||||
import csv
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
from datetime import timedelta, date, datetime
|
||||
|
||||
# import xml.etree.ElementTree as etree
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from eo_gestion.eo_banque.models import *
|
||||
from eo_gestion.eo_banque.models import solde
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
|
|
@ -1,10 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 datetime import date
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import six
|
||||
|
||||
|
||||
def solde(t=None):
|
||||
|
@ -12,7 +29,7 @@ def solde(t=None):
|
|||
if t is None:
|
||||
t = date.today()
|
||||
try:
|
||||
s = SoldeBanquePop.objects.latest('date')
|
||||
s = SoldeBanquePop.objects.latest("date")
|
||||
except SoldeBanquePop.DoesNotExist:
|
||||
return 0
|
||||
m = s.montant
|
||||
|
@ -29,17 +46,19 @@ def solde(t=None):
|
|||
return m
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class Commentaire(models.Model):
|
||||
contenu = models.TextField()
|
||||
content_type = models.ForeignKey(ContentType)
|
||||
object_id = models.PositiveIntegerField()
|
||||
creation = models.DateTimeField(auto_now_add=True)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
def __unicode__(self):
|
||||
return u'Commentaire créé le %s' % self.creation
|
||||
def __str__(self):
|
||||
return u"Commentaire créé le %s" % self.creation
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class LigneBanquePop(models.Model):
|
||||
'''
|
||||
Une ligne de notre relevé de compte Banque Populaire
|
||||
|
@ -58,7 +77,7 @@ class LigneBanquePop(models.Model):
|
|||
('compte', 'date_comptabilisation', 'date_operation', 'libelle', 'reference', 'montant'),
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return "%(date_valeur)s %(libelle)s %(montant)s" % self.__dict__
|
||||
|
||||
def montant_non_affecte(self):
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 datetime import date, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -5,7 +21,8 @@ from django import template
|
|||
from django.db.models import Sum
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from eo_gestion.eo_banque.models import *
|
||||
from ..models import LigneBanquePop, solde
|
||||
from ...decorators import cache
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -17,10 +34,10 @@ def month_before(date):
|
|||
return date.replace(month=date.month - 1)
|
||||
|
||||
|
||||
@register.inclusion_tag('eo_banque/finances.html')
|
||||
@register.inclusion_tag("eo_banque/finances.html")
|
||||
def finances(month=3):
|
||||
dates = [date.today().replace(day=1)]
|
||||
for i in xrange(1, month):
|
||||
for i in range(1, month):
|
||||
dates.append(month_before(dates[-1]))
|
||||
e = []
|
||||
lignes = LigneBanquePop.objects.filter(date_valeur__gte=min(dates), montant__gt=0)
|
||||
|
@ -64,8 +81,8 @@ def finances(month=3):
|
|||
|
||||
@register.inclusion_tag('eo_banque/total.html', takes_context=True)
|
||||
def total(context):
|
||||
qs = context['cl'].get_queryset(context['request'])
|
||||
ls = [ligne[0] for ligne in qs.values_list('montant')]
|
||||
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])
|
||||
total = sum(ls)
|
||||
|
@ -105,21 +122,22 @@ def week_start_and_end(year, month):
|
|||
]
|
||||
|
||||
|
||||
@register.inclusion_tag('admin/date_hierarchy.html')
|
||||
@register.inclusion_tag("admin/date_hierarchy.html")
|
||||
def eo_banque_date_hierarchy(cl):
|
||||
if cl.date_hierarchy:
|
||||
field_name = cl.date_hierarchy
|
||||
year_field = '%s__year' % field_name
|
||||
month_field = '%s__month' % field_name
|
||||
day_field = '%s__day' % field_name
|
||||
field_generic = '%s__' % field_name
|
||||
field_gte = '%s__gte' % field_name
|
||||
field_lte = '%s__lte' % field_name
|
||||
year_field = "%s__year" % field_name
|
||||
month_field = "%s__month" % field_name
|
||||
day_field = "%s__day" % field_name
|
||||
field_generic = "%s__" % field_name
|
||||
field_gte = "%s__gte" % field_name
|
||||
field_lte = "%s__lte" % field_name
|
||||
year_lookup = cl.params.get(year_field)
|
||||
month_lookup = cl.params.get(month_field)
|
||||
day_lookup = cl.params.get(day_field)
|
||||
|
||||
link = lambda d: cl.get_query_string(d, [field_generic])
|
||||
def link(d):
|
||||
return cl.get_query_string(d, [field_generic])
|
||||
|
||||
if year_lookup and month_lookup and not day_lookup:
|
||||
choices = []
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
# Create your views here.
|
|
@ -1,15 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import datetime as dt
|
||||
|
||||
from django.contrib import admin
|
||||
from django.conf.urls import url
|
||||
from django.shortcuts import render
|
||||
from django.template import RequestContext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.admin.options import BaseModelAdmin
|
||||
import django.http as http
|
||||
from django.contrib.humanize.templatetags.humanize import ordinal
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
from django.utils.html import format_html
|
||||
|
||||
from . import forms, models, views
|
||||
from .templatetags.eo_facture import amountformat
|
||||
|
@ -51,11 +70,11 @@ class PaymentInlineFormset(BaseInlineFormSet):
|
|||
super(PaymentInlineFormset, self).__init__(*args, **kwargs)
|
||||
for form in self.forms:
|
||||
if form.instance.id is None:
|
||||
field = form.fields['ligne_banque_pop']
|
||||
if not hasattr(field, 'parent_instance'):
|
||||
field = form.fields["ligne_banque_pop"]
|
||||
if not hasattr(field, "parent_instance"):
|
||||
field.queryset = models.encaissements_avec_solde_non_affecte()
|
||||
field = form.fields['facture']
|
||||
if not hasattr(field, 'parent_instance'):
|
||||
field = form.fields["facture"]
|
||||
if not hasattr(field, "parent_instance"):
|
||||
field.queryset = models.Facture.objects.avec_solde()
|
||||
|
||||
|
||||
|
@ -73,7 +92,7 @@ class LigneInline(SelectRelatedMixin, admin.TabularInline):
|
|||
model = models.Ligne
|
||||
show_url = True
|
||||
original = False
|
||||
verbose_name_plural = u"Lignes de facture (vous pouvez les réordonner par drag&drop)"
|
||||
verbose_name_plural = "Lignes de facture (vous pouvez les réordonner par drag&drop)"
|
||||
extra = 0
|
||||
|
||||
|
||||
|
@ -97,12 +116,11 @@ class ClientAdmin(admin.ModelAdmin):
|
|||
|
||||
def show_client(obj):
|
||||
url = reverse('admin:eo_facture_client_change', args=[obj.client.id])
|
||||
return u'<a href="%s">%s</a>' % (url, obj.client)
|
||||
return format_html('<a href="{0}">{1}</a>', url, obj.client)
|
||||
|
||||
|
||||
show_client.short_description = u'Client'
|
||||
show_client.short_description = 'Client'
|
||||
show_client.admin_order_field = 'client'
|
||||
show_client.allow_tags = True
|
||||
|
||||
|
||||
class ContratAdmin(LookupAllowed, admin.ModelAdmin):
|
||||
|
@ -111,7 +129,7 @@ class ContratAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
PrestationInline,
|
||||
]
|
||||
list_display = [
|
||||
'__unicode__',
|
||||
"__str__",
|
||||
show_client,
|
||||
'column_montant',
|
||||
'pourcentage_facture',
|
||||
|
@ -132,8 +150,8 @@ class ContratAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
|
||||
def get_queryset(self, request):
|
||||
qs = super(ContratAdmin, self).get_queryset(request)
|
||||
qs = qs.prefetch_related('prestations')
|
||||
qs = qs.prefetch_related('factures__lignes')
|
||||
qs = qs.prefetch_related("prestations")
|
||||
qs = qs.prefetch_related("factures__lignes")
|
||||
return qs
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
|
@ -164,7 +182,7 @@ class ContratAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
'opts': self.model._meta,
|
||||
}
|
||||
|
||||
return render(request, 'admin/eo_facture/contrat/duplicate.html', context=context)
|
||||
return render(request, "admin/eo_facture/contrat/duplicate.html", context=context)
|
||||
|
||||
def get_urls(self):
|
||||
urls = super(ContratAdmin, self).get_urls()
|
||||
|
@ -174,11 +192,8 @@ class ContratAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
|
||||
|
||||
def index(facture):
|
||||
return ordinal(facture.index())
|
||||
|
||||
|
||||
return format_html('{0}', ordinal(facture.index()))
|
||||
index.short_description = "Ordre"
|
||||
index.allow_tags = True
|
||||
|
||||
|
||||
class FactureAdmin(LookupAllowed, admin.ModelAdmin):
|
||||
|
@ -222,33 +237,30 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
|
||||
def column_montant(self, obj):
|
||||
return amountformat(obj.montant)
|
||||
|
||||
column_montant.short_description = 'Montant'
|
||||
|
||||
def column_montant_ttc(self, obj):
|
||||
return amountformat(obj.montant_ttc)
|
||||
|
||||
column_montant_ttc.short_description = 'Montant TTC'
|
||||
|
||||
def column_solde(self, obj):
|
||||
return amountformat(obj.solde())
|
||||
|
||||
column_solde.short_description = 'Solde'
|
||||
|
||||
def get_queryset(self, request):
|
||||
from django.db import connection
|
||||
|
||||
qs = super(FactureAdmin, self).get_queryset(request)
|
||||
qs = qs.prefetch_related('lignes__facture__client')
|
||||
qs = qs.prefetch_related('payments__ligne_banque_pop')
|
||||
qs = qs.prefetch_related('contrat__factures')
|
||||
if connection.vendor == 'postgresql':
|
||||
qs = qs.extra({'year': 'EXTRACT(year FROM emission)'})
|
||||
elif connection.vendor == 'sqlite':
|
||||
qs = qs.extra({'year': "strftime('%Y', emission)"})
|
||||
qs = qs.prefetch_related("lignes__facture__client")
|
||||
qs = qs.prefetch_related("payments__ligne_banque_pop")
|
||||
qs = qs.prefetch_related("contrat__factures")
|
||||
if connection.vendor == "postgresql":
|
||||
qs = qs.extra({"year": "EXTRACT(year FROM emission)"})
|
||||
elif connection.vendor == "sqlite":
|
||||
qs = qs.extra({"year": "strftime('%Y', emission)"})
|
||||
else:
|
||||
qs = qs.extra({'year': 'YEAR(emission)'})
|
||||
qs = qs.order_by('-year', '-proforma', '-ordre')
|
||||
qs = qs.extra({"year": "YEAR(emission)"})
|
||||
qs = qs.order_by("-year", "-proforma", "-ordre")
|
||||
return qs
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
|
@ -257,11 +269,11 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
obj.save()
|
||||
|
||||
class Media:
|
||||
js = ('js/jquery.js', 'js/jquery-ui.js', 'js/menu-sort.js')
|
||||
js = ("js/jquery.js", "js/jquery-ui.js", "js/menu-sort.js")
|
||||
|
||||
def add_simple(self, request):
|
||||
context = {}
|
||||
if request.method == 'POST':
|
||||
if request.method == "POST":
|
||||
form = forms.RapidFactureForm(request=request, data=request.POST)
|
||||
if form.is_valid():
|
||||
return http.HttpResponseRedirect(
|
||||
|
@ -276,7 +288,7 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
'opts': self.model._meta,
|
||||
}
|
||||
|
||||
return render(request, 'admin/eo_facture/facture/add_simple.html', context=context)
|
||||
return render(request, "admin/eo_facture/facture/add_simple.html", context=context)
|
||||
|
||||
def get_urls(self):
|
||||
urls = super(FactureAdmin, self).get_urls()
|
||||
|
@ -298,19 +310,15 @@ class FactureAdmin(LookupAllowed, admin.ModelAdmin):
|
|||
|
||||
def show_client(self, obj):
|
||||
url = reverse('admin:eo_facture_client_change', args=[obj.client.id])
|
||||
return u'<a href="%s">%s</a>' % (url, obj.client)
|
||||
|
||||
return format_html('<a href="{0}">{1}</a>', url, obj.client)
|
||||
show_client.short_description = u'Client'
|
||||
show_client.allow_tags = True
|
||||
|
||||
def show_contrat(self, obj):
|
||||
if obj.contrat:
|
||||
url = reverse('admin:eo_facture_contrat_change', args=[obj.contrat.id])
|
||||
return u'<a href="%s">%s</a>' % (url, obj.contrat)
|
||||
return format_html('<a href="{0}">{1}</a>', url, obj.contrat)
|
||||
return None
|
||||
|
||||
show_contrat.short_description = u'Contrat'
|
||||
show_contrat.allow_tags = True
|
||||
show_contrat.short_description = 'Contrat'
|
||||
|
||||
|
||||
class PaymentAdmin(LookupAllowed, admin.ModelAdmin, CommonPaymentInline):
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -5,6 +25,8 @@ from django.core import validators
|
|||
from django.db import models
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.six import text_type
|
||||
|
||||
|
||||
|
||||
class PercentagePerYear(list):
|
||||
|
@ -17,8 +39,8 @@ class PercentagePerYear(list):
|
|||
|
||||
class EuroField(models.DecimalField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['max_digits'] = 8
|
||||
kwargs['decimal_places'] = 2
|
||||
kwargs["max_digits"] = 8
|
||||
kwargs["decimal_places"] = 2
|
||||
super(EuroField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
|
@ -26,7 +48,7 @@ def assertion_error_to_validation_error(fun):
|
|||
def f(*args, **kwargs):
|
||||
try:
|
||||
return fun(*args, **kwargs)
|
||||
except (AssertionError, ValueError), e:
|
||||
except (AssertionError, ValueError) as e:
|
||||
raise ValidationError(*e.args)
|
||||
|
||||
return f
|
||||
|
@ -37,22 +59,22 @@ def check_percentage_per_year(value):
|
|||
years = [a for a, b in value]
|
||||
percentages = [b for a, b in value]
|
||||
# ordered
|
||||
assert years == sorted(years), 'years are not ordered'
|
||||
assert years == sorted(years), "years are not ordered"
|
||||
# sum equals 100
|
||||
assert sum(percentages) == 1, 'percentage does not sum to 100'
|
||||
assert sum(percentages) == 1, "percentage does not sum to 100"
|
||||
# no duplicate year
|
||||
assert len(years) == len(set(years)), 'years are not unique'
|
||||
assert len(years) == len(set(years)), "years are not unique"
|
||||
# consecutive
|
||||
assert years == range(years[0], years[0] + len(years)), 'years are not consecutive'
|
||||
|
||||
|
||||
def parse_percentage_per_year(value):
|
||||
msg = _('field must be numeric values separated by commas')
|
||||
values = value.split(',')
|
||||
msg = _("field must be numeric values separated by commas")
|
||||
values = value.split(",")
|
||||
decimals = []
|
||||
for value in values:
|
||||
try:
|
||||
year, decimal = value.split(':')
|
||||
year, decimal = value.split(":")
|
||||
except ValueError:
|
||||
raise ValidationError(msg)
|
||||
try:
|
||||
|
@ -77,8 +99,8 @@ class PercentagePerYearFormField(forms.Field):
|
|||
return None
|
||||
if isinstance(PercentagePerYear, value):
|
||||
return value
|
||||
if not isinstance(unicode, value):
|
||||
raise ValidationError(self.default_error_messages['invalid'])
|
||||
if not isinstance(text_type, value):
|
||||
raise ValidationError(self.default_error_messages["invalid"])
|
||||
return parse_percentage_per_year(value)
|
||||
|
||||
|
||||
|
@ -86,7 +108,7 @@ class PercentagePerYearField(models.Field):
|
|||
default_validators = [check_percentage_per_year]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['max_length'] = 64
|
||||
kwargs["max_length"] = 64
|
||||
super(PercentagePerYearField, self).__init__(*args, **kwargs)
|
||||
|
||||
def from_db_value(self, value, expression, connection, context):
|
||||
|
@ -97,12 +119,12 @@ class PercentagePerYearField(models.Field):
|
|||
return value
|
||||
if value is not None:
|
||||
percentage_per_year = []
|
||||
pairs = value.split(',')
|
||||
pairs = value.split(",")
|
||||
for pair in pairs:
|
||||
try:
|
||||
year, percentage = map(value.__class__.strip, pair.split(':'))
|
||||
year, percentage = map(value.__class__.strip, pair.split(":"))
|
||||
except ValueError:
|
||||
raise ValidationError(PercentagePerYearFormField.default_error_messages['invalid'])
|
||||
raise ValidationError(PercentagePerYearFormField.default_error_messages["invalid"])
|
||||
year = int(year)
|
||||
percentage = Decimal(percentage) / Decimal(100)
|
||||
percentage_per_year.append((year, percentage))
|
||||
|
@ -114,16 +136,16 @@ class PercentagePerYearField(models.Field):
|
|||
|
||||
def get_prep_value(self, value):
|
||||
if isinstance(value, PercentagePerYear):
|
||||
return unicode(value)
|
||||
return text_type(value)
|
||||
elif value is not None:
|
||||
return unicode(parse_percentage_per_year(value))
|
||||
return text_type(parse_percentage_per_year(value))
|
||||
return value
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'CharField'
|
||||
return "CharField"
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': PercentagePerYearFormField}
|
||||
defaults = {"form_class": PercentagePerYearFormField}
|
||||
defaults.update(kwargs)
|
||||
return super(PercentagePerYearField, self).formfield(**kwargs)
|
||||
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
from decimal import *
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
from django.db.transaction import atomic
|
||||
|
@ -15,7 +34,7 @@ class RapidFactureForm(forms.Form):
|
|||
contrat = forms.ModelChoiceField(queryset=models.Contrat.objects.all())
|
||||
pourcentage = forms.ChoiceField(choices=pourcentages, initial='1')
|
||||
solde = forms.BooleanField(
|
||||
required=False, label=u'Ignorer le pourcentage, et solder le contrat en une ligne.'
|
||||
required=False, label='Ignorer le pourcentage, et solder le contrat en une ligne.'
|
||||
)
|
||||
|
||||
def __init__(self, request=None, *args, **kwargs):
|
||||
|
@ -30,18 +49,18 @@ class RapidFactureForm(forms.Form):
|
|||
facture = models.Facture(contrat=contrat, creator=self.request.user)
|
||||
try:
|
||||
facture.clean()
|
||||
except ValidationError, e:
|
||||
except ValidationError as e:
|
||||
raise forms.ValidationError(*e.args)
|
||||
facture.save()
|
||||
self.cleaned_data['facture'] = facture
|
||||
self.cleaned_data["facture"] = facture
|
||||
lignes = []
|
||||
errors = []
|
||||
if solde:
|
||||
montant_solde = contrat.solde()
|
||||
if montant_solde == 0:
|
||||
raise ValidationError(u'Le solde du contrat est déjà nul.')
|
||||
raise ValidationError('Le solde du contrat est déjà nul.')
|
||||
models.Ligne.objects.create(
|
||||
facture=facture, intitule=u'Solde', prix_unitaire_ht=montant_solde, quantite=Decimal(1)
|
||||
facture=facture, intitule='Solde', prix_unitaire_ht=montant_solde, quantite=Decimal(1)
|
||||
)
|
||||
else:
|
||||
for prestation in contrat.prestations.all():
|
||||
|
@ -54,9 +73,9 @@ class RapidFactureForm(forms.Form):
|
|||
lignes.append(ligne)
|
||||
try:
|
||||
ligne.clean()
|
||||
except ValidationError, e:
|
||||
error = u'Il y a un problème avec la ligne « %s »: ' % prestation.intitule
|
||||
error += '; '.join(map(lambda x: x.rstrip('.'), e.messages))
|
||||
except ValidationError as e:
|
||||
error = u"Il y a un problème avec la ligne « %s »: " % prestation.intitule
|
||||
error += "; ".join(map(lambda x: x.rstrip("."), e.messages))
|
||||
errors.append(error)
|
||||
if errors:
|
||||
raise forms.ValidationError(errors)
|
||||
|
@ -68,7 +87,7 @@ class RapidFactureForm(forms.Form):
|
|||
|
||||
class DuplicateContractForm(forms.Form):
|
||||
contrat = forms.ModelChoiceField(queryset=models.Contrat.objects.all())
|
||||
new_intitule = forms.CharField(max_length=150, label=u'Nouvel intitulé')
|
||||
new_intitule = forms.CharField(max_length=150, label=u"Nouvel intitulé")
|
||||
|
||||
def __init__(self, request=None, *args, **kwargs):
|
||||
self.request = request
|
||||
|
@ -87,10 +106,10 @@ class DuplicateContractForm(forms.Form):
|
|||
)
|
||||
try:
|
||||
new_contrat.clean()
|
||||
except ValidationError, e:
|
||||
except ValidationError as e:
|
||||
raise forms.ValidationError(*e.args)
|
||||
new_contrat.save()
|
||||
self.cleaned_data['new_contrat'] = new_contrat
|
||||
self.cleaned_data["new_contrat"] = new_contrat
|
||||
for prestation in contrat.prestations.all():
|
||||
new_prestation = prestation.duplicate()
|
||||
new_prestation.contrat = new_contrat
|
||||
|
@ -102,31 +121,31 @@ class LigneForm(forms.ModelForm):
|
|||
class Meta:
|
||||
exclude = ()
|
||||
model = models.Ligne
|
||||
localized_fields = ('quantite', 'prix_unitaire_ht', 'taux_tva')
|
||||
localized_fields = ("quantite", "prix_unitaire_ht", "taux_tva")
|
||||
|
||||
|
||||
class PrestationForm(forms.ModelForm):
|
||||
class Meta:
|
||||
exclude = ()
|
||||
model = models.Prestation
|
||||
localized_fields = ('quantite', 'prix_unitaire_ht')
|
||||
localized_fields = ("quantite", "prix_unitaire_ht")
|
||||
|
||||
|
||||
class FactureForm(forms.ModelForm):
|
||||
class Meta:
|
||||
exclude = ()
|
||||
model = models.Facture
|
||||
localized_fields = ('taux_tva',)
|
||||
localized_fields = ("taux_tva",)
|
||||
|
||||
|
||||
class ClientForm(forms.ModelForm):
|
||||
class Meta:
|
||||
exclude = ()
|
||||
model = models.Client
|
||||
localized_fields = ('tva',)
|
||||
localized_fields = ("tva",)
|
||||
widgets = {
|
||||
'adresse': widgets.AdminTextareaWidget(attrs={'rows': 4}),
|
||||
'contacts': widgets.AdminTextareaWidget(attrs={'rows': 4}),
|
||||
"adresse": widgets.AdminTextareaWidget(attrs={"rows": 4}),
|
||||
"contacts": widgets.AdminTextareaWidget(attrs={"rows": 4}),
|
||||
}
|
||||
|
||||
|
||||
|
@ -134,11 +153,11 @@ class ContratForm(forms.ModelForm):
|
|||
class Meta:
|
||||
exclude = ()
|
||||
model = models.Contrat
|
||||
localized_fields = ('tva', 'montant_sous_traite')
|
||||
localized_fields = ("tva", "montant_sous_traite")
|
||||
|
||||
|
||||
class PaymentForm(forms.ModelForm):
|
||||
class Meta:
|
||||
exclude = ()
|
||||
model = models.Payment
|
||||
localized_fields = ('montant_affecte',)
|
||||
localized_fields = ("montant_affecte",)
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.25 on 2019-10-09 11:35
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal
|
||||
|
|
|
@ -1,5 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from decimal import *
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
|
@ -12,20 +31,22 @@ from django.conf import settings
|
|||
from django.db.models.signals import post_save, post_delete
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import six
|
||||
|
||||
from ..eo_banque import models as banque_models
|
||||
import fields
|
||||
from . import fields
|
||||
from eo_gestion.utils import percentage_str
|
||||
|
||||
validate_telephone = RegexValidator(r'[. 0-9]*')
|
||||
validate_telephone = RegexValidator(r"[. 0-9]*")
|
||||
|
||||
DEFAULT_TVA = getattr(settings, 'TVA', '20')
|
||||
DEFAULT_TVA = getattr(settings, "TVA", "20")
|
||||
|
||||
|
||||
def accounting_rounding(amount):
|
||||
return amount.quantize(Decimal('0.01'), ROUND_HALF_UP)
|
||||
return amount.quantize(Decimal("0.01"), ROUND_HALF_UP)
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class Client(models.Model):
|
||||
nom = models.CharField(max_length=255, unique=True)
|
||||
adresse = models.TextField()
|
||||
|
@ -40,7 +61,7 @@ class Client(models.Model):
|
|||
)
|
||||
picture = models.ImageField('Logo', upload_to='logos/', blank=True, null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.nom
|
||||
|
||||
class Meta:
|
||||
|
@ -48,9 +69,10 @@ class Client(models.Model):
|
|||
|
||||
|
||||
def one_hundred_percent_this_year():
|
||||
return u'%s:100' % datetime.date.today().year
|
||||
return u"%s:100" % datetime.date.today().year
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class Contrat(models.Model):
|
||||
client = models.ForeignKey(Client, related_name="contrats")
|
||||
intitule = models.CharField(max_length=150)
|
||||
|
@ -72,35 +94,35 @@ class Contrat(models.Model):
|
|||
|
||||
def pourcentage_facture(self):
|
||||
return percentage_str(self.montant_facture(), self.montant())
|
||||
|
||||
pourcentage_facture.short_description = u'Pourcentage facturé'
|
||||
pourcentage_facture.short_description = 'Pourcentage facturé'
|
||||
|
||||
def montant(self):
|
||||
'''
|
||||
"""
|
||||
Montant total d'un contrat, y compris les prestations
|
||||
optionnelles.
|
||||
'''
|
||||
"""
|
||||
return Decimal(sum([p.montant() for p in self.prestations.all()]))
|
||||
|
||||
def solde(self):
|
||||
return self.montant() - self.montant_facture()
|
||||
|
||||
def nom_client(self):
|
||||
'''
|
||||
"""
|
||||
Le nom du client qui a signé ce contrat avec EO.
|
||||
'''
|
||||
"""
|
||||
return self.client.nom
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.intitule
|
||||
|
||||
class Meta:
|
||||
ordering = ('-id',)
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class Prestation(models.Model):
|
||||
contrat = models.ForeignKey(Contrat, related_name='prestations')
|
||||
intitule = models.CharField(max_length=255, verbose_name=u'Intitulé')
|
||||
intitule = models.CharField(max_length=255, verbose_name='Intitulé')
|
||||
optionnel = models.BooleanField(blank=True, default=False)
|
||||
prix_unitaire_ht = models.DecimalField(max_digits=8, decimal_places=2)
|
||||
quantite = models.DecimalField(max_digits=8, decimal_places=3)
|
||||
|
@ -117,8 +139,8 @@ class Prestation(models.Model):
|
|||
quantite=self.quantite,
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s pour %5.2f € HT' % (
|
||||
def __str__(self):
|
||||
return '%s pour %5.2f € HT' % (
|
||||
self.intitule,
|
||||
accounting_rounding(self.prix_unitaire_ht * self.quantite),
|
||||
)
|
||||
|
@ -127,14 +149,14 @@ class Prestation(models.Model):
|
|||
ordering = ('contrat', 'intitule')
|
||||
|
||||
|
||||
DELAI_PAIEMENT = getattr(settings, 'DELAI_PAIEMENT', 45)
|
||||
DELAI_PAIEMENT = getattr(settings, "DELAI_PAIEMENT", 45)
|
||||
|
||||
|
||||
def today_plus_delai():
|
||||
return datetime.date.today() + datetime.timedelta(days=DELAI_PAIEMENT)
|
||||
|
||||
|
||||
echeance_verbose_name = u"Échéance (par défaut %d jours)" % getattr(settings, 'DELAI_PAIEMENT', 45)
|
||||
echeance_verbose_name = 'Échéance (par défaut %d jours)' % getattr(settings, 'DELAI_PAIEMENT', 45)
|
||||
|
||||
|
||||
class FactureQuerySet(QuerySet):
|
||||
|
@ -151,6 +173,7 @@ 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(
|
||||
|
@ -191,9 +214,9 @@ class Facture(models.Model):
|
|||
return 'Facture proforma du %s' % self.emission
|
||||
ctx = {'year': self.emission.year}
|
||||
ctx.update(self.__dict__)
|
||||
if ctx['ordre'] is None:
|
||||
return 'Ordre is missing'
|
||||
format = getattr(settings, 'FACTURE_CODE_FORMAT', u'F%(year)s%(ordre)04d')
|
||||
if ctx["ordre"] is None:
|
||||
return "Ordre is missing"
|
||||
format = getattr(settings, "FACTURE_CODE_FORMAT", u"F%(year)s%(ordre)04d")
|
||||
return format % ctx
|
||||
|
||||
def save(self):
|
||||
|
@ -207,18 +230,18 @@ class Facture(models.Model):
|
|||
self.intitule = self.contrat.intitule
|
||||
if self.client:
|
||||
if self.client != self.contrat.client:
|
||||
raise ValidationError(u'Le client de la facture et du contrat doivent être identiques.')
|
||||
raise ValidationError(u"Le client de la facture et du contrat doivent être identiques.")
|
||||
else:
|
||||
self.client = self.contrat.client
|
||||
else:
|
||||
if not self.intitule:
|
||||
raise ValidationError(u'La facture doit avoir un intitulé')
|
||||
raise ValidationError(u"La facture doit avoir un intitulé")
|
||||
if not self.proforma:
|
||||
try:
|
||||
for ligne in self.lignes.all():
|
||||
ligne.clean()
|
||||
except ValidationError:
|
||||
raise ValidationError(u'Il y a un problème avec les lignes de cette facture')
|
||||
raise ValidationError(u"Il y a un problème avec les lignes de cette facture")
|
||||
self.update_paid(save=False)
|
||||
|
||||
def index(self):
|
||||
|
@ -227,7 +250,7 @@ class Facture(models.Model):
|
|||
else:
|
||||
return 1
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.code()
|
||||
|
||||
@property
|
||||
|
@ -236,8 +259,8 @@ class Facture(models.Model):
|
|||
for ligne in self.lignes.all():
|
||||
amount_by_vat[ligne.tva] += ligne.montant
|
||||
vat = Decimal(0)
|
||||
for vat_percentage, amount in amount_by_vat.iteritems():
|
||||
var_ratio = vat_percentage / Decimal('100')
|
||||
for vat_percentage, amount in amount_by_vat.items():
|
||||
var_ratio = vat_percentage / Decimal("100")
|
||||
vat += accounting_rounding(var_ratio * amount)
|
||||
return vat
|
||||
|
||||
|
@ -271,11 +294,12 @@ class Facture(models.Model):
|
|||
return self.emission.year
|
||||
|
||||
class Meta:
|
||||
ordering = ('-id',)
|
||||
ordering = ("-id",)
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class Ligne(models.Model):
|
||||
facture = models.ForeignKey(Facture, related_name='lignes')
|
||||
facture = models.ForeignKey(Facture, related_name="lignes")
|
||||
intitule = models.TextField(blank=True)
|
||||
prix_unitaire_ht = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal('0'))
|
||||
quantite = models.DecimalField(max_digits=8, decimal_places=3, default=Decimal('1.0'))
|
||||
|
@ -300,9 +324,9 @@ class Ligne(models.Model):
|
|||
def clean(self):
|
||||
errors = []
|
||||
if self.taux_tva and self.taux_tva < 0:
|
||||
errors.append(u'Le taux de tva doit être une valeur positive ou nulle.')
|
||||
errors.append(u"Le taux de tva doit être une valeur positive ou nulle.")
|
||||
if self.prix_unitaire_ht < 0:
|
||||
errors.append(u'Le prix unitaire hors taxe doit être une valeur positive ou nulle.')
|
||||
errors.append(u"Le prix unitaire hors taxe doit être une valeur positive ou nulle.")
|
||||
if self.facture.contrat and not self.facture.proforma:
|
||||
facture = self.facture
|
||||
contrat = facture.contrat
|
||||
|
@ -310,17 +334,17 @@ class Ligne(models.Model):
|
|||
deja_facture += sum([l.montant for l in facture.lignes.all() if l != self])
|
||||
if deja_facture + self.montant > contrat.montant:
|
||||
errors.append(
|
||||
u'Cette ligne fait dépasser le montant initial du contrat de %.2f %s'
|
||||
'Cette ligne fait dépasser le montant initial du contrat de %.2f %s'
|
||||
% (deja_facture + self.montant - contrat.montant, contrat.client.monnaie)
|
||||
)
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
|
||||
class Meta:
|
||||
ordering = ('order',)
|
||||
ordering = ("order",)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s pour %s %s' % (self.intitule, self.montant, self.facture.client.monnaie)
|
||||
def __str__(self):
|
||||
return "%s pour %s %s" % (self.intitule, self.montant, self.facture.client.monnaie,)
|
||||
|
||||
|
||||
def encaissements_avec_solde_non_affecte():
|
||||
|
@ -333,43 +357,44 @@ def encaissements_avec_solde_non_affecte():
|
|||
)
|
||||
|
||||
|
||||
@six.python_2_unicode_compatible
|
||||
class Payment(models.Model):
|
||||
facture = models.ForeignKey(Facture, related_name='payments')
|
||||
ligne_banque_pop = models.ForeignKey(
|
||||
banque_models.LigneBanquePop,
|
||||
related_name='payments',
|
||||
verbose_name=u"Encaissement",
|
||||
verbose_name='Encaissement',
|
||||
limit_choices_to={'montant__gt': 0},
|
||||
)
|
||||
montant_affecte = models.DecimalField(
|
||||
max_digits=8,
|
||||
decimal_places=2,
|
||||
blank=True,
|
||||
verbose_name=u"Montant affecté",
|
||||
help_text=u"Si vide, le montant non affecté de l'encaissement est pris comme valeur",
|
||||
verbose_name='Montant affecté',
|
||||
help_text="Si vide, le montant non affecté de l'encaissement est pris comme valeur",
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
'''Vérifie la cohérence des paiements'''
|
||||
"""Vérifie la cohérence des paiements"""
|
||||
try:
|
||||
if self.montant_affecte is None:
|
||||
self.montant_affecte = min(self.ligne_banque_pop.montant, self.facture.montant_ttc)
|
||||
except (banque_models.LigneBanquePop.DoesNotExist, Facture.DoesNotExist):
|
||||
pass
|
||||
aggregate = models.Sum('montant_affecte')
|
||||
aggregate = models.Sum("montant_affecte")
|
||||
# from the ligne de banque pov
|
||||
try:
|
||||
other_payments = self.ligne_banque_pop.payments
|
||||
if self.ligne_banque_pop.montant < 0 or (self.montant_affecte and self.montant_affecte <= 0):
|
||||
raise ValidationError('Un paiement ne peut être que d\'un montant positif')
|
||||
raise ValidationError("Un paiement ne peut être que d'un montant positif")
|
||||
except banque_models.LigneBanquePop.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
deja_affecte = other_payments.aggregate(aggregate).get('montant_affecte', 0)
|
||||
deja_affecte = other_payments.aggregate(aggregate).get("montant_affecte", 0)
|
||||
if deja_affecte + self.montant_affecte > self.ligne_banque_pop.montant:
|
||||
raise ValidationError(
|
||||
u'Le montant affecté aux différentes factures '
|
||||
u'est supérieur au montant de l\'encaissement.'
|
||||
'Le montant affecté aux différentes factures '
|
||||
'est supérieur au montant de l\'encaissement.'
|
||||
)
|
||||
# from the facture pov
|
||||
try:
|
||||
|
@ -377,19 +402,18 @@ class Payment(models.Model):
|
|||
except Facture.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
deja_affecte = other_payments.aggregate(aggregate).get('montant_affecte', 0)
|
||||
deja_affecte = other_payments.aggregate(aggregate).get("montant_affecte", 0)
|
||||
if deja_affecte + self.montant_affecte > self.facture.montant_ttc:
|
||||
raise ValidationError(
|
||||
u'Le montant affecté aux différentes factures '
|
||||
u'est supérieur au montant de l\'encaissement.'
|
||||
'Le montant affecté aux différentes factures '
|
||||
'est supérieur au montant de l\'encaissement.'
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
|
||||
return u'Paiement de %.2f € sur facture %s à %s' % (
|
||||
def __str__(self):
|
||||
return 'Paiement de %.2f € sur facture %s à %s' % (
|
||||
self.ligne_banque_pop.montant,
|
||||
unicode(self.facture),
|
||||
unicode(self.facture.client),
|
||||
self.facture,
|
||||
self.facture.client,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -397,7 +421,9 @@ class Payment(models.Model):
|
|||
ordering = ('-ligne_banque_pop__date_valeur',)
|
||||
|
||||
|
||||
def update_paid(sender, instance, **kwargs):
|
||||
def update_paid(sender, instance, raw=False, **kwargs):
|
||||
if raw:
|
||||
return
|
||||
instance.facture.update_paid()
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
# coding: utf-8
|
||||
#
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from datetime import date, timedelta, datetime
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from collections import defaultdict
|
||||
|
@ -6,12 +25,15 @@ from collections import defaultdict
|
|||
from django import template
|
||||
from django.utils.formats import number_format
|
||||
from django.utils.timesince import timesince
|
||||
from django.utils.six import text_type
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from eo_gestion.eo_facture.models import Contrat, Facture, DELAI_PAIEMENT
|
||||
from eo_gestion.eo_banque.models import LigneBanquePop
|
||||
from eo_gestion.utils import percentage
|
||||
|
||||
from ...decorators import cache
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -82,8 +104,9 @@ def income():
|
|||
invoiced_by_year_and_client[invoice.accounting_year][invoice.client.id] += montant
|
||||
invoiced_clients_by_year = {}
|
||||
for year in invoiced_by_year_and_client:
|
||||
clients = invoiced_by_year_and_client[year].keys()
|
||||
clients.sort(key=lambda x: -invoiced_by_year_and_client[year][x])
|
||||
clients = sorted(
|
||||
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))
|
||||
for contract in Contrat.objects.all().prefetch_related('factures', 'factures__lignes', 'prestations'):
|
||||
|
@ -123,18 +146,18 @@ def zero_dict():
|
|||
return defaultdict(lambda: Decimal(0))
|
||||
|
||||
|
||||
class StringWithHref(unicode):
|
||||
class StringWithHref(text_type):
|
||||
def __new__(cls, v, href=None):
|
||||
return unicode.__new__(cls, v)
|
||||
return text_type.__new__(cls, v)
|
||||
|
||||
def __init__(self, v, href=None):
|
||||
super(StringWithHref, self).__init__(v)
|
||||
super(StringWithHref, self).__init__()
|
||||
self.href = href
|
||||
|
||||
|
||||
def client_and_link(c):
|
||||
s = unicode(c)
|
||||
url = reverse('admin:eo_facture_client_change', args=(c.id,))
|
||||
s = text_type(c)
|
||||
url = reverse("admin:eo_facture_client_change", args=(c.id,))
|
||||
return StringWithHref(s, url)
|
||||
|
||||
|
||||
|
@ -142,7 +165,7 @@ def dict_of_list():
|
|||
return defaultdict(lambda: [])
|
||||
|
||||
|
||||
@register.inclusion_tag('eo_facture/table.html')
|
||||
@register.inclusion_tag("eo_facture/table.html")
|
||||
def income_by_clients(year=None):
|
||||
if not year:
|
||||
year = date.today().year
|
||||
|
@ -187,9 +210,8 @@ 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.iteritems()])
|
||||
clients = total_by_clients.keys()
|
||||
clients.sort(key=lambda x: -total_by_clients[x])
|
||||
percent_by_clients = dict([(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
|
||||
pareto = dict()
|
||||
|
@ -201,16 +223,16 @@ def income_by_clients(year=None):
|
|||
name="previsional-income-by-client-%s" % year,
|
||||
headers=[
|
||||
('client', 'Client'),
|
||||
('income', "Chiffre d'affaire"),
|
||||
('invoiced', u"Facturé"),
|
||||
('percent_invoiced', u"Pourcentage facturé"),
|
||||
('contracted', u"Contracté"),
|
||||
('percent_contracted', u"Pourcentage contracté"),
|
||||
('income', 'Chiffre d\'affaire'),
|
||||
('invoiced', 'Facturé'),
|
||||
('percent_invoiced', 'Pourcentage facturé'),
|
||||
('contracted', 'Contracté'),
|
||||
('percent_contracted', 'Pourcentage contracté'),
|
||||
('percent', 'Pourcentage'),
|
||||
('pareto', 'Pareto'),
|
||||
],
|
||||
contracts_by_clients=contracts_by_clients.iteritems(),
|
||||
invoices_by_clients=invoices_by_clients.iteritems(),
|
||||
contracts_by_clients=list(contracts_by_clients.items()),
|
||||
invoices_by_clients=list(invoices_by_clients.items()),
|
||||
table=[
|
||||
(
|
||||
client_and_link(c),
|
||||
|
@ -241,7 +263,7 @@ def income_by_clients(year=None):
|
|||
|
||||
@register.inclusion_tag('eo_facture/a_facturer.html')
|
||||
def a_facturer():
|
||||
l = []
|
||||
contrats_a_facturer = []
|
||||
for contrat in (
|
||||
Contrat.objects.all()
|
||||
.select_related('client')
|
||||
|
@ -255,7 +277,7 @@ def a_facturer():
|
|||
else:
|
||||
depuis = contrat.creation
|
||||
depuis = (date.today() - depuis).days
|
||||
l.append(
|
||||
contrats_a_facturer.append(
|
||||
{
|
||||
'contrat': contrat,
|
||||
'pourcentage': (facture / a_facture) * Decimal(100),
|
||||
|
@ -263,9 +285,9 @@ def a_facturer():
|
|||
'depuis': depuis,
|
||||
}
|
||||
)
|
||||
l.sort(key=lambda x: -x['depuis'])
|
||||
montant = sum([x['montant'] for x in l])
|
||||
return {'a_facturer': l, 'montant': montant}
|
||||
contrats_a_facturer.sort(key=lambda x: -x['depuis'])
|
||||
montant = sum([x['montant'] for x in contrats_a_facturer])
|
||||
return {'a_facturer': contrats_a_facturer, 'montant': montant}
|
||||
|
||||
|
||||
@register.filter(name='ago')
|
||||
|
@ -277,4 +299,4 @@ def ago(date):
|
|||
|
||||
@register.filter(is_safe=True)
|
||||
def amountformat(value, use_l10n=True):
|
||||
return number_format(Decimal(value).quantize(Decimal('0.01')), force_grouping=True)
|
||||
return number_format(Decimal(value).quantize(Decimal("0.01")), force_grouping=True)
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
import cStringIO as StringIO
|
||||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
import cgi
|
||||
import json
|
||||
import os.path
|
||||
|
@ -6,14 +23,10 @@ import os.path
|
|||
from django import http
|
||||
from django.shortcuts import render
|
||||
from django.template.loader import get_template
|
||||
from django.template import RequestContext
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles import finders
|
||||
from weasyprint import HTML
|
||||
|
||||
import models
|
||||
|
||||
from weasyprint import default_url_fetcher, HTML
|
||||
from . import models
|
||||
|
||||
|
||||
def render_to_pdf(template_src, context_dict):
|
||||
|
@ -22,12 +35,13 @@ def render_to_pdf(template_src, context_dict):
|
|||
try:
|
||||
pdf = html.write_pdf()
|
||||
if hasattr(settings, 'FACTURE_DIR'):
|
||||
facture = context['facture']
|
||||
facture = context_dict['facture']
|
||||
filename = os.path.join(
|
||||
settings.FACTURE_DIR,
|
||||
'%s-%s.pdf' % (facture.code(), facture.contrat.client.nom.encode('utf8')),
|
||||
)
|
||||
file(filename, 'w').write(pdf)
|
||||
with open(filename, 'wb') as fd:
|
||||
fd.write(pdf)
|
||||
return http.HttpResponse(pdf, content_type='application/pdf')
|
||||
|
||||
except IOError:
|
||||
|
|
|
@ -4,21 +4,15 @@ import os.path
|
|||
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
|
||||
DEBUG = False
|
||||
|
||||
ADMINS = (('Benjamin Dauvergne', 'bdauvergne@entrouvert.com'),)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
DEBUG = True
|
||||
|
||||
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||
ALLOWED_HOSTS = []
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
|
||||
'NAME': os.path.join(
|
||||
os.path.dirname(__file__), 'facture.db'
|
||||
), # Or path to database file if using sqlite3.
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': 'barbacompta',
|
||||
'USER': '', # Not used with sqlite3.
|
||||
'PASSWORD': '', # Not used with sqlite3.
|
||||
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
|
||||
|
@ -26,6 +20,25 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
CACHES = {'default': {'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 'LOCATION': 'cache',}}
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'filters': {
|
||||
'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse',},
|
||||
'require_debug_true': {'()': 'django.utils.log.RequireDebugTrue',},
|
||||
},
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format': '[%(asctime)s] %(ip)s %(user)s %(request_id)s %(levelname)s %(name)s.%(funcName)s: %(message)s',
|
||||
'datefmt': '%Y-%m-%d %a %H:%M:%S',
|
||||
},
|
||||
},
|
||||
'handlers': {'console': {'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'verbose',},},
|
||||
'logger': {'django': {'handlers': ['console', 'mail_admins'], 'level': 'INFO',},},
|
||||
}
|
||||
|
||||
# Local time zone for this installation. Choices can be found here:
|
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||
# although not all choices may be available on all operating systems.
|
||||
|
@ -33,11 +46,11 @@ DATABASES = {
|
|||
# timezone as the operating system.
|
||||
# If running in a Windows environment this must be set to the same as your
|
||||
# system time zone.
|
||||
TIME_ZONE = 'Europe/Paris'
|
||||
TIME_ZONE = "Europe/Paris"
|
||||
|
||||
# Language code for this installation. All choices can be found here:
|
||||
# http://www.i18nguy.com/unicode/language-identifiers.html
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
LANGUAGE_CODE = "fr-fr"
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
|
@ -57,29 +70,26 @@ LOCALE_PATHS = (os.path.join(BASE_DIR, 'eo_gestion', 'locale'),)
|
|||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
|
||||
# trailing slash if there is a path component (optional in other cases).
|
||||
# Examples: "http://media.lawrence.com", "http://example.com/media/"
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# URL prefix for static files.
|
||||
# Example: "http://media.lawrence.com/static/"
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
|
||||
# trailing slash.
|
||||
# Examples: "http://foo.com/media/", "/media/".
|
||||
ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/'
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
# Make this unique, and don't share it with anybody.
|
||||
SECRET_KEY = '5w+ifr2ho!#x06q7dshr08wd#gt0wwp@wvbvw33kmtb+x$(9ts'
|
||||
SECRET_KEY = "5w+ifr2ho!#x06q7dshr08wd#gt0wwp@wvbvw33kmtb+x$(9ts"
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
MIDDLEWARE = (
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.http.ConditionalGetMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
)
|
||||
|
||||
ROOT_URLCONF = 'eo_gestion.urls'
|
||||
ROOT_URLCONF = "eo_gestion.urls"
|
||||
|
||||
# Templates
|
||||
TEMPLATES = [
|
||||
|
@ -103,40 +113,23 @@ TEMPLATES = [
|
|||
]
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
# Uncomment the next line to enable the admin:
|
||||
'django.contrib.admin',
|
||||
'eo_gestion.eo_facture',
|
||||
'eo_gestion.eo_banque',
|
||||
'mellon',
|
||||
"django.contrib.admin",
|
||||
"eo_gestion.eo_facture",
|
||||
"eo_gestion.eo_banque",
|
||||
)
|
||||
|
||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', 'mellon.backends.SAMLBackend')
|
||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
|
||||
|
||||
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
||||
LOGIN_REDIRECT_URL = "/"
|
||||
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
MELLON_ATTRIBUTE_MAPPING = {
|
||||
'username': '{attributes[username][0]}',
|
||||
'email': '{attributes[email][0]}',
|
||||
'first_name': '{attributes[first_name][0]}',
|
||||
'last_name': '{attributes[last_name][0]}',
|
||||
}
|
||||
|
||||
|
||||
MELLON_SUPERUSER_MAPPING = {'is_superuser': (u'true',)}
|
||||
MELLON_USERNAME_TEMPLATE = '{attributes[username][0]}'
|
||||
|
||||
MELLON_PUBLIC_KEYS = None
|
||||
MELLON_PRIVATE_KEY = None
|
||||
MELLON_IDENTITY_PROVIDERS = None
|
||||
|
||||
local_settings_file = os.environ.get('BARBACOMPTA_SETTINGS_FILE', 'local_settings.py')
|
||||
local_settings_file = os.environ.get("BARBACOMPTA_SETTINGS_FILE", "local_settings.py")
|
||||
if os.path.exists(local_settings_file):
|
||||
execfile(local_settings_file)
|
||||
with open(local_settings_file) as fd:
|
||||
exec(fd.read())
|
||||
|
|
|
@ -1,25 +1,40 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
# Uncomment the next two lines to enable the admin:
|
||||
from django.conf import settings
|
||||
import django.contrib.admin
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
import admin
|
||||
from . import admin
|
||||
|
||||
from eo_facture.views import api_references
|
||||
from .eo_facture.views import api_references
|
||||
|
||||
django.contrib.admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
# Example:
|
||||
# (r'^facturation/', include('facturation.foo.urls')),
|
||||
# Uncomment the admin/doc line below and add 'django.contrib.admindocs'
|
||||
# to INSTALLED_APPS to enable admin documentation:
|
||||
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
# Uncomment the next line to enable the admin:
|
||||
url(r'^favicon.ico', RedirectView.as_view(), {'url': '/static/img/favicon.ico'}),
|
||||
url(r'^api/references/$', api_references),
|
||||
url(r'^', include(admin.site.urls)),
|
||||
url(r'^accounts/mellon/', include('mellon.urls')),
|
||||
url(r'^', admin.site.urls),
|
||||
]
|
||||
|
||||
if 'mellon' in settings.INSTALLED_APPS:
|
||||
urlpatterns += [
|
||||
url(r'^accounts/mellon/', include('mellon.urls')),
|
||||
]
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
"""
|
||||
'''
|
||||
WSGI config for combo project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
|
||||
"""
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "eo_gestion.settings")
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eo_gestion.settings')
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
|
|
20
getlasso.sh
20
getlasso.sh
|
@ -1,20 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Get venv site-packages path
|
||||
DSTDIR=`python -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())'`
|
||||
|
||||
# Get not venv site-packages path
|
||||
# Remove first path (assuming that is the venv path)
|
||||
NONPATH=`echo $PATH | sed 's/^[^:]*://'`
|
||||
SRCDIR=`PATH=$NONPATH python -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())'`
|
||||
|
||||
# Clean up
|
||||
rm -f $DSTDIR/lasso.*
|
||||
rm -f $DSTDIR/_lasso.*
|
||||
|
||||
# Link
|
||||
ln -sv $SRCDIR/lasso.py $DSTDIR
|
||||
ln -sv $SRCDIR/_lasso.* $DSTDIR
|
||||
|
||||
exit 0
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
[MASTER]
|
||||
profile=no
|
||||
persistent=yes
|
||||
ignore=migrations,south_migrations
|
||||
cache-size=500
|
||||
load-plugins=pylint_django
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
# C0111 Missing docstring
|
||||
# I0011 Warning locally suppressed using disable-msg
|
||||
# I0012 Warning locally suppressed using disable-msg
|
||||
# W0704 Except doesn't do anything Used when an except clause does nothing but "pass" and there is no "else" clause
|
||||
# W0142 Used * or * magic* Used when a function or method is called using *args or **kwargs to dispatch arguments.
|
||||
# W0212 Access to a protected member %s of a client class
|
||||
# W0232 Class has no __init__ method Used when a class has no __init__ method, neither its parent classes.
|
||||
# W0613 Unused argument %r Used when a function or method argument is not used.
|
||||
# W0702 No exception's type specified Used when an except clause doesn't specify exceptions type to catch.
|
||||
# R0201 Method could be a function
|
||||
disable=C0111,I0011,I0012,W0704,W0142,W0212,W0232,W0613,W0702,R0201,C0330
|
||||
|
||||
[REPORTS]
|
||||
output-format=parseable
|
||||
include-ids=yes
|
||||
|
||||
|
||||
[BASIC]
|
||||
no-docstring-rgx=__.*__|_.*
|
||||
class-rgx=[A-Z_][a-zA-Z0-9_]+$
|
||||
function-rgx=[a-zA_][a-zA-Z0-9_]{2,70}$
|
||||
method-rgx=[a-z_][a-zA-Z0-9_]{2,70}$
|
||||
const-rgx=(([A-Z_][A-Z0-9_]*)|([a-z_][a-z0-9_]*)|(__.*__)|register|urlpatterns)$
|
||||
good-names=_,i,j,k,e,qs,pk,setUp,tearDown
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of classes names for which member attributes should not be checked
|
||||
# (useful for classes with attributes dynamically set).
|
||||
ignored-classes=SQLObject,WSGIRequest
|
||||
|
||||
# When zope mode is activated, add a predefined set of Zope acquired attributes
|
||||
# to generated-members.
|
||||
zope=no
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E0201 when accessed.
|
||||
generated-members=objects,DoesNotExist,id,pk,_meta,base_fields,context
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes
|
||||
defining-attr-methods=__init__,__new__,setUp
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
init-import=no
|
||||
dummy-variables-rgx=_|dummy
|
||||
|
||||
[SIMILARITIES]
|
||||
min-similarity-lines=6
|
||||
ignore-comments=yes
|
||||
ignore-docstrings=yes
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
notes=FIXME,XXX,TODO
|
||||
|
||||
|
||||
[FORMAT]
|
||||
max-line-length=160
|
||||
max-module-lines=500
|
||||
indent-string=' '
|
||||
|
||||
|
||||
[DESIGN]
|
||||
max-args=10
|
||||
max-locals=15
|
||||
max-returns=6
|
||||
max-branchs=12
|
||||
max-statements=50
|
||||
max-parents=14
|
||||
max-attributes=7
|
||||
min-public-methods=0
|
||||
max-public-methods=50
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e -x
|
||||
env
|
||||
set -e
|
||||
|
||||
if [ -f /var/lib/jenkins/pylint.django.rc ]; then
|
||||
PYLINT_RC=/var/lib/jenkins/pylint.django.rc
|
||||
elif [ -f pylint.django.rc ]; then
|
||||
|
@ -10,4 +10,4 @@ else
|
|||
echo No pylint RC found
|
||||
exit 0
|
||||
fi
|
||||
pylint -f parseable --rcfile ${PYLINT_RC} "$@" | tee pylint.out || /bin/true
|
||||
pylint -f parseable --rcfile ${PYLINT_RC} "$@" >pylint.out || /bin/true
|
||||
|
|
46
setup.py
46
setup.py
|
@ -1,10 +1,9 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from distutils.cmd import Command
|
||||
from distutils.command.build import build as _build
|
||||
|
@ -14,13 +13,8 @@ from setuptools.command.install_lib import install_lib as _install_lib
|
|||
|
||||
install_requires = [
|
||||
'Django>=1.11,<1.12',
|
||||
'reportlab<3',
|
||||
'html5lib',
|
||||
'weasyprint<0.43',
|
||||
'django-mellon',
|
||||
'django-model-utils<4',
|
||||
'pyPdf',
|
||||
'Pillow',
|
||||
]
|
||||
|
||||
|
||||
|
@ -53,7 +47,6 @@ def get_version():
|
|||
|
||||
class eo_sdist(sdist):
|
||||
def run(self):
|
||||
print("creating VERSION file")
|
||||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
version = get_version()
|
||||
|
@ -61,7 +54,6 @@ class eo_sdist(sdist):
|
|||
version_file.write(version)
|
||||
version_file.close()
|
||||
sdist.run(self)
|
||||
print("removing VERSION file")
|
||||
if os.path.exists('VERSION'):
|
||||
os.remove('VERSION')
|
||||
|
||||
|
@ -77,18 +69,16 @@ class compile_translations(Command):
|
|||
pass
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
from django.core.management import call_command
|
||||
from django.core.management import call_command
|
||||
|
||||
for path, dirs, files in os.walk('eo_gestion'):
|
||||
if 'locale' not in dirs:
|
||||
continue
|
||||
curdir = os.getcwd()
|
||||
os.chdir(os.path.realpath(path))
|
||||
call_command('compilemessages')
|
||||
os.chdir(curdir)
|
||||
except ImportError:
|
||||
sys.stderr.write('!!! Please install Django >= 1.4 to build translations\n')
|
||||
os.environ.pop('DJANGO_SETTINGS_MODULE', None)
|
||||
for path, dirs, files in os.walk('eo_gestion'):
|
||||
if 'locale' not in dirs:
|
||||
continue
|
||||
curdir = os.getcwd()
|
||||
os.chdir(os.path.realpath(path))
|
||||
call_command('compilemessages')
|
||||
os.chdir(curdir)
|
||||
|
||||
|
||||
class build(_build):
|
||||
|
@ -102,15 +92,15 @@ class install_lib(_install_lib):
|
|||
|
||||
|
||||
setup(
|
||||
name="barbacompta",
|
||||
name='barbacompta',
|
||||
version=get_version(),
|
||||
license="AGPLv3 or later",
|
||||
description="Logiciel de compta/facturation",
|
||||
url="http://dev.entrouvert.org/projects/gestion",
|
||||
author="Entr'ouvert",
|
||||
author_email="info@entrouvert.org",
|
||||
maintainer="Benjamin Dauvergne",
|
||||
maintainer_email="bdauvergne@entrouvert.com",
|
||||
license='AGPLv3 or later',
|
||||
description='Logiciel de compta/facturation',
|
||||
url='http://dev.entrouvert.org/projects/gestion',
|
||||
author='Entr\'ouvert',
|
||||
author_email='info@entrouvert.org',
|
||||
maintainer='Benjamin Dauvergne',
|
||||
maintainer_email='bdauvergne@entrouvert.com',
|
||||
include_package_data=True,
|
||||
scripts=['manage.py'],
|
||||
packages=find_packages(),
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.db import transaction
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
DATA = ["tests/fixture.json"]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def base_db(django_db_setup, django_db_blocker):
|
||||
with django_db_blocker.unblock():
|
||||
with transaction.atomic():
|
||||
try:
|
||||
for data in DATA:
|
||||
call_command("loaddata", data)
|
||||
admin, created = User.objects.update_or_create(
|
||||
username="admin",
|
||||
defaults=dict(email="admin@example.com", is_superuser=True, is_staff=True),
|
||||
)
|
||||
admin.set_password("admin")
|
||||
admin.save()
|
||||
yield
|
||||
finally:
|
||||
transaction.set_rollback(True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db(base_db):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def app(db, freezer):
|
||||
freezer.move_to("2019-01-01")
|
||||
wtm = django_webtest.WebTestMixin()
|
||||
wtm._patch_settings()
|
||||
try:
|
||||
return django_webtest.DjangoTestApp(extra_environ={"HTTP_HOST": "localhost"})
|
||||
finally:
|
||||
wtm._unpatch_settings()
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,17 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
ALLOWED_HOSTS = ["localhost"]
|
|
@ -0,0 +1,43 @@
|
|||
# barbacompta - accounting for dummies
|
||||
# Copyright (C) 2010-2019 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# 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 __future__ import unicode_literals
|
||||
|
||||
|
||||
def test_homepage(app):
|
||||
response = app.get("/").follow()
|
||||
|
||||
response.form.set("username", "admin")
|
||||
response.form.set("password", "admin")
|
||||
|
||||
homepage = response.form.submit().follow()
|
||||
|
||||
clients = homepage.click("Clients")
|
||||
str(clients)
|
||||
ajouter_un_client = homepage.click("Ajouter un client")
|
||||
str(ajouter_un_client)
|
||||
contrats = homepage.click("Contrats")
|
||||
contrat_2c210afd24c11596eeaf94bfb = contrats.click('2c210afd24c11596eeaf94bfb', href='change')
|
||||
str(contrat_2c210afd24c11596eeaf94bfb)
|
||||
ajouter_un_contrat = homepage.click("Ajouter un contrat")
|
||||
str(ajouter_un_client)
|
||||
compte_en_banque = homepage.click("Compte en banque")
|
||||
str(compte_en_banque)
|
||||
factures = homepage.click("Factures")
|
||||
factures_00137 = factures.click('F2019.00137')
|
||||
str(factures_00137)
|
||||
rapid = factures.click('Rapid')
|
||||
str(rapid)
|
|
@ -1,7 +0,0 @@
|
|||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_migrations(db):
|
||||
pass
|
36
tox.ini
36
tox.ini
|
@ -1,27 +1,37 @@
|
|||
[tox]
|
||||
toxworkdir = {env:TMPDIR:/tmp}/tox-{env:USER}/barbacompta/{env:BRANCH_NAME:}
|
||||
envlist = py2-django111-pylint
|
||||
envlist = py2,py3
|
||||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
basepython = python2
|
||||
setenv =
|
||||
DJANGO_SETTINGS_MODULE=eo_gestion.settings
|
||||
BARBACOMPTA_SETTINGS_FILE=tests/settings.py
|
||||
deps =
|
||||
django>=1.11,<1.12
|
||||
psycopg2-binary
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-django
|
||||
pytest<4
|
||||
WebTest
|
||||
mock
|
||||
httmock
|
||||
pylint<1.8
|
||||
pylint-django<0.8.1
|
||||
django-webtest<1.9.3
|
||||
pytest-freezegun
|
||||
pylint
|
||||
pylint-django
|
||||
django-webtest
|
||||
django-mellon
|
||||
html5lib<1.0
|
||||
pdbpp
|
||||
commands =
|
||||
./getlasso.sh
|
||||
./pylint.sh eo_gestion/
|
||||
py.test {posargs: --junitxml=test_{envname}_results.xml --cov-report xml --cov-report html --cov=eo_gestion/ tests/}
|
||||
|
||||
[testenv:pylint]
|
||||
basepython = python3
|
||||
deps=
|
||||
django>=1.11,<1.12
|
||||
pylint
|
||||
pylint-django
|
||||
uwsgidecorators
|
||||
commands =
|
||||
./pylint.sh eo_gestion/
|
||||
|
||||
[pytest]
|
||||
filterwarnings=
|
||||
ignore:Using or importing the ABCs from
|
||||
junit_family=xunit2
|
||||
|
|
Loading…
Reference in New Issue