8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-23 11:53:12 +00:00

Add Document templates to re2o

This commit is contained in:
Hugo LEVY-FALK 2019-01-03 19:52:06 +01:00
parent 1cc5d7bba0
commit 6fdf8a0406
10 changed files with 366 additions and 19 deletions

View file

@ -31,6 +31,7 @@ from reversion.admin import VersionAdmin
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
from .models import CustomInvoice, CostEstimate from .models import CustomInvoice, CostEstimate
from .tex import DocumentTemplate
class FactureAdmin(VersionAdmin): class FactureAdmin(VersionAdmin):
@ -74,6 +75,11 @@ class CotisationAdmin(VersionAdmin):
pass pass
class DocumentTemplateAdmin(VersionAdmin):
"""Admin class for DocumentTemplate"""
pass
admin.site.register(Facture, FactureAdmin) admin.site.register(Facture, FactureAdmin)
admin.site.register(Article, ArticleAdmin) admin.site.register(Article, ArticleAdmin)
admin.site.register(Banque, BanqueAdmin) admin.site.register(Banque, BanqueAdmin)
@ -82,3 +88,4 @@ admin.site.register(Vente, VenteAdmin)
admin.site.register(Cotisation, CotisationAdmin) admin.site.register(Cotisation, CotisationAdmin)
admin.site.register(CustomInvoice, CustomInvoiceAdmin) admin.site.register(CustomInvoice, CustomInvoiceAdmin)
admin.site.register(CostEstimate, CostEstimateAdmin) admin.site.register(CostEstimate, CostEstimateAdmin)
admin.site.register(DocumentTemplate, DocumentTemplateAdmin)

View file

@ -50,6 +50,7 @@ from .models import (
Article, Paiement, Facture, Banque, Article, Paiement, Facture, Banque,
CustomInvoice, Vente, CostEstimate CustomInvoice, Vente, CostEstimate
) )
from .tex import DocumentTemplate
from .payment_methods import balance from .payment_methods import balance
@ -316,3 +317,37 @@ class RechargeForm(FormRevMixin, Form):
} }
) )
return self.cleaned_data return self.cleaned_data
class DocumentTemplateForm(FormRevMixin, ModelForm):
"""
Form used to create a document template.
"""
class Meta:
model = DocumentTemplate
fields = '__all__'
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(DocumentTemplateForm, self).__init__(
*args, prefix=prefix, **kwargs)
class DelDocumentTemplateForm(FormRevMixin, Form):
"""
Form used to delete one or more document templatess.
The use must choose the one to delete by checking the boxes.
"""
document_templates = forms.ModelMultipleChoiceField(
queryset=DocumentTemplate.objects.none(),
label=_("Available document templates"),
widget=forms.CheckboxSelectMultiple
)
def __init__(self, *args, **kwargs):
instances = kwargs.pop('instances', None)
super(DelDocumentTemplateForm, self).__init__(*args, **kwargs)
if instances:
self.fields['document_templates'].queryset = instances
else:
self.fields['document_templates'].queryset = Banque.objects.all()

View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-03 16:48
from __future__ import unicode_literals
import os
from django.db import migrations, models
from django.core.files import File
from django.conf import settings
import re2o.mixins
def create_default_templates(apps, schema_editor):
DocumentTemplate = apps.get_model('cotisations', 'DocumentTemplate')
invoice_path = os.path.join(
settings.BASE_DIR,
"cotisations",
"templates",
"cotisations",
"factures.tex"
)
with open(invoice_path) as f:
tpl, _ = DocumentTemplate.objects.get_or_create(
name="Re2o default invoice",
)
tpl.template.save('default_invoice.tex', File(f))
tpl.save()
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0038_auto_20181231_1657'),
]
operations = [
migrations.CreateModel(
name='DocumentTemplate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('template', models.FileField(upload_to='templates/', verbose_name='template')),
('name', models.CharField(max_length=255, verbose_name='name')),
],
options={
'verbose_name_plural': 'document templates',
'verbose_name': 'document template',
},
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
),
migrations.RunPython(create_default_templates),
]

View file

