mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-23 11:53:12 +00:00
commit
beae8ee83e
202 changed files with 5814 additions and 2608 deletions
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -164,3 +164,17 @@ Collec new statics
|
|||
```bash
|
||||
python3 manage.py collectstatic
|
||||
```
|
||||
|
||||
## MR 391: Document templates and subscription vouchers
|
||||
|
||||
Re2o can now use templates for generated invoices. To load default templates run
|
||||
|
||||
```bash
|
||||
./install update
|
||||
```
|
||||
|
||||
Be carefull, you need the proper rights to edit a DocumentTemplate.
|
||||
|
||||
Re2o now sends subscription voucher when an invoice is controlled. It uses one
|
||||
of the templates. You also need to set the name of the president of your association
|
||||
to be set in your settings.
|
||||
|
|
|
@ -28,7 +28,7 @@ done.
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
def _create_api_permission():
|
||||
|
@ -71,4 +71,5 @@ def can_view(user):
|
|||
'codename': settings.API_PERMISSION_CODENAME
|
||||
}
|
||||
can = user.has_perm('%(app_label)s.%(codename)s' % kwargs)
|
||||
return can, None if can else _("You cannot see this application.")
|
||||
return can, None if can else _("You don't have the right to see this"
|
||||
" application.")
|
||||
|
|
|
@ -46,6 +46,6 @@ class ExpiringTokenAuthentication(TokenAuthentication):
|
|||
)
|
||||
utc_now = datetime.datetime.now(datetime.timezone.utc)
|
||||
if token.created < utc_now - token_duration:
|
||||
raise exceptions.AuthenticationFailed(_('Token has expired'))
|
||||
raise exceptions.AuthenticationFailed(_("The token has expired."))
|
||||
|
||||
return token.user, token
|
||||
|
|
40
api/locale/fr/LC_MESSAGES/django.po
Normal file
40
api/locale/fr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,40 @@
|
|||
# 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 © 2018 Maël Kervella
|
||||
#
|
||||
# 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.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 2.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-01-08 23:06+0100\n"
|
||||
"PO-Revision-Date: 2019-01-07 01:37+0100\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: acl.py:74
|
||||
msgid "You don't have the right to see this application."
|
||||
msgstr "Vous n'avez pas le droit de voir cette application."
|
||||
|
||||
#: authentication.py:49
|
||||
msgid "The token has expired."
|
||||
msgstr "Le jeton a expiré."
|
|
@ -391,13 +391,25 @@ class OptionalTopologieSerializer(NamespacedHMSerializer):
|
|||
|
||||
class Meta:
|
||||
model = preferences.OptionalTopologie
|
||||
fields = ('radius_general_policy', 'vlan_decision_ok',
|
||||
'vlan_decision_nok', 'switchs_ip_type', 'switchs_web_management',
|
||||
fields = ('switchs_ip_type', 'switchs_web_management',
|
||||
'switchs_web_management_ssl', 'switchs_rest_management',
|
||||
'switchs_management_utils', 'switchs_management_interface_ip',
|
||||
'provision_switchs_enabled', 'switchs_provision', 'switchs_management_sftp_creds')
|
||||
|
||||
|
||||
class RadiusOptionSerializer(NamespacedHMSerializer):
|
||||
"""Serialize `preferences.models.RadiusOption` objects
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = preferences.RadiusOption
|
||||
fields = ('radius_general_policy', 'unknown_machine',
|
||||
'unknown_machine_vlan', 'unknown_port',
|
||||
'unknown_port_vlan', 'unknown_room', 'unknown_room_vlan',
|
||||
'non_member', 'non_member_vlan', 'banned', 'banned_vlan',
|
||||
'vlan_decision_ok')
|
||||
|
||||
|
||||
class GeneralOptionSerializer(NamespacedHMSerializer):
|
||||
"""Serialize `preferences.models.GeneralOption` objects.
|
||||
"""
|
||||
|
@ -811,7 +823,8 @@ class SwitchPortSerializer(serializers.ModelSerializer):
|
|||
model = topologie.Switch
|
||||
fields = ('short_name', 'model', 'switchbay', 'ports', 'ipv4', 'ipv6',
|
||||
'interfaces_subnet', 'interfaces6_subnet', 'automatic_provision', 'rest_enabled',
|
||||
'web_management_enabled', 'get_radius_key_value', 'get_management_cred_value')
|
||||
'web_management_enabled', 'get_radius_key_value', 'get_management_cred_value',
|
||||
'list_modules')
|
||||
|
||||
# LOCAL EMAILS
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ router.register_viewset(r'machines/role', views.RoleViewSet)
|
|||
router.register_view(r'preferences/optionaluser', views.OptionalUserView),
|
||||
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
|
||||
router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView),
|
||||
router.register_view(r'preferences/radiusoption', views.RadiusOptionView),
|
||||
router.register_view(r'preferences/generaloption', views.GeneralOptionView),
|
||||
router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'),
|
||||
router.register_view(r'preferences/assooption', views.AssoOptionView),
|
||||
|
|
15
api/views.py
15
api/views.py
|
@ -292,6 +292,17 @@ class OptionalTopologieView(generics.RetrieveAPIView):
|
|||
return preferences.OptionalTopologie.objects.first()
|
||||
|
||||
|
||||
class RadiusOptionView(generics.RetrieveAPIView):
|
||||
"""Exposes details of `preferences.models.OptionalTopologie` settings.
|
||||
"""
|
||||
permission_classes = (ACLPermission,)
|
||||
perms_map = {'GET': [preferences.RadiusOption.can_view_all]}
|
||||
serializer_class = serializers.RadiusOptionSerializer
|
||||
|
||||
def get_object(self):
|
||||
return preferences.RadiusOption.objects.first()
|
||||
|
||||
|
||||
class GeneralOptionView(generics.RetrieveAPIView):
|
||||
"""Exposes details of `preferences.models.GeneralOption` settings.
|
||||
"""
|
||||
|
@ -600,9 +611,11 @@ class HostMacIpView(generics.ListAPIView):
|
|||
"""Exposes the associations between hostname, mac address and IPv4 in
|
||||
order to build the DHCP lease files.
|
||||
"""
|
||||
queryset = all_active_interfaces()
|
||||
serializer_class = serializers.HostMacIpSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return all_active_interfaces()
|
||||
|
||||
|
||||
# Firewall
|
||||
|
||||
|
|
|
@ -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
|
||||
from .models import CustomInvoice, CostEstimate
|
||||
|
||||
|
||||
class FactureAdmin(VersionAdmin):
|
||||
|
@ -38,6 +38,11 @@ class FactureAdmin(VersionAdmin):
|
|||
pass
|
||||
|
||||
|
||||
class CostEstimateAdmin(VersionAdmin):
|
||||
"""Admin class for cost estimates."""
|
||||
pass
|
||||
|
||||
|
||||
class CustomInvoiceAdmin(VersionAdmin):
|
||||
"""Admin class for custom invoices."""
|
||||
pass
|
||||
|
@ -76,3 +81,4 @@ admin.site.register(Paiement, PaiementAdmin)
|
|||
admin.site.register(Vente, VenteAdmin)
|
||||
admin.site.register(Cotisation, CotisationAdmin)
|
||||
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
|
||||
admin.site.register(CostEstimate, CostEstimateAdmin)
|
||||
|
|
|
@ -46,7 +46,10 @@ from django.shortcuts import get_object_or_404
|
|||
|
||||
from re2o.field_permissions import FieldPermissionFormMixin
|
||||
from re2o.mixins import FormRevMixin
|
||||
from .models import Article, Paiement, Facture, Banque, CustomInvoice
|
||||
from .models import (
|
||||
Article, Paiement, Facture, Banque,
|
||||
CustomInvoice, Vente, CostEstimate,
|
||||
)
|
||||
from .payment_methods import balance
|
||||
|
||||
|
||||
|
@ -104,7 +107,44 @@ class SelectArticleForm(FormRevMixin, Form):
|
|||
user = kwargs.pop('user')
|
||||
target_user = kwargs.pop('target_user', None)
|
||||
super(SelectArticleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(user, target_user)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(
|
||||
user, target_user)
|
||||
|
||||
|
||||
class DiscountForm(Form):
|
||||
"""
|
||||
Form used in oder to create a discount on an invoice.
|
||||
"""
|
||||
is_relative = forms.BooleanField(
|
||||
label=_("Discount is on percentage."),
|
||||
required=False,
|
||||
)
|
||||
discount = forms.DecimalField(
|
||||
label=_("Discount"),
|
||||
max_value=100,
|
||||
min_value=0,
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
required=False,
|
||||
)
|
||||
|
||||
def apply_to_invoice(self, invoice):
|
||||
invoice_price = invoice.prix_total()
|
||||
discount = self.cleaned_data['discount']
|
||||
is_relative = self.cleaned_data['is_relative']
|
||||
if is_relative:
|
||||
amount = discount/100 * invoice_price
|
||||
else:
|
||||
amount = discount
|
||||
if amount:
|
||||
name = _("{}% discount") if is_relative else _("{}€ discount")
|
||||
name = name.format(discount)
|
||||
Vente.objects.create(
|
||||
facture=invoice,
|
||||
name=name,
|
||||
prix=-amount,
|
||||
number=1
|
||||
)
|
||||
|
||||
|
||||
class CustomInvoiceForm(FormRevMixin, ModelForm):
|
||||
|
@ -116,6 +156,15 @@ class CustomInvoiceForm(FormRevMixin, ModelForm):
|
|||
fields = '__all__'
|
||||
|
||||
|
||||
class CostEstimateForm(FormRevMixin, ModelForm):
|
||||
"""
|
||||
Form used to create a cost estimate.
|
||||
"""
|
||||
class Meta:
|
||||
model = CostEstimate
|
||||
exclude = ['paid', 'final_invoice']
|
||||
|
||||
|
||||
class ArticleForm(FormRevMixin, ModelForm):
|
||||
"""
|
||||
Form used to create an article.
|
||||
|
@ -248,7 +297,8 @@ class RechargeForm(FormRevMixin, Form):
|
|||
super(RechargeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['payment'].empty_label = \
|
||||
_("Select a payment method")
|
||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(user_source).exclude(is_balance=True)
|
||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(
|
||||
user_source).exclude(is_balance=True)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
|
@ -260,10 +310,9 @@ class RechargeForm(FormRevMixin, Form):
|
|||
if balance_method.maximum_balance is not None and \
|
||||
value + self.user.solde > balance_method.maximum_balance:
|
||||
raise forms.ValidationError(
|
||||
_("Requested amount is too high. Your balance can't exceed \
|
||||
%(max_online_balance)s €.") % {
|
||||
_("Requested amount is too high. Your balance can't exceed"
|
||||
" %(max_online_balance)s €.") % {
|
||||
'max_online_balance': balance_method.maximum_balance
|
||||
}
|
||||
)
|
||||
return self.cleaned_data
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: 2.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-08-18 13:17+0200\n"
|
||||
"POT-Creation-Date: 2019-01-12 16:50+0100\n"
|
||||
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language: fr_FR\n"
|
||||
|
@ -33,79 +33,98 @@ msgstr ""
|
|||
msgid "You don't have the right to view this application."
|
||||
msgstr "Vous n'avez pas le droit de voir cette application."
|
||||
|
||||
#: forms.py:63 forms.py:274
|
||||
#: forms.py:66 forms.py:299
|
||||
msgid "Select a payment method"
|
||||
msgstr "Sélectionnez un moyen de paiement"
|
||||
|
||||
#: forms.py:66 models.py:510
|
||||
#: forms.py:69 models.py:579
|
||||
msgid "Member"
|
||||
msgstr "Adhérent"
|
||||
|
||||
#: forms.py:68
|
||||
#: forms.py:71
|
||||
msgid "Select the proprietary member"
|
||||
msgstr "Sélectionnez l'adhérent propriétaire"
|
||||
|
||||
#: forms.py:69
|
||||
#: forms.py:72
|
||||
msgid "Validated invoice"
|
||||
msgstr "Facture validée"
|
||||
|
||||
#: forms.py:82
|
||||
#: forms.py:85
|
||||
msgid "A payment method must be specified."
|
||||
msgstr "Un moyen de paiement doit être renseigné."
|
||||
|
||||
#: forms.py:96 forms.py:120 templates/cotisations/aff_article.html:33
|
||||
#: templates/cotisations/facture.html:61
|
||||
#: forms.py:97 templates/cotisations/aff_article.html:33
|
||||
#: templates/cotisations/facture.html:67
|
||||
msgid "Article"
|
||||
msgstr "Article"
|
||||
|
||||
#: forms.py:100 forms.py:124 templates/cotisations/edit_facture.html:46
|
||||
#: forms.py:101 templates/cotisations/edit_facture.html:50
|
||||
msgid "Quantity"
|
||||
msgstr "Quantité"
|
||||
|
||||
#: forms.py:154
|
||||
#: forms.py:119
|
||||
msgid "Discount is on percentage."
|
||||
msgstr "La réduction est en pourcentage."
|
||||
|
||||
#: forms.py:123 templates/cotisations/facture.html:78
|
||||
msgid "Discount"
|
||||
msgstr "Réduction"
|
||||
|
||||
#: forms.py:140
|
||||
#, python-format
|
||||
msgid "{}% discount"
|
||||
msgstr "{}% de réduction"
|
||||
|
||||
#: forms.py:140
|
||||
msgid "{}€ discount"
|
||||
msgstr "{}€ de réduction"
|
||||
|
||||
#: forms.py:179
|
||||
msgid "Article name"
|
||||
msgstr "Nom de l'article"
|
||||
|
||||
#: forms.py:164 templates/cotisations/sidebar.html:50
|
||||
#: forms.py:189 templates/cotisations/sidebar.html:55
|
||||
msgid "Available articles"
|
||||
msgstr "Articles disponibles"
|
||||
|
||||
#: forms.py:192
|
||||
#: forms.py:217
|
||||
msgid "Payment method name"
|
||||
msgstr "Nom du moyen de paiement"
|
||||
|
||||
#: forms.py:204
|
||||
#: forms.py:229
|
||||
msgid "Available payment methods"
|
||||
msgstr "Moyens de paiement disponibles"
|
||||
|
||||
#: forms.py:230
|
||||
#: forms.py:255
|
||||
msgid "Bank name"
|
||||
msgstr "Nom de la banque"
|
||||
|
||||
#: forms.py:242
|
||||
#: forms.py:267
|
||||
msgid "Available banks"
|
||||
msgstr "Banques disponibles"
|
||||
|
||||
#: forms.py:261
|
||||
#: forms.py:286
|
||||
msgid "Amount"
|
||||
msgstr "Montant"
|
||||
|
||||
#: forms.py:267 templates/cotisations/aff_cotisations.html:44
|
||||
#: forms.py:292 templates/cotisations/aff_cost_estimate.html:42
|
||||
#: templates/cotisations/aff_cotisations.html:44
|
||||
#: templates/cotisations/aff_custom_invoice.html:42
|
||||
#: templates/cotisations/control.html:66
|
||||
msgid "Payment method"
|
||||
msgstr "Moyen de paiement"
|
||||
|
||||
#: forms.py:287
|
||||
#: forms.py:313
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Requested amount is too high. Your balance can't exceed "
|
||||
"%(max_online_balance)s €."
|
||||
msgstr ""
|
||||
"Le montant demandé trop grand. Votre solde ne peut excéder "
|
||||
"Le montant demandé est trop grand. Votre solde ne peut excéder "
|
||||
"%(max_online_balance)s €."
|
||||
|
||||
#: models.py:60 templates/cotisations/aff_cotisations.html:48
|
||||
#: models.py:60 templates/cotisations/aff_cost_estimate.html:46
|
||||
#: templates/cotisations/aff_cotisations.html:48
|
||||
#: templates/cotisations/aff_custom_invoice.html:46
|
||||
#: templates/cotisations/control.html:70
|
||||
msgid "Date"
|
||||
|
@ -133,9 +152,9 @@ msgstr "Peut voir un objet facture"
|
|||
|
||||
#: models.py:158
|
||||
msgid "Can edit all the previous invoices"
|
||||
msgstr "Peut modifier toutes les factures existantes"
|
||||
msgstr "Peut modifier toutes les factures précédentes"
|
||||
|
||||
#: models.py:160 models.py:305
|
||||
#: models.py:160 models.py:373
|
||||
msgid "invoice"
|
||||
msgstr "facture"
|
||||
|
||||
|
@ -156,128 +175,149 @@ msgid ""
|
|||
"You don't have the right to edit an invoice already controlled or "
|
||||
"invalidated."
|
||||
msgstr ""
|
||||
"Vous n'avez pas le droit de modifier une facture précedemment contrôlée ou "
|
||||
"Vous n'avez pas le droit de modifier une facture précédemment contrôlée ou "
|
||||
"invalidée."
|
||||
|
||||
#: models.py:184
|
||||
msgid "You don't have the right to delete an invoice."
|
||||
msgstr "Vous n'avez pas le droit de supprimer une facture."
|
||||
|
||||
#: models.py:186
|
||||
#: models.py:187
|
||||
msgid "You don't have the right to delete this user's invoices."
|
||||
msgstr "Vous n'avez pas le droit de supprimer les factures de cet utilisateur."
|
||||
|
||||
#: models.py:189
|
||||
#: models.py:191
|
||||
msgid ""
|
||||
"You don't have the right to delete an invoice already controlled or "
|
||||
"invalidated."
|
||||
msgstr ""
|
||||
"Vous n'avez pas le droit de supprimer une facture précedement contrôlée ou "
|
||||
"Vous n'avez pas le droit de supprimer une facture précédemment contrôlée ou "
|
||||
"invalidée."
|
||||
|
||||
#: models.py:197
|
||||
#: models.py:199
|
||||
msgid "You don't have the right to view someone else's invoices history."
|
||||
msgstr ""
|
||||
"Vous n'avez pas le droit de voir l'historique des factures d'un autre "
|
||||
"utilisateur."
|
||||
|
||||
#: models.py:200
|
||||
#: models.py:202
|
||||
msgid "The invoice has been invalidated."
|
||||
msgstr "La facture a été invalidée."
|
||||
|
||||
#: models.py:210
|
||||
#: models.py:214
|
||||
msgid "You don't have the right to edit the \"controlled\" state."
|
||||
msgstr "Vous n'avez pas le droit de modifier le statut \"contrôlé\"."
|
||||
|
||||
#: models.py:224
|
||||
#: models.py:228
|
||||
msgid "There are no payment method which you can use."
|
||||
msgstr "Il n'y a pas de moyen de paiement que vous puissiez utiliser."
|
||||
|
||||
#: models.py:226
|
||||
#: models.py:230
|
||||
msgid "There are no article that you can buy."
|
||||
msgstr "Il n'y a pas d'article que vous puissiez acheter."
|
||||
|
||||
#: models.py:261
|
||||
#: models.py:272
|
||||
msgid "Can view a custom invoice object"
|
||||
msgstr "Peut voir un objet facture personnalisée"
|
||||
|
||||
#: models.py:265 templates/cotisations/aff_custom_invoice.html:36
|
||||
#: models.py:276 templates/cotisations/aff_cost_estimate.html:36
|
||||
#: templates/cotisations/aff_custom_invoice.html:36
|
||||
msgid "Recipient"
|
||||
msgstr "Destinataire"
|
||||
|
||||
#: models.py:269 templates/cotisations/aff_paiement.html:33
|
||||
#: models.py:280 templates/cotisations/aff_paiement.html:33
|
||||
msgid "Payment type"
|
||||
msgstr "Type de paiement"
|
||||
|
||||
#: models.py:273
|
||||
#: models.py:284
|
||||
msgid "Address"
|
||||
msgstr "Adresse"
|
||||
|
||||
#: models.py:276 templates/cotisations/aff_custom_invoice.html:54
|
||||
#: models.py:287 templates/cotisations/aff_custom_invoice.html:54
|
||||
msgid "Paid"
|
||||
msgstr "Payé"
|
||||
|
||||
#: models.py:296 models.py:516 models.py:764
|
||||
#: models.py:291
|
||||
msgid "Remark"
|
||||
msgstr "Remarque"
|
||||
|
||||
#: models.py:300
|
||||
msgid "Can view a cost estimate object"
|
||||
msgstr "Peut voir un objet devis"
|
||||
|
||||
#: models.py:303
|
||||
msgid "Period of validity"
|
||||
msgstr "Période de validité"
|
||||
|
||||
#: models.py:340
|
||||
msgid "You don't have the right to delete a cost estimate."
|
||||
msgstr "Vous n'avez pas le droit de supprimer un devis."
|
||||
|
||||
#: models.py:343
|
||||
msgid "The cost estimate has an invoice and can't be deleted."
|
||||
msgstr "Le devis a une facture et ne peut pas être supprimé."
|
||||
|
||||
#: models.py:364 models.py:585 models.py:852
|
||||
msgid "Connection"
|
||||
msgstr "Connexion"
|
||||
|
||||
#: models.py:297 models.py:517 models.py:765
|
||||
#: models.py:365 models.py:586 models.py:853
|
||||
msgid "Membership"
|
||||
msgstr "Adhésion"
|
||||
|
||||
#: models.py:298 models.py:512 models.py:518 models.py:766
|
||||
#: models.py:366 models.py:581 models.py:587 models.py:854
|
||||
msgid "Both of them"
|
||||
msgstr "Les deux"
|
||||
|
||||
#: models.py:310
|
||||
#: models.py:378
|
||||
msgid "amount"
|
||||
msgstr "montant"
|
||||
|
||||
#: models.py:315
|
||||
#: models.py:383
|
||||
msgid "article"
|
||||
msgstr "article"
|
||||
|
||||
#: models.py:322
|
||||
#: models.py:390
|
||||
msgid "price"
|
||||
msgstr "prix"
|
||||
|
||||
#: models.py:327 models.py:535
|
||||
#: models.py:395 models.py:604
|
||||
msgid "duration (in months)"
|
||||
msgstr "durée (en mois)"
|
||||
|
||||
#: models.py:335 models.py:549 models.py:780
|
||||
#: models.py:403 models.py:618 models.py:868
|
||||
msgid "subscription type"
|
||||
msgstr "type de cotisation"
|
||||
|
||||
#: models.py:340
|
||||
#: models.py:408
|
||||
msgid "Can view a purchase object"
|
||||
msgstr "Peut voir un objet achat"
|
||||
|
||||
#: models.py:341
|
||||
#: models.py:409
|
||||
msgid "Can edit all the previous purchases"
|
||||
msgstr "Peut modifier tous les achats précédents"
|
||||
|
||||
#: models.py:343 models.py:774
|
||||
#: models.py:411 models.py:862
|
||||
msgid "purchase"
|
||||
msgstr "achat"
|
||||
|
||||
#: models.py:344
|
||||
#: models.py:412
|
||||
msgid "purchases"
|
||||
msgstr "achats"
|
||||
|
||||
#: models.py:411 models.py:573
|
||||
#: models.py:479 models.py:642
|
||||
msgid "Duration must be specified for a subscription."
|
||||
msgstr "La durée de la cotisation doit être indiquée."
|
||||
|
||||
#: models.py:418
|
||||
#: models.py:486
|
||||
msgid "You don't have the right to edit the purchases."
|
||||
msgstr "Vous n'avez pas le droit de modifier les achats."
|
||||
|
||||
#: models.py:423
|
||||
#: models.py:491
|
||||
msgid "You don't have the right to edit this user's purchases."
|
||||
msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur."
|
||||
|
||||
#: models.py:427
|
||||
#: models.py:495
|
||||
msgid ""
|
||||
"You don't have the right to edit a purchase already controlled or "
|
||||
"invalidated."
|
||||
|
@ -285,150 +325,150 @@ msgstr ""
|
|||
"Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou "
|
||||
"invalidé."
|
||||
|
||||
#: models.py:434
|
||||
#: models.py:502
|
||||
msgid "You don't have the right to delete a purchase."
|
||||
msgstr "Vous n'avez pas le droit de supprimer un achat."
|
||||
|
||||
#: models.py:436
|
||||
#: models.py:504
|
||||
msgid "You don't have the right to delete this user's purchases."
|
||||
msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur."
|
||||
|
||||
#: models.py:439
|
||||
#: models.py:507
|
||||
msgid ""
|
||||
"You don't have the right to delete a purchase already controlled or "
|
||||
"invalidated."
|
||||
msgstr ""
|
||||
"Vous n'avez pas le droit de supprimer un achat précédement contrôlé ou "
|
||||
"Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou "
|
||||
"invalidé."
|
||||
|
||||
#: models.py:447
|
||||
#: models.py:515
|
||||
msgid "You don't have the right to view someone else's purchase history."
|
||||
msgstr ""
|
||||
"Vous n'avez pas le droit de voir l'historique des achats d'un autre "
|
||||
"utilisateur."
|
||||
|
||||
#: models.py:511
|
||||
#: models.py:580
|
||||
msgid "Club"
|
||||
msgstr "Club"
|
||||
|
||||
#: models.py:523
|
||||
#: models.py:592
|
||||
msgid "designation"
|
||||
msgstr "désignation"
|
||||
|
||||
#: models.py:529
|
||||
#: models.py:598
|
||||
msgid "unit price"
|
||||
msgstr "prix unitaire"
|
||||
|
||||
#: models.py:541
|
||||
#: models.py:610
|
||||
msgid "type of users concerned"
|
||||
msgstr "type d'utilisateurs concernés"
|
||||
|
||||
#: models.py:553 models.py:649
|
||||
#: models.py:622 models.py:733
|
||||
msgid "is available for every user"
|
||||
msgstr "est disponible pour chaque utilisateur"
|
||||
|
||||
#: models.py:560
|
||||
#: models.py:629
|
||||
msgid "Can view an article object"
|
||||
msgstr "Peut voir un objet article"
|
||||
|
||||
#: models.py:561
|
||||
#: models.py:630
|
||||
msgid "Can buy every article"
|
||||
msgstr "Peut acheter chaque article"
|
||||
|
||||
#: models.py:569
|
||||
#: models.py:638
|
||||
msgid "Balance is a reserved article name."
|
||||
msgstr "Solde est un nom d'article réservé."
|
||||
|
||||
#: models.py:594
|
||||
#: models.py:663
|
||||
msgid "You can't buy this article."
|
||||
msgstr "Vous ne pouvez pas acheter cet article."
|
||||
|
||||
#: models.py:624
|
||||
#: models.py:708
|
||||
msgid "Can view a bank object"
|
||||
msgstr "Peut voir un objet banque"
|
||||
|
||||
#: models.py:626
|
||||
#: models.py:710
|
||||
msgid "bank"
|
||||
msgstr "banque"
|
||||
|
||||
#: models.py:627
|
||||
#: models.py:711
|
||||
msgid "banks"
|
||||
msgstr "banques"
|
||||
|
||||
#: models.py:645
|
||||
#: models.py:729
|
||||
msgid "method"
|
||||
msgstr "moyen"
|
||||
|
||||
#: models.py:654
|
||||
#: models.py:738
|
||||
msgid "is user balance"
|
||||
msgstr "est solde utilisateur"
|
||||
|
||||
#: models.py:655
|
||||
#: models.py:739
|
||||
msgid "There should be only one balance payment method."
|
||||
msgstr "Il ne devrait y avoir qu'un moyen de paiement solde."
|
||||
|
||||
#: models.py:661
|
||||
#: models.py:745
|
||||
msgid "Can view a payment method object"
|
||||
msgstr "Peut voir un objet moyen de paiement"
|
||||
|
||||
#: models.py:662
|
||||
#: models.py:746
|
||||
msgid "Can use every payment method"
|
||||
msgstr "Peut utiliser chaque moyen de paiement"
|
||||
|
||||
#: models.py:664
|
||||
#: models.py:748
|
||||
msgid "payment method"
|
||||
msgstr "moyen de paiement"
|
||||
|
||||
#: models.py:665
|
||||
#: models.py:749
|
||||
msgid "payment methods"
|
||||
msgstr "moyens de paiement"
|
||||
|
||||
#: models.py:699 payment_methods/comnpay/views.py:63
|
||||
#: models.py:787 payment_methods/comnpay/views.py:63
|
||||
#, python-format
|
||||
msgid "The subscription of %(member_name)s was extended to %(end_date)s."
|
||||
msgstr "La cotisation de %(member_name)s a été étendue au %(end_date)s."
|
||||
|
||||
#: models.py:709
|
||||
#: models.py:797
|
||||
msgid "The invoice was created."
|
||||
msgstr "La facture a été créée."
|
||||
|
||||
#: models.py:730
|
||||
#: models.py:818
|
||||
msgid "You can't use this payment method."
|
||||
msgstr "Vous ne pouvez pas utiliser ce moyen de paiement."
|
||||
|
||||
#: models.py:748
|
||||
#: models.py:836
|
||||
msgid "No custom payment method."
|
||||
msgstr "Pas de moyen de paiement personnalisé."
|
||||
|
||||
#: models.py:783
|
||||
#: models.py:871
|
||||
msgid "start date"
|
||||
msgstr "date de début"
|
||||
|
||||
#: models.py:786
|
||||
#: models.py:874
|
||||
msgid "end date"
|
||||
msgstr "date de fin"
|
||||
|
||||
#: models.py:791
|
||||
#: models.py:879
|
||||
msgid "Can view a subscription object"
|
||||
msgstr "Peut voir un objet cotisation"
|
||||
|
||||
#: models.py:792
|
||||
#: models.py:880
|
||||
msgid "Can edit the previous subscriptions"
|
||||
msgstr "Peut modifier les cotisations précédentes"
|
||||
|
||||
#: models.py:794
|
||||
#: models.py:882
|
||||
msgid "subscription"
|
||||
msgstr "cotisation"
|
||||
|
||||
#: models.py:795
|
||||
#: models.py:883
|
||||
msgid "subscriptions"
|
||||
msgstr "cotisations"
|
||||
|
||||
#: models.py:799
|
||||
#: models.py:887
|
||||
msgid "You don't have the right to edit a subscription."
|
||||
msgstr "Vous n'avez pas le droit de modifier une cotisation."
|
||||
|
||||
#: models.py:803
|
||||
#: models.py:891
|
||||
msgid ""
|
||||
"You don't have the right to edit a subscription already controlled or "
|
||||
"invalidated."
|
||||
|
@ -436,11 +476,11 @@ msgstr ""
|
|||
"Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée "
|
||||
"ou invalidée."
|
||||
|
||||
#: models.py:810
|
||||
#: models.py:898
|
||||
msgid "You don't have the right to delete a subscription."
|
||||
msgstr "Vous n'avez pas le droit de supprimer une cotisation."
|
||||
|
||||
#: models.py:813
|
||||
#: models.py:901
|
||||
msgid ""
|
||||
"You don't have the right to delete a subscription already controlled or "
|
||||
"invalidated."
|
||||
|
@ -448,7 +488,7 @@ msgstr ""
|
|||
"Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée "
|
||||
"ou invalidée."
|
||||
|
||||
#: models.py:821
|
||||
#: models.py:909
|
||||
msgid "You don't have the right to view someone else's subscription history."
|
||||
msgstr ""
|
||||
"Vous n'avez pas le droit de voir l'historique des cotisations d'un autre "
|
||||
|
@ -482,11 +522,11 @@ msgstr "Le montant maximal d'argent autorisé pour le solde."
|
|||
msgid "Allow user to credit their balance"
|
||||
msgstr "Autorise l'utilisateur à créditer son solde"
|
||||
|
||||
#: payment_methods/balance/models.py:81 payment_methods/balance/models.py:112
|
||||
#: payment_methods/balance/models.py:79 payment_methods/balance/models.py:110
|
||||
msgid "Your balance is too low for this operation."
|
||||
msgstr "Votre solde est trop bas pour cette opération."
|
||||
|
||||
#: payment_methods/balance/models.py:99 validators.py:20
|
||||
#: payment_methods/balance/models.py:97 validators.py:20
|
||||
msgid "There is already a payment method for user balance."
|
||||
msgstr "Il y a déjà un moyen de paiement pour le solde utilisateur."
|
||||
|
||||
|
@ -523,11 +563,11 @@ msgstr ""
|
|||
msgid "Production mode enabled (production URL, instead of homologation)"
|
||||
msgstr "Mode production activé (URL de production, au lieu d'homologation)"
|
||||
|
||||
#: payment_methods/comnpay/models.py:104
|
||||
#: payment_methods/comnpay/models.py:102
|
||||
msgid "Pay invoice number "
|
||||
msgstr "Payer la facture numéro "
|
||||
|
||||
#: payment_methods/comnpay/models.py:116
|
||||
#: payment_methods/comnpay/models.py:114
|
||||
msgid ""
|
||||
"In order to pay your invoice with ComNpay, the price must be greater than {} "
|
||||
"€."
|
||||
|
@ -559,6 +599,30 @@ msgstr ""
|
|||
msgid "no"
|
||||
msgstr "non"
|
||||
|
||||
#: payment_methods/note_kfet/forms.py:32
|
||||
msgid "pseudo note"
|
||||
msgstr "pseudo note"
|
||||
|
||||
#: payment_methods/note_kfet/forms.py:35
|
||||
msgid "Password"
|
||||
msgstr "Mot de passe"
|
||||
|
||||
#: payment_methods/note_kfet/models.py:40
|
||||
msgid "NoteKfet"
|
||||
msgstr "NoteKfet"
|
||||
|
||||
#: payment_methods/note_kfet/models.py:50
|
||||
msgid "server"
|
||||
msgstr "serveur"
|
||||
|
||||
#: payment_methods/note_kfet/views.py:60
|
||||
msgid "Unknown error."
|
||||
msgstr "Erreur inconnue."
|
||||
|
||||
#: payment_methods/note_kfet/views.py:88
|
||||
msgid "The payment with note was done."
|
||||
msgstr "Le paiement par note a été effectué."
|
||||
|
||||
#: templates/cotisations/aff_article.html:34
|
||||
msgid "Price"
|
||||
msgstr "Prix"
|
||||
|
@ -579,34 +643,47 @@ msgstr "Utilisateurs concernés"
|
|||
msgid "Available for everyone"
|
||||
msgstr "Disponible pour tous"
|
||||
|
||||
#: templates/cotisations/aff_article.html:52
|
||||
#: templates/cotisations/aff_paiement.html:48
|
||||
#: templates/cotisations/control.html:107 views.py:483 views.py:570
|
||||
#: views.py:650
|
||||
msgid "Edit"
|
||||
msgstr "Modifier"
|
||||
|
||||
#: templates/cotisations/aff_banque.html:32
|
||||
msgid "Bank"
|
||||
msgstr "Banque"
|
||||
|
||||
#: templates/cotisations/aff_cotisations.html:38
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: templates/cotisations/aff_cost_estimate.html:39
|
||||
#: templates/cotisations/aff_cotisations.html:41
|
||||
#: templates/cotisations/aff_custom_invoice.html:39
|
||||
#: templates/cotisations/control.html:63
|
||||
#: templates/cotisations/edit_facture.html:45
|
||||
#: templates/cotisations/edit_facture.html:49
|
||||
msgid "Designation"
|
||||
msgstr "Désignation"
|
||||
|
||||
#: templates/cotisations/aff_cost_estimate.html:40
|
||||
#: templates/cotisations/aff_cotisations.html:42
|
||||
#: templates/cotisations/aff_custom_invoice.html:40
|
||||
#: templates/cotisations/control.html:64
|
||||
msgid "Total price"
|
||||
msgstr "Prix total"
|
||||
|
||||
#: templates/cotisations/aff_cost_estimate.html:50
|
||||
msgid "Validity"
|
||||
msgstr "Validité"
|
||||
|
||||
#: templates/cotisations/aff_cost_estimate.html:54
|
||||
msgid "Cost estimate ID"
|
||||
msgstr "ID devis"
|
||||
|
||||
#: templates/cotisations/aff_cost_estimate.html:58
|
||||
msgid "Invoice created"
|
||||
msgstr "Facture créée"
|
||||
|
||||
#: templates/cotisations/aff_cost_estimate.html:91
|
||||
#: templates/cotisations/aff_cotisations.html:81
|
||||
#: templates/cotisations/aff_custom_invoice.html:79
|
||||
msgid "PDF"
|
||||
msgstr "PDF"
|
||||
|
||||
#: templates/cotisations/aff_cotisations.html:38
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: templates/cotisations/aff_cotisations.html:52
|
||||
#: templates/cotisations/aff_custom_invoice.html:50
|
||||
#: templates/cotisations/control.html:56
|
||||
|
@ -617,11 +694,6 @@ msgstr "ID facture"
|
|||
msgid "Controlled invoice"
|
||||
msgstr "Facture contrôlée"
|
||||
|
||||
#: templates/cotisations/aff_cotisations.html:81
|
||||
#: templates/cotisations/aff_custom_invoice.html:79
|
||||
msgid "PDF"
|
||||
msgstr "PDF"
|
||||
|
||||
#: templates/cotisations/aff_cotisations.html:84
|
||||
msgid "Invalidated invoice"
|
||||
msgstr "Facture invalidée"
|
||||
|
@ -666,6 +738,11 @@ msgstr "Validé"
|
|||
msgid "Controlled"
|
||||
msgstr "Contrôlé"
|
||||
|
||||
#: templates/cotisations/control.html:107 views.py:642 views.py:729
|
||||
#: views.py:809
|
||||
msgid "Edit"
|
||||
msgstr "Modifier"
|
||||
|
||||
#: templates/cotisations/delete.html:29
|
||||
msgid "Deletion of subscriptions"
|
||||
msgstr "Suppression de cotisations"
|
||||
|
@ -676,33 +753,33 @@ msgid ""
|
|||
"Warning: are you sure you really want to delete this %(object_name)s object "
|
||||
"( %(objet)s )?"
|
||||
msgstr ""
|
||||
"\tAttention: voulez-vous vraiment supprimer cet objet %(object_name)s "
|
||||
"Attention: voulez-vous vraiment supprimer cet objet %(object_name)s "
|
||||
"( %(objet)s ) ?"
|
||||
|
||||
#: templates/cotisations/delete.html:38
|
||||
#: templates/cotisations/edit_facture.html:60
|
||||
#: views.py:181 views.py:235
|
||||
#: templates/cotisations/edit_facture.html:64 views.py:178 views.py:228
|
||||
#: views.py:280
|
||||
msgid "Confirm"
|
||||
msgstr "Valider"
|
||||
msgstr "Confirmer"
|
||||
|
||||
#: templates/cotisations/edit_facture.html:31
|
||||
#: templates/cotisations/facture.html:30
|
||||
msgid "Creation and editing of invoices"
|
||||
msgstr "Création et modification de factures"
|
||||
|
||||
#: templates/cotisations/edit_facture.html:38
|
||||
msgid "Edit the invoice"
|
||||
#: templates/cotisations/edit_facture.html:41
|
||||
msgid "Edit invoice"
|
||||
msgstr "Modifier la facture"
|
||||
|
||||
#: templates/cotisations/edit_facture.html:41
|
||||
#: templates/cotisations/facture.html:56
|
||||
#: templates/cotisations/edit_facture.html:45
|
||||
#: templates/cotisations/facture.html:62
|
||||
#: templates/cotisations/index_article.html:30
|
||||
msgid "Articles"
|
||||
msgstr "Articles"
|
||||
|
||||
#: templates/cotisations/facture.html:37
|
||||
msgid "Buy"
|
||||
msgstr "Acheter une cotisation"
|
||||
msgstr "Acheter"
|
||||
|
||||
#: templates/cotisations/facture.html:40
|
||||
#, python-format
|
||||
|
@ -714,11 +791,11 @@ msgstr "Solde maximum autorisé : %(max_balance)s €"
|
|||
msgid "Current balance: %(balance)s €"
|
||||
msgstr "Solde actuel : %(balance)s €"
|
||||
|
||||
#: templates/cotisations/facture.html:70
|
||||
#: templates/cotisations/facture.html:76
|
||||
msgid "Add an extra article"
|
||||
msgstr "Ajouter un article supplémentaire"
|
||||
|
||||
#: templates/cotisations/facture.html:72
|
||||
#: templates/cotisations/facture.html:82
|
||||
msgid "Total price: <span id=\"total_price\">0,00</span> €"
|
||||
msgstr "Prix total : <span id=\"total_price\">0,00</span> €"
|
||||
|
||||
|
@ -730,9 +807,8 @@ msgstr "Factures"
|
|||
msgid "Subscriptions"
|
||||
msgstr "Cotisations"
|
||||
|
||||
|
||||
#: templates/cotisations/index_article.html:33
|
||||
msgid "Article types list"
|
||||
msgid "List of article types"
|
||||
msgstr "Liste des types d'article"
|
||||
|
||||
#: templates/cotisations/index_article.html:36
|
||||
|
@ -744,12 +820,12 @@ msgid "Delete one or several article types"
|
|||
msgstr "Supprimer un ou plusieurs types d'article"
|
||||
|
||||
#: templates/cotisations/index_banque.html:30
|
||||
#: templates/cotisations/sidebar.html:55
|
||||
#: templates/cotisations/sidebar.html:60
|
||||
msgid "Banks"
|
||||
msgstr "Banques"
|
||||
|
||||
#: templates/cotisations/index_banque.html:33
|
||||
msgid "Banks list"
|
||||
msgid "List of banks"
|
||||
msgstr "Liste des banques"
|
||||
|
||||
#: templates/cotisations/index_banque.html:36
|
||||
|
@ -760,17 +836,26 @@ msgstr "Ajouter une banque"
|
|||
msgid "Delete one or several banks"
|
||||
msgstr "Supprimer une ou plusieurs banques"
|
||||
|
||||
#: templates/cotisations/index_cost_estimate.html:28
|
||||
#: templates/cotisations/sidebar.html:50
|
||||
msgid "Cost estimates"
|
||||
msgstr "Devis"
|
||||
|
||||
#: templates/cotisations/index_cost_estimate.html:31
|
||||
msgid "List of cost estimates"
|
||||
msgstr "Liste des devis"
|
||||
|
||||
#: templates/cotisations/index_custom_invoice.html:28
|
||||
#: templates/cotisations/sidebar.html:45
|
||||
msgid "Custom invoices"
|
||||
msgstr "Factures personnalisées"
|
||||
|
||||
#: templates/cotisations/index_custom_invoice.html:31
|
||||
msgid "Custom invoices list"
|
||||
msgstr "Liste des factures personalisées"
|
||||
msgid "List of custom invoices"
|
||||
msgstr "Liste des factures personnalisées"
|
||||
|
||||
#: templates/cotisations/index_paiement.html:30
|
||||
#: templates/cotisations/sidebar.html:60
|
||||
#: templates/cotisations/sidebar.html:65
|
||||
msgid "Payment methods"
|
||||
msgstr "Moyens de paiement"
|
||||
|
||||
|
@ -793,9 +878,9 @@ msgstr "Rechargement de solde"
|
|||
#: templates/cotisations/payment.html:34
|
||||
#, python-format
|
||||
msgid "Pay %(amount)s €"
|
||||
msgstr "Recharger de %(amount)s €"
|
||||
msgstr "Payer %(amount)s €"
|
||||
|
||||
#: templates/cotisations/payment.html:42 views.py:870
|
||||
#: templates/cotisations/payment.html:42 views.py:1049
|
||||
msgid "Pay"
|
||||
msgstr "Payer"
|
||||
|
||||
|
@ -807,81 +892,104 @@ msgstr "Créer une facture"
|
|||
msgid "Control the invoices"
|
||||
msgstr "Contrôler les factures"
|
||||
|
||||
#: views.py:167
|
||||
#: views.py:164
|
||||
msgid "You need to choose at least one article."
|
||||
msgstr "Vous devez choisir au moins un article."
|
||||
|
||||
#: views.py:222
|
||||
msgid "The cost estimate was created."
|
||||
msgstr "Le devis a été créé."
|
||||
|
||||
#: views.py:228
|
||||
#: views.py:232 views.py:534
|
||||
msgid "Cost estimate"
|
||||
msgstr "Devis"
|
||||
|
||||
#: views.py:274
|
||||
msgid "The custom invoice was created."
|
||||
msgstr "La facture personnalisée a été créée."
|
||||
|
||||
#: views.py:316 views.py:370
|
||||
#: views.py:363 views.py:466
|
||||
msgid "The invoice was edited."
|
||||
msgstr "La facture a été modifiée."
|
||||
|
||||
#: views.py:336 views.py:430
|
||||
#: views.py:383 views.py:589
|
||||
msgid "The invoice was deleted."
|
||||
msgstr "La facture a été supprimée."
|
||||
|
||||
#: views.py:341 views.py:435
|
||||
#: views.py:388 views.py:594
|
||||
msgid "Invoice"
|
||||
msgstr "Facture"
|
||||
|
||||
#: views.py:456
|
||||
#: views.py:417
|
||||
msgid "The cost estimate was edited."
|
||||
msgstr "Le devis a été modifié."
|
||||
|
||||
#: views.py:424
|
||||
msgid "Edit cost estimate"
|
||||
msgstr "Modifier le devis"
|
||||
|
||||
#: views.py:436
|
||||
msgid "An invoice was successfully created from your cost estimate."
|
||||
msgstr "Une facture a bien été créée à partir de votre devis."
|
||||
|
||||
#: views.py:529
|
||||
msgid "The cost estimate was deleted."
|
||||
msgstr "Le devis a été supprimé."
|
||||
|
||||
#: views.py:615
|
||||
msgid "The article was created."
|
||||
msgstr "L'article a été créé."
|
||||
|
||||
#: views.py:461 views.py:534 views.py:627
|
||||
#: views.py:620 views.py:693 views.py:786
|
||||
msgid "Add"
|
||||
msgstr "Ajouter"
|
||||
|
||||
#: views.py:462
|
||||
#: views.py:621
|
||||
msgid "New article"
|
||||
msgstr "Nouvel article"
|
||||
|
||||
#: views.py:478
|
||||
#: views.py:637
|
||||
msgid "The article was edited."
|
||||
msgstr "L'article a été modifié."
|
||||
|
||||
#: views.py:484
|
||||
#: views.py:643
|
||||
msgid "Edit article"
|
||||
msgstr "Modifier l'article"
|
||||
|
||||
#: views.py:500
|
||||
#: views.py:659
|
||||
msgid "The articles were deleted."
|
||||
msgstr "Les articles ont été supprimés."
|
||||
|
||||
#: views.py:505 views.py:605 views.py:685
|
||||
#: views.py:664 views.py:764 views.py:844
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: views.py:506
|
||||
#: views.py:665
|
||||
msgid "Delete article"
|
||||
msgstr "Supprimer l'article"
|
||||
|
||||
#: views.py:528
|
||||
#: views.py:687
|
||||
msgid "The payment method was created."
|
||||
msgstr "Le moyen de paiment a été créé."
|
||||
|
||||
#: views.py:535
|
||||
#: views.py:694
|
||||
msgid "New payment method"
|
||||
msgstr "Nouveau moyen de paiement"
|
||||
|
||||
#: views.py:564
|
||||
#: views.py:723
|
||||
msgid "The payment method was edited."
|
||||
msgstr "Le moyen de paiment a été modifié."
|
||||
|
||||
#: views.py:571
|
||||
#: views.py:730
|
||||
msgid "Edit payment method"
|
||||
msgstr "Modifier le moyen de paiement"
|
||||
|
||||
#: views.py:590
|
||||
#: views.py:749
|
||||
#, python-format
|
||||
msgid "The payment method %(method_name)s was deleted."
|
||||
msgstr "Le moyen de paiement %(method_name)s a été supprimé."
|
||||
|
||||
#: views.py:597
|
||||
#: views.py:756
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The payment method %(method_name)s can't be deleted "
|
||||
|
@ -890,52 +998,51 @@ msgstr ""
|
|||
"Le moyen de paiement %(method_name)s ne peut pas être supprimé car il y a "
|
||||
"des factures qui l'utilisent."
|
||||
|
||||
#: views.py:606
|
||||
#: views.py:765
|
||||
msgid "Delete payment method"
|
||||
msgstr "Supprimer le moyen de paiement"
|
||||
|
||||
#: views.py:622
|
||||
#: views.py:781
|
||||
msgid "The bank was created."
|
||||
msgstr "La banque a été créée."
|
||||
|
||||
#: views.py:628
|
||||
#: views.py:787
|
||||
msgid "New bank"
|
||||
msgstr "Nouvelle banque"
|
||||
|
||||
#: views.py:645
|
||||
#: views.py:804
|
||||
msgid "The bank was edited."
|
||||
msgstr "La banque a été modifiée."
|
||||
|
||||
#: views.py:651
|
||||
#: views.py:810
|
||||
msgid "Edit bank"
|
||||
msgstr "Modifier la banque"
|
||||
|
||||
#: views.py:670
|
||||
#: views.py:829
|
||||
#, python-format
|
||||
msgid "The bank %(bank_name)s was deleted."
|
||||
msgstr "La banque %(bank_name)s a été supprimée."
|
||||
|
||||
#: views.py:677
|
||||
#: views.py:836
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The bank %(bank_name)s can't be deleted because there "
|
||||
"are invoices using it."
|
||||
"The bank %(bank_name)s can't be deleted because there are invoices using it."
|
||||
msgstr ""
|
||||
"La banque %(bank_name)s ne peut pas être supprimée car il y a des factures "
|
||||
"qui l'utilisent."
|
||||
|
||||
#: views.py:686
|
||||
#: views.py:845
|
||||
msgid "Delete bank"
|
||||
msgstr "Supprimer la banque"
|
||||
|
||||
#: views.py:722
|
||||
#: views.py:881
|
||||
msgid "Your changes have been properly taken into account."
|
||||
msgstr "Vos modifications ont correctement été prises en compte."
|
||||
|
||||
#: views.py:834
|
||||
#: views.py:1016
|
||||
msgid "You are not allowed to credit your balance."
|
||||
msgstr "Vous n'êtes pas autorisés à créditer votre solde."
|
||||
|
||||
#: views.py:869
|
||||
#: views.py:1048
|
||||
msgid "Refill your balance"
|
||||
msgstr "Recharger votre solde"
|
||||
|
|
20
cotisations/migrations/0036_custominvoice_remark.py
Normal file
20
cotisations/migrations/0036_custominvoice_remark.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-12-29 14:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0035_notepayment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='custominvoice',
|
||||
name='remark',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='Remark'),
|
||||
),
|
||||
]
|
28
cotisations/migrations/0037_costestimate.py
Normal file
28
cotisations/migrations/0037_costestimate.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-12-29 21:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0036_custominvoice_remark'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CostEstimate',
|
||||
fields=[
|
||||
('custominvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.CustomInvoice')),
|
||||
('validity', models.DurationField(verbose_name='Period of validity')),
|
||||
('final_invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='origin_cost_estimate', to='cotisations.CustomInvoice')),
|
||||
],
|
||||
options={
|
||||
'permissions': (('view_costestimate', 'Can view a cost estimate object'),),
|
||||
},
|
||||
bases=('cotisations.custominvoice',),
|
||||
),
|
||||
]
|
31
cotisations/migrations/0038_auto_20181231_1657.py
Normal file
31
cotisations/migrations/0038_auto_20181231_1657.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-12-31 22:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0037_costestimate'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='costestimate',
|
||||
name='final_invoice',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='origin_cost_estimate', to='cotisations.CustomInvoice'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='costestimate',
|
||||
name='validity',
|
||||
field=models.DurationField(help_text='DD HH:MM:SS', verbose_name='Period of validity'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='custominvoice',
|
||||
name='paid',
|
||||
field=models.BooleanField(default=False, verbose_name='Paid'),
|
||||
),
|
||||
]
|
|
@ -46,11 +46,14 @@ from django.urls import reverse
|
|||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
|
||||
from preferences.models import CotisationsOption
|
||||
from machines.models import regen
|
||||
from re2o.field_permissions import FieldPermissionModelMixin
|
||||
from re2o.mixins import AclMixin, RevMixin
|
||||
|
||||
from cotisations.utils import find_payment_method, send_mail_invoice
|
||||
from cotisations.utils import (
|
||||
find_payment_method, send_mail_invoice, send_mail_voucher
|
||||
)
|
||||
from cotisations.validators import check_no_balance
|
||||
|
||||
|
||||
|
@ -236,15 +239,35 @@ class Facture(BaseInvoice):
|
|||
'control': self.can_change_control,
|
||||
}
|
||||
self.__original_valid = self.valid
|
||||
self.__original_control = self.control
|
||||
|
||||
def get_subscription(self):
|
||||
"""Returns every subscription associated with this invoice."""
|
||||
return Cotisation.objects.filter(
|
||||
vente__in=self.vente_set.filter(
|
||||
Q(type_cotisation='All') |
|
||||
Q(type_cotisation='Adhesion')
|
||||
)
|
||||
)
|
||||
|
||||
def is_subscription(self):
|
||||
"""Returns True if this invoice contains at least one subscribtion."""
|
||||
return bool(self.get_subscription())
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Facture, self).save(*args, **kwargs)
|
||||
if not self.__original_valid and self.valid:
|
||||
send_mail_invoice(self)
|
||||
if self.is_subscription() \
|
||||
and not self.__original_control \
|
||||
and self.control \
|
||||
and CotisationsOption.get_cached_value('send_voucher_mail'):
|
||||
send_mail_voucher(self)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user) + ' ' + str(self.date)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Facture)
|
||||
def facture_post_save(**kwargs):
|
||||
"""
|
||||
|
@ -284,8 +307,65 @@ class CustomInvoice(BaseInvoice):
|
|||
verbose_name=_("Address")
|
||||
)
|
||||
paid = models.BooleanField(
|
||||
verbose_name=_("Paid")
|
||||
verbose_name=_("Paid"),
|
||||
default=False
|
||||
)
|
||||
remark = models.TextField(
|
||||
verbose_name=_("Remark"),
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
|
||||
class CostEstimate(CustomInvoice):
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_costestimate', _("Can view a cost estimate object")),
|
||||
)
|
||||
validity = models.DurationField(
|
||||
verbose_name=_("Period of validity"),
|
||||
help_text="DD HH:MM:SS"
|
||||
)
|
||||
final_invoice = models.ForeignKey(
|
||||
CustomInvoice,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="origin_cost_estimate",
|
||||
primary_key=False
|
||||
)
|
||||
|
||||
def create_invoice(self):
|
||||
"""Create a CustomInvoice from the CostEstimate."""
|
||||
if self.final_invoice is not None:
|
||||
return self.final_invoice
|
||||
invoice = CustomInvoice()
|
||||
invoice.recipient = self.recipient
|
||||
invoice.payment = self.payment
|
||||
invoice.address = self.address
|
||||
invoice.paid = False
|
||||
invoice.remark = self.remark
|
||||
invoice.date = timezone.now()
|
||||
invoice.save()
|
||||
self.final_invoice = invoice
|
||||
self.save()
|
||||
for sale in self.vente_set.all():
|
||||
Vente.objects.create(
|
||||
facture=invoice,
|
||||
name=sale.name,
|
||||
prix=sale.prix,
|
||||
number=sale.number,
|
||||
)
|
||||
return invoice
|
||||
|
||||
def can_delete(self, user_request, *args, **kwargs):
|
||||
if not user_request.has_perm('cotisations.delete_costestimate'):
|
||||
return False, _("You don't have the right "
|
||||
"to delete a cost estimate.")
|
||||
if self.final_invoice is not None:
|
||||
return False, _("The cost estimate has an "
|
||||
"invoice and can't be deleted.")
|
||||
return True, None
|
||||
|
||||
|
||||
# TODO : change Vente to Purchase
|
||||
|
@ -624,7 +704,7 @@ class Article(RevMixin, AclMixin, models.Model):
|
|||
objects_pool = cls.objects.filter(
|
||||
Q(type_user='All') | Q(type_user='Adherent')
|
||||
)
|
||||
if not target_user.is_adherent():
|
||||
if target_user is not None and not target_user.is_adherent():
|
||||
objects_pool = objects_pool.filter(
|
||||
Q(type_cotisation='All') | Q(type_cotisation='Adhesion')
|
||||
)
|
||||
|
@ -718,7 +798,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
|||
if payment_method is not None and use_payment_method:
|
||||
return payment_method.end_payment(invoice, request)
|
||||
|
||||
## So make this invoice valid, trigger send mail
|
||||
# So make this invoice valid, trigger send mail
|
||||
invoice.valid = True
|
||||
invoice.save()
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ def note_payment(request, facture, factureid):
|
|||
user = facture.user
|
||||
payment_method = find_payment_method(facture.paiement)
|
||||
if not payment_method or not isinstance(payment_method, NotePayment):
|
||||
messages.error(request, "Erreur inconnue")
|
||||
messages.error(request, _("Unknown error."))
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': user.id}
|
||||
|
@ -85,7 +85,7 @@ def note_payment(request, facture, factureid):
|
|||
)
|
||||
facture.valid = True
|
||||
facture.save()
|
||||
messages.success(request, "Le paiement par note a bien été effectué")
|
||||
messages.success(request, _("The payment with note was done."))
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': user.id}
|
||||
|
|
|
@ -49,9 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ article.available_for_everyone | tick }}</td>
|
||||
<td class="text-right">
|
||||
{% can_edit article %}
|
||||
<a class="btn btn-primary btn-sm" role="button" title="{% trans "Edit" %}" href="{% url 'cotisations:edit-article' article.id %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% include 'buttons/edit.html' with href='cotisations:edit-article' id=article.id %}
|
||||
{% acl_end %}
|
||||
{% history_button article %}
|
||||
</td>
|
||||
|
|
101
cotisations/templates/cotisations/aff_cost_estimate.html
Normal file
101
cotisations/templates/cotisations/aff_cost_estimate.html
Normal file
|
@ -0,0 +1,101 @@
|
|||
{% 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 © 2018 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 i18n %}
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load design %}
|
||||
|
||||
<div class="table-responsive">
|
||||
{% if cost_estimate_list.paginator %}
|
||||
{% include 'pagination.html' with list=cost_estimate_list%}
|
||||
{% endif %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% trans "Recipient" as tr_recip %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_recip %}
|
||||
</th>
|
||||
<th>{% trans "Designation" %}</th>
|
||||
<th>{% trans "Total price" %}</th>
|
||||
<th>
|
||||
{% trans "Payment method" as tr_payment_method %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='payement' text=tr_payment_method %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Date" as tr_date %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='date' text=tr_date %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Validity" as tr_validity %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='validity' text=tr_validity %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Cost estimate ID" as tr_estimate_id %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_estimate_id %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Invoice created" as tr_invoice_created%}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='paid' text=tr_invoice_created %}
|
||||
</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for estimate in cost_estimate_list %}
|
||||
<tr>
|
||||
<td>{{ estimate.recipient }}</td>
|
||||
<td>{{ estimate.name }}</td>
|
||||
<td>{{ estimate.prix_total }}</td>
|
||||
<td>{{ estimate.payment }}</td>
|
||||
<td>{{ estimate.date }}</td>
|
||||
<td>{{ estimate.validity }}</td>
|
||||
<td>{{ estimate.id }}</td>
|
||||
<td>
|
||||
{% if estimate.final_invoice %}
|
||||
<a href="{% url 'cotisations:edit-custom-invoice' estimate.final_invoice.pk %}"><i style="color: #1ECA18;" class="fa fa-check"></i></a>
|
||||
{% else %}
|
||||
<i style="color: #D10115;" class="fa fa-times"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% can_edit estimate %}
|
||||
{% include 'buttons/edit.html' with href='cotisations:edit-cost-estimate' id=estimate.id %}
|
||||
{% acl_end %}
|
||||
{% history_button estimate %}
|
||||
{% include 'buttons/suppr.html' with href='cotisations:del-cost-estimate' id=estimate.id %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:cost-estimate-to-invoice' estimate.id %}">
|
||||
<i class="fa fa-file"></i>
|
||||
</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:cost-estimate-pdf' estimate.id %}">
|
||||
<i class="fa fa-file-pdf-o"></i> {% trans "PDF" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% if custom_invoice_list.paginator %}
|
||||
{% include 'pagination.html' with list=custom_invoice_list %}
|
||||
{% endif %}
|
||||
</div>
|
|
@ -83,6 +83,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% else %}
|
||||
<i class="text-danger">{% trans "Invalidated invoice" %}</i>
|
||||
{% endif %}
|
||||
{% if facture.control and facture.is_subscription %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:voucher-pdf' facture.id %}">
|
||||
<i class="fa fa-file-pdf-o"></i> {% trans "Voucher" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<th>
|
||||
{% trans "Recipient" as tr_recip %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_recip %}
|
||||
</th>
|
||||
<th>{% trans "Designation" %}</th>
|
||||
<th>{% trans "Total price" %}</th>
|
||||
|
|
|
@ -45,9 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</td>
|
||||
<td class="text-right">
|
||||
{% can_edit paiement %}
|
||||
<a class="btn btn-primary btn-sm" role="button" title="{% trans "Edit" %}" href="{% url 'cotisations:edit-paiement' paiement.id %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% include 'buttons/edit.html' with href='cotisations:edit-paiement' id=paiement.id %}
|
||||
{% acl_end %}
|
||||
{% history_button paiement %}
|
||||
</td>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
@ -35,7 +35,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans "Edit the invoice" %}</h3>
|
||||
{% if title %}
|
||||
<h3>{{title}}</h3>
|
||||
{% else %}
|
||||
<h3>{% trans "Edit invoice" %}</h3>
|
||||
{% endif %}
|
||||
{% massive_bootstrap_form factureform 'user' %}
|
||||
{{ venteform.management_form }}
|
||||
<h3>{% trans "Articles" %}</h3>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
Bonjour {{name}} !
|
||||
|
||||
Nous vous informons que votre cotisation auprès de {{asso_name}} a été acceptée. Vous voilà donc membre de l'association.
|
||||
|
||||
Vous trouverez en pièce jointe un reçu.
|
||||
|
||||
Pour nous faire part de toute remarque, suggestion ou problème vous pouvez nous envoyer un mail à {{asso_email}}.
|
||||
|
||||
À bientôt,
|
||||
L'équipe de {{asso_name}}.
|
||||
|
||||
---
|
||||
|
||||
Your subscription to {{asso_name}} has just been accepted. You are now a full member of {{asso_name}}.
|
||||
|
||||
You will find with this email a subscription voucher.
|
||||
|
||||
For any information, suggestion or problem, you can contact us via email at
|
||||
{{asso_email}}.
|
||||
|
||||
Regards,
|
||||
The {{asso_name}} team.
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
@ -44,6 +44,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% blocktrans %}Current balance: {{ balance }} €{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if factureform %}
|
||||
{% bootstrap_form_errors factureform %}
|
||||
{% endif %}
|
||||
{% if discount_form %}
|
||||
{% bootstrap_form_errors discount_form %}
|
||||
{% endif %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -68,6 +74,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</div>
|
||||
<input class="btn btn-primary btn-block" role="button" value="{% trans "Add an extra article"%}" id="add_one">
|
||||
{% if discount_form %}
|
||||
<h3>{% trans "Discount" %}</h3>
|
||||
{% bootstrap_form discount_form %}
|
||||
{% endif %}
|
||||
<p>
|
||||
{% blocktrans %}Total price: <span id="total_price">0,00</span> €{% endblocktrans %}
|
||||
</p>
|
||||
|
@ -119,6 +129,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
'id_form-' + i.toString() + '-quantity').value;
|
||||
price += article_price * quantity;
|
||||
}
|
||||
{% if discount_form %}
|
||||
var relative_discount = document.getElementById('id_is_relative').checked;
|
||||
var discount = document.getElementById('id_discount').value;
|
||||
if(relative_discount) {
|
||||
discount = discount/100 * price;
|
||||
}
|
||||
price -= discount;
|
||||
{% endif %}
|
||||
document.getElementById('total_price').innerHTML =
|
||||
price.toFixed(2).toString().replace('.', ',');
|
||||
}
|
||||
|
@ -148,6 +166,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
for (i = 0; i < product_count; ++i){
|
||||
add_listenner_for_id(i);
|
||||
}
|
||||
document.getElementById('id_discount')
|
||||
.addEventListener('change', update_price, true);
|
||||
document.getElementById('id_is_relative')
|
||||
.addEventListener('click', update_price, true);
|
||||
update_price();
|
||||
});
|
||||
{% endif %}
|
||||
|
|
|
@ -75,8 +75,12 @@
|
|||
{\bf Pour :} {{recipient_name|safe}} & {\bf Date :} {{DATE}} \\
|
||||
{\bf Adresse :} {% if address is None %}Aucune adresse renseignée{% else %}{{address}}{% endif %} & \\
|
||||
{% if fid is not None %}
|
||||
{% if is_estimate %}
|
||||
{\bf Devis n\textsuperscript{o} :} {{ fid }} & \\
|
||||
{% else %}
|
||||
{\bf Facture n\textsuperscript{o} :} {{ fid }} & \\
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
\end{tabular*}
|
||||
\\
|
||||
|
||||
|
@ -104,12 +108,30 @@
|
|||
\begin{tabular}{|l|r|}
|
||||
\hline
|
||||
\textbf{Total} & {{total|floatformat:2}} \euro \\
|
||||
{% if not is_estimate %}
|
||||
\textbf{Votre règlement} & {% if paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro \\
|
||||
\doublehline
|
||||
\textbf{À PAYER} & {% if not paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro\\
|
||||
{% endif %}
|
||||
\hline
|
||||
\end{tabular}
|
||||
|
||||
\vspace{1cm}
|
||||
\begin{tabularx}{\textwidth}{r X}
|
||||
\hline
|
||||
\textbf{Moyen de paiement} & {{payment_method|default:"Non spécifié"}} \\
|
||||
\hline
|
||||
{% if remark %}
|
||||
\textbf{Remarque} & {{remark|safe}} \\
|
||||
\hline
|
||||
{% endif %}
|
||||
{% if end_validity %}
|
||||
\textbf{Validité} & Jusqu'au {{end_validity}} \\
|
||||
\hline
|
||||
{% endif %}
|
||||
\end{tabularx}
|
||||
|
||||
|
||||
\vfill
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% block title %}{% trans "Articles" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Article types list" %}</h2>
|
||||
<h2>{% trans "List of article types" %}</h2>
|
||||
{% can_create Article %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-article' %}">
|
||||
<i class="fa fa-cart-plus"></i> {% trans "Add an article type" %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% block title %}{% trans "Banks" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Banks list" %}</h2>
|
||||
<h2>{% trans "List of banks" %}</h2>
|
||||
{% can_create Banque %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-banque' %}">
|
||||
<i class="fa fa-cart-plus"></i> {% trans "Add a bank" %}
|
||||
|
|
36
cotisations/templates/cotisations/index_cost_estimate.html
Normal file
36
cotisations/templates/cotisations/index_cost_estimate.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
{% 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 © 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 %}
|
||||
|
||||
{% block title %}{% trans "Cost estimates" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "List of cost estimates" %}</h2>
|
||||
{% can_create CostEstimate %}
|
||||
{% include 'buttons/add.html' with href='cotisations:new-cost-estimate'%}
|
||||
{% acl_end %}
|
||||
{% include 'cotisations/aff_cost_estimate.html' %}
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
@ -28,9 +28,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% block title %}{% trans "Custom invoices" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Custom invoices list" %}</h2>
|
||||
<h2>{% trans "List of custom invoices" %}</h2>
|
||||
{% can_create CustomInvoice %}
|
||||
{% include "buttons/add.html" with href='cotisations:new-custom-invoice'%}
|
||||
{% include 'buttons/add.html' with href='cotisations:new-custom-invoice'%}
|
||||
{% acl_end %}
|
||||
{% include 'cotisations/aff_custom_invoice.html' with custom_invoice_list=custom_invoice_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "cotisations/sidebar.html" %}
|
||||
{% 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends 'base.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
|
||||
|
@ -28,35 +28,40 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block sidebar %}
|
||||
{% can_create CustomInvoice %}
|
||||
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-custom-invoice" %}">
|
||||
<a class="list-group-item list-group-item-success" href="{% url 'cotisations:new-custom-invoice' %}">
|
||||
<i class="fa fa-plus"></i> {% trans "Create an invoice" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-warning" href="{% url "cotisations:control" %}">
|
||||
<a class="list-group-item list-group-item-warning" href="{% url 'cotisations:control' %}">
|
||||
<i class="fa fa-eye"></i> {% trans "Control the invoices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Facture %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index' %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Invoices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all CustomInvoice %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-custom-invoice" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-custom-invoice' %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Custom invoices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all CostEstimate %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-cost-estimate' %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Cost estimates" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Article %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-article" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-article' %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Available articles" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Banque %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-banque" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-banque' %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Banks" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Paiement %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-paiement" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-paiement' %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Payment methods" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
|
|
87
cotisations/templates/cotisations/voucher.tex
Normal file
87
cotisations/templates/cotisations/voucher.tex
Normal file
|
@ -0,0 +1,87 @@
|
|||
{% load i18n %}
|
||||
{% language 'fr' %}
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% Invoice Template
|
||||
% LaTeX Template
|
||||
% Version 1.0 (3/11/12)
|
||||
%% This template has been downloaded from:
|
||||
% http://www.LaTeXTemplates.com
|
||||
%
|
||||
% Original author:
|
||||
% Trey Hunner (http://www.treyhunner.com/)
|
||||
%
|
||||
% License:
|
||||
% CC BY-NC-SA 3.0 (http://creativecommons.org/licenses/by-nc-sa/3.0/)
|
||||
%
|
||||
% Important note:
|
||||
% This template requires the invoice.cls file to be in the same directory as
|
||||
% the .tex file. The invoice.cls file provides the style used for structuring the
|
||||
% document.
|
||||
%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
%----------------------------------------------------------------------------------------
|
||||
% DOCUMENT CONFIGURATION
|
||||
%----------------------------------------------------------------------------------------
|
||||
|
||||
\documentclass[12pt]{article} % Use the custom invoice class (invoice.cls)
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[letterpaper,hmargin=0.79in,vmargin=0.79in]{geometry}
|
||||
\usepackage{longtable}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{tabularx}
|
||||
\usepackage{eurosym}
|
||||
\usepackage{multicol}
|
||||
|
||||
\pagestyle{empty} % No page numbers
|
||||
|
||||
\linespread{1.5}
|
||||
|
||||
\newcommand{\doublehline}{\noalign{\hrule height 1pt}}
|
||||
\setlength{\parindent}{0cm}
|
||||
|
||||
|
||||
\begin{document}
|
||||
|
||||
%----------------------------------------------------------------------------------------
|
||||
% HEADING SECTION
|
||||
%----------------------------------------------------------------------------------------
|
||||
\begin{center}
|
||||
{\Huge\bf Reçu d'adhésion \\ {{asso_name|safe}} } % Company providing the invoice
|
||||
\end{center}
|
||||
|
||||
\bigskip
|
||||
\hrule
|
||||
\bigskip
|
||||
|
||||
\vfill
|
||||
|
||||
Je sousigné, {{pres_name|safe}}, déclare par la présente avoir reçu le bulletin d'adhésion de:
|
||||
|
||||
\begin{center}
|
||||
\setlength{\tabcolsep}{10pt} % Make table columns tighter, usefull for postionning
|
||||
\begin{tabular}{r l r l}
|
||||
{\bf Prénom :}~ & {{firstname|safe}} & {% if phone %}{\bf Téléphone :}~ & {{phone}}{% else %} & {% endif %} \\
|
||||
{\bf Nom :}~ & {{lastname|safe}} & {\bf Mail :}~ & {{email|safe}} \\
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\bigskip
|
||||
|
||||
ainsi que sa cotisation.
|
||||
|
||||
Le postulant, déclare reconnaître l'objet de l'association, et en a accepté les statuts ainsi que le règlement intérieur qui sont mis à sa disposition dans les locaux de l'association. L'adhésion du membre sus-nommé est ainsi validée. Ce reçu confirme la qualité de membre du postulant, et ouvre droit à la participation à l'assemblée générale de l'association jusqu'au {{date_end|date:"d F Y"}}.
|
||||
|
||||
\bigskip
|
||||
|
||||
Validé électroniquement par {{pres_name|safe}}, le {{date_begin|date:"d/m/Y"}}.
|
||||
|
||||
\vfill
|
||||
\hrule
|
||||
\smallskip
|
||||
\footnotesize
|
||||
Les informations recueillies sont nécessaires pour votre adhésion. Conformément à la loi "Informatique et Libertés" du 6 janvier 1978, vous disposez d'un droit d'accès et de rectification aux données personnelles vous concernant. Pour l'exercer, adressez-vous au secrétariat de l'association.
|
||||
|
||||
|
||||
\end{document}
|
||||
{% endlanguage %}
|
|
@ -31,11 +31,16 @@ from subprocess import Popen, PIPE
|
|||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.template.loader import get_template
|
||||
from django.template import Context
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from re2o.mixins import AclMixin, RevMixin
|
||||
from preferences.models import CotisationsOption
|
||||
|
||||
|
||||
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
|
||||
|
@ -48,15 +53,40 @@ def render_invoice(_request, ctx={}):
|
|||
Render an invoice using some available information such as the current
|
||||
date, the user, the articles, the prices, ...
|
||||
"""
|
||||
options, _ = CotisationsOption.objects.get_or_create()
|
||||
is_estimate = ctx.get('is_estimate', False)
|
||||
filename = '_'.join([
|
||||
'invoice',
|
||||
'cost_estimate' if is_estimate else 'invoice',
|
||||
slugify(ctx.get('asso_name', "")),
|
||||
slugify(ctx.get('recipient_name', "")),
|
||||
str(ctx.get('DATE', datetime.now()).year),
|
||||
str(ctx.get('DATE', datetime.now()).month),
|
||||
str(ctx.get('DATE', datetime.now()).day),
|
||||
])
|
||||
r = render_tex(_request, 'cotisations/factures.tex', ctx)
|
||||
templatename = options.invoice_template.template.name.split('/')[-1]
|
||||
r = render_tex(_request, templatename, ctx)
|
||||
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
||||
name=filename
|
||||
)
|
||||
return r
|
||||
|
||||
|
||||
def render_voucher(_request, ctx={}):
|
||||
"""
|
||||
Render a subscribtion voucher.
|
||||
"""
|
||||
options, _ = CotisationsOption.objects.get_or_create()
|
||||
filename = '_'.join([
|
||||
'voucher',
|
||||
slugify(ctx.get('asso_name', "")),
|
||||
slugify(ctx.get('firstname', "")),
|
||||
slugify(ctx.get('lastname', "")),
|
||||
str(ctx.get('date_begin', datetime.now()).year),
|
||||
str(ctx.get('date_begin', datetime.now()).month),
|
||||
str(ctx.get('date_begin', datetime.now()).day),
|
||||
])
|
||||
templatename = options.voucher_template.template.name.split('/')[-1]
|
||||
r = render_tex(_request, templatename, ctx)
|
||||
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
||||
name=filename
|
||||
)
|
||||
|
@ -81,10 +111,11 @@ def create_pdf(template, ctx={}):
|
|||
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
for _ in range(2):
|
||||
with open("/var/www/re2o/out.log", "w") as f:
|
||||
process = Popen(
|
||||
['pdflatex', '-output-directory', tempdir],
|
||||
stdin=PIPE,
|
||||
stdout=PIPE,
|
||||
stdout=f,#PIPE,
|
||||
)
|
||||
process.communicate(rendered_tpl)
|
||||
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
|
||||
|
@ -93,6 +124,20 @@ def create_pdf(template, ctx={}):
|
|||
return pdf
|
||||
|
||||
|
||||
def escape_chars(string):
|
||||
"""Escape the '%' and the '€' signs to avoid messing with LaTeX"""
|
||||
if not isinstance(string, str):
|
||||
return string
|
||||
mapping = (
|
||||
('€', r'\euro'),
|
||||
('%', r'\%'),
|
||||
)
|
||||
r = str(string)
|
||||
for k, v in mapping:
|
||||
r = r.replace(k, v)
|
||||
return r
|
||||
|
||||
|
||||
def render_tex(_request, template, ctx={}):
|
||||
"""Creates a PDF from a LaTex templates using pdflatex.
|
||||
|
||||
|
|
|
@ -51,11 +51,46 @@ urlpatterns = [
|
|||
views.facture_pdf,
|
||||
name='facture-pdf'
|
||||
),
|
||||
url(
|
||||
r'^voucher_pdf/(?P<factureid>[0-9]+)$',
|
||||
views.voucher_pdf,
|
||||
name='voucher-pdf'
|
||||
),
|
||||
url(
|
||||
r'^new_cost_estimate/$',
|
||||
views.new_cost_estimate,
|
||||
name='new-cost-estimate'
|
||||
),
|
||||
url(
|
||||
r'^index_cost_estimate/$',
|
||||
views.index_cost_estimate,
|
||||
name='index-cost-estimate'
|
||||
),
|
||||
url(
|
||||
r'^cost_estimate_pdf/(?P<costestimateid>[0-9]+)$',
|
||||
views.cost_estimate_pdf,
|
||||
name='cost-estimate-pdf',
|
||||
),
|
||||
url(
|
||||
r'^index_custom_invoice/$',
|
||||
views.index_custom_invoice,
|
||||
name='index-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^edit_cost_estimate/(?P<costestimateid>[0-9]+)$',
|
||||
views.edit_cost_estimate,
|
||||
name='edit-cost-estimate'
|
||||
),
|
||||
url(
|
||||
r'^cost_estimate_to_invoice/(?P<costestimateid>[0-9]+)$',
|
||||
views.cost_estimate_to_invoice,
|
||||
name='cost-estimate-to-invoice'
|
||||
),
|
||||
url(
|
||||
r'^del_cost_estimate/(?P<costestimateid>[0-9]+)$',
|
||||
views.del_cost_estimate,
|
||||
name='del-cost-estimate'
|
||||
),
|
||||
url(
|
||||
r'^new_custom_invoice/$',
|
||||
views.new_custom_invoice,
|
||||
|
|
|
@ -25,7 +25,7 @@ from django.template.loader import get_template
|
|||
from django.core.mail import EmailMessage
|
||||
|
||||
from .tex import create_pdf
|
||||
from preferences.models import AssoOption, GeneralOption
|
||||
from preferences.models import AssoOption, GeneralOption, CotisationsOption
|
||||
from re2o.settings import LOGO_PATH
|
||||
from re2o import settings
|
||||
|
||||
|
@ -93,3 +93,38 @@ def send_mail_invoice(invoice):
|
|||
attachments=[('invoice.pdf', pdf, 'application/pdf')]
|
||||
)
|
||||
mail.send()
|
||||
|
||||
|
||||
def send_mail_voucher(invoice):
|
||||
"""Creates a voucher from an invoice and sends it by email to the client"""
|
||||
ctx = {
|
||||
'asso_name': AssoOption.get_cached_value('name'),
|
||||
'pres_name': AssoOption.get_cached_value('pres_name'),
|
||||
'firstname': invoice.user.name,
|
||||
'lastname': invoice.user.surname,
|
||||
'email': invoice.user.email,
|
||||
'phone': invoice.user.telephone,
|
||||
'date_end': invoice.get_subscription().latest('date_end').date_end,
|
||||
'date_begin': invoice.get_subscription().earliest('date_start').date_start
|
||||
}
|
||||
templatename = CotisationsOption.get_cached_value('voucher_template').template.name.split('/')[-1]
|
||||
pdf = create_pdf(templatename, ctx)
|
||||
template = get_template('cotisations/email_subscription_accepted')
|
||||
|
||||
ctx = {
|
||||
'name': "{} {}".format(
|
||||
invoice.user.name,
|
||||
invoice.user.surname
|
||||
),
|
||||
'asso_email': AssoOption.get_cached_value('contact'),
|
||||
'asso_name': AssoOption.get_cached_value('name')
|
||||
}
|
||||
|
||||
mail = EmailMessage(
|
||||
'Votre reçu / Your voucher',
|
||||
template.render(ctx),
|
||||
GeneralOption.get_cached_value('email_from'),
|
||||
[invoice.user.get_mail],
|
||||
attachments=[('voucher.pdf', pdf, 'application/pdf')]
|
||||
)
|
||||
mail.send()
|
||||
|
|
|
@ -68,7 +68,8 @@ from .models import (
|
|||
Paiement,
|
||||
Banque,
|
||||
CustomInvoice,
|
||||
BaseInvoice
|
||||
BaseInvoice,
|
||||
CostEstimate,
|
||||
)
|
||||
from .forms import (
|
||||
FactureForm,
|
||||
|
@ -80,9 +81,11 @@ from .forms import (
|
|||
DelBanqueForm,
|
||||
SelectArticleForm,
|
||||
RechargeForm,
|
||||
CustomInvoiceForm
|
||||
CustomInvoiceForm,
|
||||
DiscountForm,
|
||||
CostEstimateForm,
|
||||
)
|
||||
from .tex import render_invoice
|
||||
from .tex import render_invoice, render_voucher, escape_chars
|
||||
from .payment_methods.forms import payment_method_factory
|
||||
from .utils import find_payment_method
|
||||
|
||||
|
@ -178,7 +181,59 @@ def new_facture(request, user, userid):
|
|||
)
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
@login_required
|
||||
@can_create(CostEstimate)
|
||||
def new_cost_estimate(request):
|
||||
"""
|
||||
View used to generate a custom invoice. It's mainly used to
|
||||
get invoices that are not taken into account, for the administrative
|
||||
point of view.
|
||||
"""
|
||||
# The template needs the list of articles (for the JS part)
|
||||
articles = Article.objects.filter(
|
||||
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||
)
|
||||
# Building the invocie form and the article formset
|
||||
cost_estimate_form = CostEstimateForm(request.POST or None)
|
||||
|
||||
articles_formset = formset_factory(SelectArticleForm)(
|
||||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
)
|
||||
discount_form = DiscountForm(request.POST or None)
|
||||
|
||||
if cost_estimate_form.is_valid() and articles_formset.is_valid() and discount_form.is_valid():
|
||||
cost_estimate_instance = cost_estimate_form.save()
|
||||
for art_item in articles_formset:
|
||||
if art_item.cleaned_data:
|
||||
article = art_item.cleaned_data['article']
|
||||
quantity = art_item.cleaned_data['quantity']
|
||||
Vente.objects.create(
|
||||
facture=cost_estimate_instance,
|
||||
name=article.name,
|
||||
prix=article.prix,
|
||||
type_cotisation=article.type_cotisation,
|
||||
duration=article.duration,
|
||||
number=quantity
|
||||
)
|
||||
discount_form.apply_to_invoice(cost_estimate_instance)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
_("The cost estimate was created.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-cost-estimate'))
|
||||
|
||||
return form({
|
||||
'factureform': cost_estimate_form,
|
||||
'action_name': _("Confirm"),
|
||||
'articlesformset': articles_formset,
|
||||
'articlelist': articles,
|
||||
'discount_form': discount_form,
|
||||
'title': _("Cost estimate"),
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_create(CustomInvoice)
|
||||
def new_custom_invoice(request):
|
||||
|
@ -198,8 +253,9 @@ def new_custom_invoice(request):
|
|||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
)
|
||||
discount_form = DiscountForm(request.POST or None)
|
||||
|
||||
if invoice_form.is_valid() and articles_formset.is_valid():
|
||||
if invoice_form.is_valid() and articles_formset.is_valid() and discount_form.is_valid():
|
||||
new_invoice_instance = invoice_form.save()
|
||||
for art_item in articles_formset:
|
||||
if art_item.cleaned_data:
|
||||
|
@ -213,6 +269,7 @@ def new_custom_invoice(request):
|
|||
duration=article.duration,
|
||||
number=quantity
|
||||
)
|
||||
discount_form.apply_to_invoice(new_invoice_instance)
|
||||
messages.success(
|
||||
request,
|
||||
_("The custom invoice was created.")
|
||||
|
@ -223,7 +280,8 @@ def new_custom_invoice(request):
|
|||
'factureform': invoice_form,
|
||||
'action_name': _("Confirm"),
|
||||
'articlesformset': articles_formset,
|
||||
'articlelist': articles
|
||||
'articlelist': articles,
|
||||
'discount_form': discount_form
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
|
@ -266,7 +324,8 @@ def facture_pdf(request, facture, **_kwargs):
|
|||
'siret': AssoOption.get_cached_value('siret'),
|
||||
'email': AssoOption.get_cached_value('contact'),
|
||||
'phone': AssoOption.get_cached_value('telephone'),
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH),
|
||||
'payment_method': facture.paiement.moyen,
|
||||
})
|
||||
|
||||
|
||||
|
@ -331,6 +390,55 @@ def del_facture(request, facture, **_kwargs):
|
|||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(CostEstimate)
|
||||
def edit_cost_estimate(request, invoice, **kwargs):
|
||||
# Building the invocie form and the article formset
|
||||
invoice_form = CostEstimateForm(
|
||||
request.POST or None,
|
||||
instance=invoice
|
||||
)
|
||||
purchases_objects = Vente.objects.filter(facture=invoice)
|
||||
purchase_form_set = modelformset_factory(
|
||||
Vente,
|
||||
fields=('name', 'number'),
|
||||
extra=0,
|
||||
max_num=len(purchases_objects)
|
||||
)
|
||||
purchase_form = purchase_form_set(
|
||||
request.POST or None,
|
||||
queryset=purchases_objects
|
||||
)
|
||||
if invoice_form.is_valid() and purchase_form.is_valid():
|
||||
if invoice_form.changed_data:
|
||||
invoice_form.save()
|
||||
purchase_form.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The cost estimate was edited.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-cost-estimate'))
|
||||
|
||||
return form({
|
||||
'factureform': invoice_form,
|
||||
'venteform': purchase_form,
|
||||
'title': _("Edit cost estimate")
|
||||
}, 'cotisations/edit_facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(CostEstimate)
|
||||
@can_create(CustomInvoice)
|
||||
def cost_estimate_to_invoice(request, cost_estimate, **_kwargs):
|
||||
"""Create a custom invoice from a cos estimate"""
|
||||
cost_estimate.create_invoice()
|
||||
messages.success(
|
||||
request,
|
||||
_("An invoice was successfully created from your cost estimate.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(CustomInvoice)
|
||||
def edit_custom_invoice(request, invoice, **kwargs):
|
||||
|
@ -367,22 +475,21 @@ def edit_custom_invoice(request, invoice, **kwargs):
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view(CustomInvoice)
|
||||
def custom_invoice_pdf(request, invoice, **_kwargs):
|
||||
@can_view(CostEstimate)
|
||||
def cost_estimate_pdf(request, invoice, **_kwargs):
|
||||
"""
|
||||
View used to generate a PDF file from an existing invoice in database
|
||||
View used to generate a PDF file from an existing cost estimate in database
|
||||
Creates a line for each Purchase (thus article sold) and generate the
|
||||
invoice with the total price, the payment method, the address and the
|
||||
legal information for the user.
|
||||
"""
|
||||
# TODO : change vente to purchase
|
||||
purchases_objects = Vente.objects.all().filter(facture=invoice)
|
||||
# Get the article list and build an list out of it
|
||||
# contiaining (article_name, article_price, quantity, total_price)
|
||||
purchases_info = []
|
||||
for purchase in purchases_objects:
|
||||
purchases_info.append({
|
||||
'name': purchase.name,
|
||||
'name': escape_chars(purchase.name),
|
||||
'price': purchase.prix,
|
||||
'quantity': purchase.number,
|
||||
'total_price': purchase.prix_total
|
||||
|
@ -401,11 +508,74 @@ def custom_invoice_pdf(request, invoice, **_kwargs):
|
|||
'siret': AssoOption.get_cached_value('siret'),
|
||||
'email': AssoOption.get_cached_value('contact'),
|
||||
'phone': AssoOption.get_cached_value('telephone'),
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH),
|
||||
'payment_method': invoice.payment,
|
||||
'remark': invoice.remark,
|
||||
'end_validity': invoice.date + invoice.validity,
|
||||
'is_estimate': True,
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@can_delete(CostEstimate)
|
||||
def del_cost_estimate(request, estimate, **_kwargs):
|
||||
"""
|
||||
View used to delete an existing invocie.
|
||||
"""
|
||||
if request.method == "POST":
|
||||
estimate.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_("The cost estimate was deleted.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-cost-estimate'))
|
||||
return form({
|
||||
'objet': estimate,
|
||||
'objet_name': _("Cost estimate")
|
||||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view(CustomInvoice)
|
||||
def custom_invoice_pdf(request, invoice, **_kwargs):
|
||||
"""
|
||||
View used to generate a PDF file from an existing invoice in database
|
||||
Creates a line for each Purchase (thus article sold) and generate the
|
||||
invoice with the total price, the payment method, the address and the
|
||||
legal information for the user.
|
||||
"""
|
||||
# TODO : change vente to purchase
|
||||
purchases_objects = Vente.objects.all().filter(facture=invoice)
|
||||
# Get the article list and build an list out of it
|
||||
# contiaining (article_name, article_price, quantity, total_price)
|
||||
purchases_info = []
|
||||
for purchase in purchases_objects:
|
||||
purchases_info.append({
|
||||
'name': escape_chars(purchase.name),
|
||||
'price': purchase.prix,
|
||||
'quantity': purchase.number,
|
||||
'total_price': purchase.prix_total
|
||||
})
|
||||
return render_invoice(request, {
|
||||
'paid': invoice.paid,
|
||||
'fid': invoice.id,
|
||||
'DATE': invoice.date,
|
||||
'recipient_name': invoice.recipient,
|
||||
'address': invoice.address,
|
||||
'article': purchases_info,
|
||||
'total': invoice.prix_total(),
|
||||
'asso_name': AssoOption.get_cached_value('name'),
|
||||
'line1': AssoOption.get_cached_value('adresse1'),
|
||||
'line2': AssoOption.get_cached_value('adresse2'),
|
||||
'siret': AssoOption.get_cached_value('siret'),
|
||||
'email': AssoOption.get_cached_value('contact'),
|
||||
'phone': AssoOption.get_cached_value('telephone'),
|
||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH),
|
||||
'payment_method': invoice.payment,
|
||||
'remark': invoice.remark,
|
||||
})
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
@login_required
|
||||
@can_delete(CustomInvoice)
|
||||
def del_custom_invoice(request, invoice, **_kwargs):
|
||||
|
@ -663,8 +833,8 @@ def del_banque(request, instances):
|
|||
except ProtectedError:
|
||||
messages.error(
|
||||
request,
|
||||
_("The bank %(bank_name)s can't be deleted \
|
||||
because there are invoices using it.") % {
|
||||
_("The bank %(bank_name)s can't be deleted because there"
|
||||
" are invoices using it.") % {
|
||||
'bank_name': bank_del
|
||||
}
|
||||
)
|
||||
|
@ -756,12 +926,36 @@ def index_banque(request):
|
|||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view_all(CustomInvoice)
|
||||
def index_cost_estimate(request):
|
||||
"""View used to display every custom invoice."""
|
||||
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
||||
cost_estimate_list = CostEstimate.objects.prefetch_related('vente_set')
|
||||
cost_estimate_list = SortTable.sort(
|
||||
cost_estimate_list,
|
||||
request.GET.get('col'),
|
||||
request.GET.get('order'),
|
||||
SortTable.COTISATIONS_CUSTOM
|
||||
)
|
||||
cost_estimate_list = re2o_paginator(
|
||||
request,
|
||||
cost_estimate_list,
|
||||
pagination_number,
|
||||
)
|
||||
return render(request, 'cotisations/index_cost_estimate.html', {
|
||||
'cost_estimate_list': cost_estimate_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view_all(CustomInvoice)
|
||||
def index_custom_invoice(request):
|
||||
"""View used to display every custom invoice."""
|
||||
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
||||
custom_invoice_list = CustomInvoice.objects.prefetch_related('vente_set')
|
||||
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 = SortTable.sort(
|
||||
custom_invoice_list,
|
||||
request.GET.get('col'),
|
||||
|
@ -827,7 +1021,8 @@ def credit_solde(request, user, **_kwargs):
|
|||
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():
|
||||
price = refill_form.cleaned_data['value']
|
||||
invoice = Facture(user=user)
|
||||
|
@ -857,3 +1052,29 @@ def credit_solde(request, user, **_kwargs):
|
|||
'max_balance': find_payment_method(p).maximum_balance,
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view(Facture)
|
||||
def voucher_pdf(request, invoice, **_kwargs):
|
||||
"""
|
||||
View used to generate a PDF file from a controlled invoice
|
||||
Creates a line for each Purchase (thus article sold) and generate the
|
||||
invoice with the total price, the payment method, the address and the
|
||||
legal information for the user.
|
||||
"""
|
||||
if not invoice.control:
|
||||
messages.error(
|
||||
request,
|
||||
_("Could not find a voucher for that invoice.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index'))
|
||||
return render_voucher(request, {
|
||||
'asso_name': AssoOption.get_cached_value('name'),
|
||||
'pres_name': AssoOption.get_cached_value('pres_name'),
|
||||
'firstname': invoice.user.name,
|
||||
'lastname': invoice.user.surname,
|
||||
'email': invoice.user.email,
|
||||
'phone': invoice.user.telephone,
|
||||
'date_end': invoice.get_subscription().latest('date_end').date_end,
|
||||
'date_begin': invoice.date
|
||||
})
|
||||
|
|
|
@ -289,7 +289,7 @@ def check_user_machine_and_register(nas_type, username, mac_address):
|
|||
Renvoie le mot de passe ntlm de l'user si tout est ok
|
||||
Utilise pour les authentifications en 802.1X"""
|
||||
interface = Interface.objects.filter(mac_address=mac_address).first()
|
||||
user = User.objects.filter(pseudo=username).first()
|
||||
user = User.objects.filter(pseudo__iexact=username).first()
|
||||
if not user:
|
||||
return (False, u"User inconnu", '')
|
||||
if not user.has_access():
|
||||
|
|
|
@ -324,6 +324,21 @@ 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/default_invoice.tex
|
||||
cp cotisations/templates/cotisations/voucher.tex media/templates/default_voucher.tex
|
||||
chown -R www-data:www-data media/templates/
|
||||
echo "Copying LaTeX templates: Done"
|
||||
}
|
||||
|
||||
|
||||
|
||||
create_superuser() {
|
||||
### Usage: create_superuser
|
||||
#
|
||||
|
@ -748,9 +763,10 @@ main_function() {
|
|||
echo " * {help} ---------- Display this quick usage documentation"
|
||||
echo " * {setup} --------- Launch the full interactive guide to setup entirely"
|
||||
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 " * {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-settings} Interactively rewrite the settings file"
|
||||
echo " * {reset-db} ------ Erase the previous local database, setup a new empty"
|
||||
|
@ -782,9 +798,14 @@ main_function() {
|
|||
|
||||
update )
|
||||
install_requirements
|
||||
copy_templates_files
|
||||
update_django
|
||||
;;
|
||||
|
||||
copy-templates-files )
|
||||
copy_templates_files
|
||||
;;
|
||||
|
||||
update-django )
|
||||
update_django
|
||||
;;
|
||||
|
|
|
@ -21,7 +21,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: 2.5\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-08-15 20:12+0200\n"
|
||||
"POT-Creation-Date: 2019-01-08 23:16+0100\n"
|
||||
"PO-Revision-Date: 2018-06-23 16:01+0200\n"
|
||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||
"Language-Team: \n"
|
||||
|
@ -57,7 +57,7 @@ msgstr "Commentaire"
|
|||
|
||||
#: templates/logs/aff_stats_logs.html:58 templates/logs/aff_summary.html:62
|
||||
#: templates/logs/aff_summary.html:85 templates/logs/aff_summary.html:104
|
||||
#: templates/logs/aff_summary.html:123 templates/logs/aff_summary.html:142
|
||||
#: templates/logs/aff_summary.html:128 templates/logs/aff_summary.html:147
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
|
@ -113,15 +113,19 @@ msgstr "%(username)s a mis à jour"
|
|||
|
||||
#: templates/logs/aff_summary.html:113
|
||||
#, python-format
|
||||
msgid "%(username)s has sold %(number)sx %(name)s to"
|
||||
msgstr "%(username)s a vendu %(number)sx %(name)s à"
|
||||
msgid "%(username)s has sold %(number)sx %(name)s"
|
||||
msgstr "%(username)s a vendu %(number)sx %(name)s"
|
||||
|
||||
#: templates/logs/aff_summary.html:116
|
||||
msgid " to"
|
||||
msgstr " à"
|
||||
|
||||
#: templates/logs/aff_summary.html:119
|
||||
#, python-format
|
||||
msgid "+%(duration)s months"
|
||||
msgstr "+%(duration)s mois"
|
||||
|
||||
#: templates/logs/aff_summary.html:132
|
||||
#: templates/logs/aff_summary.html:137
|
||||
#, python-format
|
||||
msgid "%(username)s has edited an interface of"
|
||||
msgstr "%(username)s a modifié une interface de"
|
||||
|
@ -149,7 +153,7 @@ msgstr "Confirmer"
|
|||
msgid "Statistics"
|
||||
msgstr "Statistiques"
|
||||
|
||||
#: templates/logs/index.html:32 templates/logs/stats_logs.html:32 views.py:403
|
||||
#: templates/logs/index.html:32 templates/logs/stats_logs.html:32 views.py:414
|
||||
msgid "Actions performed"
|
||||
msgstr "Actions effectuées"
|
||||
|
||||
|
@ -173,7 +177,7 @@ msgstr "Base de données"
|
|||
msgid "Wiring actions"
|
||||
msgstr "Actions de câblage"
|
||||
|
||||
#: templates/logs/sidebar.html:53 views.py:325
|
||||
#: templates/logs/sidebar.html:53 views.py:336
|
||||
msgid "Users"
|
||||
msgstr "Utilisateurs"
|
||||
|
||||
|
@ -189,150 +193,154 @@ msgstr "Statistiques sur la base de données"
|
|||
msgid "Statistics about users"
|
||||
msgstr "Statistiques sur les utilisateurs"
|
||||
|
||||
#: views.py:191
|
||||
#: views.py:194
|
||||
msgid "Nonexistent revision."
|
||||
msgstr "Révision inexistante."
|
||||
|
||||
#: views.py:194
|
||||
#: views.py:197
|
||||
msgid "The action was deleted."
|
||||
msgstr "L'action a été supprimée."
|
||||
|
||||
#: views.py:227
|
||||
#: views.py:230
|
||||
msgid "Category"
|
||||
msgstr "Catégorie"
|
||||
|
||||
#: views.py:228
|
||||
#: views.py:231
|
||||
msgid "Number of users (members and clubs)"
|
||||
msgstr "Nombre d'utilisateurs (adhérents et clubs)"
|
||||
|
||||
#: views.py:229
|
||||
#: views.py:232
|
||||
msgid "Number of members"
|
||||
msgstr "Nombre d'adhérents"
|
||||
|
||||
#: views.py:230
|
||||
#: views.py:233
|
||||
msgid "Number of clubs"
|
||||
msgstr "Nombre de clubs"
|
||||
|
||||
#: views.py:234
|
||||
#: views.py:237
|
||||
msgid "Activated users"
|
||||
msgstr "Utilisateurs activés"
|
||||
|
||||
#: views.py:242
|
||||
#: views.py:245
|
||||
msgid "Disabled users"
|
||||
msgstr "Utilisateurs désactivés"
|
||||
|
||||
#: views.py:250
|
||||
#: views.py:253
|
||||
msgid "Archived users"
|
||||
msgstr "Utilisateurs archivés"
|
||||
|
||||
#: views.py:258
|
||||
#: views.py:261
|
||||
msgid "Not yet active users"
|
||||
msgstr "Utilisateurs pas encore actifs"
|
||||
|
||||
#: views.py:269
|
||||
msgid "Contributing members"
|
||||
msgstr "Adhérents cotisants"
|
||||
|
||||
#: views.py:264
|
||||
#: views.py:275
|
||||
msgid "Users benefiting from a connection"
|
||||
msgstr "Utilisateurs bénéficiant d'une connexion"
|
||||
|
||||
#: views.py:270
|
||||
#: views.py:281
|
||||
msgid "Banned users"
|
||||
msgstr "Utilisateurs bannis"
|
||||
|
||||
#: views.py:276
|
||||
#: views.py:287
|
||||
msgid "Users benefiting from a free connection"
|
||||
msgstr "Utilisateurs bénéficiant d'une connexion gratuite"
|
||||
|
||||
#: views.py:282
|
||||
#: views.py:293
|
||||
msgid "Active interfaces (with access to the network)"
|
||||
msgstr "Interfaces actives (ayant accès au réseau)"
|
||||
|
||||
#: views.py:292
|
||||
#: views.py:303
|
||||
msgid "Active interfaces assigned IPv4"
|
||||
msgstr "Interfaces actives assignées IPv4"
|
||||
|
||||
#: views.py:305
|
||||
#: views.py:316
|
||||
msgid "IP range"
|
||||
msgstr "Plage d'IP"
|
||||
|
||||
#: views.py:306
|
||||
#: views.py:317
|
||||
msgid "VLAN"
|
||||
msgstr "VLAN"
|
||||
|
||||
#: views.py:307
|
||||
#: views.py:318
|
||||
msgid "Total number of IP addresses"
|
||||
msgstr "Nombre total d'adresses IP"
|
||||
|
||||
#: views.py:308
|
||||
#: views.py:319
|
||||
msgid "Number of assigned IP addresses"
|
||||
msgstr "Nombre d'adresses IP non assignées"
|
||||
|
||||
#: views.py:309
|
||||
#: views.py:320
|
||||
msgid "Number of IP address assigned to an activated machine"
|
||||
msgstr "Nombre d'adresses IP assignées à une machine activée"
|
||||
|
||||
#: views.py:310
|
||||
#: views.py:321
|
||||
msgid "Number of nonassigned IP addresses"
|
||||
msgstr "Nombre d'adresses IP non assignées"
|
||||
|
||||
#: views.py:337
|
||||
#: views.py:348
|
||||
msgid "Subscriptions"
|
||||
msgstr "Cotisations"
|
||||
|
||||
#: views.py:359 views.py:420
|
||||
#: views.py:370 views.py:431
|
||||
msgid "Machines"
|
||||
msgstr "Machines"
|
||||
|
||||
#: views.py:386
|
||||
#: views.py:397
|
||||
msgid "Topology"
|
||||
msgstr "Topologie"
|
||||
|
||||
#: views.py:405
|
||||
#: views.py:416
|
||||
msgid "Number of actions"
|
||||
msgstr "Nombre d'actions"
|
||||
|
||||
#: views.py:419 views.py:437 views.py:442 views.py:447 views.py:462
|
||||
#: views.py:430 views.py:448 views.py:453 views.py:458 views.py:473
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: views.py:423
|
||||
#: views.py:434
|
||||
msgid "Invoice"
|
||||
msgstr "Facture"
|
||||
|
||||
#: views.py:426
|
||||
#: views.py:437
|
||||
msgid "Ban"
|
||||
msgstr "Bannissement"
|
||||
|
||||
#: views.py:429
|
||||
#: views.py:440
|
||||
msgid "Whitelist"
|
||||
msgstr "Accès gracieux"
|
||||
|
||||
#: views.py:432
|
||||
#: views.py:443
|
||||
msgid "Rights"
|
||||
msgstr "Droits"
|
||||
|
||||
#: views.py:436
|
||||
#: views.py:447
|
||||
msgid "School"
|
||||
msgstr "Établissement"
|
||||
|
||||
#: views.py:441
|
||||
#: views.py:452
|
||||
msgid "Payment method"
|
||||
msgstr "Moyen de paiement"
|
||||
|
||||
#: views.py:446
|
||||
#: views.py:457
|
||||
msgid "Bank"
|
||||
msgstr "Banque"
|
||||
|
||||
#: views.py:463
|
||||
#: views.py:474
|
||||
msgid "Action"
|
||||
msgstr "Action"
|
||||
|
||||
#: views.py:494
|
||||
#: views.py:505
|
||||
msgid "No model found."
|
||||
msgstr "Aucun modèle trouvé."
|
||||
|
||||
#: views.py:500
|
||||
#: views.py:511
|
||||
msgid "Nonexistent entry."
|
||||
msgstr "Entrée inexistante."
|
||||
|
||||
#: views.py:507
|
||||
#: views.py:518
|
||||
msgid "You don't have the right to access this menu."
|
||||
msgstr "Vous n'avez pas le droit d'accéder à ce menu."
|
||||
|
|
|
@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% if revisions_list.paginator %}
|
||||
{% include "pagination.html" with list=revisions_list %}
|
||||
{% include 'pagination.html' with list=revisions_list %}
|
||||
{% endif %}
|
||||
|
||||
{% load logs_extra %}
|
||||
|
@ -36,9 +36,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th>{% trans "Edited object" %}</th>
|
||||
<th>{% trans "Object type" %}</th>
|
||||
{% trans "Edited by" as tr_edited_by %}
|
||||
<th>{% include "buttons/sort.html" with prefix='logs' col='author' text=tr_edited_by %}</th>
|
||||
<th>{% include 'buttons/sort.html' with prefix='logs' col='author' text=tr_edited_by %}</th>
|
||||
{% trans "Date of editing" as tr_date_of_editing %}
|
||||
<th>{% include "buttons/sort.html" with prefix='logs' col='date' text=tr_date_of_editing %}</th>
|
||||
<th>{% include 'buttons/sort.html' with prefix='logs' col='date' text=tr_date_of_editing %}</th>
|
||||
<th>{% trans "Comment" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -65,6 +65,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</table>
|
||||
|
||||
{% if revisions_list.paginator %}
|
||||
{% include "pagination.html" with list=revisions_list %}
|
||||
{% include 'pagination.html' with list=revisions_list %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% if versions_list.paginator %}
|
||||
{% include "pagination.html" with list=versions_list %}
|
||||
{% include 'pagination.html' with list=versions_list %}
|
||||
{% endif %}
|
||||
|
||||
{% load logs_extra %}
|
||||
|
@ -35,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<thead>
|
||||
<tr>
|
||||
{% trans "Date" as tr_date %}
|
||||
<th>{% include "buttons/sort.html" with prefix='sum' col='date' text=tr_date %}</th>
|
||||
<th>{% include 'buttons/sort.html' with prefix='sum' col='date' text=tr_date %}</th>
|
||||
<th>{% trans "Editing" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -154,6 +154,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</table>
|
||||
|
||||
{% if versions_list.paginator %}
|
||||
{% include "pagination.html" with list=versions_list %}
|
||||
{% include 'pagination.html' with list=versions_list %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "logs/sidebar.html" %}
|
||||
{% extends 'logs/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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "logs/sidebar.html" %}
|
||||
{% extends 'logs/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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h2>{% trans "Actions performed" %}</h2>
|
||||
{% include "logs/aff_summary.html" with versions_list=versions_list %}
|
||||
{% include 'logs/aff_summary.html' with versions_list=versions_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends 'base.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
|
||||
|
@ -28,27 +28,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block sidebar %}
|
||||
{% can_view_app logs %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:index" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'logs:index' %}">
|
||||
<i class="fa fa-clipboard"></i>
|
||||
{% trans "Summary" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-logs" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-logs' %}">
|
||||
<i class="fa fa-calendar"></i>
|
||||
{% trans "Events" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-general" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-general' %}">
|
||||
<i class="fa fa-area-chart"></i>
|
||||
{% trans "General" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-models" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-models' %}">
|
||||
<i class="fa fa-database"></i>
|
||||
{% trans "Database" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-actions" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-actions' %}">
|
||||
<i class="fa fa-plug"></i>
|
||||
{% trans "Wiring actions" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-users" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-users' %}">
|
||||
<i class="fa fa-users"></i>
|
||||
{% trans "Users" %}
|
||||
</a>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "logs/sidebar.html" %}
|
||||
{% extends 'logs/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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h2>{% trans "General statistics" %}</h2>
|
||||
{% include "logs/aff_stats_general.html" with stats_list=stats_list %}
|
||||
{% include 'logs/aff_stats_general.html' with stats_list=stats_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "logs/sidebar.html" %}
|
||||
{% extends 'logs/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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h2>{% trans "Actions performed" %}</h2>
|
||||
{% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %}
|
||||
{% include 'logs/aff_stats_logs.html' with revisions_list=revisions_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "logs/sidebar.html" %}
|
||||
{% extends 'logs/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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h2>{% trans "Database statistics" %}</h2>
|
||||
{% include "logs/aff_stats_models.html" with stats_list=stats_list %}
|
||||
{% include 'logs/aff_stats_models.html' with stats_list=stats_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "logs/sidebar.html" %}
|
||||
{% extends 'logs/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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h2>{% trans "Statistics about users" %}</h2>
|
||||
{% include "logs/aff_stats_users.html" with stats_list=stats_list %}
|
||||
{% include 'logs/aff_stats_users.html' with stats_list=stats_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
|
File diff suppressed because it is too large
Load diff
20
machines/migrations/0098_auto_20190102_1745.py
Normal file
20
machines/migrations/0098_auto_20190102_1745.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2019-01-02 23:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0097_extension_dnssec'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='specific_role',
|
||||
field=models.CharField(blank=True, choices=[('dhcp-server', 'DHCP server'), ('switch-conf-server', 'Switches configuration server'), ('dns-recursif-server', 'Recursive DNS server'), ('dns-recursive-server', 'Recursive DNS server'), ('ntp-server', 'NTP server'), ('radius-server', 'RADIUS server'), ('log-server', 'Log server'), ('ldap-master-server', 'LDAP master server'), ('ldap-backup-server', 'LDAP backup server'), ('smtp-server', 'SMTP server'), ('postgresql-server', 'postgreSQL server'), ('mysql-server', 'mySQL server'), ('sql-client', 'SQL client'), ('gateway', 'Gateway')], max_length=32, null=True),
|
||||
),
|
||||
]
|
26
machines/migrations/0099_role_recursive_dns.py
Normal file
26
machines/migrations/0099_role_recursive_dns.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2019-01-02 23:45
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate(apps, schema_editor):
|
||||
Role = apps.get_model('machines', 'Role')
|
||||
|
||||
for role in Role.objects.filter(specific_role='dns-recursif-server'):
|
||||
role.specific_role = 'dns-recursive-server'
|
||||
role.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0098_auto_20190102_1745'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(migrate),
|
||||
]
|
||||
|
||||
|
20
machines/migrations/0100_auto_20190102_1753.py
Normal file
20
machines/migrations/0100_auto_20190102_1753.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2019-01-02 23:53
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0099_role_recursive_dns'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='specific_role',
|
||||
field=models.CharField(blank=True, choices=[('dhcp-server', 'DHCP server'), ('switch-conf-server', 'Switches configuration server'), ('dns-recursive-server', 'Recursive DNS server'), ('ntp-server', 'NTP server'), ('radius-server', 'RADIUS server'), ('log-server', 'Log server'), ('ldap-master-server', 'LDAP master server'), ('ldap-backup-server', 'LDAP backup server'), ('smtp-server', 'SMTP server'), ('postgresql-server', 'postgreSQL server'), ('mysql-server', 'mySQL server'), ('sql-client', 'SQL client'), ('gateway', 'Gateway')], max_length=32, null=True),
|
||||
),
|
||||
]
|
34
machines/migrations/0101_auto_20190108_1623.py
Normal file
34
machines/migrations/0101_auto_20190108_1623.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2019-01-08 22:23
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0100_auto_20190102_1753'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='ouvertureport',
|
||||
options={'verbose_name': 'ports opening', 'verbose_name_plural': 'ports openings'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='nas',
|
||||
name='port_access_mode',
|
||||
field=models.CharField(choices=[('802.1X', '802.1X'), ('Mac-address', 'MAC-address')], default='802.1X', max_length=32),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='igmp',
|
||||
field=models.BooleanField(default=False, help_text='v4 multicast management'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vlan',
|
||||
name='mld',
|
||||
field=models.BooleanField(default=False, help_text='v6 multicast management'),
|
||||
),
|
||||
]
|
|
@ -201,7 +201,7 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
|
|||
if interfaces_set:
|
||||
return str(interfaces_set.domain.name)
|
||||
else:
|
||||
return "None"
|
||||
return _("No name")
|
||||
|
||||
@cached_property
|
||||
def complete_name(self):
|
||||
|
@ -340,7 +340,7 @@ class IpType(RevMixin, AclMixin, models.Model):
|
|||
("use_all_iptype", _("Can use all IP types")),
|
||||
)
|
||||
verbose_name = _("IP type")
|
||||
verbose_name_plural = "IP types"
|
||||
verbose_name_plural = _("IP types")
|
||||
|
||||
@cached_property
|
||||
def ip_range(self):
|
||||
|
@ -534,11 +534,11 @@ class Vlan(RevMixin, AclMixin, models.Model):
|
|||
dhcpv6_snooping = models.BooleanField(default=False)
|
||||
igmp = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Gestion multicast v4"
|
||||
help_text=_("v4 multicast management")
|
||||
)
|
||||
mld = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Gestion multicast v6"
|
||||
help_text=_("v6 multicast management")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -559,7 +559,7 @@ class Nas(RevMixin, AclMixin, models.Model):
|
|||
default_mode = '802.1X'
|
||||
AUTH = (
|
||||
('802.1X', '802.1X'),
|
||||
('Mac-address', 'Mac-address'),
|
||||
('Mac-address', _("MAC-address")),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
|
@ -666,7 +666,7 @@ class SOA(RevMixin, AclMixin, models.Model):
|
|||
utilisée dans les migrations de la BDD. """
|
||||
return cls.objects.get_or_create(
|
||||
name=_("SOA to edit"),
|
||||
mail="postmaser@example.com"
|
||||
mail="postmaster@example.com"
|
||||
)[0].pk
|
||||
|
||||
|
||||
|
@ -934,7 +934,7 @@ class SshFp(RevMixin, AclMixin, models.Model):
|
|||
|
||||
machine = models.ForeignKey('Machine', on_delete=models.CASCADE)
|
||||
pub_key_entry = models.TextField(
|
||||
help_text="SSH public key",
|
||||
help_text=_("SSH public key"),
|
||||
max_length=2048
|
||||
)
|
||||
algo = models.CharField(
|
||||
|
@ -942,7 +942,7 @@ class SshFp(RevMixin, AclMixin, models.Model):
|
|||
max_length=32
|
||||
)
|
||||
comment = models.CharField(
|
||||
help_text="Comment",
|
||||
help_text=_("Comment"),
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True
|
||||
|
@ -1110,23 +1110,6 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
except:
|
||||
raise ValidationError(_("The given MAC address is invalid."))
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
||||
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
||||
# If type was an invalid value, django won't create an attribute type
|
||||
# but try clean() as we may be able to create it from another value
|
||||
# so even if the error as yet been detected at this point, django
|
||||
# continues because the error might not prevent us from creating the
|
||||
# instance.
|
||||
# But in our case, it's impossible to create a type value so we raise
|
||||
# the error.
|
||||
if not hasattr(self, 'type'):
|
||||
raise ValidationError(_("The selected IP type is invalid."))
|
||||
self.filter_macaddress()
|
||||
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
||||
self.assign_ipv4()
|
||||
super(Interface, self).clean(*args, **kwargs)
|
||||
|
||||
def assign_ipv4(self):
|
||||
""" Assigne une ip à l'interface """
|
||||
free_ips = self.type.ip_type.free_ip()
|
||||
|
@ -1146,6 +1129,42 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
self.clean()
|
||||
self.save()
|
||||
|
||||
def has_private_ip(self):
|
||||
""" True si l'ip associée est privée"""
|
||||
if self.ipv4:
|
||||
return IPAddress(str(self.ipv4)).is_private()
|
||||
else:
|
||||
return False
|
||||
|
||||
def may_have_port_open(self):
|
||||
""" True si l'interface a une ip et une ip publique.
|
||||
Permet de ne pas exporter des ouvertures sur des ip privées
|
||||
(useless)"""
|
||||
return self.ipv4 and not self.has_private_ip()
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
||||
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
||||
# If type was an invalid value, django won't create an attribute type
|
||||
# but try clean() as we may be able to create it from another value
|
||||
# so even if the error as yet been detected at this point, django
|
||||
# continues because the error might not prevent us from creating the
|
||||
# instance.
|
||||
# But in our case, it's impossible to create a type value so we raise
|
||||
# the error.
|
||||
if not hasattr(self, 'type'):
|
||||
raise ValidationError(_("The selected IP type is invalid."))
|
||||
self.filter_macaddress()
|
||||
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
||||
self.assign_ipv4()
|
||||
super(Interface, self).clean(*args, **kwargs)
|
||||
|
||||
def validate_unique(self, *args, **kwargs):
|
||||
super(Interface, self).validate_unique(*args, **kwargs)
|
||||
interfaces_similar = Interface.objects.filter(mac_address=self.mac_address, type__ip_type=self.type.ip_type)
|
||||
if interfaces_similar and interfaces_similar.first() != self:
|
||||
raise ValidationError(_("Mac address already registered in this Machine Type/Subnet"))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.filter_macaddress()
|
||||
# On verifie la cohérence en forçant l'extension par la méthode
|
||||
|
@ -1153,6 +1172,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
if self.type.ip_type != self.ipv4.ip_type:
|
||||
raise ValidationError(_("The IPv4 address and the machine type"
|
||||
" don't match."))
|
||||
self.validate_unique()
|
||||
super(Interface, self).save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
|
@ -1250,19 +1270,6 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
domain = None
|
||||
return str(domain)
|
||||
|
||||
def has_private_ip(self):
|
||||
""" True si l'ip associée est privée"""
|
||||
if self.ipv4:
|
||||
return IPAddress(str(self.ipv4)).is_private()
|
||||
else:
|
||||
return False
|
||||
|
||||
def may_have_port_open(self):
|
||||
""" True si l'interface a une ip et une ip publique.
|
||||
Permet de ne pas exporter des ouvertures sur des ip privées
|
||||
(useless)"""
|
||||
return self.ipv4 and not self.has_private_ip()
|
||||
|
||||
|
||||
class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||
""" A list of IPv6 """
|
||||
|
@ -1380,7 +1387,10 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
.filter(interface=self.interface, slaac_ip=True)
|
||||
.exclude(id=self.id)):
|
||||
raise ValidationError(_("A SLAAC IP address is already registered."))
|
||||
try:
|
||||
prefix_v6 = self.interface.type.ip_type.prefix_v6.encode().decode('utf-8')
|
||||
except AttributeError: # Prevents from crashing when there is no defined prefix_v6
|
||||
prefix_v6 = None
|
||||
if prefix_v6:
|
||||
if (IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[:20] !=
|
||||
IPv6Address(prefix_v6).exploded[:20]):
|
||||
|
@ -1609,7 +1619,7 @@ class Role(RevMixin, AclMixin, models.Model):
|
|||
ROLE = (
|
||||
('dhcp-server', _("DHCP server")),
|
||||
('switch-conf-server', _("Switches configuration server")),
|
||||
('dns-recursif-server', _("Recursive DNS server")),
|
||||
('dns-recursive-server', _("Recursive DNS server")),
|
||||
('ntp-server', _("NTP server")),
|
||||
('radius-server', _("RADIUS server")),
|
||||
('log-server', _("Log server")),
|
||||
|
@ -1869,7 +1879,8 @@ class OuverturePort(RevMixin, AclMixin, models.Model):
|
|||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("ports openings")
|
||||
verbose_name = _("ports opening")
|
||||
verbose_name_plural = _("ports openings")
|
||||
|
||||
def __str__(self):
|
||||
if self.begin == self.end:
|
||||
|
|
|
@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
<div class="table-responsive">
|
||||
{% if machines_list.paginator %}
|
||||
{% include "pagination.html" with list=machines_list %}
|
||||
{% include 'pagination.html' with list=machines_list go_to_id="machines" %}
|
||||
{% endif %}
|
||||
|
||||
<table class="table" id="machines_table">
|
||||
|
@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</colgroup>
|
||||
<thead>
|
||||
{% trans "DNS name" as tr_dns_name %}
|
||||
<th>{% include "buttons/sort.html" with prefix='machine' col='name' text=tr_dns_name %}</th>
|
||||
<th>{% include 'buttons/sort.html' with prefix='machine' col='name' text=tr_dns_name %}</th>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "MAC address" %}</th>
|
||||
<th>{% trans "IP address" %}</th>
|
||||
|
@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td colspan="4">
|
||||
{% trans "No name" as tr_no_name %}
|
||||
{% trans "View the profile" as tr_view_the_profile %}
|
||||
<b>{{ machine.get_name|default:'<i>tr_no_name</i>' }}</b> <i class="fa fa-angle-right"></i>
|
||||
<b>{{ machine.get_name|default:tr_no_name }}</b> <i class="fa fa-angle-right"></i>
|
||||
<a href="{% url 'users:profil' userid=machine.user.id %}" title=tr_view_the_profile>
|
||||
<i class="fa fa-user"></i> {{ machine.user }}
|
||||
</a>
|
||||
|
@ -215,6 +215,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</script>
|
||||
|
||||
{% if machines_list.paginator %}
|
||||
{% include "pagination.html" with list=machines_list %}
|
||||
{% include 'pagination.html' with list=machines_list go_to_id="machines" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -26,14 +26,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Creation and editing of machines" %}{% endblock %}
|
||||
{% block title %}{% trans "Deletion of machines" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<h4>{% blocktrans %}Warning: are you sure you want to delete this object {{ objet_name }} ( {{ objet }}
|
||||
)?{% endblocktrans %}</h4>
|
||||
<h4>{% blocktrans %}Warning: are you sure you want to delete this object {{ objet_name }} ( {{ objet }} )?{% endblocktrans %}</h4>
|
||||
{% trans "Confirm" as tr_confirm %}
|
||||
{% bootstrap_button tr_confirm button_type="submit" icon='trash' button_class='btn-danger' %}
|
||||
</form>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h2>{% trans "Machines" %}</h2>
|
||||
{% include "machines/aff_machines.html" with machines_list=machines_list %}
|
||||
{% include 'machines/aff_machines.html' with machines_list=machines_list %}
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
class="fa fa-plus"></i>{% trans " Add an alias" %}</a>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-alias' interface_id %}"><i
|
||||
class="fa fa-trash"></i>{% trans " Delete one or several aliases" %}</a>
|
||||
{% include "machines/aff_alias.html" with alias_list=alias_list %}
|
||||
{% include 'machines/aff_alias.html' with alias_list=alias_list %}
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-extension' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several extensions" %}
|
||||
</a>
|
||||
{% include "machines/aff_extension.html" with extension_list=extension_list %}
|
||||
{% include 'machines/aff_extension.html' with extension_list=extension_list %}
|
||||
|
||||
<h2>{% trans "List of SOA records" %}</h2>
|
||||
{% can_create SOA %}
|
||||
|
@ -50,7 +50,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several SOA records" %}
|
||||
</a>
|
||||
{% include "machines/aff_soa.html" with soa_list=soa_list %}
|
||||
{% include 'machines/aff_soa.html' with soa_list=soa_list %}
|
||||
|
||||
<h2>{% trans "List of MX records" %}</h2>
|
||||
{% can_create Mx %}
|
||||
|
@ -61,7 +61,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-mx' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several MX records" %}
|
||||
</a>
|
||||
{% include "machines/aff_mx.html" with mx_list=mx_list %}
|
||||
{% include 'machines/aff_mx.html' with mx_list=mx_list %}
|
||||
|
||||
<h2>{% trans "List of NS records" %}</h2>
|
||||
{% can_create Ns %}
|
||||
|
@ -72,7 +72,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-ns' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several NS records" %}
|
||||
</a>
|
||||
{% include "machines/aff_ns.html" with ns_list=ns_list %}
|
||||
{% include 'machines/aff_ns.html' with ns_list=ns_list %}
|
||||
|
||||
<h2>{% trans "List of TXT records" %}</h2>
|
||||
{% can_create Txt %}
|
||||
|
@ -83,7 +83,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-txt' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several TXT records" %}
|
||||
</a>
|
||||
{% include "machines/aff_txt.html" with txt_list=txt_list %}
|
||||
{% include 'machines/aff_txt.html' with txt_list=txt_list %}
|
||||
|
||||
<h2>{% trans "List of DNAME records" %}</h2>
|
||||
{% can_create DName %}
|
||||
|
@ -94,7 +94,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-dname' %}">
|
||||
<i class="fa fa-trash"></i> {% trans " Delete one or several DNAME records" %}
|
||||
</a>
|
||||
{% include "machines/aff_dname.html" with dname_list=dname_list %}
|
||||
{% include 'machines/aff_dname.html' with dname_list=dname_list %}
|
||||
|
||||
<h2>{% trans "List of SRV records" %}</h2>
|
||||
{% can_create Srv %}
|
||||
|
@ -105,5 +105,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-srv' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several SRV records" %}
|
||||
</a>
|
||||
{% include "machines/aff_srv.html" with srv_list=srv_list %}
|
||||
{% include 'machines/aff_srv.html' with srv_list=srv_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -39,5 +39,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-iptype' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several IP types" %}
|
||||
</a>
|
||||
{% include "machines/aff_iptype.html" with iptype_list=iptype_list %}
|
||||
{% include 'machines/aff_iptype.html' with iptype_list=iptype_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<i class="fa fa-plus"></i>{% trans " Add an IPv6 address" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% include "machines/aff_ipv6.html" with ipv6_list=ipv6_list %}
|
||||
{% include 'machines/aff_ipv6.html' with ipv6_list=ipv6_list %}
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-machinetype' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several machine types" %}
|
||||
</a>
|
||||
{% include "machines/aff_machinetype.html" with machinetype_list=machinetype_list %}
|
||||
{% include 'machines/aff_machinetype.html' with machinetype_list=machinetype_list %}
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-nas' %}">
|
||||
<i class="fa fa-trash"></i>{% trans " Delete one or several NAS device types" %}
|
||||
</a>
|
||||
{% include "machines/aff_nas.html" with nas_list=nas_list %}
|
||||
{% include 'machines/aff_nas.html' with nas_list=nas_list %}
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/sidebar.html' %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -37,5 +37,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-role' %}"><i
|
||||
class="fa fa-trash"></i>{% trans " Delete one or several roles" %}</a>
|
||||
{% include "machines/aff_role.html" with role_list=role_list %}
|
||||
{% include 'machines/aff_role.html' with role_list=role_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -37,8 +37,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-service' %}"><i
|
||||
class="fa fa-trash"></i>{% trans " Delete one or several services" %}</a>
|
||||
{% include "machines/aff_service.html" with service_list=service_list %}
|
||||
{% include 'machines/aff_service.html' with service_list=service_list %}
|
||||
|
||||
<h2>{% trans "States of servers" %}</h2>
|
||||
{% include "machines/aff_servers.html" with servers_list=servers_list %}
|
||||
{% include 'machines/aff_servers.html' with servers_list=servers_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -34,5 +34,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<i class="fa fa-plus"></i>{% trans " Add an SSH fingerprint" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% include "machines/aff_sshfp.html" with sshfp_list=sshfp_list %}
|
||||
{% include 'machines/aff_sshfp.html' with sshfp_list=sshfp_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -37,5 +37,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-vlan' %}"><i
|
||||
class="fa fa-trash"></i>{% trans " Delete one or several VLANs" %}</a>
|
||||
{% include "machines/aff_vlan.html" with vlan_list=vlan_list %}
|
||||
{% include 'machines/aff_vlan.html' with vlan_list=vlan_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "machines/sidebar.html" %}
|
||||
{% extends 'machines/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
|
||||
|
@ -171,3 +171,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% bootstrap_button action_name button_type="submit" icon='ok' button_class='btn-success' %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends 'base.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
|
||||
|
@ -28,57 +28,58 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block sidebar %}
|
||||
{% can_view_all Machine %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "Machines" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all MachineType %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-machinetype" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-machinetype' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "Machine types" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Extension %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-extension" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-extension' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "Extensions and zones" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all IpType %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-iptype" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-iptype' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "IP ranges" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Vlan %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-vlan" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-vlan' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "VLANs" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Nas %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-nas" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-nas' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "NAS devices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all machines.Service %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-service" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-service' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "Services (DHCP, DNS, ...)" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all Role %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-role" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-role' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "Server roles" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all OuverturePortList %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "machines:index-portlist" %}">
|
||||
<a class="list-group-item list-group-item-info" href="{% url 'machines:index-portlist' %}">
|
||||
<i class="fa fa-list-ul"></i>
|
||||
{% trans "Ports openings" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -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,9 +43,12 @@ from .models import (
|
|||
RadiusKey,
|
||||
SwitchManagementCred,
|
||||
RadiusOption,
|
||||
CotisationsOption,
|
||||
DocumentTemplate
|
||||
)
|
||||
from topologie.models import Switch
|
||||
|
||||
|
||||
class EditOptionalUserForm(ModelForm):
|
||||
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
|
||||
class Meta:
|
||||
|
@ -115,11 +118,6 @@ class EditOptionalTopologieForm(ModelForm):
|
|||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
self.fields['radius_general_policy'].label = _("RADIUS general policy")
|
||||
self.fields['vlan_decision_ok'].label = _("VLAN for machines accepted"
|
||||
" by RADIUS")
|
||||
self.fields['vlan_decision_nok'].label = _("VLAN for machines rejected"
|
||||
" by RADIUS")
|
||||
|
||||
self.initial['automatic_provision_switchs'] = Switch.objects.filter(automatic_provision=True).order_by('interface__domain__name')
|
||||
|
||||
|
@ -187,9 +185,6 @@ class EditAssoOptionForm(ModelForm):
|
|||
self.fields['pseudo'].label = _("Usual name")
|
||||
self.fields['utilisateur_asso'].label = _("Account used for editing"
|
||||
" from /admin")
|
||||
self.fields['payment'].label = _("Payment")
|
||||
self.fields['payment_id'].label = _("Payment ID")
|
||||
self.fields['payment_pass'].label = _("Payment password")
|
||||
self.fields['description'].label = _("Description")
|
||||
|
||||
|
||||
|
@ -236,6 +231,34 @@ class EditRadiusOptionForm(ModelForm):
|
|||
model = RadiusOption
|
||||
fields = '__all__'
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
ignored=('radius_general_policy', 'vlan_decision_ok')
|
||||
fields = (
|
||||
f for f in self.fields.keys()
|
||||
if 'vlan' not in f and f not in ignored
|
||||
)
|
||||
for f in fields:
|
||||
choice = cleaned_data.get(f)
|
||||
vlan = cleaned_data.get(f+'_vlan')
|
||||
if choice == RadiusOption.SET_VLAN and vlan is None:
|
||||
self.add_error(
|
||||
f,
|
||||
_("You chose to set vlan but did not set any VLAN."),
|
||||
)
|
||||
self.add_error(
|
||||
f+'_vlan',
|
||||
_("Please, choose a VLAN."),
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class EditCotisationsOptionForm(ModelForm):
|
||||
"""Edition forms for Cotisations options"""
|
||||
class Meta:
|
||||
model = CotisationsOption
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ServiceForm(ModelForm):
|
||||
"""Edition, ajout de services sur la page d'accueil"""
|
||||
|
@ -343,7 +366,7 @@ class DelMailContactForm(Form):
|
|||
"""Delete contact email adress"""
|
||||
mailcontacts = forms.ModelMultipleChoiceField(
|
||||
queryset=MailContact.objects.none(),
|
||||
label="Enregistrements adresses actuels",
|
||||
label=_("Current email addresses"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -355,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()
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,19 +7,6 @@ import django.db.models.deletion
|
|||
import re2o.mixins
|
||||
|
||||
|
||||
def create_radius_policy(apps, schema_editor):
|
||||
OptionalTopologie = apps.get_model('preferences', 'OptionalTopologie')
|
||||
RadiusOption = apps.get_model('preferences', 'RadiusOption')
|
||||
|
||||
option,_ = OptionalTopologie.objects.get_or_create()
|
||||
|
||||
radius_option = RadiusOption()
|
||||
radius_option.radius_general_policy = option.radius_general_policy
|
||||
radius_option.vlan_decision_ok = option.vlan_decision_ok
|
||||
|
||||
radius_option.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
|
@ -94,18 +81,4 @@ class Migration(migrations.Migration):
|
|||
name='vlan_decision_ok',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlan_ok_option', to='machines.Vlan'),
|
||||
),
|
||||
|
||||
migrations.RunPython(create_radius_policy),
|
||||
migrations.RemoveField(
|
||||
model_name='optionaltopologie',
|
||||
name='radius_general_policy',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='optionaltopologie',
|
||||
name='vlan_decision_nok',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='optionaltopologie',
|
||||
name='vlan_decision_ok',
|
||||
),
|
||||
]
|
36
preferences/migrations/0056_2_radiusoption.py
Normal file
36
preferences/migrations/0056_2_radiusoption.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-10-13 14:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
def create_radius_policy(apps, schema_editor):
|
||||
OptionalTopologie = apps.get_model('preferences', 'OptionalTopologie')
|
||||
RadiusOption = apps.get_model('preferences', 'RadiusOption')
|
||||
|
||||
option,_ = OptionalTopologie.objects.get_or_create()
|
||||
|
||||
radius_option = RadiusOption()
|
||||
radius_option.radius_general_policy = option.radius_general_policy
|
||||
radius_option.vlan_decision_ok = option.vlan_decision_ok
|
||||
|
||||
radius_option.save()
|
||||
|
||||
def revert_radius(apps, schema_editor):
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0095_auto_20180919_2225'),
|
||||
('preferences', '0055_generaloption_main_site_url'),
|
||||
('preferences', '0056_1_radiusoption'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_radius_policy, revert_radius),
|
||||
]
|
31
preferences/migrations/0056_3_radiusoption.py
Normal file
31
preferences/migrations/0056_3_radiusoption.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-10-13 14:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0095_auto_20180919_2225'),
|
||||
('preferences', '0055_generaloption_main_site_url'),
|
||||
('preferences', '0056_2_radiusoption'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='optionaltopologie',
|
||||
name='radius_general_policy',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='optionaltopologie',
|
||||
name='vlan_decision_nok',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='optionaltopologie',
|
||||
name='vlan_decision_ok',
|
||||
),
|
||||
]
|
|
@ -8,7 +8,7 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0056_radiusoption'),
|
||||
('preferences', '0056_3_radiusoption'),
|
||||
]
|
||||
|
||||
operations = [
|
20
preferences/migrations/0057_optionaluser_all_users_active.py
Normal file
20
preferences/migrations/0057_optionaluser_all_users_active.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2019-01-05 17:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0056_4_radiusoption'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='optionaluser',
|
||||
name='all_users_active',
|
||||
field=models.BooleanField(default=False, help_text='If True, all new created and connected users are active. If False, only when a valid registration has been paid'),
|
||||
),
|
||||
]
|
208
preferences/migrations/0058_auto_20190108_1650.py
Normal file
208
preferences/migrations/0058_auto_20190108_1650.py
Normal file
|
@ -0,0 +1,208 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2019-01-08 22:50
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import re2o.aes_field
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0057_optionaluser_all_users_active'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='radiuskey',
|
||||
options={'permissions': (('view_radiuskey', 'Can view a RADIUS key object'),), 'verbose_name': 'RADIUS key', 'verbose_name_plural': 'RADIUS keys'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='radiusoption',
|
||||
options={'verbose_name': 'RADIUS policy', 'verbose_name_plural': 'RADIUS policies'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='reminder',
|
||||
options={'permissions': (('view_reminder', 'Can view a reminder object'),), 'verbose_name': 'reminder', 'verbose_name_plural': 'reminders'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='switchmanagementcred',
|
||||
options={'permissions': (('view_switchmanagementcred', 'Can view a switch management credentials object'),), 'verbose_name': 'switch management credentials'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailmessageoption',
|
||||
name='welcome_mail_en',
|
||||
field=models.TextField(default='', help_text='Welcome email in English'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mailmessageoption',
|
||||
name='welcome_mail_fr',
|
||||
field=models.TextField(default='', help_text='Welcome email in French'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='sftp_login',
|
||||
field=models.CharField(blank=True, help_text='SFTP login for switches', max_length=32, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='sftp_pass',
|
||||
field=re2o.aes_field.AESEncryptedField(blank=True, help_text='SFTP password', max_length=63, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='switchs_ip_type',
|
||||
field=models.OneToOneField(blank=True, help_text='IP range for the management of switches', null=True, on_delete=django.db.models.deletion.PROTECT, to='machines.IpType'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='switchs_provision',
|
||||
field=models.CharField(choices=[('sftp', 'sftp'), ('tftp', 'tftp')], default='tftp', help_text='Provision of configuration mode for switches', max_length=32),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='switchs_rest_management',
|
||||
field=models.BooleanField(default=False, help_text='REST management, activated in case of automatic provision'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='switchs_web_management',
|
||||
field=models.BooleanField(default=False, help_text='Web management, activated in case of automatic provision'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaltopologie',
|
||||
name='switchs_web_management_ssl',
|
||||
field=models.BooleanField(default=False, help_text='SSL web management, make sure that a certificate is installed on the switch'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='all_can_create_adherent',
|
||||
field=models.BooleanField(default=False, help_text='Users can create a member.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='all_can_create_club',
|
||||
field=models.BooleanField(default=False, help_text='Users can create a club.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='all_users_active',
|
||||
field=models.BooleanField(default=False, help_text='If True, all new created and connected users are active. If False, only when a valid registration has been paid.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='delete_notyetactive',
|
||||
field=models.IntegerField(default=15, help_text='Not yet active users will be deleted after this number of days.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='local_email_accounts_enabled',
|
||||
field=models.BooleanField(default=False, help_text='Enable local email accounts for users.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='max_email_address',
|
||||
field=models.IntegerField(default=15, help_text='Maximum number of local email addresses for a standard user.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='self_adhesion',
|
||||
field=models.BooleanField(default=False, help_text='A new user can create their account on Re2o.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='self_change_room',
|
||||
field=models.BooleanField(default=False, help_text='Users can edit their room.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='optionaluser',
|
||||
name='self_change_shell',
|
||||
field=models.BooleanField(default=False, help_text='Users can edit their shell.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiuskey',
|
||||
name='comment',
|
||||
field=models.CharField(blank=True, help_text='Comment for this key', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiuskey',
|
||||
name='default_switch',
|
||||
field=models.BooleanField(default=True, help_text='Default key for switches', unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiuskey',
|
||||
name='radius_key',
|
||||
field=re2o.aes_field.AESEncryptedField(help_text='RADIUS key', max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='banned',
|
||||
field=models.CharField(choices=[('REJECT', 'Reject the machine'), ('SET_VLAN', 'Place the machine on the VLAN')], default='REJECT', max_length=32, verbose_name='Policy for banned users'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='banned_vlan',
|
||||
field=models.ForeignKey(blank=True, help_text='VLAN for banned users if not rejected', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='banned_vlan', to='machines.Vlan', verbose_name='Banned users VLAN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='non_member',
|
||||
field=models.CharField(choices=[('REJECT', 'Reject the machine'), ('SET_VLAN', 'Place the machine on the VLAN')], default='REJECT', max_length=32, verbose_name='Policy for non members'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='non_member_vlan',
|
||||
field=models.ForeignKey(blank=True, help_text='VLAN for non members if not rejected', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='non_member_vlan', to='machines.Vlan', verbose_name='Non members VLAN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='unknown_machine_vlan',
|
||||
field=models.ForeignKey(blank=True, help_text='VLAN for unknown machines if not rejected', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='unknown_machine_vlan', to='machines.Vlan', verbose_name='Unknown machines VLAN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='unknown_port',
|
||||
field=models.CharField(choices=[('REJECT', 'Reject the machine'), ('SET_VLAN', 'Place the machine on the VLAN')], default='REJECT', max_length=32, verbose_name='Policy for unknown ports'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='unknown_port_vlan',
|
||||
field=models.ForeignKey(blank=True, help_text='VLAN for unknown ports if not rejected', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='unknown_port_vlan', to='machines.Vlan', verbose_name='Unknown ports VLAN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='unknown_room',
|
||||
field=models.CharField(choices=[('REJECT', 'Reject the machine'), ('SET_VLAN', 'Place the machine on the VLAN')], default='REJECT', max_length=32, verbose_name='Policy for machines connecting from unregistered rooms (relevant on ports with STRICT RADIUS mode)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='radiusoption',
|
||||
name='unknown_room_vlan',
|
||||
field=models.ForeignKey(blank=True, help_text='VLAN for unknown rooms if not rejected', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='unknown_room_vlan', to='machines.Vlan', verbose_name='Unknown rooms VLAN'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='reminder',
|
||||
name='days',
|
||||
field=models.IntegerField(default=7, help_text="Delay between the email and the membership's end", unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='reminder',
|
||||
name='message',
|
||||
field=models.CharField(blank=True, default='', help_text='Message displayed specifically for this reminder', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='switchmanagementcred',
|
||||
name='default_switch',
|
||||
field=models.BooleanField(default=True, help_text='Default credentials for switches', unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='switchmanagementcred',
|
||||
name='management_id',
|
||||
field=models.CharField(help_text='Switch login', max_length=63),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='switchmanagementcred',
|
||||
name='management_pass',
|
||||
field=re2o.aes_field.AESEncryptedField(help_text='Password', max_length=63),
|
||||
),
|
||||
]
|
62
preferences/migrations/0059_auto_20190120_1739.py
Normal file
62
preferences/migrations/0059_auto_20190120_1739.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# 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 preferences.models
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
def create_defaults(apps, schema_editor):
|
||||
CotisationsOption = apps.get_model('preferences', 'CotisationsOption')
|
||||
CotisationsOption.objects.get_or_create()
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('preferences', '0058_auto_20190108_1650'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CotisationsOption',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('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.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),
|
||||
]
|
|
@ -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
|
||||
|
@ -36,7 +37,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
import machines.models
|
||||
|
||||
from re2o.mixins import AclMixin
|
||||
from re2o.mixins import AclMixin, RevMixin
|
||||
from re2o.aes_field import AESEncryptedField
|
||||
|
||||
from datetime import timedelta
|
||||
|
@ -73,11 +74,11 @@ class OptionalUser(AclMixin, PreferencesModel):
|
|||
gpg_fingerprint = models.BooleanField(default=True)
|
||||
all_can_create_club = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("Users can create a club")
|
||||
help_text=_("Users can create a club.")
|
||||
)
|
||||
all_can_create_adherent = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("Users can create a member"),
|
||||
help_text=_("Users can create a member."),
|
||||
)
|
||||
|
||||
shell_default = models.OneToOneField(
|
||||
|
@ -88,15 +89,15 @@ class OptionalUser(AclMixin, PreferencesModel):
|
|||
)
|
||||
self_change_shell = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("Users can edit their shell")
|
||||
help_text=_("Users can edit their shell.")
|
||||
)
|
||||
self_change_room = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("Users can edit their room")
|
||||
help_text=_("Users can edit their room.")
|
||||
)
|
||||
local_email_accounts_enabled = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("Enable local email accounts for users")
|
||||
help_text=_("Enable local email accounts for users.")
|
||||
)
|
||||
local_email_domain = models.CharField(
|
||||
max_length=32,
|
||||
|
@ -106,15 +107,21 @@ class OptionalUser(AclMixin, PreferencesModel):
|
|||
max_email_address = models.IntegerField(
|
||||
default=15,
|
||||
help_text=_("Maximum number of local email addresses for a standard"
|
||||
" user")
|
||||
" user.")
|
||||
)
|
||||
delete_notyetactive = models.IntegerField(
|
||||
default=15,
|
||||
help_text=_("Inactive users will be deleted after this number of days")
|
||||
help_text=_("Not yet active users will be deleted after this number of"
|
||||
" days.")
|
||||
)
|
||||
self_adhesion = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("A new user can create their account on Re2o")
|
||||
help_text=_("A new user can create their account on Re2o.")
|
||||
)
|
||||
all_users_active = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_("If True, all new created and connected users are active."
|
||||
" If False, only when a valid registration has been paid.")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -201,40 +208,41 @@ class OptionalTopologie(AclMixin, PreferencesModel):
|
|||
|
||||
switchs_web_management = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Web management, activé si provision automatique"
|
||||
help_text=_("Web management, activated in case of automatic provision")
|
||||
)
|
||||
switchs_web_management_ssl = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Web management ssl. Assurez-vous que un certif est installé sur le switch !"
|
||||
help_text=_("SSL web management, make sure that a certificate is"
|
||||
" installed on the switch")
|
||||
)
|
||||
switchs_rest_management = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Rest management, activé si provision auto"
|
||||
help_text=_("REST management, activated in case of automatic provision")
|
||||
)
|
||||
switchs_ip_type = models.OneToOneField(
|
||||
'machines.IpType',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text="Plage d'ip de management des switchs"
|
||||
help_text=_("IP range for the management of switches")
|
||||
)
|
||||
switchs_provision = models.CharField(
|
||||
max_length=32,
|
||||
choices=CHOICE_PROVISION,
|
||||
default='tftp',
|
||||
help_text="Mode de récupération des confs par les switchs"
|
||||
help_text=_("Provision of configuration mode for switches")
|
||||
)
|
||||
sftp_login = models.CharField(
|
||||
max_length=32,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Login sftp des switchs"
|
||||
help_text=_("SFTP login for switches")
|
||||
)
|
||||
sftp_pass = AESEncryptedField(
|
||||
max_length=63,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Mot de passe sftp"
|
||||
help_text=_("SFTP password")
|
||||
)
|
||||
|
||||
@cached_property
|
||||
|
@ -278,12 +286,13 @@ class OptionalTopologie(AclMixin, PreferencesModel):
|
|||
log_servers = Role.all_interfaces_for_roletype("log-server").filter(type__ip_type=self.switchs_ip_type)
|
||||
radius_servers = Role.all_interfaces_for_roletype("radius-server").filter(type__ip_type=self.switchs_ip_type)
|
||||
dhcp_servers = Role.all_interfaces_for_roletype("dhcp-server")
|
||||
dns_recursive_servers = Role.all_interfaces_for_roletype("dns-recursive-server").filter(type__ip_type=self.switchs_ip_type)
|
||||
subnet = None
|
||||
subnet6 = None
|
||||
if self.switchs_ip_type:
|
||||
subnet = self.switchs_ip_type.ip_set_full_info
|
||||
subnet6 = self.switchs_ip_type.ip6_set_full_info
|
||||
return {'ntp_servers': return_ips_dict(ntp_servers), 'log_servers': return_ips_dict(log_servers), 'radius_servers': return_ips_dict(radius_servers), 'dhcp_servers': return_ips_dict(dhcp_servers), 'subnet': subnet, 'subnet6': subnet6}
|
||||
return {'ntp_servers': return_ips_dict(ntp_servers), 'log_servers': return_ips_dict(log_servers), 'radius_servers': return_ips_dict(radius_servers), 'dhcp_servers': return_ips_dict(dhcp_servers), 'dns_recursive_servers': return_ips_dict(dns_recursive_servers), 'subnet': subnet, 'subnet6': subnet6}
|
||||
|
||||
@cached_property
|
||||
def provision_switchs_enabled(self):
|
||||
|
@ -308,52 +317,56 @@ class RadiusKey(AclMixin, models.Model):
|
|||
"""Class of a radius key"""
|
||||
radius_key = AESEncryptedField(
|
||||
max_length=255,
|
||||
help_text="Clef radius"
|
||||
help_text=_("RADIUS key")
|
||||
)
|
||||
comment = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Commentaire de cette clef"
|
||||
help_text=_("Comment for this key")
|
||||
)
|
||||
default_switch = models.BooleanField(
|
||||
default=True,
|
||||
unique=True,
|
||||
help_text= "Clef par défaut des switchs"
|
||||
help_text=_("Default key for switches")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_radiuskey", "Peut voir un objet radiuskey"),
|
||||
("view_radiuskey", _("Can view a RADIUS key object")),
|
||||
)
|
||||
verbose_name = _("RADIUS key")
|
||||
verbose_name_plural = _("RADIUS keys")
|
||||
|
||||
def __str__(self):
|
||||
return "Clef radius " + str(self.id) + " " + str(self.comment)
|
||||
return _("RADIUS key ") + str(self.id) + " " + str(self.comment)
|
||||
|
||||
|
||||
class SwitchManagementCred(AclMixin, models.Model):
|
||||
"""Class of a management creds of a switch, for rest management"""
|
||||
management_id = models.CharField(
|
||||
max_length=63,
|
||||
help_text="Login du switch"
|
||||
help_text=_("Switch login")
|
||||
)
|
||||
management_pass = AESEncryptedField(
|
||||
max_length=63,
|
||||
help_text="Mot de passe"
|
||||
help_text=_("Password")
|
||||
)
|
||||
default_switch = models.BooleanField(
|
||||
default=True,
|
||||
unique=True,
|
||||
help_text= "Creds par défaut des switchs"
|
||||
help_text=_("Default credentials for switches")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_switchmanagementcred", "Peut voir un objet switchmanagementcred"),
|
||||
("view_switchmanagementcred", _("Can view a switch management"
|
||||
" credentials object")),
|
||||
)
|
||||
verbose_name = _("switch management credentials")
|
||||
|
||||
def __str__(self):
|
||||
return "Identifiant " + str(self.management_id)
|
||||
return _("Switch login ") + str(self.management_id)
|
||||
|
||||
|
||||
class Reminder(AclMixin, models.Model):
|
||||
|
@ -361,25 +374,26 @@ class Reminder(AclMixin, models.Model):
|
|||
Days: liste des nombres de jours pour lesquells un mail est envoyé
|
||||
optionalMessage: message additionel pour le mail
|
||||
"""
|
||||
PRETTY_NAME="Options pour le mail de fin d'adhésion"
|
||||
|
||||
days = models.IntegerField(
|
||||
default=7,
|
||||
unique=True,
|
||||
help_text="Délais entre le mail et la fin d'adhésion"
|
||||
help_text=_("Delay between the email and the membership's end")
|
||||
)
|
||||
message = models.CharField(
|
||||
max_length=255,
|
||||
default="",
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Message affiché spécifiquement pour ce rappel"
|
||||
help_text=_("Message displayed specifically for this reminder")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_reminder", "Peut voir un objet reminder"),
|
||||
("view_reminder", _("Can view a reminder object")),
|
||||
)
|
||||
verbose_name = _("reminder")
|
||||
verbose_name_plural = _("reminders")
|
||||
|
||||
def users_to_remind(self):
|
||||
from re2o.utils import all_has_access
|
||||
|
@ -466,8 +480,7 @@ class MailContact(AclMixin, models.Model):
|
|||
commentary = models.CharField(
|
||||
blank = True,
|
||||
null = True,
|
||||
help_text = _(
|
||||
"Description of the associated email address."),
|
||||
help_text = _("Description of the associated email address."),
|
||||
max_length = 256
|
||||
)
|
||||
|
||||
|
@ -509,6 +522,12 @@ class AssoOption(AclMixin, PreferencesModel):
|
|||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
pres_name = models.CharField(
|
||||
max_length=255,
|
||||
default="",
|
||||
verbose_name=_("President of the association"),
|
||||
help_text=_("Displayed on subscription vouchers")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
|
@ -558,8 +577,8 @@ def homeoption_post_save(**kwargs):
|
|||
class MailMessageOption(AclMixin, models.Model):
|
||||
"""Reglages, mail de bienvenue et autre"""
|
||||
|
||||
welcome_mail_fr = models.TextField(default="", help_text="Mail de bienvenue en français")
|
||||
welcome_mail_en = models.TextField(default="", help_text="Mail de bienvenue en anglais")
|
||||
welcome_mail_fr = models.TextField(default="", help_text=_("Welcome email in French"))
|
||||
welcome_mail_en = models.TextField(default="", help_text=_("Welcome email in English"))
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
|
@ -571,7 +590,8 @@ class MailMessageOption(AclMixin, models.Model):
|
|||
|
||||
class RadiusOption(AclMixin, PreferencesModel):
|
||||
class Meta:
|
||||
verbose_name = _("radius policies")
|
||||
verbose_name = _("RADIUS policy")
|
||||
verbose_name_plural = _("RADIUS policies")
|
||||
|
||||
MACHINE = 'MACHINE'
|
||||
DEFINED = 'DEFINED'
|
||||
|
@ -582,8 +602,8 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
REJECT = 'REJECT'
|
||||
SET_VLAN = 'SET_VLAN'
|
||||
CHOICE_POLICY = (
|
||||
(REJECT, _('Reject the machine')),
|
||||
(SET_VLAN, _('Place the machine on the VLAN'))
|
||||
(REJECT, _("Reject the machine")),
|
||||
(SET_VLAN, _("Place the machine on the VLAN"))
|
||||
)
|
||||
radius_general_policy = models.CharField(
|
||||
max_length=32,
|
||||
|
@ -602,16 +622,14 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
related_name='unknown_machine_vlan',
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Unknown machine Vlan'),
|
||||
help_text=_(
|
||||
'Vlan for unknown machines if not rejected.'
|
||||
)
|
||||
verbose_name=_("Unknown machines VLAN"),
|
||||
help_text=_("VLAN for unknown machines if not rejected")
|
||||
)
|
||||
unknown_port = models.CharField(
|
||||
max_length=32,
|
||||
choices=CHOICE_POLICY,
|
||||
default=REJECT,
|
||||
verbose_name=_("Policy for unknown port"),
|
||||
verbose_name=_("Policy for unknown ports"),
|
||||
)
|
||||
unknown_port_vlan = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
|
@ -619,20 +637,15 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
related_name='unknown_port_vlan',
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Unknown port Vlan'),
|
||||
help_text=_(
|
||||
'Vlan for unknown ports if not rejected.'
|
||||
)
|
||||
verbose_name=_("Unknown ports VLAN"),
|
||||
help_text=_("VLAN for unknown ports if not rejected")
|
||||
)
|
||||
unknown_room = models.CharField(
|
||||
max_length=32,
|
||||
choices=CHOICE_POLICY,
|
||||
default=REJECT,
|
||||
verbose_name=_(
|
||||
"Policy for machine connecting from "
|
||||
"unregistered room (relevant on ports with STRICT "
|
||||
"radius mode)"
|
||||
),
|
||||
verbose_name=_("Policy for machines connecting from unregistered rooms"
|
||||
" (relevant on ports with STRICT RADIUS mode)"),
|
||||
)
|
||||
unknown_room_vlan = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
|
@ -640,16 +653,14 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Unknown room Vlan'),
|
||||
help_text=_(
|
||||
'Vlan for unknown room if not rejected.'
|
||||
)
|
||||
verbose_name=_("Unknown rooms VLAN"),
|
||||
help_text=_("VLAN for unknown rooms if not rejected")
|
||||
)
|
||||
non_member = models.CharField(
|
||||
max_length=32,
|
||||
choices=CHOICE_POLICY,
|
||||
default=REJECT,
|
||||
verbose_name=_("Policy non member users."),
|
||||
verbose_name=_("Policy for non members"),
|
||||
)
|
||||
non_member_vlan = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
|
@ -657,16 +668,14 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Non member Vlan'),
|
||||
help_text=_(
|
||||
'Vlan for non members if not rejected.'
|
||||
)
|
||||
verbose_name=_("Non members VLAN"),
|
||||
help_text=_("VLAN for non members if not rejected")
|
||||
)
|
||||
banned = models.CharField(
|
||||
max_length=32,
|
||||
choices=CHOICE_POLICY,
|
||||
default=REJECT,
|
||||
verbose_name=_("Policy for banned users."),
|
||||
verbose_name=_("Policy for banned users"),
|
||||
)
|
||||
banned_vlan = models.ForeignKey(
|
||||
'machines.Vlan',
|
||||
|
@ -674,10 +683,8 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_('Banned Vlan'),
|
||||
help_text=_(
|
||||
'Vlan for banned if not rejected.'
|
||||
)
|
||||
verbose_name=_("Banned users VLAN"),
|
||||
help_text=_("VLAN for banned users if not rejected")
|
||||
)
|
||||
vlan_decision_ok = models.OneToOneField(
|
||||
'machines.Vlan',
|
||||
|
@ -687,3 +694,95 @@ class RadiusOption(AclMixin, PreferencesModel):
|
|||
null=True
|
||||
)
|
||||
|
||||
|
||||
def default_invoice():
|
||||
tpl, _ = DocumentTemplate.objects.get_or_create(
|
||||
name="Re2o default invoice",
|
||||
template="templates/default_invoice.tex"
|
||||
)
|
||||
return tpl.id
|
||||
|
||||
|
||||
def default_voucher():
|
||||
tpl, _ = DocumentTemplate.objects.get_or_create(
|
||||
name="Re2o default voucher",
|
||||
template="templates/default_voucher.tex"
|
||||
)
|
||||
return tpl.id
|
||||
|
||||
|
||||
class CotisationsOption(AclMixin, PreferencesModel):
|
||||
class Meta:
|
||||
verbose_name = _("cotisations options")
|
||||
|
||||
invoice_template = models.OneToOneField(
|
||||
'preferences.DocumentTemplate',
|
||||
verbose_name=_("Template for invoices"),
|
||||
related_name="invoice_template",
|
||||
on_delete=models.PROTECT,
|
||||
default=default_invoice,
|
||||
)
|
||||
voucher_template = models.OneToOneField(
|
||||
'preferences.DocumentTemplate',
|
||||
verbose_name=_("Template for subscription voucher"),
|
||||
related_name="voucher_template",
|
||||
on_delete=models.PROTECT,
|
||||
default=default_voucher,
|
||||
)
|
||||
send_voucher_mail = models.BooleanField(
|
||||
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)
|
||||
|
|
50
preferences/templates/preferences/aff_document_template.html
Normal file
50
preferences/templates/preferences/aff_document_template.html
Normal 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='preferences:edit-document-template' id=template.id %}
|
||||
{% acl_end %}
|
||||
{% history_button template %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
@ -23,13 +23,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id Clef</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Clef par default des switchs</th>
|
||||
<th>Clef utilisée par les switchs</th>
|
||||
<th>{% trans "RADIUS key ID" %}</th>
|
||||
<th>{% trans "Comment" %}</th>
|
||||
<th>{% trans "Default RADIUS key for switches" %}</th>
|
||||
<th>{% trans "RADIUS key used by the swithes" %}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -45,9 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% include 'buttons/edit.html' with href='preferences:edit-radiuskey' id=radiuskey.id %}
|
||||
{% acl_end %}
|
||||
{% can_delete radiuskey %}
|
||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'preferences:del-radiuskey' radiuskey.pk %}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
{% include 'buttons/suppr.html' with href='preferences:del-radiuskey' id=radiuskey.id %}
|
||||
{% acl_end %}
|
||||
{% history_button radiuskey %}
|
||||
</td>
|
||||
|
|
|
@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "VLAN for machines accepted by RADIUS" %}</th>
|
||||
<td><span class="label label-success">Vlan {{ radiusoptions.vlan_decision_ok }}</span></td>
|
||||
<td><span class="label label-success">{% blocktrans with vlan_decision_ok=radiusoptions.vlan_decision_ok %}VLAN {{ vlan_decision_ok }}{% endblocktrans %}</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr/>
|
||||
|
@ -39,7 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Situation" %}</th>
|
||||
<th>{% trans "Behavior" %}</th>
|
||||
<th>{% trans "Behaviour" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
|
@ -48,7 +48,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if radiusoptions.unknown_machine == 'REJECT' %}
|
||||
<span class="label label-danger">{% trans "Reject" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">Vlan {{ radiusoptions.unknown_machine_vlan }}</span>
|
||||
<span class="label label-success">{% blocktrans with unknown_machine_vlan=radiusoptions.unknown_machine_vlan %}VLAN {{ unknown_machine_vlan }}{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -58,7 +58,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if radiusoptions.unknown_port == 'REJECT' %}
|
||||
<span class="label label-danger">{% trans "Reject" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">Vlan {{ radiusoptions.unknown_port_vlan }}</span>
|
||||
<span class="label label-success">{% blocktrans with unknown_port_vlan=radiusoptions.unknown_port_vlan %}VLAN {{ unknown_port_vlan }}{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -68,7 +68,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if radiusoptions.unknown_room == 'REJECT' %}
|
||||
<span class="label label-danger">{% trans "Reject" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">Vlan {{ radiusoptions.unknown_room_vlan }}</span>
|
||||
<span class="label label-success">{% blocktrans with unknown_room_vlan=radiusoptions.unknown_room_vlan %}VLAN {{ unknown_room_vlan }}{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -78,7 +78,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if radiusoptions.non_member == 'REJECT' %}
|
||||
<span class="label label-danger">{% trans "Reject" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">Vlan {{ radiusoptions.non_member_vlan }}</span>
|
||||
<span class="label label-success">{% blocktrans with non_member_vlan=radiusoptions.non_member_vlan %}VLAN {{ non_member_vlan }}{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -88,7 +88,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if radiusoptions.unknown_port == 'REJECT' %}
|
||||
<span class="label label-danger">{% trans "Reject" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-success">Vlan {{ radiusoptions.banned_vlan }}</span>
|
||||
<span class="label label-success">{% blocktrans with banned_vlan=radiusoptions.banned_vlan %}VLAN {{ banned_vlan }}{% endblocktrans %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -23,11 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nombre de jours avant le rappel</th>
|
||||
<th>Message custom pour ce rappel</th>
|
||||
<th>{% trans "Number of days before the reminder" %}</th>
|
||||
<th>{% trans "Message for this reminder" %}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -39,11 +41,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td class="text-right">
|
||||
{% can_edit reminder %}
|
||||
{% include 'buttons/edit.html' with href='preferences:edit-reminder' id=reminder.id %}
|
||||
{% can_delete reminder %}
|
||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'preferences:del-reminder' reminder.id %}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_delete reminder %}
|
||||
{% include 'buttons/suppr.html' with href='preferences:del-reminder' id=reminder.id %}
|
||||
{% acl_end %}
|
||||
{% history_button reminder %}
|
||||
</td>
|
||||
|
|
|
@ -44,11 +44,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td class="text-right">
|
||||
{% can_edit service%}
|
||||
{% include 'buttons/edit.html' with href='preferences:edit-service' id=service.id %}
|
||||
{% can_delete service %}
|
||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'preferences:del-service' service.id %}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_delete service %}
|
||||
{% include 'buttons/suppr.html' with href='preferences:del-service' id=service.id %}
|
||||
{% acl_end %}
|
||||
{% history_button service %}
|
||||
</td>
|
||||
|
|
|
@ -23,12 +23,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Identifiant</th>
|
||||
<th>Creds par default des switchs</th>
|
||||
<th>Utilisé pour les switchs</th>
|
||||
<th>{% trans "Switch login" %}</th>
|
||||
<th>{% trans "Default switch management credentials" %}</th>
|
||||
<th>{% trans "Management credentials used by the switches" %}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -43,9 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% include 'buttons/edit.html' with href='preferences:edit-switchmanagementcred' id=switchmanagementcred.id %}
|
||||
{% acl_end %}
|
||||
{% can_delete switchmanagementcred %}
|
||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'preferences:del-switchmanagementcred' switchmanagementcred.pk %}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
{% include 'buttons/suppr.html' with href='preferences:del-switchmanagementcred' id=switchmanagementcred.id %}
|
||||
{% acl_end %}
|
||||
{% history_button switchmanagementcred %}
|
||||
</td>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "topologie/sidebar.html" %}
|
||||
{% extends 'preferences/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
|
||||
|
@ -24,15 +24,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Création et modification de machines{% endblock %}
|
||||
{% block title %}{% trans "Deletion of preferences" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<h4>Attention, voulez-vous vraiment supprimer cet objet {{ objet_name }} ( {{ objet }} ) ?</h4>
|
||||
{% bootstrap_button "Confirmer" button_type="submit" icon="trash" %}
|
||||
<h4>{% blocktrans %}Warning: are you sure you want to delete this {{ objet_name }} object ( {{ objet }} )?{% endblocktrans %}</h4>
|
||||
{% trans "Confirm" as tr_confirm %}
|
||||
{% bootstrap_button tr_confirm button_type="submit" icon="trash" %}
|
||||
</form>
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "preferences/sidebar.html" %}
|
||||
{% extends 'preferences/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
|
||||
|
@ -69,7 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<th>{% trans "General message displayed on the website" %}</th>
|
||||
<td>{{ generaloptions.general_message }}</td>
|
||||
<th>{% trans "Main site url" %}</th>
|
||||
<th>{% trans "Main site URL" %}</th>
|
||||
<td>{{ generaloptions.main_site_url }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -120,7 +120,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th>{% trans "Self registration" %}</th>
|
||||
<td>{{ useroptions.self_adhesion|tick }}</td>
|
||||
<th>{% trans "Delete not yet active users after" %}</th>
|
||||
<td>{{ useroptions.delete_notyetactive }} days</td>
|
||||
<td>{% blocktrans with delete_notyetactive=useroptions.delete_notyetactive %}{{ delete_notyetactive }} days{% endblocktrans %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "All users are active by default" %}</th>
|
||||
<td>{{ useroptions.all_users_active|tick }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -214,11 +218,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
<h4>Clef radius</h4>
|
||||
<h4>{% trans "RADIUS keys" %}</h4>
|
||||
{% can_create RadiusKey%}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-radiuskey' %}"><i class="fa fa-plus"></i> Ajouter une clef radius</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-radiuskey' %}"><i class="fa fa-plus"></i>{% trans " Add a RADIUS key" %}</a>
|
||||
{% acl_end %}
|
||||
{% include "preferences/aff_radiuskey.html" with radiuskey_list=radiuskey_list %}
|
||||
{% include 'preferences/aff_radiuskey.html' with radiuskey_list=radiuskey_list %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -226,7 +230,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<div class="panel panel-default" id="switches">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#collapse_switches">
|
||||
<h4 class="panel-title">
|
||||
<a><i class="fa fa-server"></i> Configuration des Switches</a>
|
||||
<a><i class="fa fa-server"></i>{% trans "Configuration of switches" %}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapse_switches" class="panel-collapse panel-body collapse">
|
||||
|
@ -239,64 +243,64 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Web management, activé si provision automatique</th>
|
||||
<th>{% trans "Web management, activated in case of automatic provision" %}</th>
|
||||
<td>{{ topologieoptions.switchs_web_management }}</td>
|
||||
<th>Rest management, activé si provision auto</th>
|
||||
<th>{% trans "REST management, activated in case of automatic provision" %}</th>
|
||||
<td>{{ topologieoptions.switchs_rest_management }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<h5>{% if topologieoptions.provision_switchs_enabled %}<span class="label label-success">Provision de la config des switchs{% else %}<span class="label label-danger">Provision de la config des switchs{% endif%}</span></h5>
|
||||
<h5>{% if topologieoptions.provision_switchs_enabled %}<span class="label label-success">{% trans "Provision of configuration for switches" %}{% else %}<span class="label label-danger">{% trans "Provision of configuration for switches" %}{% endif%}</span></h5>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Switchs configurés automatiquement</th>
|
||||
<td>{{ topologieoptions.provisioned_switchs|join:", " }} {% if topologieoptions.provisioned_switchs %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span></td>
|
||||
<th>{% trans "Switches with automatic provision" %}</th>
|
||||
<td>{{ topologieoptions.provisioned_switchs|join:", " }} {% if topologieoptions.provisioned_switchs %}<span class="label label-success">{% trans "OK" %}{% else %}<span class="label label-danger">{% trans "Missing" %}{% endif %}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Plage d'ip de management des switchs</th>
|
||||
<td>{{ topologieoptions.switchs_ip_type }} {% if topologieoptions.switchs_ip_type %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span></td>
|
||||
<th>{% trans "IP range for the management of switches" %}</th>
|
||||
<td>{{ topologieoptions.switchs_ip_type }} {% if topologieoptions.switchs_ip_type %}<span class="label label-success">{% trans "OK" %}{% else %}<span class="label label-danger">{% trans "Missing" %}{% endif %}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Serveur des config des switchs</th>
|
||||
<td>{{ topologieoptions.switchs_management_interface }} {% if topologieoptions.switchs_management_interface %} - {{ topologieoptions.switchs_management_interface_ip }} <span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span></td>
|
||||
<th>{% trans "Server for the configuration of switches" %}</th>
|
||||
<td>{{ topologieoptions.switchs_management_interface }} {% if topologieoptions.switchs_management_interface %} - {{ topologieoptions.switchs_management_interface_ip }} <span class="label label-success">{% trans "OK" %}{% else %}<span class="label label-danger">{% trans "Missing" %}{% endif %}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mode de provision des switchs</th>
|
||||
<th>{% trans "Provision of configuration mode for switches" %}</th>
|
||||
<td>{{ topologieoptions.switchs_provision }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mode TFTP</th>
|
||||
<td><span class="label label-success"> OK</span></td>
|
||||
<th>{% trans "TFTP mode" %}</th>
|
||||
<td><span class="label label-success">{% trans "OK" %}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mode SFTP</th>
|
||||
<td>{% if topologieoptions.switchs_management_sftp_creds %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Creds manquants{% endif %}</span></td>
|
||||
<th>{% trans "SFTP mode" %}</th>
|
||||
<td>{% if topologieoptions.switchs_management_sftp_creds %}<span class="label label-success">{% trans "OK" %}{% else %}<span class="label label-danger">{% trans "Missing credentials" %}{% endif %}</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h6>Creds de management des switchs</h6>
|
||||
<h6>{% trans "Switch management credentials" %}</h6>
|
||||
{% can_create SwitchManagementCred%}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-switchmanagementcred' %}"><i class="fa fa-plus"></i> Ajouter un id/mdp de management switch</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-switchmanagementcred' %}"><i class="fa fa-plus"></i>{% trans " Add switch management credentials" %}</a>
|
||||
{% acl_end %}
|
||||
<p>
|
||||
</p>
|
||||
{% if switchmanagementcred_list %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span>
|
||||
{% include "preferences/aff_switchmanagementcred.html" with switchmanagementcred_list=switchmanagementcred_list %}
|
||||
{% if switchmanagementcred_list %}<span class="label label-success">{% trans "OK" %}{% else %}<span class="label label-danger">{% trans "Missing" %}{% endif %}</span>
|
||||
{% include 'preferences/aff_switchmanagementcred.html' with switchmanagementcred_list=switchmanagementcred_list %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" id="radius">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#collapse_radius">
|
||||
<h4 class="panel-title"><a><i class="fa fa-circle"></i> {% trans "Radius preferences" %}</h4></a>
|
||||
<h4 class="panel-title"><a><i class="fa fa-circle"></i> {% trans "RADIUS preferences" %}</h4></a>
|
||||
</div>
|
||||
<div id="collapse_radius" class="panel-collapse panel-body collapse">
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'RadiusOption' %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
{% include "preferences/aff_radiusoptions.html" %}
|
||||
{% include 'preferences/aff_radiusoptions.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -339,14 +343,64 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th>{% trans "Description of the organisation" %}</th>
|
||||
<td>{{ assooptions.description|safe }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "President of the association"%}</th>
|
||||
<td>{{ assooptions.pres_name }}</td>
|
||||
</tr>
|
||||
</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">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#collapse_cotisation">
|
||||
<h4 class="panel-title">
|
||||
<a><i class="fa fa-eur"></i> {% trans "Cotisation's options" %}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapse_cotisation" class="panel-collapse panel-body collapse">
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'CotisationsOption' %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>{% trans "Send voucher by email" %}</th>
|
||||
<td>{{ cotisationsoptions.send_voucher_mail | tick }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "Invoices' template" %}</th>
|
||||
<td>{{ cotisationsoptions.invoice_template }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{% trans "Vouchers' template" %}</th>
|
||||
<td>{{ cotisationsoptions.voucher_template }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel panel-default" id="mail">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#collapse_mail">
|
||||
<h4 class="panel-title">
|
||||
<a><i class="fa fa-comment"></i> Message pour les mails</a>
|
||||
<a><i class="fa fa-comment"></i>{% trans "Message for emails" %}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapse_mail" class="panel-collapse panel-body collapse">
|
||||
|
@ -373,16 +427,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<div class="panel panel-default" id="rappels">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#collapse_rappels">
|
||||
<h4 class="panel-title">
|
||||
<a><i class="fa fa-bell"></i> Options pour le mail de fin d'adhésion</a>
|
||||
<a><i class="fa fa-bell"></i>{% trans "Options for the membership's end email" %}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapse_rappels" class="panel-collapse panel-body collapse">
|
||||
{% can_create preferences.Reminder%}
|
||||
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-reminder' %}"><i class="fa fa-plus"></i> Ajouter un rappel</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-reminder' %}"><i class="fa fa-plus"></i>{% trans " Add a reminder" %}</a>
|
||||
<p></p>
|
||||
{% acl_end %}
|
||||
{% include "preferences/aff_reminder.html" with reminder_list=reminder_list %}
|
||||
{% include 'preferences/aff_reminder.html' with reminder_list=reminder_list %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -399,7 +453,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-service' %}"><i class="fa fa-plus"></i>{% trans " Add a service" %}</a>
|
||||
<p></p>
|
||||
{% acl_end %}
|
||||
{% include "preferences/aff_service.html" with service_list=service_list %}
|
||||
{% include 'preferences/aff_service.html' with service_list=service_list %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -417,14 +471,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'preferences:del-mailcontact' %}"><i class="fa fa-trash"></i>{% trans " Delete one or several addresses" %}</a>
|
||||
<p></p>
|
||||
{% include "preferences/aff_mailcontact.html" with mailcontact_list=mailcontact_list %}
|
||||
{% include 'preferences/aff_mailcontact.html' with mailcontact_list=mailcontact_list %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default" id="social">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#collapse_social">
|
||||
<h4 class="panel-title">
|
||||
<a><i class="fa fa-facebook"></i><i class="fa fa-twitter"></i> Réseaux sociaux</a>
|
||||
<a><i class="fa fa-facebook"></i><i class="fa fa-twitter"></i>{% trans "Social networks" %}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="collapse_social" class="panel-collapse panel-body collapse">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "preferences/sidebar.html" %}
|
||||
{% extends 'preferences/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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "preferences/sidebar.html" %}
|
||||
{% extends 'preferences/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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends 'base.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
|
||||
|
|
|
@ -71,6 +71,11 @@ urlpatterns = [
|
|||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(
|
||||
r'^edit_options/(?P<section>CotisationsOption)$',
|
||||
views.edit_options,
|
||||
name='edit-options'
|
||||
),
|
||||
url(r'^add_service/$', views.add_service, name='add-service'),
|
||||
url(
|
||||
r'^edit_service/(?P<serviceid>[0-9]+)$',
|
||||
|
@ -106,5 +111,20 @@ urlpatterns = [
|
|||
name='edit-switchmanagementcred'
|
||||
),
|
||||
url(r'^del_switchmanagementcred/(?P<switchmanagementcredid>[0-9]+)$', views.del_switchmanagementcred, name='del-switchmanagementcred'),
|
||||
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'^$', views.display_options, name='display-options'),
|
||||
]
|
||||
|
|
|
@ -48,7 +48,9 @@ from .forms import (
|
|||
ServiceForm,
|
||||
ReminderForm,
|
||||
RadiusKeyForm,
|
||||
SwitchManagementCredForm
|
||||
SwitchManagementCredForm,
|
||||
DocumentTemplateForm,
|
||||
DelDocumentTemplateForm
|
||||
)
|
||||
from .models import (
|
||||
Service,
|
||||
|
@ -64,6 +66,8 @@ from .models import (
|
|||
RadiusKey,
|
||||
SwitchManagementCred,
|
||||
RadiusOption,
|
||||
CotisationsOption,
|
||||
DocumentTemplate
|
||||
)
|
||||
from . import models
|
||||
from . import forms
|
||||
|
@ -88,6 +92,8 @@ def display_options(request):
|
|||
radiuskey_list = RadiusKey.objects.all()
|
||||
switchmanagementcred_list = SwitchManagementCred.objects.all()
|
||||
radiusoptions, _ = RadiusOption.objects.get_or_create()
|
||||
cotisationsoptions, _created = CotisationsOption.objects.get_or_create()
|
||||
document_template_list = DocumentTemplate.objects.order_by('name')
|
||||
return form({
|
||||
'useroptions': useroptions,
|
||||
'machineoptions': machineoptions,
|
||||
|
@ -102,6 +108,8 @@ def display_options(request):
|
|||
'radiuskey_list' : radiuskey_list,
|
||||
'switchmanagementcred_list': switchmanagementcred_list,
|
||||
'radiusoptions' : radiusoptions,
|
||||
'cotisationsoptions': cotisationsoptions,
|
||||
'document_template_list': document_template_list,
|
||||
}, 'preferences/display_preferences.html', request)
|
||||
|
||||
|
||||
|
@ -111,7 +119,7 @@ def edit_options(request, section):
|
|||
model = getattr(models, section, None)
|
||||
form_instance = getattr(forms, 'Edit' + section + 'Form', None)
|
||||
if not (model or form_instance):
|
||||
messages.error(request, _("Unknown object"))
|
||||
messages.error(request, _("Unknown object."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
|
||||
options_instance, _created = model.objects.get_or_create()
|
||||
|
@ -186,7 +194,7 @@ def del_service(request, service_instance, **_kwargs):
|
|||
"""Suppression d'un service de la page d'accueil"""
|
||||
if request.method == "POST":
|
||||
service_instance.delete()
|
||||
messages.success(request, "Le service a été détruit")
|
||||
messages.success(request, _("The service was deleted."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'objet': service_instance, 'objet_name': 'service'},
|
||||
|
@ -204,7 +212,7 @@ def add_reminder(request):
|
|||
messages.success(request, _("The reminder was added."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': reminder, 'action_name': _("Add a service")},
|
||||
{'preferenceform': reminder, 'action_name': _("Add a reminder")},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
@ -220,7 +228,7 @@ def edit_reminder(request, reminder_instance, **_kwargs):
|
|||
)
|
||||
if reminder.is_valid():
|
||||
reminder.save()
|
||||
messages.success(request, _("The service was edited."))
|
||||
messages.success(request, _("The reminder was edited."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': reminder, 'action_name': _("Edit")},
|
||||
|
@ -236,7 +244,7 @@ def del_reminder(request, reminder_instance, **_kwargs):
|
|||
"""Destruction d'un reminder"""
|
||||
if request.method == "POST":
|
||||
reminder_instance.delete()
|
||||
messages.success(request, "Le reminder a été détruit")
|
||||
messages.success(request, _("The reminder was deleted."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'objet': reminder_instance, 'objet_name': 'reminder'},
|
||||
|
@ -252,10 +260,10 @@ def add_radiuskey(request):
|
|||
radiuskey = RadiusKeyForm(request.POST or None)
|
||||
if radiuskey.is_valid():
|
||||
radiuskey.save()
|
||||
messages.success(request, "Cette clef a été ajouté")
|
||||
messages.success(request, _("The RADIUS key was added."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': radiuskey, 'action_name': 'Ajouter'},
|
||||
{'preferenceform': radiuskey, 'action_name': _("Add a RADIUS key")},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
@ -266,10 +274,10 @@ def edit_radiuskey(request, radiuskey_instance, **_kwargs):
|
|||
radiuskey = RadiusKeyForm(request.POST or None, instance=radiuskey_instance)
|
||||
if radiuskey.is_valid():
|
||||
radiuskey.save()
|
||||
messages.success(request, "Radiuskey modifié")
|
||||
messages.success(request, _("The RADIUS key was edited."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': radiuskey, 'action_name': 'Editer'},
|
||||
{'preferenceform': radiuskey, 'action_name': _("Edit")},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
@ -282,10 +290,10 @@ def del_radiuskey(request, radiuskey_instance, **_kwargs):
|
|||
if request.method == "POST":
|
||||
try:
|
||||
radiuskey_instance.delete()
|
||||
messages.success(request, "La radiuskey a été détruite")
|
||||
messages.success(request, _("The RADIUS key was deleted."))
|
||||
except ProtectedError:
|
||||
messages.error(request, "Erreur la\
|
||||
clef ne peut être supprimé, elle est affectée à des switchs")
|
||||
messages.error(request, _("The RADIUS key is assigned to at least"
|
||||
" one switch, you can't delete it."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'objet': radiuskey_instance, 'objet_name': 'radiuskey'},
|
||||
|
@ -301,10 +309,10 @@ def add_switchmanagementcred(request):
|
|||
switchmanagementcred = SwitchManagementCredForm(request.POST or None)
|
||||
if switchmanagementcred.is_valid():
|
||||
switchmanagementcred.save()
|
||||
messages.success(request, "Ces creds ont été ajoutés")
|
||||
messages.success(request, _("The switch management credentials were added."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': switchmanagementcred, 'action_name': 'Ajouter'},
|
||||
{'preferenceform': switchmanagementcred, 'action_name': _("Add switch management credentials")},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
@ -315,10 +323,10 @@ def edit_switchmanagementcred(request, switchmanagementcred_instance, **_kwargs)
|
|||
switchmanagementcred = SwitchManagementCredForm(request.POST or None, instance=switchmanagementcred_instance)
|
||||
if switchmanagementcred.is_valid():
|
||||
switchmanagementcred.save()
|
||||
messages.success(request, "Creds de managament modifié")
|
||||
messages.success(request, _("The switch management credentials were edited."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'preferenceform': switchmanagementcred, 'action_name': 'Editer'},
|
||||
{'preferenceform': switchmanagementcred, 'action_name': _("Edit")},
|
||||
'preferences/preferences.html',
|
||||
request
|
||||
)
|
||||
|
@ -331,10 +339,11 @@ def del_switchmanagementcred(request, switchmanagementcred_instance, **_kwargs):
|
|||
if request.method == "POST":
|
||||
try:
|
||||
switchmanagementcred_instance.delete()
|
||||
messages.success(request, "Ces creds ont été détruits")
|
||||
messages.success(request, _("The switch management credentials were deleted."))
|
||||
except ProtectedError:
|
||||
messages.error(request, "Erreur ces\
|
||||
creds ne peuvent être supprimés, ils sont affectés à des switchs")
|
||||
messages.error(request, _("The switch management credentials are"
|
||||
" assigned to at least one switch, you"
|
||||
" can't delete them."))
|
||||
return redirect(reverse('preferences:display-options'))
|
||||
return form(
|
||||
{'objet': switchmanagementcred_instance, 'objet_name': 'switchmanagementcred'},
|
||||
|
@ -404,3 +413,86 @@ def del_mailcontact(request, instances):
|
|||
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('preferences:display-options'))
|
||||
return form({
|
||||
'preferenceform': document_template,
|
||||
'action_name': _("Add"),
|
||||
'title': _("New document template")
|
||||
}, 'preferences/preferences.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('preferences:display-options'))
|
||||
return form({
|
||||
'preferenceform': document_template,
|
||||
'action_name': _("Edit"),
|
||||
'title': _("Edit document template")
|
||||
}, 'preferences/preferences.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('preferences:display-options'))
|
||||
return form({
|
||||
'preferenceform': document_template,
|
||||
'action_name': _("Delete"),
|
||||
'title': _("Delete document template")
|
||||
}, 'preferences/preferences.html', request)
|
||||
|
|
|
@ -82,16 +82,22 @@ class AESEncryptedField(models.CharField):
|
|||
return None
|
||||
try:
|
||||
return decrypt(settings.AES_KEY, binascii.a2b_base64(value)).decode('utf-8')
|
||||
except Exception as e:
|
||||
raise ValueError(value)
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValueError(
|
||||
"Could not decode your field %s, your settings.AES_KEY "
|
||||
"is probably wrong." % self.name
|
||||
)
|
||||
|
||||
def from_db_value(self, value, *args, **kwargs):
|
||||
if value is None:
|
||||
return value
|
||||
try:
|
||||
return decrypt(settings.AES_KEY, binascii.a2b_base64(value)).decode('utf-8')
|
||||
except Exception as e:
|
||||
raise ValueError(value)
|
||||
except UnicodeDecodeError as e:
|
||||
raise ValueError(
|
||||
"Could not decode your field %s, your settings.AES_KEY "
|
||||
"is probably wrong." % self.name
|
||||
)
|
||||
|
||||
def get_prep_value(self, value):
|
||||
if value is None:
|
||||
|
|
|
@ -73,9 +73,9 @@ def smtp_check(local_part):
|
|||
reply_code = srv.getreply()[0]
|
||||
srv.close()
|
||||
if reply_code in [250, 252]:
|
||||
return True, _("This domain is already taken")
|
||||
return True, _("This domain is already taken.")
|
||||
except:
|
||||
return True, _("Smtp unreachable")
|
||||
return True, _("SMTP unreachable.")
|
||||
return False, None
|
||||
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue