Commit 4fef4a60 authored by klafyvel's avatar klafyvel

Déplace les templates dans préférences.

parent f4b6f10d
Pipeline #1776 passed with stage
in 5 minutes and 4 seconds
......@@ -30,7 +30,7 @@ from django.contrib import admin
from reversion.admin import VersionAdmin
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
from .models import CustomInvoice, CostEstimate, DocumentTemplate
from .models import CustomInvoice, CostEstimate
class FactureAdmin(VersionAdmin):
......@@ -74,11 +74,6 @@ class CotisationAdmin(VersionAdmin):
pass
class DocumentTemplateAdmin(VersionAdmin):
"""Admin class for DocumentTemplate"""
pass
admin.site.register(Facture, FactureAdmin)
admin.site.register(Article, ArticleAdmin)
admin.site.register(Banque, BanqueAdmin)
......@@ -87,4 +82,3 @@ admin.site.register(Vente, VenteAdmin)
admin.site.register(Cotisation, CotisationAdmin)
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
admin.site.register(CostEstimate, CostEstimateAdmin)
admin.site.register(DocumentTemplate, DocumentTemplateAdmin)
......@@ -48,7 +48,7 @@ from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
from .models import (
Article, Paiement, Facture, Banque,
CustomInvoice, Vente, CostEstimate, DocumentTemplate
CustomInvoice, Vente, CostEstimate,
)
from .payment_methods import balance
......@@ -316,37 +316,3 @@ class RechargeForm(FormRevMixin, Form):
}
)
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()
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-03 16:48
from __future__ import unicode_literals
from django.db import migrations, models
import re2o.mixins
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),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-20 23:08
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0039_documenttemplate'),
]
operations = [
migrations.AlterField(
model_name='documenttemplate',
name='name',
field=models.CharField(max_length=125, unique=True, verbose_name='name'),
),
]
......@@ -33,7 +33,6 @@ each.
from __future__ import unicode_literals
from dateutil.relativedelta import relativedelta
import os
from django.db import models
from django.db.models import Q, Max
......@@ -956,56 +955,3 @@ def cotisation_post_delete(**_kwargs):
"""
regen('mac_ip_list')
regen('mailing')
class DocumentTemplate(RevMixin, AclMixin, models.Model):
"""Represent a template in order to create documents such as invoice or
subscription voucher.
"""
template = models.FileField(
upload_to='templates/',
verbose_name=_('template')
)
name = models.CharField(
max_length=125,
verbose_name=_('name'),
unique=True
)
class Meta:
verbose_name = _("document template")
verbose_name_plural = _("document templates")
def __str__(self):
return str(self.name)
@receiver(models.signals.post_delete, sender=DocumentTemplate)
def auto_delete_file_on_delete(sender, instance, **kwargs):
"""
Deletes file from filesystem
when corresponding `DocumentTemplate` object is deleted.
"""
if instance.template:
if os.path.isfile(instance.template.path):
os.remove(instance.template.path)
@receiver(models.signals.pre_save, sender=DocumentTemplate)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding `DocumentTemplate` object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_file = DocumentTemplate.objects.get(pk=instance.pk).template
except DocumentTemplate.DoesNotExist:
return False
new_file = instance.template
if not old_file == new_file:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
......@@ -51,7 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% bootstrap_form_errors discount_form %}
{% endif %}
<form class="form" method="post" enctype="multipart/form-data">
<form class="form" method="post">
{% csrf_token %}
{% bootstrap_form factureform %}
{% if payment_method %}
......
{% 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 %}
......@@ -181,25 +181,5 @@ urlpatterns = [
views.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
......@@ -70,7 +70,6 @@ from .models import (
CustomInvoice,
BaseInvoice,
CostEstimate,
DocumentTemplate
)
from .forms import (
FactureForm,
......@@ -85,8 +84,6 @@ from .forms import (
CustomInvoiceForm,
DiscountForm,
CostEstimateForm,
DocumentTemplateForm,
DelDocumentTemplateForm
)
from .tex import render_invoice, render_voucher, escape_chars
from .payment_methods.forms import payment_method_factory
......@@ -1056,102 +1053,6 @@ def credit_solde(request, user, **_kwargs):
}, '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,
request.FILES 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,
request.FILES 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
})
@login_required
@can_view(Facture)
def voucher_pdf(request, invoice, **_kwargs):
......
......@@ -40,7 +40,8 @@ from .models import (
HomeOption,
RadiusKey,
SwitchManagementCred,
Reminder
Reminder,
DocumentTemplate
)
......@@ -101,6 +102,12 @@ class ReminderAdmin(VersionAdmin):
"""Class reminder for switch"""
pass
class DocumentTemplateAdmin(VersionAdmin):
"""Admin class for DocumentTemplate"""
pass
admin.site.register(OptionalUser, OptionalUserAdmin)
admin.site.register(OptionalMachine, OptionalMachineAdmin)
admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
......@@ -113,3 +120,4 @@ admin.site.register(RadiusKey, RadiusKeyAdmin)
admin.site.register(SwitchManagementCred, SwitchManagementCredAdmin)
admin.site.register(AssoOption, AssoOptionAdmin)
admin.site.register(MailMessageOption, MailMessageOptionAdmin)
admin.site.register(DocumentTemplate, DocumentTemplateAdmin)
......@@ -43,10 +43,12 @@ from .models import (
RadiusKey,
SwitchManagementCred,
RadiusOption,
CotisationsOption
CotisationsOption,
DocumentTemplate
)
from topologie.models import Switch
class EditOptionalUserForm(ModelForm):
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
class Meta:
......@@ -376,3 +378,36 @@ class DelMailContactForm(Form):
else:
self.fields['mailcontacts'].queryset = MailContact.objects.all()
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()
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-03 19:56
# Generated by Django 1.10.7 on 2019-01-20 23:39
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import re2o.mixins
import preferences.models
import re2o.mixins
def initialize_invoice_template(apps, schema_editor):
def create_defaults(apps, schema_editor):
CotisationsOption = apps.get_model('preferences', 'CotisationsOption')
CotisationsOption.objects.get_or_create()
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0039_documenttemplate'),
('preferences', '0058_auto_20190108_1650'),
]
......@@ -25,13 +23,40 @@ class Migration(migrations.Migration):
name='CotisationsOption',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('invoice_template', models.OneToOneField(default=preferences.models.default_invoice,on_delete=django.db.models.deletion.PROTECT, related_name='invoice_template', to='cotisations.DocumentTemplate', verbose_name='Template for invoices')),
('voucher_template', models.OneToOneField(default=preferences.models.default_voucher, on_delete=django.db.models.deletion.PROTECT, related_name='voucher_template', to='cotisations.DocumentTemplate', verbose_name='Template for subscription voucher')),
('send_voucher_mail', models.BooleanField(default=False, verbose_name='Send voucher by email when the invoice is controlled.')),
],
options={
'verbose_name': 'cotisations options',
},
bases=(re2o.mixins.AclMixin, models.Model),
),
migrations.RunPython(initialize_invoice_template),
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=125, unique=True, verbose_name='name')),
],
options={
'verbose_name': 'document template',
'verbose_name_plural': 'document templates',
},
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
),
migrations.AddField(
model_name='assooption',
name='pres_name',
field=models.CharField(default='', help_text='Displayed on subscription vouchers', max_length=255, verbose_name='President of the association'),
),
migrations.AddField(
model_name='cotisationsoption',
name='invoice_template',
field=models.OneToOneField(default=preferences.models.default_invoice, on_delete=django.db.models.deletion.PROTECT, related_name='invoice_template', to='preferences.DocumentTemplate', verbose_name='Template for invoices'),
),
migrations.AddField(
model_name='cotisationsoption',
name='voucher_template',
field=models.OneToOneField(default=preferences.models.default_voucher, on_delete=django.db.models.deletion.PROTECT, related_name='voucher_template', to='preferences.DocumentTemplate', verbose_name='Template for subscription voucher'),
),
migrations.RunPython(create_defaults),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-10 22:13
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0059_cotisationsoption'),
]
operations = [
migrations.AddField(
model_name='assooption',
name='pres_name',
field=models.CharField(default='', help_text='Displayed on subscription vouchers', max_length=255, verbose_name='President of the association'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2019-01-20 18:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('preferences', '0060_assooption_pres_name'),
]
operations = [
migrations.AddField(
model_name='cotisationsoption',
name='send_voucher_mail',
field=models.BooleanField(default=False, verbose_name='Send voucher by email when the invoice is controlled.'),
),
]
......@@ -24,6 +24,7 @@
Reglages généraux, machines, utilisateurs, mail, general pour l'application.
"""
from __future__ import unicode_literals
import os
from django.utils.functional import cached_property
from django.utils import timezone
......@@ -35,9 +36,8 @@ from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
import machines.models
import cotisations.models
from re2o.mixins import AclMixin
from re2o.mixins import AclMixin, RevMixin
from re2o.aes_field import AESEncryptedField
from datetime import timedelta
......@@ -696,7 +696,7 @@ class RadiusOption(AclMixin, PreferencesModel):
def default_invoice():
tpl, _ = cotisations.models.DocumentTemplate.objects.get_or_create(
tpl, _ = DocumentTemplate.objects.get_or_create(
name="Re2o default invoice",
template="templates/default_invoice.tex"
)
......@@ -704,7 +704,7 @@ def default_invoice():
def default_voucher():
tpl, _ = cotisations.models.DocumentTemplate.objects.get_or_create(
tpl, _ = DocumentTemplate.objects.get_or_create(
name="Re2o default voucher",
template="templates/default_voucher.tex"
)
......@@ -716,14 +716,14 @@ class CotisationsOption(AclMixin, PreferencesModel):
verbose_name = _("cotisations options")
invoice_template = models.OneToOneField(
'cotisations.DocumentTemplate',
'preferences.DocumentTemplate',
verbose_name=_("Template for invoices"),
related_name="invoice_template",
on_delete=models.PROTECT,
default=default_invoice,
)
voucher_template = models.OneToOneField(
'cotisations.DocumentTemplate',
'preferences.DocumentTemplate',
verbose_name=_("Template for subscription voucher"),
related_name="voucher_template",
on_delete=models.PROTECT,
......@@ -733,3 +733,56 @@ class CotisationsOption(AclMixin, PreferencesModel):
verbose_name=_("Send voucher by email when the invoice is controlled."),
default=False,
)
class DocumentTemplate(RevMixin, AclMixin, models.Model):
"""Represent a template in order to create documents such as invoice or
subscription voucher.
"""
template = models.FileField(
upload_to='templates/',
verbose_name=_('template')
)
name = models.CharField(
max_length=125,
verbose_name=_('name'),
unique=True
)
class Meta:
verbose_name = _("document template")
verbose_name_plural = _("document templates")
def __str__(self):
return str(self.name)
@receiver(models.signals.post_delete, sender=DocumentTemplate)
def auto_delete_file_on_delete(sender, instance, **kwargs):
"""
Deletes file from filesystem
when corresponding `DocumentTemplate` object is deleted.
"""
if instance.template:
if os.path.isfile(instance.template.path):
os.remove(instance.template.path)
@receiver(models.signals.pre_save, sender=DocumentTemplate)
def auto_delete_file_on_change(sender, instance, **kwargs):
"""
Deletes old file from filesystem
when corresponding `DocumentTemplate` object is updated
with new file.
"""
if not instance.pk:
return False
try:
old_file = DocumentTemplate.objects.get(pk=instance.pk).template
except DocumentTemplate.DoesNotExist:
return False
new_file = instance.template
if not old_file == new_file:
if os.path.isfile(old_file.path):
os.remove(old_file.path)
......@@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<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 %}
{% include 'buttons/edit.html' with href='preferences:edit-document-template' id=template.id %}
{% acl_end %}
{% history_button template %}
</td>
......
......@@ -350,6 +350,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</table>
</div>
</div>
<div class="panel panel-default" id="templates">
<div class="panel-heading" data-toggle="collapse" href="#collapse_templates">
<h4 class="panel-title">
<a><i class="fa fa-edit"></i> {% trans "Document templates" %}</a>
</h4>
</div>
<div id="collapse_templates" class="panel-collapse panel-body collapse">
{% can_create DocumentTemplate %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences: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 'preferences:del-document-template' %}">
<i class="fa fa-trash"></i> {% trans "Delete one or several document templates" %}
</a>
{% include 'preferences/aff_document_template.html' %}
</div>
</div>
<div class="panel panel-default" id="cotisation">