@ -0,0 +1,50 @@
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Goulven Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load acl %}
{% load i18n %}
{% load logs_extra %}
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Document template" %}</th>
<th>{% trans "File" %}</th>
<th></th>
</tr>
</thead>
{% for template in document_template_list %}
<tr>
<td>{{ template.name }}</td>
<td><a href="{{template.template.url}}">{{template.template}}</a></td>
<td class="text-right">
{% can_edit template %}
{% include 'buttons/edit.html' with href='cotisations:edit-document-template' id=template.id %}
{% acl_end %}
{% history_button template %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -0,0 +1,42 @@
{% extends "cotisations/sidebar.html" %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2019 Hugo LEVY-FALK
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load acl %}
{% load i18n %}
{% block title %}{% trans "Document Templates" %}{% endblock %}
{% block content %}
<h2>{% trans "Document templates list" %}</h2>
{% can_create Banque %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-document-template' %}">
<i class="fa fa-cart-plus"></i> {% trans "Add a document template" %}
</a>
{% acl_end %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-document-template' %}">
<i class="fa fa-trash"></i> {% trans "Delete one or several document templates" %}
</a>
{% include 'cotisations/aff_document_template.html' %}
{% endblock %}

View file

@ -65,5 +65,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<i class="fa fa-list-ul"></i> {% trans "Payment methods" %} <i class="fa fa-list-ul"></i> {% trans "Payment methods" %}
</a> </a>
{% acl_end %} {% acl_end %}
{% can_view_all DocumentTemplate %}
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-document-template" %}">
<i class="fa fa-list-ul"></i> {% trans "Document templates" %}
</a>
{% acl_end %}
{% endblock %} {% endblock %}

View file

@ -31,12 +31,15 @@ from subprocess import Popen, PIPE
import os import os
from datetime import datetime from datetime import datetime
from django.db import models
from django.template.loader import get_template from django.template.loader import get_template
from django.template import Context from django.template import Context
from django.http import HttpResponse from django.http import HttpResponse
from django.conf import settings from django.conf import settings
from django.utils.text import slugify from django.utils.text import slugify
import logging from django.utils.translation import ugettext_lazy as _
from re2o.mixins import AclMixin, RevMixin
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-') TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
@ -44,6 +47,27 @@ CACHE_PREFIX = getattr(settings, 'TEX_CACHE_PREFIX', 'render-tex')
CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400) # 1 day CACHE_TIMEOUT = getattr(settings, 'TEX_CACHE_TIMEOUT', 86400) # 1 day
class DocumentTemplate(RevMixin, AclMixin, models.Model):
"""Represent a template in order to create documents such as invoice or
subscribtion voucher.
"""
template = models.FileField(
upload_to='templates/',
verbose_name=_('template')
)
name = models.CharField(
max_length=255,
verbose_name=_('name')
)
class Meta:
verbose_name = _("document template")
verbose_name_plural = _("document templates")
def __str__(self):
return str(self.name)
def render_invoice(_request, ctx={}): def render_invoice(_request, ctx={}):
""" """
Render an invoice using some available information such as the current Render an invoice using some available information such as the current

View file

@ -176,5 +176,25 @@ urlpatterns = [
views.control, views.control,
name='control' name='control'
), ),
url(
r'^add_document_template/$',
views.add_document_template,
name='add-document-template'
),
url(
r'^edit_document_template/(?P<documenttemplateid>[0-9]+)$',
views.edit_document_template,
name='edit-document-template'
),
url(
r'^del_document_template/$',
views.del_document_template,
name='del-document-template'
),
url(
r'^index_document_template/$',
views.index_document_template,
name='index-document-template'
),
url(r'^$', views.index, name='index'), url(r'^$', views.index, name='index'),
] + payment_methods.urls.urlpatterns ] + payment_methods.urls.urlpatterns

View file

@ -84,8 +84,10 @@ from .forms import (
CustomInvoiceForm, CustomInvoiceForm,
DiscountForm, DiscountForm,
CostEstimateForm, CostEstimateForm,
DocumentTemplateForm,
DelDocumentTemplateForm
) )
from .tex import render_invoice, escape_chars from .tex import render_invoice, escape_chars, DocumentTemplate
from .payment_methods.forms import payment_method_factory from .payment_methods.forms import payment_method_factory
from .utils import find_payment_method from .utils import find_payment_method
@ -720,7 +722,7 @@ def edit_paiement(request, paiement_instance, **_kwargs):
if payment_method is not None: if payment_method is not None:
payment_method.save() payment_method.save()
messages.success( messages.success(
request,_("The payment method was edited.") request, _("The payment method was edited.")
) )
return redirect(reverse('cotisations:index-paiement')) return redirect(reverse('cotisations:index-paiement'))
return form({ return form({
@ -954,7 +956,8 @@ def index_custom_invoice(request):
"""View used to display every custom invoice.""" """View used to display every custom invoice."""
pagination_number = GeneralOption.get_cached_value('pagination_number') pagination_number = GeneralOption.get_cached_value('pagination_number')
cost_estimate_ids = [i for i, in CostEstimate.objects.values_list('id')] cost_estimate_ids = [i for i, in CostEstimate.objects.values_list('id')]
custom_invoice_list = CustomInvoice.objects.prefetch_related('vente_set').exclude(id__in=cost_estimate_ids) custom_invoice_list = CustomInvoice.objects.prefetch_related(
'vente_set').exclude(id__in=cost_estimate_ids)
custom_invoice_list = SortTable.sort( custom_invoice_list = SortTable.sort(
custom_invoice_list, custom_invoice_list,
request.GET.get('col'), request.GET.get('col'),
@ -1020,7 +1023,8 @@ def credit_solde(request, user, **_kwargs):
kwargs={'userid': user.id} kwargs={'userid': user.id}
)) ))
refill_form = RechargeForm(request.POST or None, user=user, user_source=request.user) refill_form = RechargeForm(
request.POST or None, user=user, user_source=request.user)
if refill_form.is_valid(): if refill_form.is_valid():
price = refill_form.cleaned_data['value'] price = refill_form.cleaned_data['value']
invoice = Facture(user=user) invoice = Facture(user=user)
@ -1050,3 +1054,93 @@ def credit_solde(request, user, **_kwargs):
'max_balance': find_payment_method(p).maximum_balance, 'max_balance': find_payment_method(p).maximum_balance,
}, 'cotisations/facture.html', request) }, 'cotisations/facture.html', request)
@login_required
@can_create(DocumentTemplate)
def add_document_template(request):
"""
View used to add a document template.
"""
document_template = DocumentTemplateForm(request.POST or None)
if document_template.is_valid():
document_template.save()
messages.success(
request,
_("The document template was created.")
)
return redirect(reverse('cotisations:index-document-template'))
return form({
'factureform': document_template,
'action_name': _("Add"),
'title': _("New document template")
}, 'cotisations/facture.html', request)
@login_required
@can_edit(DocumentTemplate)
def edit_document_template(request, document_template_instance, **_kwargs):
"""
View used to edit a document_template.
"""
document_template = DocumentTemplateForm(
request.POST or None, instance=document_template_instance)
if document_template.is_valid():
if document_template.changed_data:
document_template.save()
messages.success(
request,
_("The document template was edited.")
)
return redirect(reverse('cotisations:index-document-template'))
return form({
'factureform': document_template,
'action_name': _("Edit"),
'title': _("Edit document template")
}, 'cotisations/facture.html', request)
@login_required
@can_delete_set(DocumentTemplate)
def del_document_template(request, instances):
"""
View used to delete a set of document template.
"""
document_template = DelDocumentTemplateForm(
request.POST or None, instances=instances)
if document_template.is_valid():
document_template_del = document_template.cleaned_data['document_templates']
for document_template in document_template_del:
try:
document_template.delete()
messages.success(
request,
_("The document template %(document_template)s was deleted.") % {
'document_template': document_template
}
)
except ProtectedError:
messages.error(
request,
_("The document template %(document_template)s can't be deleted \
because it is currently being used.") % {
'document_template': document_template
}
)
return redirect(reverse('cotisations:index-document-template'))
return form({
'factureform': document_template,
'action_name': _("Delete"),
'title': _("Delete document template")
}, 'cotisations/facture.html', request)
@login_required
@can_view_all(DocumentTemplate)
def index_document_template(request):
"""
View used to display the list of all available document templates.
"""
document_template_list = DocumentTemplate.objects.order_by('name')
return render(request, 'cotisations/index_document_template.html', {
'document_template_list': document_template_list
})

View file

@ -324,6 +324,19 @@ update_django() {
copy_templates_files() {
### Usage: copy_templates_files
#
# This will copy LaTeX templates in the media root.
echo "Copying LaTeX templates ..."
mkdir -p media/templates/
cp cotisations/templates/cotisations/factures.tex media/templates
echo "Copying LaTeX templates: Done"
}
create_superuser() { create_superuser() {
### Usage: create_superuser ### Usage: create_superuser
# #
@ -748,9 +761,10 @@ main_function() {
echo " * {help} ---------- Display this quick usage documentation" echo " * {help} ---------- Display this quick usage documentation"
echo " * {setup} --------- Launch the full interactive guide to setup entirely" echo " * {setup} --------- Launch the full interactive guide to setup entirely"
echo " re2o from scratch" echo " re2o from scratch"
echo " * {update} -------- Collect frontend statics, install the missing APT" echo " * {update} -------- Collect frontend statics, install the missing APT and copy LaTeX templates files"
echo " and pip packages and apply the migrations to the DB" echo " and pip packages and apply the migrations to the DB"
echo " * {update-django} - Apply Django migration and collect frontend statics" echo " * {update-django} - Apply Django migration and collect frontend statics"
echo " * {copy-template-files} - Copy LaTeX templates files to media/templates"
echo " * {update-packages} Install the missing APT and pip packages" echo " * {update-packages} Install the missing APT and pip packages"
echo " * {update-settings} Interactively rewrite the settings file" echo " * {update-settings} Interactively rewrite the settings file"
echo " * {reset-db} ------ Erase the previous local database, setup a new empty" echo " * {reset-db} ------ Erase the previous local database, setup a new empty"
@ -783,6 +797,11 @@ main_function() {
update ) update )
install_requirements install_requirements
update_django update_django
copy_templates_files
;;
copy-templates-files )
copy_templates_files
;; ;;
update-django ) update-django )