mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-09-10 16:23:09 +00:00
Release : 2.6.1
This commit is contained in:
commit
ef4e430eba
240 changed files with 12666 additions and 3917 deletions
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -120,3 +120,33 @@ Don't forget to run migrations, several settings previously in the `preferences`
|
|||
in their own Payment models.
|
||||
|
||||
To have a closer look on how the payments works, please go to the wiki.
|
||||
|
||||
## MR 182: Add role models
|
||||
|
||||
Adds the Role model.
|
||||
You need to ensure that your database character set is utf-8.
|
||||
```sql
|
||||
ALTER DATABASE re2o CHARACTER SET utf8;
|
||||
```
|
||||
|
||||
## MR 247: Fix des comptes mails
|
||||
|
||||
Fix several issues with email accounts, you need to collect the static files.
|
||||
|
||||
```bash
|
||||
./manage.py collectstatic
|
||||
```
|
||||
|
||||
## MR 203 Add custom invoices
|
||||
|
||||
The custom invoices are now stored in database. You need to migrate your database :
|
||||
|
||||
```bash
|
||||
python3 manage.py migrate
|
||||
```
|
||||
|
||||
On some database engines (postgreSQL) you also need to update the id sequences:
|
||||
|
||||
```bash
|
||||
python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell
|
||||
```
|
||||
|
|
|
@ -338,6 +338,7 @@ class OptionalMachineSerializer(NamespacedHMSerializer):
|
|||
class OptionalTopologieSerializer(NamespacedHMSerializer):
|
||||
"""Serialize `preferences.models.OptionalTopologie` objects.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = preferences.OptionalTopologie
|
||||
fields = ('radius_general_policy', 'vlan_decision_ok',
|
||||
|
@ -469,10 +470,10 @@ class SwitchPortSerializer(NamespacedHMSerializer):
|
|||
class Meta:
|
||||
model = topologie.Port
|
||||
fields = ('switch', 'port', 'room', 'machine_interface', 'related',
|
||||
'radius', 'vlan_force', 'details', 'api_url')
|
||||
'custom_profile', 'state', 'details', 'api_url')
|
||||
extra_kwargs = {
|
||||
'related': {'view_name': 'switchport-detail'},
|
||||
'api_url': {'view_name': 'switchport-detail'}
|
||||
'api_url': {'view_name': 'switchport-detail'},
|
||||
}
|
||||
|
||||
|
||||
|
@ -484,6 +485,18 @@ class RoomSerializer(NamespacedHMSerializer):
|
|||
fields = ('name', 'details', 'api_url')
|
||||
|
||||
|
||||
class PortProfileSerializer(NamespacedHMSerializer):
|
||||
vlan_untagged = VlanSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = topologie.PortProfile
|
||||
fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged',
|
||||
'radius_type', 'radius_mode', 'speed', 'mac_limit',
|
||||
'flow_control', 'dhcp_snooping', 'dhcpv6_snooping',
|
||||
'arp_protect', 'ra_guard', 'loop_protect', 'vlan_untagged',
|
||||
'vlan_tagged')
|
||||
|
||||
|
||||
# USERS
|
||||
|
||||
|
||||
|
@ -534,11 +547,20 @@ class AdherentSerializer(NamespacedHMSerializer):
|
|||
fields = ('name', 'surname', 'pseudo', 'email', 'local_email_redirect',
|
||||
'local_email_enabled', 'school', 'shell', 'comment',
|
||||
'state', 'registered', 'telephone', 'room', 'solde',
|
||||
'access', 'end_access', 'uid', 'api_url')
|
||||
'access', 'end_access', 'uid', 'api_url','gid')
|
||||
extra_kwargs = {
|
||||
'shell': {'view_name': 'shell-detail'}
|
||||
}
|
||||
|
||||
class HomeCreationSerializer(NamespacedHMSerializer):
|
||||
"""Serialize 'users.models.User' minimal infos to create home
|
||||
"""
|
||||
uid = serializers.IntegerField(source='uid_number')
|
||||
gid = serializers.IntegerField(source='gid_number')
|
||||
|
||||
class Meta:
|
||||
model = users.User
|
||||
fields = ('pseudo', 'uid', 'gid')
|
||||
|
||||
class ServiceUserSerializer(NamespacedHMSerializer):
|
||||
"""Serialize `users.models.ServiceUser` objects.
|
||||
|
@ -599,7 +621,7 @@ class WhitelistSerializer(NamespacedHMSerializer):
|
|||
class EMailAddressSerializer(NamespacedHMSerializer):
|
||||
"""Serialize `users.models.EMailAddress` objects.
|
||||
"""
|
||||
|
||||
user = serializers.CharField(source='user.pseudo', read_only=True)
|
||||
class Meta:
|
||||
model = users.EMailAddress
|
||||
fields = ('user', 'local_part', 'complete_email_address', 'api_url')
|
||||
|
@ -635,9 +657,42 @@ class LocalEmailUsersSerializer(NamespacedHMSerializer):
|
|||
class Meta:
|
||||
model = users.User
|
||||
fields = ('local_email_enabled', 'local_email_redirect',
|
||||
'email_address')
|
||||
'email_address', 'email')
|
||||
|
||||
|
||||
#Firewall
|
||||
|
||||
class FirewallPortListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = machines.OuverturePort
|
||||
fields = ('begin', 'end', 'protocole', 'io', 'show_port')
|
||||
|
||||
class FirewallOuverturePortListSerializer(serializers.ModelSerializer):
|
||||
tcp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
|
||||
udp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
|
||||
tcp_ports_out = FirewallPortListSerializer(many=True, read_only=True)
|
||||
udp_ports_out = FirewallPortListSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = machines.OuverturePortList
|
||||
fields = ('tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', 'udp_ports_out')
|
||||
|
||||
class SubnetPortsOpenSerializer(serializers.ModelSerializer):
|
||||
ouverture_ports = FirewallOuverturePortListSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = machines.IpType
|
||||
fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'complete_prefixv6', 'ouverture_ports')
|
||||
|
||||
class InterfacePortsOpenSerializer(serializers.ModelSerializer):
|
||||
port_lists = FirewallOuverturePortListSerializer(read_only=True, many=True)
|
||||
ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True)
|
||||
ipv6 = Ipv6ListSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = machines.Interface
|
||||
fields = ('port_lists', 'ipv4', 'ipv6')
|
||||
|
||||
# DHCP
|
||||
|
||||
|
||||
|
@ -679,7 +734,7 @@ class NSRecordSerializer(NsSerializer):
|
|||
"""Serialize `machines.models.Ns` objects with the data needed to
|
||||
generate a NS DNS record.
|
||||
"""
|
||||
target = serializers.CharField(source='ns.name', read_only=True)
|
||||
target = serializers.CharField(source='ns', read_only=True)
|
||||
|
||||
class Meta(NsSerializer.Meta):
|
||||
fields = ('target',)
|
||||
|
@ -689,7 +744,7 @@ class MXRecordSerializer(MxSerializer):
|
|||
"""Serialize `machines.models.Mx` objects with the data needed to
|
||||
generate a MX DNS record.
|
||||
"""
|
||||
target = serializers.CharField(source='name.name', read_only=True)
|
||||
target = serializers.CharField(source='name', read_only=True)
|
||||
|
||||
class Meta(MxSerializer.Meta):
|
||||
fields = ('target', 'priority')
|
||||
|
@ -761,13 +816,12 @@ class CNAMERecordSerializer(serializers.ModelSerializer):
|
|||
"""Serialize `machines.models.Domain` objects with the data needed to
|
||||
generate a CNAME DNS record.
|
||||
"""
|
||||
alias = serializers.CharField(source='cname.name', read_only=True)
|
||||
alias = serializers.CharField(source='cname', read_only=True)
|
||||
hostname = serializers.CharField(source='name', read_only=True)
|
||||
extension = serializers.CharField(source='extension.name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = machines.Domain
|
||||
fields = ('alias', 'hostname', 'extension')
|
||||
fields = ('alias', 'hostname')
|
||||
|
||||
|
||||
class DNSZonesSerializer(serializers.ModelSerializer):
|
||||
|
@ -792,6 +846,25 @@ class DNSZonesSerializer(serializers.ModelSerializer):
|
|||
'aaaa_records', 'cname_records', 'sshfp_records')
|
||||
|
||||
|
||||
class DNSReverseZonesSerializer(serializers.ModelSerializer):
|
||||
"""Serialize the data about DNS Zones.
|
||||
"""
|
||||
soa = SOARecordSerializer(source='extension.soa')
|
||||
extension = serializers.CharField(source='extension.name', read_only=True)
|
||||
cidrs = serializers.ListField(child=serializers.CharField(), source='ip_set_cidrs_as_str', read_only=True)
|
||||
ns_records = NSRecordSerializer(many=True, source='extension.ns_set')
|
||||
mx_records = MXRecordSerializer(many=True, source='extension.mx_set')
|
||||
txt_records = TXTRecordSerializer(many=True, source='extension.txt_set')
|
||||
ptr_records = ARecordSerializer(many=True, source='get_associated_ptr_records')
|
||||
ptr_v6_records = AAAARecordSerializer(many=True, source='get_associated_ptr_v6_records')
|
||||
|
||||
|
||||
class Meta:
|
||||
model = machines.IpType
|
||||
fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records',
|
||||
'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs',
|
||||
'prefix_v6', 'prefix_v6_length')
|
||||
|
||||
# MAILING
|
||||
|
||||
|
||||
|
@ -799,7 +872,7 @@ class MailingMemberSerializer(UserSerializer):
|
|||
"""Serialize the data about a mailing member.
|
||||
"""
|
||||
class Meta(UserSerializer.Meta):
|
||||
fields = ('name', 'pseudo', 'email')
|
||||
fields = ('name', 'pseudo', 'get_mail')
|
||||
|
||||
class MailingSerializer(ClubSerializer):
|
||||
"""Serialize the data about a mailing.
|
||||
|
|
|
@ -81,10 +81,12 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet)
|
|||
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
|
||||
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
|
||||
router.register_viewset(r'topologie/building', views.BuildingViewSet)
|
||||
router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
|
||||
router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
|
||||
router.register_viewset(r'topologie/room', views.RoomViewSet)
|
||||
router.register(r'topologie/portprofile', views.PortProfileViewSet)
|
||||
# USERS
|
||||
router.register_viewset(r'users/user', views.UserViewSet)
|
||||
router.register_viewset(r'users/homecreation', views.HomeCreationViewSet)
|
||||
router.register_viewset(r'users/club', views.ClubViewSet)
|
||||
router.register_viewset(r'users/adherent', views.AdherentViewSet)
|
||||
router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet)
|
||||
|
@ -100,8 +102,12 @@ router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name=
|
|||
router.register_view(r'dhcp/hostmacip', views.HostMacIpView),
|
||||
# LOCAL EMAILS
|
||||
router.register_view(r'localemail/users', views.LocalEmailUsersView),
|
||||
# Firewall
|
||||
router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView),
|
||||
router.register_view(r'firewall/interface-ports', views.InterfacePortsOpenView),
|
||||
# DNS
|
||||
router.register_view(r'dns/zones', views.DNSZonesView),
|
||||
router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView),
|
||||
# MAILING
|
||||
router.register_view(r'mailing/standard', views.StandardMailingView),
|
||||
router.register_view(r'mailing/club', views.ClubMailingView),
|
||||
|
|
30
api/views.py
30
api/views.py
|
@ -403,6 +403,12 @@ class RoomViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
serializer_class = serializers.RoomSerializer
|
||||
|
||||
|
||||
class PortProfileViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""Exposes list and details of `topologie.models.PortProfile` objects.
|
||||
"""
|
||||
queryset = topologie.PortProfile.objects.all()
|
||||
serializer_class = serializers.PortProfileSerializer
|
||||
|
||||
# USER
|
||||
|
||||
|
||||
|
@ -412,6 +418,11 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
queryset = users.User.objects.all()
|
||||
serializer_class = serializers.UserSerializer
|
||||
|
||||
class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""Exposes infos of `users.models.Users` objects to create homes.
|
||||
"""
|
||||
queryset = users.User.objects.all()
|
||||
serializer_class = serializers.HomeCreationSerializer
|
||||
|
||||
class ClubViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""Exposes list and details of `users.models.Club` objects.
|
||||
|
@ -532,6 +543,16 @@ class HostMacIpView(generics.ListAPIView):
|
|||
serializer_class = serializers.HostMacIpSerializer
|
||||
|
||||
|
||||
#Firewall
|
||||
|
||||
class SubnetPortsOpenView(generics.ListAPIView):
|
||||
queryset = machines.IpType.objects.all()
|
||||
serializer_class = serializers.SubnetPortsOpenSerializer
|
||||
|
||||
class InterfacePortsOpenView(generics.ListAPIView):
|
||||
queryset = machines.Interface.objects.filter(port_lists__isnull=False).distinct()
|
||||
serializer_class = serializers.InterfacePortsOpenSerializer
|
||||
|
||||
# DNS
|
||||
|
||||
|
||||
|
@ -549,6 +570,15 @@ class DNSZonesView(generics.ListAPIView):
|
|||
.all())
|
||||
serializer_class = serializers.DNSZonesSerializer
|
||||
|
||||
class DNSReverseZonesView(generics.ListAPIView):
|
||||
"""Exposes the detailed information about each extension (hostnames,
|
||||
IPs, DNS records, etc.) in order to build the DNS zone files.
|
||||
"""
|
||||
queryset = (machines.IpType.objects.all())
|
||||
serializer_class = serializers.DNSReverseZonesSerializer
|
||||
|
||||
|
||||
|
||||
|
||||
# MAILING
|
||||
|
||||
|
|
|
@ -42,4 +42,5 @@ def can_view(user):
|
|||
if can:
|
||||
return can, None
|
||||
else:
|
||||
return can, _("You don't have the rights to see this application.")
|
||||
return can, _("You don't have the right to view this application.")
|
||||
|
||||
|
|
|
@ -30,6 +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
|
||||
|
||||
|
||||
class FactureAdmin(VersionAdmin):
|
||||
|
@ -37,6 +38,11 @@ class FactureAdmin(VersionAdmin):
|
|||
pass
|
||||
|
||||
|
||||
class CustomInvoiceAdmin(VersionAdmin):
|
||||
"""Admin class for custom invoices."""
|
||||
pass
|
||||
|
||||
|
||||
class VenteAdmin(VersionAdmin):
|
||||
"""Class admin d'une vente, tous les champs (facture related)"""
|
||||
pass
|
||||
|
@ -69,3 +75,4 @@ admin.site.register(Banque, BanqueAdmin)
|
|||
admin.site.register(Paiement, PaiementAdmin)
|
||||
admin.site.register(Vente, VenteAdmin)
|
||||
admin.site.register(Cotisation, CotisationAdmin)
|
||||
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
|
||||
|
|
|
@ -40,13 +40,13 @@ from django import forms
|
|||
from django.db.models import Q
|
||||
from django.forms import ModelForm, Form
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _l
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
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
|
||||
from .models import Article, Paiement, Facture, Banque, CustomInvoice
|
||||
from .payment_methods import balance
|
||||
|
||||
|
||||
|
@ -84,71 +84,36 @@ class FactureForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
|
|||
return cleaned_data
|
||||
|
||||
|
||||
class SelectUserArticleForm(FormRevMixin, Form):
|
||||
class SelectArticleForm(FormRevMixin, Form):
|
||||
"""
|
||||
Form used to select an article during the creation of an invoice for a
|
||||
member.
|
||||
"""
|
||||
article = forms.ModelChoiceField(
|
||||
queryset=Article.objects.filter(
|
||||
Q(type_user='All') | Q(type_user='Adherent')
|
||||
),
|
||||
label=_l("Article"),
|
||||
queryset=Article.objects.none(),
|
||||
label=_("Article"),
|
||||
required=True
|
||||
)
|
||||
quantity = forms.IntegerField(
|
||||
label=_l("Quantity"),
|
||||
label=_("Quantity"),
|
||||
validators=[MinValueValidator(1)],
|
||||
required=True
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user = kwargs.pop('user')
|
||||
super(SelectUserArticleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(user)
|
||||
target_user = kwargs.pop('target_user')
|
||||
super(SelectArticleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(user, target_user)
|
||||
|
||||
|
||||
class SelectClubArticleForm(Form):
|
||||
class CustomInvoiceForm(FormRevMixin, ModelForm):
|
||||
"""
|
||||
Form used to select an article during the creation of an invoice for a
|
||||
club.
|
||||
Form used to create a custom invoice.
|
||||
"""
|
||||
article = forms.ModelChoiceField(
|
||||
queryset=Article.objects.filter(
|
||||
Q(type_user='All') | Q(type_user='Club')
|
||||
),
|
||||
label=_l("Article"),
|
||||
required=True
|
||||
)
|
||||
quantity = forms.IntegerField(
|
||||
label=_l("Quantity"),
|
||||
validators=[MinValueValidator(1)],
|
||||
required=True
|
||||
)
|
||||
|
||||
def __init__(self, user, *args, **kwargs):
|
||||
super(SelectClubArticleForm, self).__init__(*args, **kwargs)
|
||||
self.fields['article'].queryset = Article.find_allowed_articles(user)
|
||||
|
||||
|
||||
# TODO : change Facture to Invoice
|
||||
class NewFactureFormPdf(Form):
|
||||
"""
|
||||
Form used to create a custom PDF invoice.
|
||||
"""
|
||||
paid = forms.BooleanField(label=_l("Paid"), required=False)
|
||||
# TODO : change dest field to recipient
|
||||
dest = forms.CharField(
|
||||
required=True,
|
||||
max_length=255,
|
||||
label=_l("Recipient")
|
||||
)
|
||||
# TODO : change chambre field to address
|
||||
chambre = forms.CharField(
|
||||
required=False,
|
||||
max_length=10,
|
||||
label=_l("Address")
|
||||
)
|
||||
class Meta:
|
||||
model = CustomInvoice
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ArticleForm(FormRevMixin, ModelForm):
|
||||
|
@ -172,7 +137,7 @@ class DelArticleForm(FormRevMixin, Form):
|
|||
"""
|
||||
articles = forms.ModelMultipleChoiceField(
|
||||
queryset=Article.objects.none(),
|
||||
label=_l("Existing articles"),
|
||||
label=_("Available articles"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -212,7 +177,7 @@ class DelPaiementForm(FormRevMixin, Form):
|
|||
# TODO : change paiement to payment
|
||||
paiements = forms.ModelMultipleChoiceField(
|
||||
queryset=Paiement.objects.none(),
|
||||
label=_l("Existing payment method"),
|
||||
label=_("Available payment methods"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -250,7 +215,7 @@ class DelBanqueForm(FormRevMixin, Form):
|
|||
# TODO : change banque to bank
|
||||
banques = forms.ModelMultipleChoiceField(
|
||||
queryset=Banque.objects.none(),
|
||||
label=_l("Existing banks"),
|
||||
label=_("Available banks"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -269,21 +234,21 @@ class RechargeForm(FormRevMixin, Form):
|
|||
Form used to refill a user's balance
|
||||
"""
|
||||
value = forms.FloatField(
|
||||
label=_l("Amount"),
|
||||
label=_("Amount"),
|
||||
min_value=0.01,
|
||||
validators=[]
|
||||
)
|
||||
payment = forms.ModelChoiceField(
|
||||
queryset=Paiement.objects.none(),
|
||||
label=_l("Payment method")
|
||||
label=_("Payment method")
|
||||
)
|
||||
|
||||
def __init__(self, *args, user=None, **kwargs):
|
||||
def __init__(self, *args, user=None, user_source=None, **kwargs):
|
||||
self.user = user
|
||||
super(RechargeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['payment'].empty_label = \
|
||||
_("Select a payment method")
|
||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(user)
|
||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(user_source).exclude(is_balance=True)
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
|
@ -301,3 +266,4 @@ class RechargeForm(FormRevMixin, Form):
|
|||
}
|
||||
)
|
||||
return self.cleaned_data
|
||||
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
20
cotisations/migrations/0031_comnpaypayment_production.py
Normal file
20
cotisations/migrations/0031_comnpaypayment_production.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-11 23:03
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0030_custom_payment'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='comnpaypayment',
|
||||
name='production',
|
||||
field=models.BooleanField(default=True, verbose_name='Production mode enabled (production url, instead of homologation)'),
|
||||
),
|
||||
]
|
104
cotisations/migrations/0032_custom_invoice.py
Normal file
104
cotisations/migrations/0032_custom_invoice.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-07-21 20:01
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.contrib.auth.management import create_permissions
|
||||
import re2o.field_permissions
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
def reattribute_ids(apps, schema_editor):
|
||||
Facture = apps.get_model('cotisations', 'Facture')
|
||||
BaseInvoice = apps.get_model('cotisations', 'BaseInvoice')
|
||||
|
||||
for f in Facture.objects.all():
|
||||
base = BaseInvoice.objects.create(id=f.pk)
|
||||
base.date = f.date
|
||||
base.save()
|
||||
f.baseinvoice_ptr = base
|
||||
f.save()
|
||||
|
||||
|
||||
def update_rights(apps, schema_editor):
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
|
||||
# creates needed permissions
|
||||
app = apps.get_app_config('cotisations')
|
||||
app.models_module = True
|
||||
create_permissions(app)
|
||||
app.models_module = False
|
||||
|
||||
former = Permission.objects.get(codename='change_facture_pdf')
|
||||
new_1 = Permission.objects.get(codename='add_custominvoice')
|
||||
new_2 = Permission.objects.get(codename='change_custominvoice')
|
||||
new_3 = Permission.objects.get(codename='view_custominvoice')
|
||||
new_4 = Permission.objects.get(codename='delete_custominvoice')
|
||||
for group in former.group_set.all():
|
||||
group.permissions.remove(former)
|
||||
group.permissions.add(new_1)
|
||||
group.permissions.add(new_2)
|
||||
group.permissions.add(new_3)
|
||||
group.permissions.add(new_4)
|
||||
group.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0031_comnpaypayment_production'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BaseInvoice',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
|
||||
],
|
||||
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, re2o.field_permissions.FieldPermissionModelMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomInvoice',
|
||||
fields=[
|
||||
('baseinvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice')),
|
||||
('recipient', models.CharField(max_length=255, verbose_name='Recipient')),
|
||||
('payment', models.CharField(max_length=255, verbose_name='Payment type')),
|
||||
('address', models.CharField(max_length=255, verbose_name='Address')),
|
||||
('paid', models.BooleanField(verbose_name='Paid')),
|
||||
],
|
||||
bases=('cotisations.baseinvoice',),
|
||||
options={'permissions': (('view_custominvoice', 'Can view a custom invoice'),)},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='facture',
|
||||
name='baseinvoice_ptr',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='cotisations.BaseInvoice', null=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.RunPython(reattribute_ids),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='facture',
|
||||
field=models.ForeignKey(on_delete=models.CASCADE, verbose_name='Invoice', to='cotisations.BaseInvoice')
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='facture',
|
||||
name='id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='facture',
|
||||
name='date',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='facture',
|
||||
name='baseinvoice_ptr',
|
||||
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.BaseInvoice'),
|
||||
),
|
||||
migrations.RunPython(update_rights),
|
||||
migrations.AlterModelOptions(
|
||||
name='facture',
|
||||
options={'permissions': (('change_facture_control', 'Can change the "controlled" state'), ('view_facture', "Can see an invoice's details"), ('change_all_facture', 'Can edit all the previous invoices')), 'verbose_name': 'Invoice', 'verbose_name_plural': 'Invoices'},
|
||||
),
|
||||
]
|
181
cotisations/migrations/0033_auto_20180818_1319.py
Normal file
181
cotisations/migrations/0033_auto_20180818_1319.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-18 11:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import cotisations.validators
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import re2o.aes_field
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cotisations', '0032_custom_invoice'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='article',
|
||||
options={'permissions': (('view_article', 'Can view an article object'), ('buy_every_article', 'Can buy every article')), 'verbose_name': 'article', 'verbose_name_plural': 'articles'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='balancepayment',
|
||||
options={'verbose_name': 'user balance'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='banque',
|
||||
options={'permissions': (('view_banque', 'Can view a bank object'),), 'verbose_name': 'bank', 'verbose_name_plural': 'banks'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='cotisation',
|
||||
options={'permissions': (('view_cotisation', 'Can view a subscription object'), ('change_all_cotisation', 'Can edit the previous subscriptions')), 'verbose_name': 'subscription', 'verbose_name_plural': 'subscriptions'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='custominvoice',
|
||||
options={'permissions': (('view_custominvoice', 'Can view a custom invoice object'),)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='facture',
|
||||
options={'permissions': (('change_facture_control', 'Can edit the "controlled" state'), ('view_facture', 'Can view an invoice object'), ('change_all_facture', 'Can edit all the previous invoices')), 'verbose_name': 'invoice', 'verbose_name_plural': 'invoices'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='paiement',
|
||||
options={'permissions': (('view_paiement', 'Can view a payment method object'), ('use_every_payment', 'Can use every payment method')), 'verbose_name': 'payment method', 'verbose_name_plural': 'payment methods'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vente',
|
||||
options={'permissions': (('view_vente', 'Can view a purchase object'), ('change_all_vente', 'Can edit all the previous purchases')), 'verbose_name': 'purchase', 'verbose_name_plural': 'purchases'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='available_for_everyone',
|
||||
field=models.BooleanField(default=False, verbose_name='is available for every user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='duration',
|
||||
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration (in months)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255, verbose_name='designation'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='prix',
|
||||
field=models.DecimalField(decimal_places=2, max_digits=5, verbose_name='unit price'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='type_cotisation',
|
||||
field=models.CharField(blank=True, choices=[('Connexion', 'Connection'), ('Adhesion', 'Membership'), ('All', 'Both of them')], default=None, max_length=255, null=True, verbose_name='subscription type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='type_user',
|
||||
field=models.CharField(choices=[('Adherent', 'Member'), ('Club', 'Club'), ('All', 'Both of them')], default='All', max_length=255, verbose_name='type of users concerned'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='banque',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='comnpaypayment',
|
||||
name='payment_credential',
|
||||
field=models.CharField(blank=True, default='', max_length=255, verbose_name='ComNpay VAT Number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='comnpaypayment',
|
||||
name='payment_pass',
|
||||
field=re2o.aes_field.AESEncryptedField(blank=True, max_length=255, null=True, verbose_name='ComNpay secret key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='comnpaypayment',
|
||||
name='production',
|
||||
field=models.BooleanField(default=True, verbose_name='Production mode enabled (production URL, instead of homologation)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cotisation',
|
||||
name='date_end',
|
||||
field=models.DateTimeField(verbose_name='end date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cotisation',
|
||||
name='date_start',
|
||||
field=models.DateTimeField(verbose_name='start date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cotisation',
|
||||
name='type_cotisation',
|
||||
field=models.CharField(choices=[('Connexion', 'Connection'), ('Adhesion', 'Membership'), ('All', 'Both of them')], default='All', max_length=255, verbose_name='subscription type'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='cotisation',
|
||||
name='vente',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='cotisations.Vente', verbose_name='purchase'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='facture',
|
||||
name='cheque',
|
||||
field=models.CharField(blank=True, max_length=255, verbose_name='cheque number'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='facture',
|
||||
name='control',
|
||||
field=models.BooleanField(default=False, verbose_name='controlled'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='facture',
|
||||
name='valid',
|
||||
field=models.BooleanField(default=True, verbose_name='validated'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='paiement',
|
||||
name='available_for_everyone',
|
||||
field=models.BooleanField(default=False, verbose_name='is available for every user'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='paiement',
|
||||
name='is_balance',
|
||||
field=models.BooleanField(default=False, editable=False, help_text='There should be only one balance payment method.', validators=[cotisations.validators.check_no_balance], verbose_name='is user balance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='paiement',
|
||||
name='moyen',
|
||||
field=models.CharField(max_length=255, verbose_name='method'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='duration',
|
||||
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='duration (in months)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='facture',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cotisations.BaseInvoice', verbose_name='invoice'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255, verbose_name='article'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='number',
|
||||
field=models.IntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='amount'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='prix',
|
||||
field=models.DecimalField(decimal_places=2, max_digits=5, verbose_name='price'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vente',
|
||||
name='type_cotisation',
|
||||
field=models.CharField(blank=True, choices=[('Connexion', 'Connection'), ('Adhesion', 'Membership'), ('All', 'Both of them')], max_length=255, null=True, verbose_name='subscription type'),
|
||||
),
|
||||
]
|
|
@ -41,8 +41,7 @@ from django.dispatch import receiver
|
|||
from django.forms import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _l
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib import messages
|
||||
|
@ -55,80 +54,11 @@ from cotisations.utils import find_payment_method
|
|||
from cotisations.validators import check_no_balance
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||
"""
|
||||
The model for an invoice. It reprensents the fact that a user paid for
|
||||
something (it can be multiple article paid at once).
|
||||
|
||||
An invoice is linked to :
|
||||
* one or more purchases (one for each article sold that time)
|
||||
* a user (the one who bought those articles)
|
||||
* a payment method (the one used by the user)
|
||||
* (if applicable) a bank
|
||||
* (if applicable) a cheque number.
|
||||
Every invoice is dated throught the 'date' value.
|
||||
An invoice has a 'controlled' value (default : False) which means that
|
||||
someone with high enough rights has controlled that invoice and taken it
|
||||
into account. It also has a 'valid' value (default : True) which means
|
||||
that someone with high enough rights has decided that this invoice was not
|
||||
valid (thus it's like the user never paid for his articles). It may be
|
||||
necessary in case of non-payment.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||
# TODO : change paiement to payment
|
||||
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
|
||||
# TODO : change banque to bank
|
||||
banque = models.ForeignKey(
|
||||
'Banque',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
# TODO : maybe change to cheque nummber because not evident
|
||||
cheque = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name=_l("Cheque number")
|
||||
)
|
||||
class BaseInvoice(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||
date = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name=_l("Date")
|
||||
verbose_name=_("Date")
|
||||
)
|
||||
# TODO : change name to validity for clarity
|
||||
valid = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_l("Validated")
|
||||
)
|
||||
# TODO : changed name to controlled for clarity
|
||||
control = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_l("Controlled")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
permissions = (
|
||||
# TODO : change facture to invoice
|
||||
('change_facture_control',
|
||||
_l("Can change the \"controlled\" state")),
|
||||
# TODO : seems more likely to be call create_facture_pdf
|
||||
# or create_invoice_pdf
|
||||
('change_facture_pdf',
|
||||
_l("Can create a custom PDF invoice")),
|
||||
('view_facture',
|
||||
_l("Can see an invoice's details")),
|
||||
('change_all_facture',
|
||||
_l("Can edit all the previous invoices")),
|
||||
)
|
||||
verbose_name = _l("Invoice")
|
||||
verbose_name_plural = _l("Invoices")
|
||||
|
||||
def linked_objects(self):
|
||||
"""Return linked objects : machine and domain.
|
||||
Usefull in history display"""
|
||||
return self.vente_set.all()
|
||||
|
||||
# TODO : change prix to price
|
||||
def prix(self):
|
||||
|
@ -167,6 +97,74 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
).values_list('name', flat=True))
|
||||
return name
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
class Facture(BaseInvoice):
|
||||
"""
|
||||
The model for an invoice. It reprensents the fact that a user paid for
|
||||
something (it can be multiple article paid at once).
|
||||
|
||||
An invoice is linked to :
|
||||
* one or more purchases (one for each article sold that time)
|
||||
* a user (the one who bought those articles)
|
||||
* a payment method (the one used by the user)
|
||||
* (if applicable) a bank
|
||||
* (if applicable) a cheque number.
|
||||
Every invoice is dated throught the 'date' value.
|
||||
An invoice has a 'controlled' value (default : False) which means that
|
||||
someone with high enough rights has controlled that invoice and taken it
|
||||
into account. It also has a 'valid' value (default : True) which means
|
||||
that someone with high enough rights has decided that this invoice was not
|
||||
valid (thus it's like the user never paid for his articles). It may be
|
||||
necessary in case of non-payment.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
||||
# TODO : change paiement to payment
|
||||
paiement = models.ForeignKey('Paiement', on_delete=models.PROTECT)
|
||||
# TODO : change banque to bank
|
||||
banque = models.ForeignKey(
|
||||
'Banque',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
# TODO : maybe change to cheque nummber because not evident
|
||||
cheque = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name=_("cheque number")
|
||||
)
|
||||
# TODO : change name to validity for clarity
|
||||
valid = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("validated")
|
||||
)
|
||||
# TODO : changed name to controlled for clarity
|
||||
control = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("controlled")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = False
|
||||
permissions = (
|
||||
# TODO : change facture to invoice
|
||||
('change_facture_control',
|
||||
_("Can edit the \"controlled\" state")),
|
||||
('view_facture',
|
||||
_("Can view an invoice object")),
|
||||
('change_all_facture',
|
||||
_("Can edit all the previous invoices")),
|
||||
)
|
||||
verbose_name = _("invoice")
|
||||
verbose_name_plural = _("invoices")
|
||||
|
||||
def linked_objects(self):
|
||||
"""Return linked objects : machine and domain.
|
||||
Usefull in history display"""
|
||||
return self.vente_set.all()
|
||||
|
||||
def can_edit(self, user_request, *args, **kwargs):
|
||||
if not user_request.has_perm('cotisations.change_facture'):
|
||||
return False, _("You don't have the right to edit an invoice.")
|
||||
|
@ -196,7 +194,7 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
def can_view(self, user_request, *_args, **_kwargs):
|
||||
if not user_request.has_perm('cotisations.view_facture') and \
|
||||
self.user != user_request:
|
||||
return False, _("You don't have the right to see someone else's "
|
||||
return False, _("You don't have the right to view someone else's "
|
||||
"invoices history.")
|
||||
elif not self.valid:
|
||||
return False, _("The invoice has been invalidated.")
|
||||
|
@ -212,14 +210,6 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
_("You don't have the right to edit the \"controlled\" state.")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def can_change_pdf(user_request, *_args, **_kwargs):
|
||||
""" Returns True if the user can change this invoice """
|
||||
return (
|
||||
user_request.has_perm('cotisations.change_facture_pdf'),
|
||||
_("You don't have the right to edit an invoice.")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def can_create(user_request, *_args, **_kwargs):
|
||||
"""Check if a user can create an invoice.
|
||||
|
@ -231,8 +221,8 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
|||
if user_request.has_perm('cotisations.add_facture'):
|
||||
return True, None
|
||||
if len(Paiement.find_allowed_payments(user_request)) <= 0:
|
||||
return False, _("There are no payment types which you can use.")
|
||||
if len(Article.find_allowed_articles(user_request)) <= 0:
|
||||
return False, _("There are no payment method which you can use.")
|
||||
if len(Article.find_allowed_articles(user_request, user_request)) <= 0:
|
||||
return False, _("There are no article that you can buy.")
|
||||
return True, None
|
||||
|
||||
|
@ -265,6 +255,28 @@ def facture_post_delete(**kwargs):
|
|||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
class CustomInvoice(BaseInvoice):
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_custominvoice', _("Can view a custom invoice object")),
|
||||
)
|
||||
recipient = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Recipient")
|
||||
)
|
||||
payment = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Payment type")
|
||||
)
|
||||
address = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("Address")
|
||||
)
|
||||
paid = models.BooleanField(
|
||||
verbose_name=_("Paid")
|
||||
)
|
||||
|
||||
|
||||
# TODO : change Vente to Purchase
|
||||
class Vente(RevMixin, AclMixin, models.Model):
|
||||
"""
|
||||
|
@ -281,38 +293,38 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
|
||||
# TODO : change this to English
|
||||
COTISATION_TYPE = (
|
||||
('Connexion', _l("Connexion")),
|
||||
('Adhesion', _l("Membership")),
|
||||
('All', _l("Both of them")),
|
||||
('Connexion', _("Connection")),
|
||||
('Adhesion', _("Membership")),
|
||||
('All', _("Both of them")),
|
||||
)
|
||||
|
||||
# TODO : change facture to invoice
|
||||
facture = models.ForeignKey(
|
||||
'Facture',
|
||||
'BaseInvoice',
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_l("Invoice")
|
||||
verbose_name=_("invoice")
|
||||
)
|
||||
# TODO : change number to amount for clarity
|
||||
number = models.IntegerField(
|
||||
validators=[MinValueValidator(1)],
|
||||
verbose_name=_l("Amount")
|
||||
verbose_name=_("amount")
|
||||
)
|
||||
# TODO : change this field for a ForeinKey to Article
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Article")
|
||||
verbose_name=_("article")
|
||||
)
|
||||
# TODO : change prix to price
|
||||
# TODO : this field is not needed if you use Article ForeignKey
|
||||
prix = models.DecimalField(
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
verbose_name=_l("Price"))
|
||||
verbose_name=_("price"))
|
||||
# TODO : this field is not needed if you use Article ForeignKey
|
||||
duration = models.PositiveIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name=_l("Duration (in whole month)")
|
||||
verbose_name=_("duration (in months)")
|
||||
)
|
||||
# TODO : this field is not needed if you use Article ForeignKey
|
||||
type_cotisation = models.CharField(
|
||||
|
@ -320,16 +332,16 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
blank=True,
|
||||
null=True,
|
||||
max_length=255,
|
||||
verbose_name=_l("Type of cotisation")
|
||||
verbose_name=_("subscription type")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_vente', _l("Can see a purchase's details")),
|
||||
('change_all_vente', _l("Can edit all the previous purchases")),
|
||||
('view_vente', _("Can view a purchase object")),
|
||||
('change_all_vente', _("Can edit all the previous purchases")),
|
||||
)
|
||||
verbose_name = _l("Purchase")
|
||||
verbose_name_plural = _l("Purchases")
|
||||
verbose_name = _("purchase")
|
||||
verbose_name_plural = _("purchases")
|
||||
|
||||
# TODO : change prix_total to total_price
|
||||
def prix_total(self):
|
||||
|
@ -355,6 +367,10 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
cotisation_type defined (which means the article sold represents
|
||||
a cotisation)
|
||||
"""
|
||||
try:
|
||||
invoice = self.facture.facture
|
||||
except Facture.DoesNotExist:
|
||||
return
|
||||
if not hasattr(self, 'cotisation') and self.type_cotisation:
|
||||
cotisation = Cotisation(vente=self)
|
||||
cotisation.type_cotisation = self.type_cotisation
|
||||
|
@ -362,7 +378,7 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
end_cotisation = Cotisation.objects.filter(
|
||||
vente__in=Vente.objects.filter(
|
||||
facture__in=Facture.objects.filter(
|
||||
user=self.facture.user
|
||||
user=invoice.user
|
||||
).exclude(valid=False))
|
||||
).filter(
|
||||
Q(type_cotisation='All') |
|
||||
|
@ -371,9 +387,9 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
date_start__lt=date_start
|
||||
).aggregate(Max('date_end'))['date_end__max']
|
||||
elif self.type_cotisation == "Adhesion":
|
||||
end_cotisation = self.facture.user.end_adhesion()
|
||||
end_cotisation = invoice.user.end_adhesion()
|
||||
else:
|
||||
end_cotisation = self.facture.user.end_connexion()
|
||||
end_cotisation = invoice.user.end_connexion()
|
||||
date_start = date_start or timezone.now()
|
||||
end_cotisation = end_cotisation or date_start
|
||||
date_max = max(end_cotisation, date_start)
|
||||
|
@ -392,7 +408,7 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
# Checking that if a cotisation is specified, there is also a duration
|
||||
if self.type_cotisation and not self.duration:
|
||||
raise ValidationError(
|
||||
_("A cotisation should always have a duration.")
|
||||
_("Duration must be specified for a subscription.")
|
||||
)
|
||||
self.update_cotisation()
|
||||
super(Vente, self).save(*args, **kwargs)
|
||||
|
@ -428,7 +444,7 @@ class Vente(RevMixin, AclMixin, models.Model):
|
|||
def can_view(self, user_request, *_args, **_kwargs):
|
||||
if (not user_request.has_perm('cotisations.view_vente') and
|
||||
self.facture.user != user_request):
|
||||
return False, _("You don't have the right to see someone "
|
||||
return False, _("You don't have the right to view someone "
|
||||
"else's purchase history.")
|
||||
else:
|
||||
return True, None
|
||||
|
@ -445,6 +461,10 @@ def vente_post_save(**kwargs):
|
|||
LDAP user when a purchase has been saved.
|
||||
"""
|
||||
purchase = kwargs['instance']
|
||||
try:
|
||||
purchase.facture.facture
|
||||
except Facture.DoesNotExist:
|
||||
return
|
||||
if hasattr(purchase, 'cotisation'):
|
||||
purchase.cotisation.vente = purchase
|
||||
purchase.cotisation.save()
|
||||
|
@ -462,8 +482,12 @@ def vente_post_delete(**kwargs):
|
|||
Synchronise the LDAP user after a purchase has been deleted.
|
||||
"""
|
||||
purchase = kwargs['instance']
|
||||
try:
|
||||
invoice = purchase.facture.facture
|
||||
except Facture.DoesNotExist:
|
||||
return
|
||||
if purchase.type_cotisation:
|
||||
user = purchase.facture.user
|
||||
user = invoice.user
|
||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||
|
||||
|
||||
|
@ -483,38 +507,38 @@ class Article(RevMixin, AclMixin, models.Model):
|
|||
|
||||
# TODO : Either use TYPE or TYPES in both choices but not both
|
||||
USER_TYPES = (
|
||||
('Adherent', _l("Member")),
|
||||
('Club', _l("Club")),
|
||||
('All', _l("Both of them")),
|
||||
('Adherent', _("Member")),
|
||||
('Club', _("Club")),
|
||||
('All', _("Both of them")),
|
||||
)
|
||||
|
||||
COTISATION_TYPE = (
|
||||
('Connexion', _l("Connexion")),
|
||||
('Adhesion', _l("Membership")),
|
||||
('All', _l("Both of them")),
|
||||
('Connexion', _("Connection")),
|
||||
('Adhesion', _("Membership")),
|
||||
('All', _("Both of them")),
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Designation")
|
||||
verbose_name=_("designation")
|
||||
)
|
||||
# TODO : change prix to price
|
||||
prix = models.DecimalField(
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
verbose_name=_l("Unitary price")
|
||||
verbose_name=_("unit price")
|
||||
)
|
||||
duration = models.PositiveIntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=[MinValueValidator(0)],
|
||||
verbose_name=_l("Duration (in whole month)")
|
||||
verbose_name=_("duration (in months)")
|
||||
)
|
||||
type_user = models.CharField(
|
||||
choices=USER_TYPES,
|
||||
default='All',
|
||||
max_length=255,
|
||||
verbose_name=_l("Type of users concerned")
|
||||
verbose_name=_("type of users concerned")
|
||||
)
|
||||
type_cotisation = models.CharField(
|
||||
choices=COTISATION_TYPE,
|
||||
|
@ -522,31 +546,31 @@ class Article(RevMixin, AclMixin, models.Model):
|
|||
blank=True,
|
||||
null=True,
|
||||
max_length=255,
|
||||
verbose_name=_l("Type of cotisation")
|
||||
verbose_name=_("subscription type")
|
||||
)
|
||||
available_for_everyone = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_l("Is available for every user")
|
||||
verbose_name=_("is available for every user")
|
||||
)
|
||||
|
||||
unique_together = ('name', 'type_user')
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_article', _l("Can see an article's details")),
|
||||
('buy_every_article', _l("Can buy every_article"))
|
||||
('view_article', _("Can view an article object")),
|
||||
('buy_every_article', _("Can buy every article"))
|
||||
)
|
||||
verbose_name = "Article"
|
||||
verbose_name_plural = "Articles"
|
||||
verbose_name = "article"
|
||||
verbose_name_plural = "articles"
|
||||
|
||||
def clean(self):
|
||||
if self.name.lower() == 'solde':
|
||||
raise ValidationError(
|
||||
_("Solde is a reserved article name")
|
||||
_("Balance is a reserved article name.")
|
||||
)
|
||||
if self.type_cotisation and not self.duration:
|
||||
raise ValidationError(
|
||||
_("Duration must be specified for a cotisation")
|
||||
_("Duration must be specified for a subscription.")
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
|
@ -567,19 +591,28 @@ class Article(RevMixin, AclMixin, models.Model):
|
|||
self.available_for_everyone
|
||||
or user.has_perm('cotisations.buy_every_article')
|
||||
or user.has_perm('cotisations.add_facture'),
|
||||
_("You cannot buy this Article.")
|
||||
_("You can't buy this article.")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def find_allowed_articles(cls, user):
|
||||
"""Finds every allowed articles for an user.
|
||||
def find_allowed_articles(cls, user, target_user):
|
||||
"""Finds every allowed articles for an user, on a target user.
|
||||
|
||||
Args:
|
||||
user: The user requesting articles.
|
||||
target_user: The user to sell articles
|
||||
"""
|
||||
if target_user.is_class_club:
|
||||
objects_pool = cls.objects.filter(
|
||||
Q(type_user='All') | Q(type_user='Club')
|
||||
)
|
||||
else:
|
||||
objects_pool = cls.objects.filter(
|
||||
Q(type_user='All') | Q(type_user='Adherent')
|
||||
)
|
||||
if user.has_perm('cotisations.buy_every_article'):
|
||||
return cls.objects.all()
|
||||
return cls.objects.filter(available_for_everyone=True)
|
||||
return objects_pool
|
||||
return objects_pool.filter(available_for_everyone=True)
|
||||
|
||||
|
||||
class Banque(RevMixin, AclMixin, models.Model):
|
||||
|
@ -593,15 +626,14 @@ class Banque(RevMixin, AclMixin, models.Model):
|
|||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Name")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_banque', _l("Can see a bank's details")),
|
||||
('view_banque', _("Can view a bank object")),
|
||||
)
|
||||
verbose_name = _l("Bank")
|
||||
verbose_name_plural = _l("Banks")
|
||||
verbose_name = _("bank")
|
||||
verbose_name_plural = _("banks")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -619,33 +651,33 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
|||
# TODO : change moyen to method
|
||||
moyen = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_l("Method")
|
||||
verbose_name=_("method")
|
||||
)
|
||||
available_for_everyone = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_l("Is available for every user")
|
||||
verbose_name=_("is available for every user")
|
||||
)
|
||||
is_balance = models.BooleanField(
|
||||
default=False,
|
||||
editable=False,
|
||||
verbose_name=_l("Is user balance"),
|
||||
help_text=_l("There should be only one balance payment method."),
|
||||
verbose_name=_("is user balance"),
|
||||
help_text=_("There should be only one balance payment method."),
|
||||
validators=[check_no_balance]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_paiement', _l("Can see a payement's details")),
|
||||
('use_every_payment', _l("Can use every payement")),
|
||||
('view_paiement', _("Can view a payment method object")),
|
||||
('use_every_payment', _("Can use every payment method")),
|
||||
)
|
||||
verbose_name = _l("Payment method")
|
||||
verbose_name_plural = _l("Payment methods")
|
||||
verbose_name = _("payment method")
|
||||
verbose_name_plural = _("payment methods")
|
||||
|
||||
def __str__(self):
|
||||
return self.moyen
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
"""l
|
||||
Override of the herited clean function to get a correct name
|
||||
"""
|
||||
self.moyen = self.moyen.title()
|
||||
|
@ -673,8 +705,8 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
|||
if any(sell.type_cotisation for sell in invoice.vente_set.all()):
|
||||
messages.success(
|
||||
request,
|
||||
_("The cotisation of %(member_name)s has been \
|
||||
extended to %(end_date)s.") % {
|
||||
_("The subscription of %(member_name)s was extended to"
|
||||
" %(end_date)s.") % {
|
||||
'member_name': invoice.user.pseudo,
|
||||
'end_date': invoice.user.end_adhesion()
|
||||
}
|
||||
|
@ -683,7 +715,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
|||
else:
|
||||
messages.success(
|
||||
request,
|
||||
_("The invoice has been created.")
|
||||
_("The invoice was created.")
|
||||
)
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
|
@ -704,7 +736,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
|||
self.available_for_everyone
|
||||
or user.has_perm('cotisations.use_every_payment')
|
||||
or user.has_perm('cotisations.add_facture'),
|
||||
_("You cannot use this Payment.")
|
||||
_("You can't use this payment method.")
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -722,7 +754,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
|||
p = find_payment_method(self)
|
||||
if p is not None:
|
||||
return p._meta.verbose_name
|
||||
return _("No custom payment method")
|
||||
return _("No custom payment method.")
|
||||
|
||||
|
||||
class Cotisation(RevMixin, AclMixin, models.Model):
|
||||
|
@ -738,9 +770,9 @@ class Cotisation(RevMixin, AclMixin, models.Model):
|
|||
"""
|
||||
|
||||
COTISATION_TYPE = (
|
||||
('Connexion', _l("Connexion")),
|
||||
('Adhesion', _l("Membership")),
|
||||
('All', _l("Both of them")),
|
||||
('Connexion', _("Connection")),
|
||||
('Adhesion', _("Membership")),
|
||||
('All', _("Both of them")),
|
||||
)
|
||||
|
||||
# TODO : change vente to purchase
|
||||
|
@ -748,34 +780,36 @@ class Cotisation(RevMixin, AclMixin, models.Model):
|
|||
'Vente',
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
verbose_name=_l("Purchase")
|
||||
verbose_name=_("purchase")
|
||||
)
|
||||
type_cotisation = models.CharField(
|
||||
choices=COTISATION_TYPE,
|
||||
max_length=255,
|
||||
default='All',
|
||||
verbose_name=_l("Type of cotisation")
|
||||
verbose_name=_("subscription type")
|
||||
)
|
||||
date_start = models.DateTimeField(
|
||||
verbose_name=_l("Starting date")
|
||||
verbose_name=_("start date")
|
||||
)
|
||||
date_end = models.DateTimeField(
|
||||
verbose_name=_l("Ending date")
|
||||
verbose_name=_("end date")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_cotisation', _l("Can see a cotisation's details")),
|
||||
('change_all_cotisation', _l("Can edit the previous cotisations")),
|
||||
('view_cotisation', _("Can view a subscription object")),
|
||||
('change_all_cotisation', _("Can edit the previous subscriptions")),
|
||||
)
|
||||
verbose_name = _("subscription")
|
||||
verbose_name_plural = _("subscriptions")
|
||||
|
||||
def can_edit(self, user_request, *_args, **_kwargs):
|
||||
if not user_request.has_perm('cotisations.change_cotisation'):
|
||||
return False, _("You don't have the right to edit a cotisation.")
|
||||
return False, _("You don't have the right to edit a subscription.")
|
||||
elif not user_request.has_perm('cotisations.change_all_cotisation') \
|
||||
and (self.vente.facture.control or
|
||||
not self.vente.facture.valid):
|
||||
return False, _("You don't have the right to edit a cotisation "
|
||||
return False, _("You don't have the right to edit a subscription "
|
||||
"already controlled or invalidated.")
|
||||
else:
|
||||
return True, None
|
||||
|
@ -783,9 +817,9 @@ class Cotisation(RevMixin, AclMixin, models.Model):
|
|||
def can_delete(self, user_request, *_args, **_kwargs):
|
||||
if not user_request.has_perm('cotisations.delete_cotisation'):
|
||||
return False, _("You don't have the right to delete a "
|
||||
"cotisation.")
|
||||
"subscription.")
|
||||
if self.vente.facture.control or not self.vente.facture.valid:
|
||||
return False, _("You don't have the right to delete a cotisation "
|
||||
return False, _("You don't have the right to delete a subscription "
|
||||
"already controlled or invalidated.")
|
||||
else:
|
||||
return True, None
|
||||
|
@ -793,8 +827,8 @@ class Cotisation(RevMixin, AclMixin, models.Model):
|
|||
def can_view(self, user_request, *_args, **_kwargs):
|
||||
if not user_request.has_perm('cotisations.view_cotisation') and\
|
||||
self.vente.facture.user != user_request:
|
||||
return False, _("You don't have the right to see someone else's "
|
||||
"cotisation history.")
|
||||
return False, _("You don't have the right to view someone else's "
|
||||
"subscription history.")
|
||||
else:
|
||||
return True, None
|
||||
|
||||
|
@ -822,3 +856,4 @@ def cotisation_post_delete(**_kwargs):
|
|||
"""
|
||||
regen('mac_ip_list')
|
||||
regen('mailing')
|
||||
|
||||
|
|
|
@ -21,8 +21,7 @@
|
|||
from django.db import models
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _l
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
|
@ -36,7 +35,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
|||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _l("User Balance")
|
||||
verbose_name = _("user balance")
|
||||
|
||||
payment = models.OneToOneField(
|
||||
Paiement,
|
||||
|
@ -45,8 +44,8 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
|||
editable=False
|
||||
)
|
||||
minimum_balance = models.DecimalField(
|
||||
verbose_name=_l("Minimum balance"),
|
||||
help_text=_l("The minimal amount of money allowed for the balance"
|
||||
verbose_name=_("Minimum balance"),
|
||||
help_text=_("The minimal amount of money allowed for the balance"
|
||||
" at the end of a payment. You can specify negative "
|
||||
"amount."
|
||||
),
|
||||
|
@ -55,8 +54,8 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
|||
default=0,
|
||||
)
|
||||
maximum_balance = models.DecimalField(
|
||||
verbose_name=_l("Maximum balance"),
|
||||
help_text=_l("The maximal amount of money allowed for the balance."),
|
||||
verbose_name=_("Maximum balance"),
|
||||
help_text=_("The maximal amount of money allowed for the balance."),
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
default=50,
|
||||
|
@ -64,7 +63,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
|||
null=True,
|
||||
)
|
||||
credit_balance_allowed = models.BooleanField(
|
||||
verbose_name=_l("Allow user to credit their balance"),
|
||||
verbose_name=_("Allow user to credit their balance"),
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
@ -97,7 +96,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
|||
if len(p) > 0:
|
||||
form.add_error(
|
||||
'payment_method',
|
||||
_("There is already a payment type for user balance")
|
||||
_("There is already a payment method for user balance.")
|
||||
)
|
||||
|
||||
def alter_payment(self, payment):
|
||||
|
@ -118,3 +117,4 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
|||
len(Paiement.find_allowed_payments(user_request)
|
||||
.exclude(is_balance=True)) > 0
|
||||
) and self.credit_balance_allowed
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
from django.db import models
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _l
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cotisations.models import Paiement
|
||||
from cotisations.payment_methods.mixins import PaymentMethodMixin
|
||||
|
@ -33,7 +33,7 @@ class ChequePayment(PaymentMethodMixin, models.Model):
|
|||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _l("Cheque")
|
||||
verbose_name = _("Cheque")
|
||||
|
||||
payment = models.OneToOneField(
|
||||
Paiement,
|
||||
|
@ -52,3 +52,4 @@ class ChequePayment(PaymentMethodMixin, models.Model):
|
|||
'cotisations:cheque:validate',
|
||||
kwargs={'invoice_pk': invoice.pk}
|
||||
))
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ def cheque(request, invoice_pk):
|
|||
if invoice.valid or not isinstance(payment_method, ChequePayment):
|
||||
messages.error(
|
||||
request,
|
||||
_("You cannot pay this invoice with a cheque.")
|
||||
_("You can't pay this invoice with a cheque.")
|
||||
)
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
|
@ -67,3 +67,4 @@ def cheque(request, invoice_pk):
|
|||
'amount': invoice.prix_total()
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -21,8 +21,7 @@
|
|||
from django.db import models
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _l
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cotisations.models import Paiement
|
||||
from cotisations.payment_methods.mixins import PaymentMethodMixin
|
||||
|
@ -37,7 +36,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
|
|||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = "ComNpay"
|
||||
verbose_name = _("ComNpay")
|
||||
|
||||
payment = models.OneToOneField(
|
||||
Paiement,
|
||||
|
@ -49,22 +48,32 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
|
|||
max_length=255,
|
||||
default='',
|
||||
blank=True,
|
||||
verbose_name=_l("ComNpay VAD Number"),
|
||||
verbose_name=_("ComNpay VAT Number"),
|
||||
)
|
||||
payment_pass = AESEncryptedField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_l("ComNpay Secret Key"),
|
||||
verbose_name=_("ComNpay secret key"),
|
||||
)
|
||||
minimum_payment = models.DecimalField(
|
||||
verbose_name=_l("Minimum payment"),
|
||||
help_text=_l("The minimal amount of money you have to use when paying"
|
||||
verbose_name=_("Minimum payment"),
|
||||
help_text=_("The minimal amount of money you have to use when paying"
|
||||
" with ComNpay"),
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
default=1,
|
||||
)
|
||||
production = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("Production mode enabled (production URL, instead of homologation)"),
|
||||
)
|
||||
|
||||
def return_url_comnpay(self):
|
||||
if self.production:
|
||||
return 'https://secure.comnpay.com'
|
||||
else:
|
||||
return 'https://secure.homologation.comnpay.com'
|
||||
|
||||
def end_payment(self, invoice, request):
|
||||
"""
|
||||
|
@ -87,11 +96,12 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
|
|||
"",
|
||||
"D"
|
||||
)
|
||||
|
||||
r = {
|
||||
'action': 'https://secure.homologation.comnpay.com',
|
||||
'action': self.return_url_comnpay(),
|
||||
'method': 'POST',
|
||||
'content': p.buildSecretHTML(
|
||||
_("Pay invoice no : ")+str(invoice.id),
|
||||
_("Pay invoice number ")+str(invoice.id),
|
||||
invoice.prix_total(),
|
||||
idTransaction=str(invoice.id)
|
||||
),
|
||||
|
@ -103,6 +113,6 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
|
|||
"""Checks that the price meets the requirement to be paid with ComNpay.
|
||||
"""
|
||||
return ((price >= self.minimum_payment),
|
||||
_('In order to pay your invoice with ComNpay'
|
||||
', the price must be grater than {} €')
|
||||
.format(self.minimum_payment))
|
||||
_("In order to pay your invoice with ComNpay, the price must"
|
||||
" be greater than {} €.").format(self.minimum_payment))
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ def accept_payment(request, factureid):
|
|||
if invoice.valid:
|
||||
messages.success(
|
||||
request,
|
||||
_("The payment of %(amount)s € has been accepted.") % {
|
||||
_("The payment of %(amount)s € was accepted.") % {
|
||||
'amount': invoice.prix_total()
|
||||
}
|
||||
)
|
||||
|
@ -60,8 +60,8 @@ def accept_payment(request, factureid):
|
|||
for purchase in invoice.vente_set.all()):
|
||||
messages.success(
|
||||
request,
|
||||
_("The cotisation of %(member_name)s has been \
|
||||
extended to %(end_date)s.") % {
|
||||
_("The subscription of %(member_name)s was extended to"
|
||||
" %(end_date)s.") % {
|
||||
'member_name': request.user.pseudo,
|
||||
'end_date': request.user.end_adhesion()
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ def refuse_payment(request):
|
|||
"""
|
||||
messages.error(
|
||||
request,
|
||||
_("The payment has been refused.")
|
||||
_("The payment was refused.")
|
||||
)
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
|
@ -136,3 +136,4 @@ def ipn(request):
|
|||
# Everything worked we send a reponse to Comnpay indicating that
|
||||
# it's ok for them to proceed
|
||||
return HttpResponse("HTTP/1.0 200 OK")
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_lazy as _l
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from . import PAYMENT_METHODS
|
||||
from cotisations.utils import find_payment_method
|
||||
|
@ -58,8 +57,8 @@ class PaymentMethodForm(forms.Form):
|
|||
"""
|
||||
|
||||
payment_method = forms.ChoiceField(
|
||||
label=_l("Special payment method"),
|
||||
help_text=_l("Warning : You will not be able to change the payment "
|
||||
label=_("Special payment method"),
|
||||
help_text=_("Warning: you will not be able to change the payment "
|
||||
"method later. But you will be allowed to edit the other "
|
||||
"options."
|
||||
),
|
||||
|
@ -70,7 +69,7 @@ class PaymentMethodForm(forms.Form):
|
|||
super(PaymentMethodForm, self).__init__(*args, **kwargs)
|
||||
prefix = kwargs.get('prefix', None)
|
||||
self.fields['payment_method'].choices = [(i,p.NAME) for (i,p) in enumerate(PAYMENT_METHODS)]
|
||||
self.fields['payment_method'].choices.insert(0, ('', _l('no')))
|
||||
self.fields['payment_method'].choices.insert(0, ('', _('no')))
|
||||
self.fields['payment_method'].widget.attrs = {
|
||||
'id': 'paymentMethodSelect'
|
||||
}
|
||||
|
|
|
@ -32,10 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<th>{% trans "Article" %}</th>
|
||||
<th>{% trans "Price" %}</th>
|
||||
<th>{% trans "Cotisation type" %}</th>
|
||||
<th>{% trans "Duration (month)" %}</th>
|
||||
<th>{% trans "Subscription type" %}</th>
|
||||
<th>{% trans "Duration (in months)" %}</th>
|
||||
<th>{% trans "Concerned users" %}</th>
|
||||
<th>{% trans "Available for everyone" | tick %}</th>
|
||||
<th>{% trans "Available for everyone" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -46,7 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ article.type_cotisation }}</td>
|
||||
<td>{{ article.duration }}</td>
|
||||
<td>{{ article.type_user }}</td>
|
||||
<td>{{ article.available_for_everyone }}</td>
|
||||
<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 %}">
|
||||
|
|
|
@ -26,7 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load i18n %}
|
||||
{% load logs_extra %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Bank" %}</th>
|
||||
|
@ -38,13 +38,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ banque.name }}</td>
|
||||
<td class="text-right">
|
||||
{% can_edit banque %}
|
||||
<a class="btn btn-primary btn-sm" role="button" title="{% trans "Edit" %}" href="{% url 'cotisations:edit-banque' banque.id %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% include 'buttons/edit.html' with href='cotisations:edit-banque' id=banque.id %}
|
||||
{% acl_end %}
|
||||
{% history_button banque %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% include 'buttons/sort.html' with prefix='cotis' col='date' text=tr_date %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Invoice id" as tr_invoice_id %}
|
||||
{% trans "Invoice ID" as tr_invoice_id %}
|
||||
{% include 'buttons/sort.html' with prefix='cotis' col='id' text=tr_invoice_id %}
|
||||
</th>
|
||||
<th></th>
|
||||
|
@ -65,32 +65,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ facture.date }}</td>
|
||||
<td>{{ facture.id }}</td>
|
||||
<td>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-default dropdown-toggle" type="button" id="editinvoice" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
{% trans "Edit" %}<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="editinvoice">
|
||||
{% can_edit facture %}
|
||||
<li>
|
||||
<a href="{% url 'cotisations:edit-facture' facture.id %}">
|
||||
<i class="fa fa-dollar-sign"></i> {% trans "Edit" %}
|
||||
</a>
|
||||
</li>
|
||||
{% include 'buttons/edit.html' with href='cotisations:edit-facture' id=facture.id %}
|
||||
{% acl_else %}
|
||||
<li>{% trans "Controlled invoice" %}</li>
|
||||
{% trans "Controlled invoice" %}
|
||||
{% acl_end %}
|
||||
{% can_delete facture %}
|
||||
<li>
|
||||
<a href="{% url 'cotisations:del-facture' facture.id %}">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
</li>
|
||||
{% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %}
|
||||
{% acl_end %}
|
||||
<li>
|
||||
{% history_button facture text=True html_class=False%}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% history_button facture %}
|
||||
</td>
|
||||
<td>
|
||||
{% if facture.valid %}
|
||||
|
@ -109,3 +92,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% include 'pagination.html' with list=facture_list %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
89
cotisations/templates/cotisations/aff_custom_invoice.html
Normal file
89
cotisations/templates/cotisations/aff_custom_invoice.html
Normal file
|
@ -0,0 +1,89 @@
|
|||
{% 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 custom_invoice_list.paginator %}
|
||||
{% include 'pagination.html' with list=custom_invoice_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_user %}
|
||||
</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 "Invoice ID" as tr_invoice_id %}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_invoice_id %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Paid" as tr_invoice_paid%}
|
||||
{% include 'buttons/sort.html' with prefix='invoice' col='paid' text=tr_invoice_paid %}
|
||||
</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for invoice in custom_invoice_list %}
|
||||
<tr>
|
||||
<td>{{ invoice.recipient }}</td>
|
||||
<td>{{ invoice.name }}</td>
|
||||
<td>{{ invoice.prix_total }}</td>
|
||||
<td>{{ invoice.payment }}</td>
|
||||
<td>{{ invoice.date }}</td>
|
||||
<td>{{ invoice.id }}</td>
|
||||
<td>{{ invoice.paid|tick }}</td>
|
||||
<td>
|
||||
{% can_edit invoice %}
|
||||
{% include 'buttons/edit.html' with href='cotisations:edit-custom-invoice' id=invoice.id %}
|
||||
{% acl_end %}
|
||||
{% can_delete invoice %}
|
||||
{% include 'buttons/suppr.html' with href='cotisations:del-custom-invoice' id=invoice.id %}
|
||||
{% acl_end %}
|
||||
{% history_button invoice %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:custom-invoice-pdf' invoice.id %}">
|
||||
<i class="fa fa-file-pdf"></i> {% trans "PDF" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% if custom_invoice_list.paginator %}
|
||||
{% include 'pagination.html' with list=custom_invoice_list %}
|
||||
{% endif %}
|
||||
</div>
|
|
@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ paiement.moyen }}</td>
|
||||
<td>{{ paiement.available_for_everyone|tick }}</td>
|
||||
<td>
|
||||
{{paiement.get_payment_method_name}}
|
||||
{{ paiement.get_payment_method_name }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% can_edit paiement %}
|
||||
|
|
|
@ -30,17 +30,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% block title %}{% trans "Invoice control" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Invoice control and validation" %}</h2>
|
||||
|
||||
{% if facture_list.paginator %}
|
||||
{% include 'pagination.html' with list=facture_list %}
|
||||
{% endif %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
{{ controlform.management_form }}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Profil" %}</th>
|
||||
<th>{% trans "Profile" %}</th>
|
||||
<th>
|
||||
{% trans "Last name" as tr_last_name %}
|
||||
{% include 'buttons/sort.html' with prefix='control' col='name' text=tr_last_name %}
|
||||
|
@ -50,11 +53,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% include 'buttons/sort.html' with prefix='control' col='surname' text=tr_first_name %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Invoice id" as tr_invoice_id %}
|
||||
{% trans "Invoice ID" as tr_invoice_id %}
|
||||
{% include 'buttons/sort.html' with prefix='control' col='id' text=tr_invoice_id %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "User id" as tr_user_id %}
|
||||
{% trans "User ID" as tr_user_id %}
|
||||
{% include 'buttons/sort.html' with prefix='control' col='user-id' text=tr_user_id %}
|
||||
</th>
|
||||
<th>{% trans "Designation" %}</th>
|
||||
|
@ -65,7 +68,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</th>
|
||||
<th>
|
||||
{% trans "Date" as tr_date %}
|
||||
{% include 'buttons/sort.html' with prefix='control' col='date' text=tr_date %}i
|
||||
{% include 'buttons/sort.html' with prefix='control' col='date' text=tr_date %}
|
||||
</th>
|
||||
<th>
|
||||
{% trans "Validated" as tr_validated %}
|
||||
|
@ -109,3 +112,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% if facture_list.paginator %}
|
||||
{% include 'pagination.html' with list=facture_list %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -26,18 +26,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Deletion of cotisations" %}{% endblock %}
|
||||
{% block title %}{% trans "Deletion of subscriptions" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<h4>
|
||||
{% blocktrans %}
|
||||
Warning. Are you sure you really want te delete this {{ object_name }} object ( {{ objet }} ) ?
|
||||
{% endblocktrans %}
|
||||
{% blocktrans %}Warning: are you sure you really want to delete this {{ object_name }} object ( {{ objet }} )?{% endblocktrans %}
|
||||
</h4>
|
||||
{% trans "Confirm" as tr_confirm %}
|
||||
{% bootstrap_button tr_confirm button_type='submit' icon='trash' %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load massive_bootstrap_form %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Invoices creation and edition" %}{% endblock %}
|
||||
{% block title %}{% trans "Creation and editing of invoices" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% bootstrap_form_errors factureform %}
|
||||
|
@ -62,3 +62,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
22
cotisations/templates/cotisations/email_invoice
Normal file
22
cotisations/templates/cotisations/email_invoice
Normal file
|
@ -0,0 +1,22 @@
|
|||
=== English version below ===
|
||||
|
||||
Bonjour {{name}},
|
||||
|
||||
Nous vous remercions pour votre achat auprès de {{asso_name}} et nous vous en joignons la facture.
|
||||
|
||||
En cas de question, n’hésitez pas à nous contacter par mail à {{contact_mail}}.
|
||||
|
||||
Cordialement,
|
||||
L’équipe de {{asso_name}}
|
||||
|
||||
|
||||
=== English version ===
|
||||
|
||||
Dear {{name}},
|
||||
|
||||
Thank you for your purchase. Here is your invoice.
|
||||
|
||||
Should you need extra information, you can email us at {{contact_mail}}.
|
||||
|
||||
Best regards,
|
||||
{{ asso_name }}'s team
|
|
@ -27,20 +27,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load staticfiles%}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Invoices creation and edition" %}{% endblock %}
|
||||
{% block title %}{% trans "Creation and editing of invoices" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if title %}
|
||||
<h3>{{title}}</h3>
|
||||
<h3>{{ title }}</h3>
|
||||
{% else %}
|
||||
<h3>{% trans "New invoice" %}</h3>
|
||||
{% endif %}
|
||||
{% if max_balance %}
|
||||
<h4>{% trans "Maximum allowed balance : "%}{{max_balance}} €</h4>
|
||||
<h4>{% blocktrans %}Maximum allowed balance: {{ max_balance }} €{% endblocktrans %}</h4>
|
||||
{% endif %}
|
||||
{% if balance is not None %}
|
||||
<p>
|
||||
{% trans "Current balance :" %} {{ balance }} €
|
||||
{% blocktrans %}Current balance: {{ balance }} €{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
@ -68,9 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</div>
|
||||
<input class="btn btn-primary btn-sm" role="button" value="{% trans "Add an article"%}" id="add_one">
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
Total price : <span id="total_price">0,00</span> €
|
||||
{% endblocktrans %}
|
||||
{% blocktrans %}Total price: <span id="total_price">0,00</span> €{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% bootstrap_button action_name button_type='submit' icon='star' %}
|
||||
|
@ -183,3 +182,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% block title %}{% trans "Invoices" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Cotisations" %}</h2>
|
||||
<h2>{% trans "Subscriptions" %}</h2>
|
||||
{% include 'cotisations/aff_cotisations.html' with facture_list=facture_list %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-article' %}">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete article types" %}
|
||||
<i class="fa fa-trash"></i> {% trans "Delete one or several article types" %}
|
||||
</a>
|
||||
{% include 'cotisations/aff_article.html' with article_list=article_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -37,7 +37,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-banque' %}">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete banks" %}
|
||||
<i class="fa fa-trash"></i> {% trans "Delete one or several banks" %}
|
||||
</a>
|
||||
{% include 'cotisations/aff_banque.html' with banque_list=banque_list %}
|
||||
{% endblock %}
|
||||
|
|
36
cotisations/templates/cotisations/index_custom_invoice.html
Normal file
36
cotisations/templates/cotisations/index_custom_invoice.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 "Custom invoices" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Custom invoices list" %}</h2>
|
||||
{% can_create CustomInvoice %}
|
||||
{% 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 %}
|
|
@ -27,17 +27,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Payments" %}{% endblock %}
|
||||
{% block title %}{% trans "Payment methods" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans "Payment types list" %}</h2>
|
||||
<h2>{% trans "List of payment methods" %}</h2>
|
||||
{% can_create Paiement %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-paiement' %}">
|
||||
<i class="fa fa-cart-plus"></i> {% trans "Add a payment type" %}
|
||||
<i class="fa fa-cart-plus"></i> {% trans "Add a payment method" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'cotisations:del-paiement' %}">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete payment types" %}
|
||||
<i class="fa fa-trash"></i> {% trans "Delete one or several payment methods" %}
|
||||
</a>
|
||||
{% include 'cotisations/aff_paiement.html' with paiement_list=paiement_list %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -31,11 +31,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% block content %}
|
||||
<h3>
|
||||
{% blocktrans %}
|
||||
Pay {{ amount }} €
|
||||
{% endblocktrans %}
|
||||
{% blocktrans %}Pay {{ amount }} €{% endblocktrans %}
|
||||
</h3>
|
||||
<form class="form" method="{{ method | default:"post" }}" action="{{ action }}">
|
||||
<form class="form" method="{{ method|default:"post" }}" action="{{ action }}">
|
||||
{{ content | safe }}
|
||||
{% if form %}
|
||||
{% csrf_token %}
|
||||
|
@ -45,3 +43,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% bootstrap_button tr_pay button_type='submit' icon='piggy-bank' %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -27,8 +27,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load i18n %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% can_change Facture pdf %}
|
||||
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-facture-pdf" %}">
|
||||
{% can_create CustomInvoice %}
|
||||
<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" %}">
|
||||
|
@ -40,6 +40,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<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" %}">
|
||||
<i class="fa fa-list-ul"></i> {% trans "Custom invoices" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_view_all 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" %}
|
||||
|
@ -56,3 +61,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</a>
|
||||
{% acl_end %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# coding: utf-8
|
||||
# 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.
|
||||
|
@ -24,6 +25,7 @@ Module in charge of rendering some LaTex templates.
|
|||
Used to generated PDF invoice.
|
||||
"""
|
||||
|
||||
|
||||
import tempfile
|
||||
from subprocess import Popen, PIPE
|
||||
import os
|
||||
|
@ -61,18 +63,24 @@ def render_invoice(_request, ctx={}):
|
|||
return r
|
||||
|
||||
|
||||
def render_tex(_request, template, ctx={}):
|
||||
"""
|
||||
Creates a PDF from a LaTex templates using pdflatex.
|
||||
Writes it in a temporary directory and send back an HTTP response for
|
||||
accessing this file.
|
||||
def create_pdf(template, ctx={}):
|
||||
"""Creates and returns a PDF from a LaTeX template using pdflatex.
|
||||
|
||||
It create a temporary file for the PDF then read it to return its content.
|
||||
|
||||
Args:
|
||||
template: Path to the LaTeX template.
|
||||
ctx: Dict with the context for rendering the template.
|
||||
|
||||
Returns:
|
||||
The content of the temporary PDF file generated.
|
||||
"""
|
||||
context = Context(ctx)
|
||||
template = get_template(template)
|
||||
rendered_tpl = template.render(context).encode('utf-8')
|
||||
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
for i in range(2):
|
||||
for _ in range(2):
|
||||
process = Popen(
|
||||
['pdflatex', '-output-directory', tempdir],
|
||||
stdin=PIPE,
|
||||
|
@ -81,6 +89,25 @@ def render_tex(_request, template, ctx={}):
|
|||
process.communicate(rendered_tpl)
|
||||
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
|
||||
pdf = f.read()
|
||||
|
||||
return pdf
|
||||
|
||||
|
||||
def render_tex(_request, template, ctx={}):
|
||||
"""Creates a PDF from a LaTex templates using pdflatex.
|
||||
|
||||
Calls `create_pdf` and send back an HTTP response for
|
||||
accessing this file.
|
||||
|
||||
Args:
|
||||
_request: Unused, but allow using this function as a Django view.
|
||||
template: Path to the LaTeX template.
|
||||
ctx: Dict with the context for rendering the template.
|
||||
|
||||
Returns:
|
||||
An HttpResponse with type `application/pdf` containing the PDF file.
|
||||
"""
|
||||
pdf = create_pdf(template, ctx)
|
||||
r = HttpResponse(content_type='application/pdf')
|
||||
r.write(pdf)
|
||||
return r
|
||||
|
|
|
@ -52,9 +52,29 @@ urlpatterns = [
|
|||
name='facture-pdf'
|
||||
),
|
||||
url(
|
||||
r'^new_facture_pdf/$',
|
||||
views.new_facture_pdf,
|
||||
name='new-facture-pdf'
|
||||
r'^index_custom_invoice/$',
|
||||
views.index_custom_invoice,
|
||||
name='index-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^new_custom_invoice/$',
|
||||
views.new_custom_invoice,
|
||||
name='new-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^edit_custom_invoice/(?P<custominvoiceid>[0-9]+)$',
|
||||
views.edit_custom_invoice,
|
||||
name='edit-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^custom_invoice_pdf/(?P<custominvoiceid>[0-9]+)$',
|
||||
views.custom_invoice_pdf,
|
||||
name='custom-invoice-pdf',
|
||||
),
|
||||
url(
|
||||
r'^del_custom_invoice/(?P<custominvoiceid>[0-9]+)$',
|
||||
views.del_custom_invoice,
|
||||
name='del-custom-invoice'
|
||||
),
|
||||
url(
|
||||
r'^credit_solde/(?P<userid>[0-9]+)$',
|
||||
|
|
|
@ -19,6 +19,16 @@
|
|||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import os
|
||||
|
||||
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 re2o.settings import LOGO_PATH
|
||||
from re2o import settings
|
||||
|
||||
|
||||
def find_payment_method(payment):
|
||||
"""Finds the payment method associated to the payment if it exists."""
|
||||
|
@ -30,3 +40,56 @@ def find_payment_method(payment):
|
|||
except method.PaymentMethod.DoesNotExist:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def send_mail_invoice(invoice):
|
||||
"""Creates the pdf of the invoice and sends it by email to the client"""
|
||||
purchases_info = []
|
||||
for purchase in invoice.vente_set.all():
|
||||
purchases_info.append({
|
||||
'name': purchase.name,
|
||||
'price': purchase.prix,
|
||||
'quantity': purchase.number,
|
||||
'total_price': purchase.prix_total
|
||||
})
|
||||
|
||||
ctx = {
|
||||
'paid': True,
|
||||
'fid': invoice.id,
|
||||
'DATE': invoice.date,
|
||||
'recipient_name': "{} {}".format(
|
||||
invoice.user.name,
|
||||
invoice.user.surname
|
||||
),
|
||||
'address': invoice.user.room,
|
||||
'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)
|
||||
}
|
||||
|
||||
pdf = create_pdf('cotisations/factures.tex', ctx)
|
||||
template = get_template('cotisations/email_invoice')
|
||||
|
||||
ctx = {
|
||||
'name': "{} {}".format(
|
||||
invoice.user.name,
|
||||
invoice.user.surname
|
||||
),
|
||||
'contact_mail': AssoOption.get_cached_value('contact'),
|
||||
'asso_name': AssoOption.get_cached_value('name')
|
||||
}
|
||||
|
||||
mail = EmailMessage(
|
||||
'Votre facture / Your invoice',
|
||||
template.render(ctx),
|
||||
GeneralOption.get_cached_value('email_from'),
|
||||
[invoice.user.email],
|
||||
attachments=[('invoice.pdf', pdf, 'application/pdf')]
|
||||
)
|
||||
mail.send()
|
||||
|
|
|
@ -17,5 +17,6 @@ def check_no_balance(is_balance):
|
|||
p = Paiement.objects.filter(is_balance=True)
|
||||
if len(p) > 0:
|
||||
raise ValidationError(
|
||||
_("There are already payment method(s) for user balance")
|
||||
_("There is already a payment method for user balance.")
|
||||
)
|
||||
|
||||
|
|
|
@ -58,7 +58,15 @@ from re2o.acl import (
|
|||
can_change,
|
||||
)
|
||||
from preferences.models import AssoOption, GeneralOption
|
||||
from .models import Facture, Article, Vente, Paiement, Banque
|
||||
from .models import (
|
||||
Facture,
|
||||
Article,
|
||||
Vente,
|
||||
Paiement,
|
||||
Banque,
|
||||
CustomInvoice,
|
||||
BaseInvoice
|
||||
)
|
||||
from .forms import (
|
||||
FactureForm,
|
||||
ArticleForm,
|
||||
|
@ -67,14 +75,13 @@ from .forms import (
|
|||
DelPaiementForm,
|
||||
BanqueForm,
|
||||
DelBanqueForm,
|
||||
NewFactureFormPdf,
|
||||
SelectUserArticleForm,
|
||||
SelectClubArticleForm,
|
||||
RechargeForm
|
||||
SelectArticleForm,
|
||||
RechargeForm,
|
||||
CustomInvoiceForm
|
||||
)
|
||||
from .tex import render_invoice
|
||||
from .payment_methods.forms import payment_method_factory
|
||||
from .utils import find_payment_method
|
||||
from .utils import find_payment_method, send_mail_invoice
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -102,15 +109,9 @@ def new_facture(request, user, userid):
|
|||
creation=True
|
||||
)
|
||||
|
||||
if request.user.is_class_club:
|
||||
article_formset = formset_factory(SelectClubArticleForm)(
|
||||
article_formset = formset_factory(SelectArticleForm)(
|
||||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
)
|
||||
else:
|
||||
article_formset = formset_factory(SelectUserArticleForm)(
|
||||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
form_kwargs={'user': request.user, 'target_user': user}
|
||||
)
|
||||
|
||||
if invoice_form.is_valid() and article_formset.is_valid():
|
||||
|
@ -147,6 +148,8 @@ def new_facture(request, user, userid):
|
|||
p.facture = new_invoice_instance
|
||||
p.save()
|
||||
|
||||
send_mail_invoice(new_invoice_instance)
|
||||
|
||||
return new_invoice_instance.paiement.end_payment(
|
||||
new_invoice_instance,
|
||||
request
|
||||
|
@ -161,6 +164,7 @@ def new_facture(request, user, userid):
|
|||
balance = user.solde
|
||||
else:
|
||||
balance = None
|
||||
|
||||
return form(
|
||||
{
|
||||
'factureform': invoice_form,
|
||||
|
@ -175,10 +179,10 @@ def new_facture(request, user, userid):
|
|||
|
||||
# TODO : change facture to invoice
|
||||
@login_required
|
||||
@can_change(Facture, 'pdf')
|
||||
def new_facture_pdf(request):
|
||||
@can_create(CustomInvoice)
|
||||
def new_custom_invoice(request):
|
||||
"""
|
||||
View used to generate a custom PDF invoice. It's mainly used to
|
||||
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.
|
||||
"""
|
||||
|
@ -187,56 +191,39 @@ def new_facture_pdf(request):
|
|||
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||
)
|
||||
# Building the invocie form and the article formset
|
||||
invoice_form = NewFactureFormPdf(request.POST or None)
|
||||
if request.user.is_class_club:
|
||||
articles_formset = formset_factory(SelectClubArticleForm)(
|
||||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
)
|
||||
else:
|
||||
articles_formset = formset_factory(SelectUserArticleForm)(
|
||||
request.POST or None,
|
||||
form_kwargs={'user': request.user}
|
||||
)
|
||||
if invoice_form.is_valid() and articles_formset.is_valid():
|
||||
# Get the article list and build an list out of it
|
||||
# contiaining (article_name, article_price, quantity, total_price)
|
||||
articles_info = []
|
||||
for articles_form in articles_formset:
|
||||
if articles_form.cleaned_data:
|
||||
article = articles_form.cleaned_data['article']
|
||||
quantity = articles_form.cleaned_data['quantity']
|
||||
articles_info.append({
|
||||
'name': article.name,
|
||||
'price': article.prix,
|
||||
'quantity': quantity,
|
||||
'total_price': article.prix * quantity
|
||||
})
|
||||
paid = invoice_form.cleaned_data['paid']
|
||||
recipient = invoice_form.cleaned_data['dest']
|
||||
address = invoice_form.cleaned_data['chambre']
|
||||
total_price = sum(a['total_price'] for a in articles_info)
|
||||
invoice_form = CustomInvoiceForm(request.POST or None)
|
||||
|
||||
article_formset = formset_factory(SelectArticleForm)(
|
||||
request.POST or None,
|
||||
form_kwargs={'user': request.user, 'target_user': user}
|
||||
)
|
||||
|
||||
if invoice_form.is_valid() and articles_formset.is_valid():
|
||||
new_invoice_instance = invoice_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=new_invoice_instance,
|
||||
name=article.name,
|
||||
prix=article.prix,
|
||||
type_cotisation=article.type_cotisation,
|
||||
duration=article.duration,
|
||||
number=quantity
|
||||
)
|
||||
messages.success(
|
||||
request,
|
||||
_("The custom invoice was created.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||
|
||||
|
||||
return render_invoice(request, {
|
||||
'DATE': timezone.now(),
|
||||
'recipient_name': recipient,
|
||||
'address': address,
|
||||
'article': articles_info,
|
||||
'total': total_price,
|
||||
'paid': paid,
|
||||
'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)
|
||||
})
|
||||
return form({
|
||||
'factureform': invoice_form,
|
||||
'action_name': _("Create"),
|
||||
'articlesformset': articles_formset,
|
||||
'articles': articles
|
||||
'articlelist': articles
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
||||
|
@ -289,7 +276,7 @@ def facture_pdf(request, facture, **_kwargs):
|
|||
def edit_facture(request, facture, **_kwargs):
|
||||
"""
|
||||
View used to edit an existing invoice.
|
||||
Articles can be added or remove to the invoice and quantity
|
||||
Articles can be added or removed to the invoice and quantity
|
||||
can be set as desired. This is also the view used to invalidate
|
||||
an invoice.
|
||||
"""
|
||||
|
@ -315,7 +302,7 @@ def edit_facture(request, facture, **_kwargs):
|
|||
purchase_form.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The invoice has been successfully edited.")
|
||||
_("The invoice was edited.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index'))
|
||||
return form({
|
||||
|
@ -335,7 +322,7 @@ def del_facture(request, facture, **_kwargs):
|
|||
facture.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_("The invoice has been successfully deleted.")
|
||||
_("The invoice was deleted.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index'))
|
||||
return form({
|
||||
|
@ -344,6 +331,100 @@ def del_facture(request, facture, **_kwargs):
|
|||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(CustomInvoice)
|
||||
def edit_custom_invoice(request, invoice, **kwargs):
|
||||
# Building the invocie form and the article formset
|
||||
invoice_form = CustomInvoiceForm(
|
||||
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 invoice was edited.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||
|
||||
return form({
|
||||
'factureform': invoice_form,
|
||||
'venteform': purchase_form
|
||||
}, 'cotisations/edit_facture.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': 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)
|
||||
})
|
||||
|
||||
|
||||
# TODO : change facture to invoice
|
||||
@login_required
|
||||
@can_delete(CustomInvoice)
|
||||
def del_custom_invoice(request, invoice, **_kwargs):
|
||||
"""
|
||||
View used to delete an existing invocie.
|
||||
"""
|
||||
if request.method == "POST":
|
||||
invoice.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_("The invoice was deleted.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||
return form({
|
||||
'objet': invoice,
|
||||
'objet_name': _("Invoice")
|
||||
}, 'cotisations/delete.html', request)
|
||||
|
||||
|
||||
@login_required
|
||||
@can_create(Article)
|
||||
def add_article(request):
|
||||
|
@ -361,7 +442,7 @@ def add_article(request):
|
|||
article.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The article has been successfully created.")
|
||||
_("The article was created.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-article'))
|
||||
return form({
|
||||
|
@ -383,7 +464,7 @@ def edit_article(request, article_instance, **_kwargs):
|
|||
article.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The article has been successfully edited.")
|
||||
_("The article was edited.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-article'))
|
||||
return form({
|
||||
|
@ -405,7 +486,7 @@ def del_article(request, instances):
|
|||
article_del.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_("The article(s) have been successfully deleted.")
|
||||
_("The articles were deleted.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-article'))
|
||||
return form({
|
||||
|
@ -433,7 +514,7 @@ def add_paiement(request):
|
|||
payment_method.save(payment)
|
||||
messages.success(
|
||||
request,
|
||||
_("The payment method has been successfully created.")
|
||||
_("The payment method was created.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-paiement'))
|
||||
return form({
|
||||
|
@ -469,8 +550,7 @@ def edit_paiement(request, paiement_instance, **_kwargs):
|
|||
if payment_method is not None:
|
||||
payment_method.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The payement method has been successfully edited.")
|
||||
request,_("The payment method was edited.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-paiement'))
|
||||
return form({
|
||||
|
@ -496,8 +576,7 @@ def del_paiement(request, instances):
|
|||
payment_del.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_("The payment method %(method_name)s has been \
|
||||
successfully deleted.") % {
|
||||
_("The payment method %(method_name)s was deleted.") % {
|
||||
'method_name': payment_del
|
||||
}
|
||||
)
|
||||
|
@ -529,7 +608,7 @@ def add_banque(request):
|
|||
bank.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The bank has been successfully created.")
|
||||
_("The bank was created.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-banque'))
|
||||
return form({
|
||||
|
@ -552,7 +631,7 @@ def edit_banque(request, banque_instance, **_kwargs):
|
|||
bank.save()
|
||||
messages.success(
|
||||
request,
|
||||
_("The bank has been successfully edited")
|
||||
_("The bank was edited.")
|
||||
)
|
||||
return redirect(reverse('cotisations:index-banque'))
|
||||
return form({
|
||||
|
@ -577,8 +656,7 @@ def del_banque(request, instances):
|
|||
bank_del.delete()
|
||||
messages.success(
|
||||
request,
|
||||
_("The bank %(bank_name)s has been successfully \
|
||||
deleted.") % {
|
||||
_("The bank %(bank_name)s was deleted.") % {
|
||||
'bank_name': bank_del
|
||||
}
|
||||
)
|
||||
|
@ -678,8 +756,31 @@ def index_banque(request):
|
|||
})
|
||||
|
||||
|
||||
@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')
|
||||
custom_invoice_list = SortTable.sort(
|
||||
custom_invoice_list,
|
||||
request.GET.get('col'),
|
||||
request.GET.get('order'),
|
||||
SortTable.COTISATIONS_CUSTOM
|
||||
)
|
||||
custom_invoice_list = re2o_paginator(
|
||||
request,
|
||||
custom_invoice_list,
|
||||
pagination_number,
|
||||
)
|
||||
return render(request, 'cotisations/index_custom_invoice.html', {
|
||||
'custom_invoice_list': custom_invoice_list
|
||||
})
|
||||
|
||||
|
||||
@login_required
|
||||
@can_view_all(Facture)
|
||||
@can_view_all(CustomInvoice)
|
||||
def index(request):
|
||||
"""
|
||||
View used to display the list of all exisitng invoices.
|
||||
|
@ -695,7 +796,7 @@ def index(request):
|
|||
)
|
||||
invoice_list = re2o_paginator(request, invoice_list, pagination_number)
|
||||
return render(request, 'cotisations/index.html', {
|
||||
'facture_list': invoice_list
|
||||
'facture_list': invoice_list,
|
||||
})
|
||||
|
||||
|
||||
|
@ -726,7 +827,7 @@ def credit_solde(request, user, **_kwargs):
|
|||
kwargs={'userid': user.id}
|
||||
))
|
||||
|
||||
refill_form = RechargeForm(request.POST or None, user=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)
|
||||
|
@ -746,12 +847,16 @@ def credit_solde(request, user, **_kwargs):
|
|||
prix=refill_form.cleaned_data['value'],
|
||||
number=1
|
||||
)
|
||||
|
||||
send_mail_invoice(invoice)
|
||||
|
||||
return invoice.paiement.end_payment(invoice, request)
|
||||
p = get_object_or_404(Paiement, is_balance=True)
|
||||
return form({
|
||||
'factureform': refill_form,
|
||||
'balance': request.user.solde,
|
||||
'balance': user.solde,
|
||||
'title': _("Refill your balance"),
|
||||
'action_name': _("Pay"),
|
||||
'max_balance': p.payment_method.maximum_balance,
|
||||
}, 'cotisations/facture.html', request)
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ from preferences.models import OptionalTopologie
|
|||
options, created = OptionalTopologie.objects.get_or_create()
|
||||
VLAN_NOK = options.vlan_decision_nok.vlan_id
|
||||
VLAN_OK = options.vlan_decision_ok.vlan_id
|
||||
RADIUS_POLICY = options.radius_general_policy
|
||||
|
||||
#: Serveur radius de test (pas la prod)
|
||||
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
|
||||
|
@ -347,7 +348,7 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
if not nas_machine:
|
||||
return ('?', u'Chambre inconnue', u'Nas inconnu', VLAN_OK)
|
||||
|
||||
sw_name = str(nas_machine)
|
||||
sw_name = str(getattr(nas_machine, 'short_name', str(nas_machine)))
|
||||
|
||||
port = (Port.objects
|
||||
.filter(
|
||||
|
@ -355,27 +356,47 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
port=port_number
|
||||
)
|
||||
.first())
|
||||
|
||||
# Si le port est inconnu, on place sur le vlan defaut
|
||||
# Aucune information particulière ne permet de déterminer quelle
|
||||
# politique à appliquer sur ce port
|
||||
if not port:
|
||||
return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK)
|
||||
|
||||
# Si un vlan a été précisé, on l'utilise pour VLAN_OK
|
||||
if port.vlan_force:
|
||||
DECISION_VLAN = int(port.vlan_force.vlan_id)
|
||||
# On récupère le profil du port
|
||||
port_profile = port.get_port_profile
|
||||
|
||||
# Si un vlan a été précisé dans la config du port,
|
||||
# on l'utilise pour VLAN_OK
|
||||
if port_profile.vlan_untagged:
|
||||
DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id)
|
||||
extra_log = u"Force sur vlan " + str(DECISION_VLAN)
|
||||
else:
|
||||
DECISION_VLAN = VLAN_OK
|
||||
|
||||
if port.radius == 'NO':
|
||||
# Si le port est désactivé, on rejette sur le vlan de déconnexion
|
||||
if not port.state:
|
||||
return (sw_name, port.room, u'Port desactivé', VLAN_NOK)
|
||||
|
||||
# Si radius est désactivé, on laisse passer
|
||||
if port_profile.radius_type == 'NO':
|
||||
return (sw_name,
|
||||
"",
|
||||
u"Pas d'authentification sur ce port" + extra_log,
|
||||
DECISION_VLAN)
|
||||
|
||||
if port.radius == 'BLOQ':
|
||||
return (sw_name, port.room, u'Port desactive', VLAN_NOK)
|
||||
# Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment
|
||||
# Par conséquent, on laisse passer sur le bon vlan
|
||||
if nas_type.port_access_mode == '802.1X' and port_profile.radius_type == '802.1X':
|
||||
room = port.room or "Chambre/local inconnu"
|
||||
return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN)
|
||||
|
||||
if port.radius == 'STRICT':
|
||||
# Sinon, cela veut dire qu'on fait de l'auth radius par mac
|
||||
# Si le port est en mode strict, on vérifie que tous les users
|
||||
# rattachés à ce port sont bien à jour de cotisation. Sinon on rejette (anti squattage)
|
||||
# Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis
|
||||
# dedans
|
||||
if port_profile.radius_mode == 'STRICT':
|
||||
room = port.room
|
||||
if not room:
|
||||
return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK)
|
||||
|
@ -390,7 +411,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
return (sw_name, room, u'Chambre resident desactive', VLAN_NOK)
|
||||
# else: user OK, on passe à la verif MAC
|
||||
|
||||
if port.radius == 'COMMON' or port.radius == 'STRICT':
|
||||
# Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd
|
||||
if port_profile.radius_mode == 'COMMON' or port_profile.radius_mode == 'STRICT':
|
||||
# Authentification par mac
|
||||
interface = (Interface.objects
|
||||
.filter(mac_address=mac_address)
|
||||
|
@ -399,15 +421,19 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
.first())
|
||||
if not interface:
|
||||
room = port.room
|
||||
# On essaye de register la mac
|
||||
# On essaye de register la mac, si l'autocapture a été activée
|
||||
# Sinon on rejette sur vlan_nok
|
||||
if not nas_type.autocapture_mac:
|
||||
return (sw_name, "", u'Machine inconnue', VLAN_NOK)
|
||||
# On ne peut autocapturer que si on connait la chambre et donc l'user correspondant
|
||||
elif not room:
|
||||
return (sw_name,
|
||||
"Inconnue",
|
||||
u'Chambre et machine inconnues',
|
||||
VLAN_NOK)
|
||||
else:
|
||||
# Si la chambre est vide (local club, prises en libre services)
|
||||
# Impossible d'autocapturer
|
||||
if not room_user:
|
||||
room_user = User.objects.filter(
|
||||
Q(club__room=port.room) | Q(adherent__room=port.room)
|
||||
|
@ -418,6 +444,8 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
u'Machine et propriétaire de la chambre '
|
||||
'inconnus',
|
||||
VLAN_NOK)
|
||||
# Si il y a plus d'un user dans la chambre, impossible de savoir à qui
|
||||
# Ajouter la machine
|
||||
elif room_user.count() > 1:
|
||||
return (sw_name,
|
||||
room,
|
||||
|
@ -425,19 +453,24 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
'dans la chambre/local -> ajout de mac '
|
||||
'automatique impossible',
|
||||
VLAN_NOK)
|
||||
# Si l'adhérent de la chambre n'est pas à jour de cotis, pas d'autocapture
|
||||
elif not room_user.first().has_access():
|
||||
return (sw_name,
|
||||
room,
|
||||
u'Machine inconnue et adhérent non cotisant',
|
||||
VLAN_NOK)
|
||||
# Sinon on capture et on laisse passer sur le bon vlan
|
||||
else:
|
||||
result, reason = (room_user
|
||||
interface, reason = (room_user
|
||||
.first()
|
||||
.autoregister_machine(
|
||||
mac_address,
|
||||
nas_type
|
||||
))
|
||||
if result:
|
||||
if interface:
|
||||
## Si on choisi de placer les machines sur le vlan correspondant à leur type :
|
||||
if RADIUS_POLICY == 'MACHINE':
|
||||
DECISION_VLAN = interface.type.ip_type.vlan.vlan_id
|
||||
return (sw_name,
|
||||
room,
|
||||
u'Access Ok, Capture de la mac: ' + extra_log,
|
||||
|
@ -449,6 +482,9 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
reason + str(mac_address)
|
||||
),
|
||||
VLAN_NOK)
|
||||
# L'interface a été trouvée, on vérifie qu'elle est active, sinon on reject
|
||||
# Si elle n'a pas d'ipv4, on lui en met une
|
||||
# Enfin on laisse passer sur le vlan pertinent
|
||||
else:
|
||||
room = port.room
|
||||
if not interface.is_active:
|
||||
|
@ -456,7 +492,10 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
|||
room,
|
||||
u'Machine non active / adherent non cotisant',
|
||||
VLAN_NOK)
|
||||
elif not interface.ipv4:
|
||||
## Si on choisi de placer les machines sur le vlan correspondant à leur type :
|
||||
if RADIUS_POLICY == 'MACHINE':
|
||||
DECISION_VLAN = interface.type.ip_type.vlan.vlan_id
|
||||
if not interface.ipv4:
|
||||
interface.assign_ipv4()
|
||||
return (sw_name,
|
||||
room,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
python re2o {
|
||||
module = auth
|
||||
|
||||
python_path = /etc/freeradius/3.0:/usr/lib/python2.7/:/usr/lib/python2.7/dist-packages/:/usr/local/lib/python2.7/site-packages/:/usr/local/lib/python2.7/dist-packages/
|
||||
python_path = /etc/freeradius/3.0:/usr/lib/python2.7:/usr/lib/python2.7/dist-packages:/usr/local/lib/python2.7/site-packages:/usr/lib/python2.7/lib-dynload:/usr/local/lib/python2.7/dist-packages
|
||||
|
||||
mod_instantiate = ${.module}
|
||||
func_instantiate = instantiate
|
||||
|
|
|
@ -1157,6 +1157,7 @@ olcDbIndex: dc eq
|
|||
olcDbIndex: entryCSN eq
|
||||
olcDbIndex: entryUUID eq
|
||||
olcDbIndex: radiusCallingStationId eq
|
||||
olcSizeLimit: 50000
|
||||
structuralObjectClass: olcHdbConfig
|
||||
entryUUID: fc8fa138-514b-1034-9c36-0faf5bc7ead5
|
||||
creatorsName: cn=admin,cn=config
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
Here are defined some functions to check acl on the application.
|
||||
"""
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
def can_view(user):
|
||||
|
@ -38,4 +39,6 @@ def can_view(user):
|
|||
viewing is granted and msg is a message (can be None).
|
||||
"""
|
||||
can = user.has_module_perms('admin')
|
||||
return can, None if can else "Vous ne pouvez pas voir cette application."
|
||||
return can, None if can else _("You don't have the right to view this"
|
||||
" application.")
|
||||
|
||||
|
|
BIN
logs/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
logs/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
338
logs/locale/fr/LC_MESSAGES/django.po
Normal file
338
logs/locale/fr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,338 @@
|
|||
# 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: 2018-08-15 20:12+0200\n"
|
||||
"PO-Revision-Date: 2018-06-23 16:01+0200\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"
|
||||
|
||||
#: acl.py:42
|
||||
msgid "You don't have the right to view this application."
|
||||
msgstr "Vous n'avez pas le droit de voir cette application."
|
||||
|
||||
#: templates/logs/aff_stats_logs.html:36
|
||||
msgid "Edited object"
|
||||
msgstr "Objet modifié"
|
||||
|
||||
#: templates/logs/aff_stats_logs.html:37
|
||||
#: templates/logs/aff_stats_models.html:32
|
||||
msgid "Object type"
|
||||
msgstr "Type d'objet"
|
||||
|
||||
#: templates/logs/aff_stats_logs.html:38
|
||||
msgid "Edited by"
|
||||
msgstr "Modifié par"
|
||||
|
||||
#: templates/logs/aff_stats_logs.html:40
|
||||
msgid "Date of editing"
|
||||
msgstr "Date de modification"
|
||||
|
||||
#: templates/logs/aff_stats_logs.html:42
|
||||
msgid "Comment"
|
||||
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
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: templates/logs/aff_stats_models.html:29
|
||||
#, python-format
|
||||
msgid "Statistics of the set %(key)s"
|
||||
msgstr "Statistiques de l'ensemble %(key)s"
|
||||
|
||||
#: templates/logs/aff_stats_models.html:33
|
||||
msgid "Number of stored entries"
|
||||
msgstr "Nombre d'entrées enregistrées"
|
||||
|
||||
#: templates/logs/aff_stats_users.html:31
|
||||
#, python-format
|
||||
msgid "Statistics per %(key_dict)s of %(key)s"
|
||||
msgstr "Statistiques par %(key_dict)s de %(key)s"
|
||||
|
||||
#: templates/logs/aff_stats_users.html:34
|
||||
#, python-format
|
||||
msgid "Number of %(key)s per %(key_dict)s"
|
||||
msgstr "Nombre de %(key)s par %(key_dict)s"
|
||||
|
||||
#: templates/logs/aff_stats_users.html:35
|
||||
msgid "Rank"
|
||||
msgstr "Rang"
|
||||
|
||||
#: templates/logs/aff_summary.html:37
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
#: templates/logs/aff_summary.html:39
|
||||
msgid "Editing"
|
||||
msgstr "Modification"
|
||||
|
||||
#: templates/logs/aff_summary.html:48
|
||||
#, python-format
|
||||
msgid "%(username)s has banned"
|
||||
msgstr "%(username)s a banni"
|
||||
|
||||
#: templates/logs/aff_summary.html:52 templates/logs/aff_summary.html:75
|
||||
msgid "No reason"
|
||||
msgstr "Aucun motif"
|
||||
|
||||
#: templates/logs/aff_summary.html:71
|
||||
#, python-format
|
||||
msgid "%(username)s has graciously authorised"
|
||||
msgstr "%(username)s a autorisé gracieusement"
|
||||
|
||||
#: templates/logs/aff_summary.html:94
|
||||
#, python-format
|
||||
msgid "%(username)s has updated"
|
||||
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 à"
|
||||
|
||||
#: templates/logs/aff_summary.html:116
|
||||
#, python-format
|
||||
msgid "+%(duration)s months"
|
||||
msgstr "+%(duration)s mois"
|
||||
|
||||
#: templates/logs/aff_summary.html:132
|
||||
#, python-format
|
||||
msgid "%(username)s has edited an interface of"
|
||||
msgstr "%(username)s a modifié une interface de"
|
||||
|
||||
#: templates/logs/delete.html:29
|
||||
msgid "Deletion of actions"
|
||||
msgstr "Suppression d'actions"
|
||||
|
||||
#: templates/logs/delete.html:35
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Warning: are you sure you want to delete this action %(objet_name)s "
|
||||
"( %(objet)s )?"
|
||||
msgstr ""
|
||||
"Attention: voulez-vous vraiment supprimer cette action %(objet_name)s "
|
||||
"( %(objet)s ) ?"
|
||||
|
||||
#: templates/logs/delete.html:36
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmer"
|
||||
|
||||
#: templates/logs/index.html:29 templates/logs/stats_general.html:29
|
||||
#: templates/logs/stats_logs.html:29 templates/logs/stats_models.html:29
|
||||
#: templates/logs/stats_users.html:29
|
||||
msgid "Statistics"
|
||||
msgstr "Statistiques"
|
||||
|
||||
#: templates/logs/index.html:32 templates/logs/stats_logs.html:32 views.py:403
|
||||
msgid "Actions performed"
|
||||
msgstr "Actions effectuées"
|
||||
|
||||
#: templates/logs/sidebar.html:33
|
||||
msgid "Summary"
|
||||
msgstr "Résumé"
|
||||
|
||||
#: templates/logs/sidebar.html:37
|
||||
msgid "Events"
|
||||
msgstr "Évènements"
|
||||
|
||||
#: templates/logs/sidebar.html:41
|
||||
msgid "General"
|
||||
msgstr "Général"
|
||||
|
||||
#: templates/logs/sidebar.html:45
|
||||
msgid "Database"
|
||||
msgstr "Base de données"
|
||||
|
||||
#: templates/logs/sidebar.html:49
|
||||
msgid "Wiring actions"
|
||||
msgstr "Actions de câblage"
|
||||
|
||||
#: templates/logs/sidebar.html:53 views.py:325
|
||||
msgid "Users"
|
||||
msgstr "Utilisateurs"
|
||||
|
||||
#: templates/logs/stats_general.html:32
|
||||
msgid "General statistics"
|
||||
msgstr "Statistiques générales"
|
||||
|
||||
#: templates/logs/stats_models.html:32
|
||||
msgid "Database statistics"
|
||||
msgstr "Statistiques sur la base de données"
|
||||
|
||||
#: templates/logs/stats_users.html:32
|
||||
msgid "Statistics about users"
|
||||
msgstr "Statistiques sur les utilisateurs"
|
||||
|
||||
#: views.py:191
|
||||
msgid "Nonexistent revision."
|
||||
msgstr "Révision inexistante."
|
||||
|
||||
#: views.py:194
|
||||
msgid "The action was deleted."
|
||||
msgstr "L'action a été supprimée."
|
||||
|
||||
#: views.py:227
|
||||
msgid "Category"
|
||||
msgstr "Catégorie"
|
||||
|
||||
#: views.py:228
|
||||
msgid "Number of users (members and clubs)"
|
||||
msgstr "Nombre d'utilisateurs (adhérents et clubs)"
|
||||
|
||||
#: views.py:229
|
||||
msgid "Number of members"
|
||||
msgstr "Nombre d'adhérents"
|
||||
|
||||
#: views.py:230
|
||||
msgid "Number of clubs"
|
||||
msgstr "Nombre de clubs"
|
||||
|
||||
#: views.py:234
|
||||
msgid "Activated users"
|
||||
msgstr "Utilisateurs activés"
|
||||
|
||||
#: views.py:242
|
||||
msgid "Disabled users"
|
||||
msgstr "Utilisateurs désactivés"
|
||||
|
||||
#: views.py:250
|
||||
msgid "Archived users"
|
||||
msgstr "Utilisateurs archivés"
|
||||
|
||||
#: views.py:258
|
||||
msgid "Contributing members"
|
||||
msgstr "Adhérents cotisants"
|
||||
|
||||
#: views.py:264
|
||||
msgid "Users benefiting from a connection"
|
||||
msgstr "Utilisateurs bénéficiant d'une connexion"
|
||||
|
||||
#: views.py:270
|
||||
msgid "Banned users"
|
||||
msgstr "Utilisateurs bannis"
|
||||
|
||||
#: views.py:276
|
||||
msgid "Users benefiting from a free connection"
|
||||
msgstr "Utilisateurs bénéficiant d'une connexion gratuite"
|
||||
|
||||
#: views.py:282
|
||||
msgid "Active interfaces (with access to the network)"
|
||||
msgstr "Interfaces actives (ayant accès au réseau)"
|
||||
|
||||
#: views.py:292
|
||||
msgid "Active interfaces assigned IPv4"
|
||||
msgstr "Interfaces actives assignées IPv4"
|
||||
|
||||
#: views.py:305
|
||||
msgid "IP range"
|
||||
msgstr "Plage d'IP"
|
||||
|
||||
#: views.py:306
|
||||
msgid "VLAN"
|
||||
msgstr "VLAN"
|
||||
|
||||
#: views.py:307
|
||||
msgid "Total number of IP addresses"
|
||||
msgstr "Nombre total d'adresses IP"
|
||||
|
||||
#: views.py:308
|
||||
msgid "Number of assigned IP addresses"
|
||||
msgstr "Nombre d'adresses IP non assignées"
|
||||
|
||||
#: views.py:309
|
||||
msgid "Number of IP address assigned to an activated machine"
|
||||
msgstr "Nombre d'adresses IP assignées à une machine activée"
|
||||
|
||||
#: views.py:310
|
||||
msgid "Number of nonassigned IP addresses"
|
||||
msgstr "Nombre d'adresses IP non assignées"
|
||||
|
||||
#: views.py:337
|
||||
msgid "Subscriptions"
|
||||
msgstr "Cotisations"
|
||||
|
||||
#: views.py:359 views.py:420
|
||||
msgid "Machines"
|
||||
msgstr "Machines"
|
||||
|
||||
#: views.py:386
|
||||
msgid "Topology"
|
||||
msgstr "Topologie"
|
||||
|
||||
#: views.py:405
|
||||
msgid "Number of actions"
|
||||
msgstr "Nombre d'actions"
|
||||
|
||||
#: views.py:419 views.py:437 views.py:442 views.py:447 views.py:462
|
||||
msgid "User"
|
||||
msgstr "Utilisateur"
|
||||
|
||||
#: views.py:423
|
||||
msgid "Invoice"
|
||||
msgstr "Facture"
|
||||
|
||||
#: views.py:426
|
||||
msgid "Ban"
|
||||
msgstr "Bannissement"
|
||||
|
||||
#: views.py:429
|
||||
msgid "Whitelist"
|
||||
msgstr "Accès gracieux"
|
||||
|
||||
#: views.py:432
|
||||
msgid "Rights"
|
||||
msgstr "Droits"
|
||||
|
||||
#: views.py:436
|
||||
msgid "School"
|
||||
msgstr "Établissement"
|
||||
|
||||
#: views.py:441
|
||||
msgid "Payment method"
|
||||
msgstr "Moyen de paiement"
|
||||
|
||||
#: views.py:446
|
||||
msgid "Bank"
|
||||
msgstr "Banque"
|
||||
|
||||
#: views.py:463
|
||||
msgid "Action"
|
||||
msgstr "Action"
|
||||
|
||||
#: views.py:494
|
||||
msgid "No model found."
|
||||
msgstr "Aucun modèle trouvé."
|
||||
|
||||
#: views.py:500
|
||||
msgid "Nonexistent entry."
|
||||
msgstr "Entrée inexistante."
|
||||
|
||||
#: views.py:507
|
||||
msgid "You don't have the right to access this menu."
|
||||
msgstr "Vous n'avez pas le droit d'accéder à ce menu."
|
|
@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
{% endcomment %}
|
||||
|
||||
{% for stats in stats_list %}
|
||||
{% for stats in stats_list %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -39,4 +39,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -28,15 +28,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load logs_extra %}
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Objet modifié</th>
|
||||
<th>Type de l'objet</th>
|
||||
<th>{% include "buttons/sort.html" with prefix='logs' col='author' text='Modification par' %}</th>
|
||||
<th>{% include "buttons/sort.html" with prefix='logs' col='date' text='Date de modification' %}</th>
|
||||
<th>Commentaire</th>
|
||||
<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>
|
||||
{% 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>{% trans "Comment" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -52,15 +55,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' revision.id %}">
|
||||
<i class="fa fa-times"></i>
|
||||
Annuler
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</td>
|
||||
{% acl_end %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</table>
|
||||
|
||||
{% if revisions_list.paginator %}
|
||||
{% include "pagination.html" with list=revisions_list %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -22,13 +22,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
{% endcomment %}
|
||||
|
||||
{% for key, stats in stats_list.items %}
|
||||
{% load i18n %}
|
||||
|
||||
{% for key, stats in stats_list.items %}
|
||||
<table class="table table-striped">
|
||||
<h4>Statistiques de l'ensemble {{ key }}</h4>
|
||||
<h4>{% blocktrans %}Statistics of the set {{ key }}{% endblocktrans %}</h4>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type d'objet</th>
|
||||
<th>Nombre d'entrée stockées</th>
|
||||
<th>{% trans "Object type" %}</th>
|
||||
<th>{% trans "Number of stored entries" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for key, stat in stats.items %}
|
||||
|
@ -38,4 +40,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -22,15 +22,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
{% endcomment %}
|
||||
|
||||
{% for key_dict, stats_dict in stats_list.items %}
|
||||
{% load i18n %}
|
||||
|
||||
{% for key_dict, stats_dict in stats_list.items %}
|
||||
{% for key, stats in stats_dict.items %}
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<h4>Statistiques par {{ key_dict }} de {{ key }}</h4>
|
||||
<h4>{% blocktrans %}Statistics per {{ key_dict }} of {{ key }}{% endblocktrans %}</h4>
|
||||
<tr>
|
||||
<th>{{ key_dict }}</th>
|
||||
<th>Nombre de {{ key }} par {{ key_dict }}</th>
|
||||
<th>Rang</th>
|
||||
<th>{% blocktrans %}Number of {{ key }} per {{ key_dict }}{% endblocktrans %}</th>
|
||||
<th>{% trans "Rank" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for stat in stats %}
|
||||
|
@ -42,4 +44,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -28,11 +28,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load logs_extra %}
|
||||
{% load acl %}
|
||||
<table class="table table-striped">
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% include "buttons/sort.html" with prefix='sum' col='date' text='Date' %}</th>
|
||||
<th>Modification</th>
|
||||
{% trans "Date" as tr_date %}
|
||||
<th>{% include "buttons/sort.html" with prefix='sum' col='date' text=tr_date %}</th>
|
||||
<th>{% trans "Editing" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -41,11 +45,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr class="danger">
|
||||
<td>{{ v.datetime }}</td>
|
||||
<td>
|
||||
{{ v.username }} a banni
|
||||
{% blocktrans with username=v.username %}{{ username }} has banned{% endblocktrans %}
|
||||
<a href="{% url 'users:profil' v.version.object.user_id %}">{{ v.version.object.user.get_username }}</a>
|
||||
(<i>
|
||||
{% if v.version.object.raison == '' %}
|
||||
Aucune raison
|
||||
{% trans "No reason" %}
|
||||
{% else %}
|
||||
{{ v.version.object.raison }}
|
||||
{% endif %}
|
||||
|
@ -55,7 +59,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
|
||||
<i class="fa fa-times"></i>
|
||||
Annuler
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</td>
|
||||
{% acl_end %}
|
||||
|
@ -64,11 +68,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr class="success">
|
||||
<td>{{ v.datetime }}</td>
|
||||
<td>
|
||||
{{ v.username }} a autorisé gracieusement
|
||||
{% blocktrans with username=v.username %}{{ username }} has graciously authorised{% endblocktrans %}
|
||||
<a href="{% url 'users:profil' v.version.object.user_id %}">{{ v.version.object.user.get_username }}</a>
|
||||
(<i>
|
||||
{% if v.version.object.raison == '' %}
|
||||
Aucune raison
|
||||
{% trans "No reason" %}
|
||||
{% else %}
|
||||
{{ v.version.object.raison }}
|
||||
{% endif %}
|
||||
|
@ -78,7 +82,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
|
||||
<i class="fa fa-times"></i>
|
||||
Annuler
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</td>
|
||||
{% acl_end %}
|
||||
|
@ -87,7 +91,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<td>{{ v.datetime }}</td>
|
||||
<td>
|
||||
{{ v.username }} a mis à jour
|
||||
{% blocktrans with username=v.username %}{{ username }} has updated{% endblocktrans %}
|
||||
<a href="{% url 'users:profil' v.version.object.id %}">{{ v.version.object.get_username }}</a>
|
||||
{% if v.comment != '' %}
|
||||
(<i>{{ v.comment }}</i>)
|
||||
|
@ -97,7 +101,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
|
||||
<i class="fa fa-times"></i>
|
||||
Annuler
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</td>
|
||||
{% acl_end %}
|
||||
|
@ -106,17 +110,22 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<td>{{ v.datetime }}</td>
|
||||
<td>
|
||||
{{ v.username }} a vendu {{ v.version.object.number }}x {{ v.version.object.name }} à
|
||||
<a href="{% url 'users:profil' v.version.object.facture.user_id %}">{{ v.version.object.facture.user.get_username }}</a>
|
||||
{% blocktrans with username=v.username number=v.version.object.number name=v.version.object.name %}{{ username }} has sold {{ number }}x {{ name }}{% endblocktrans %}
|
||||
{% with invoice=v.version.object.facture %}
|
||||
{% if invoice|is_facture %}
|
||||
{% trans " to" %}
|
||||
<a href="{% url 'users:profil' v.version.object.facture.facture.user_id %}">{{ v.version.object.facture.facture.user.get_username }}</a>
|
||||
{% if v.version.object.iscotisation %}
|
||||
(<i>+{{ v.version.object.duration }} mois</i>)
|
||||
(<i>{% blocktrans with duration=v.version.object.duration %}+{{ duration }} months{% endblocktrans %}</i>)
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
{% can_edit_history %}
|
||||
<td>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
|
||||
<i class="fa fa-times"></i>
|
||||
Annuler
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</td>
|
||||
{% acl_end %}
|
||||
|
@ -125,7 +134,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<td>{{ v.datetime }}</td>
|
||||
<td>
|
||||
{{ v.username }} a modifié une interface de
|
||||
{% blocktrans with username=v.username %}{{ username }} has edited an interface of{% endblocktrans %}
|
||||
<a href="{% url 'users:profil' v.version.object.machine.user_id %}">{{ v.version.object.machine.user.get_username }}</a>
|
||||
{% if v.comment != '' %}
|
||||
(<i>{{ v.comment }}</i>)
|
||||
|
@ -135,15 +144,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' v.rev_id %}">
|
||||
<i class="fa fa-times"></i>
|
||||
Annuler
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</td>
|
||||
{% acl_end %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</table>
|
||||
|
||||
{% if versions_list.paginator %}
|
||||
{% include "pagination.html" with list=versions_list %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -24,17 +24,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Supression d'action{% endblock %}
|
||||
{% block title %}{% trans "Deletion of actions" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form class="form" method="post">
|
||||
{% csrf_token %}
|
||||
<h4>Attention, voulez-vous vraiment annuler cette action {{ objet_name }} ( {{ objet }} ) ?</h4>
|
||||
{% bootstrap_button "Confirmer" button_type="submit" icon="trash" %}
|
||||
<h4>{% blocktrans %}Warning: are you sure you want to delete this action {{ objet_name }} ( {{ objet }} )?{% endblocktrans %}</h4>
|
||||
{% trans "Confirm" as tr_confirm %}
|
||||
{% bootstrap_button tr_confirm button_type="submit" icon="trash" %}
|
||||
</form>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Statistiques{% endblock %}
|
||||
{% block title %}{%trans "Statistics" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Actions effectuées</h2>
|
||||
<h2>{% trans "Actions performed" %}</h2>
|
||||
{% include "logs/aff_summary.html" with versions_list=versions_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,32 +24,34 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block sidebar %}
|
||||
{% can_view_app logs %}
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:index" %}">
|
||||
<i class="fa fa-clipboard-list"></i>
|
||||
Résumé
|
||||
{% trans "Summary" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-logs" %}">
|
||||
<i class="fa fa-calendar-alt"></i>
|
||||
Évènements
|
||||
{% trans "Events" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-general" %}">
|
||||
<i class="fa fa-chart-area"></i>
|
||||
Général
|
||||
{% trans "General" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-models" %}">
|
||||
<i class="fa fa-database"></i>
|
||||
Base de données
|
||||
{% trans "Database" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-actions" %}">
|
||||
<i class="fa fa-plug"></i>
|
||||
Actions de cablage
|
||||
{% trans "Wiring actions" %}
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-users" %}">
|
||||
<i class="fa fa-users"></i>
|
||||
Utilisateurs
|
||||
{% trans "Users" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Statistiques générales{% endblock %}
|
||||
{% block title %}{% trans "Statistics" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Statistiques générales</h2>
|
||||
<h2>{% trans "General statistics" %}</h2>
|
||||
{% include "logs/aff_stats_general.html" with stats_list=stats_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Statistiques{% endblock %}
|
||||
{% block title %}{% trans "Statistics" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Actions effectuées</h2>
|
||||
<h2>{% trans "Actions performed" %}</h2>
|
||||
{% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Statistiques des objets base de données{% endblock %}
|
||||
{% block title %}{% trans "Statistics" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Statistiques bdd</h2>
|
||||
<h2>{% trans "Database statistics" %}</h2>
|
||||
{% include "logs/aff_stats_models.html" with stats_list=stats_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,13 +24,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Statistiques par utilisateur{% endblock %}
|
||||
{% block title %}{% trans "Statistics" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Statistiques par utilisateur</h2>
|
||||
<h2>{% trans "Statistics about users" %}</h2>
|
||||
{% include "logs/aff_stats_users.html" with stats_list=stats_list %}
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
# 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.
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 021}10-1301 USA.
|
||||
"""logs.templatetags.logs_extra
|
||||
A templatetag to get the class name for a given object
|
||||
"""
|
||||
|
@ -34,6 +34,10 @@ def classname(obj):
|
|||
""" Returns the object class name """
|
||||
return obj.__class__.__name__
|
||||
|
||||
@register.filter
|
||||
def is_facture(baseinvoice):
|
||||
"""Returns True if a baseinvoice has a `Facture` child."""
|
||||
return hasattr(baseinvoice, 'facture')
|
||||
|
||||
@register.inclusion_tag('buttons/history.html')
|
||||
def history_button(instance, text=False, html_class=True):
|
||||
|
|
165
logs/views.py
165
logs/views.py
|
@ -188,10 +188,10 @@ def revert_action(request, revision_id):
|
|||
try:
|
||||
revision = Revision.objects.get(id=revision_id)
|
||||
except Revision.DoesNotExist:
|
||||
messages.error(request, u"Revision inexistante")
|
||||
messages.error(request, _("Nonexistent revision."))
|
||||
if request.method == "POST":
|
||||
revision.revert()
|
||||
messages.success(request, "L'action a été supprimée")
|
||||
messages.success(request, _("The action was deleted."))
|
||||
return redirect(reverse('logs:index'))
|
||||
return form({
|
||||
'objet': revision,
|
||||
|
@ -224,14 +224,14 @@ def stats_general(request):
|
|||
stats = [
|
||||
[ # First set of data (about users)
|
||||
[ # Headers
|
||||
"Categorie",
|
||||
"Nombre d'utilisateurs (total club et adhérents)",
|
||||
"Nombre d'adhérents",
|
||||
"Nombre de clubs"
|
||||
_("Category"),
|
||||
_("Number of users (members and clubs)"),
|
||||
_("Number of members"),
|
||||
_("Number of clubs")
|
||||
],
|
||||
{ # Data
|
||||
'active_users': [
|
||||
"Users actifs",
|
||||
_("Activated users"),
|
||||
User.objects.filter(state=User.STATE_ACTIVE).count(),
|
||||
(Adherent.objects
|
||||
.filter(state=Adherent.STATE_ACTIVE)
|
||||
|
@ -239,7 +239,7 @@ def stats_general(request):
|
|||
Club.objects.filter(state=Club.STATE_ACTIVE).count()
|
||||
],
|
||||
'inactive_users': [
|
||||
"Users désactivés",
|
||||
_("Disabled users"),
|
||||
User.objects.filter(state=User.STATE_DISABLED).count(),
|
||||
(Adherent.objects
|
||||
.filter(state=Adherent.STATE_DISABLED)
|
||||
|
@ -247,7 +247,7 @@ def stats_general(request):
|
|||
Club.objects.filter(state=Club.STATE_DISABLED).count()
|
||||
],
|
||||
'archive_users': [
|
||||
"Users archivés",
|
||||
_("Archived users"),
|
||||
User.objects.filter(state=User.STATE_ARCHIVE).count(),
|
||||
(Adherent.objects
|
||||
.filter(state=Adherent.STATE_ARCHIVE)
|
||||
|
@ -255,31 +255,31 @@ def stats_general(request):
|
|||
Club.objects.filter(state=Club.STATE_ARCHIVE).count()
|
||||
],
|
||||
'adherent_users': [
|
||||
"Cotisant à l'association",
|
||||
_("Contributing members"),
|
||||
_all_adherent.count(),
|
||||
_all_adherent.exclude(adherent__isnull=True).count(),
|
||||
_all_adherent.exclude(club__isnull=True).count()
|
||||
],
|
||||
'connexion_users': [
|
||||
"Utilisateurs bénéficiant d'une connexion",
|
||||
_("Users benefiting from a connection"),
|
||||
_all_has_access.count(),
|
||||
_all_has_access.exclude(adherent__isnull=True).count(),
|
||||
_all_has_access.exclude(club__isnull=True).count()
|
||||
],
|
||||
'ban_users': [
|
||||
"Utilisateurs bannis",
|
||||
_("Banned users"),
|
||||
_all_baned.count(),
|
||||
_all_baned.exclude(adherent__isnull=True).count(),
|
||||
_all_baned.exclude(club__isnull=True).count()
|
||||
],
|
||||
'whitelisted_user': [
|
||||
"Utilisateurs bénéficiant d'une connexion gracieuse",
|
||||
_("Users benefiting from a free connection"),
|
||||
_all_whitelisted.count(),
|
||||
_all_whitelisted.exclude(adherent__isnull=True).count(),
|
||||
_all_whitelisted.exclude(club__isnull=True).count()
|
||||
],
|
||||
'actives_interfaces': [
|
||||
"Interfaces actives (ayant accès au reseau)",
|
||||
_("Active interfaces (with access to the network)"),
|
||||
_all_active_interfaces_count.count(),
|
||||
(_all_active_interfaces_count
|
||||
.exclude(machine__user__adherent__isnull=True)
|
||||
|
@ -289,7 +289,7 @@ def stats_general(request):
|
|||
.count())
|
||||
],
|
||||
'actives_assigned_interfaces': [
|
||||
"Interfaces actives et assignées ipv4",
|
||||
_("Active interfaces assigned IPv4"),
|
||||
_all_active_assigned_interfaces_count.count(),
|
||||
(_all_active_assigned_interfaces_count
|
||||
.exclude(machine__user__adherent__isnull=True)
|
||||
|
@ -302,12 +302,12 @@ def stats_general(request):
|
|||
],
|
||||
[ # Second set of data (about ip adresses)
|
||||
[ # Headers
|
||||
"Range d'ip",
|
||||
"Vlan",
|
||||
"Nombre d'ip totales",
|
||||
"Ip assignées",
|
||||
"Ip assignées à une machine active",
|
||||
"Ip non assignées"
|
||||
_("IP range"),
|
||||
_("VLAN"),
|
||||
_("Total number of IP addresses"),
|
||||
_("Number of assigned IP addresses"),
|
||||
_("Number of IP address assigned to an activated machine"),
|
||||
_("Number of nonassigned IP addresses")
|
||||
],
|
||||
ip_dict # Data already prepared
|
||||
]
|
||||
|
@ -322,79 +322,87 @@ def stats_models(request):
|
|||
nombre d'users, d'écoles, de droits, de bannissements,
|
||||
de factures, de ventes, de banque, de machines, etc"""
|
||||
stats = {
|
||||
'Users': {
|
||||
'users': [User.PRETTY_NAME, User.objects.count()],
|
||||
'adherents': [Adherent.PRETTY_NAME, Adherent.objects.count()],
|
||||
'clubs': [Club.PRETTY_NAME, Club.objects.count()],
|
||||
'serviceuser': [ServiceUser.PRETTY_NAME,
|
||||
_("Users"): {
|
||||
'users': [User._meta.verbose_name, User.objects.count()],
|
||||
'adherents': [Adherent._meta.verbose_name, Adherent.objects.count()],
|
||||
'clubs': [Club._meta.verbose_name, Club.objects.count()],
|
||||
'serviceuser': [ServiceUser._meta.verbose_name,
|
||||
ServiceUser.objects.count()],
|
||||
'school': [School.PRETTY_NAME, School.objects.count()],
|
||||
'listright': [ListRight.PRETTY_NAME, ListRight.objects.count()],
|
||||
'listshell': [ListShell.PRETTY_NAME, ListShell.objects.count()],
|
||||
'ban': [Ban.PRETTY_NAME, Ban.objects.count()],
|
||||
'whitelist': [Whitelist.PRETTY_NAME, Whitelist.objects.count()]
|
||||
'school': [School._meta.verbose_name, School.objects.count()],
|
||||
'listright': [ListRight._meta.verbose_name, ListRight.objects.count()],
|
||||
'listshell': [ListShell._meta.verbose_name, ListShell.objects.count()],
|
||||
'ban': [Ban._meta.verbose_name, Ban.objects.count()],
|
||||
'whitelist': [Whitelist._meta.verbose_name, Whitelist.objects.count()]
|
||||
},
|
||||
'Cotisations': {
|
||||
_("Subscriptions"): {
|
||||
'factures': [
|
||||
Facture._meta.verbose_name.title(),
|
||||
Facture._meta.verbose_name,
|
||||
Facture.objects.count()
|
||||
],
|
||||
'vente': [
|
||||
Vente._meta.verbose_name.title(),
|
||||
Vente._meta.verbose_name,
|
||||
Vente.objects.count()
|
||||
],
|
||||
'cotisation': [
|
||||
Cotisation._meta.verbose_name.title(),
|
||||
Cotisation._meta.verbose_name,
|
||||
Cotisation.objects.count()
|
||||
],
|
||||
'article': [
|
||||
Article._meta.verbose_name.title(),
|
||||
Article._meta.verbose_name,
|
||||
Article.objects.count()
|
||||
],
|
||||
'banque': [
|
||||
Banque._meta.verbose_name.title(),
|
||||
Banque._meta.verbose_name,
|
||||
Banque.objects.count()
|
||||
],
|
||||
},
|
||||
'Machines': {
|
||||
'machine': [Machine.PRETTY_NAME, Machine.objects.count()],
|
||||
'typemachine': [MachineType.PRETTY_NAME,
|
||||
_("Machines"): {
|
||||
'machine': [Machine._meta.verbose_name,
|
||||
Machine.objects.count()],
|
||||
'typemachine': [MachineType._meta.verbose_name,
|
||||
MachineType.objects.count()],
|
||||
'typeip': [IpType.PRETTY_NAME, IpType.objects.count()],
|
||||
'extension': [Extension.PRETTY_NAME, Extension.objects.count()],
|
||||
'interface': [Interface.PRETTY_NAME, Interface.objects.count()],
|
||||
'alias': [Domain.PRETTY_NAME,
|
||||
'typeip': [IpType._meta.verbose_name,
|
||||
IpType.objects.count()],
|
||||
'extension': [Extension._meta.verbose_name,
|
||||
Extension.objects.count()],
|
||||
'interface': [Interface._meta.verbose_name,
|
||||
Interface.objects.count()],
|
||||
'alias': [Domain._meta.verbose_name,
|
||||
Domain.objects.exclude(cname=None).count()],
|
||||
'iplist': [IpList.PRETTY_NAME, IpList.objects.count()],
|
||||
'service': [Service.PRETTY_NAME, Service.objects.count()],
|
||||
'iplist': [IpList._meta.verbose_name,
|
||||
IpList.objects.count()],
|
||||
'service': [Service._meta.verbose_name,
|
||||
Service.objects.count()],
|
||||
'ouvertureportlist': [
|
||||
OuverturePortList.PRETTY_NAME,
|
||||
OuverturePortList._meta.verbose_name,
|
||||
OuverturePortList.objects.count()
|
||||
],
|
||||
'vlan': [Vlan.PRETTY_NAME, Vlan.objects.count()],
|
||||
'SOA': [SOA.PRETTY_NAME, SOA.objects.count()],
|
||||
'Mx': [Mx.PRETTY_NAME, Mx.objects.count()],
|
||||
'Ns': [Ns.PRETTY_NAME, Ns.objects.count()],
|
||||
'nas': [Nas.PRETTY_NAME, Nas.objects.count()],
|
||||
'vlan': [Vlan._meta.verbose_name, Vlan.objects.count()],
|
||||
'SOA': [SOA._meta.verbose_name, SOA.objects.count()],
|
||||
'Mx': [Mx._meta.verbose_name, Mx.objects.count()],
|
||||
'Ns': [Ns._meta.verbose_name, Ns.objects.count()],
|
||||
'nas': [Nas._meta.verbose_name, Nas.objects.count()],
|
||||
},
|
||||
'Topologie': {
|
||||
'switch': [Switch.PRETTY_NAME, Switch.objects.count()],
|
||||
'bornes': [AccessPoint.PRETTY_NAME, AccessPoint.objects.count()],
|
||||
'port': [Port.PRETTY_NAME, Port.objects.count()],
|
||||
'chambre': [Room.PRETTY_NAME, Room.objects.count()],
|
||||
'stack': [Stack.PRETTY_NAME, Stack.objects.count()],
|
||||
_("Topology"): {
|
||||
'switch': [Switch._meta.verbose_name,
|
||||
Switch.objects.count()],
|
||||
'bornes': [AccessPoint._meta.verbose_name,
|
||||
AccessPoint.objects.count()],
|
||||
'port': [Port._meta.verbose_name, Port.objects.count()],
|
||||
'chambre': [Room._meta.verbose_name, Room.objects.count()],
|
||||
'stack': [Stack._meta.verbose_name, Stack.objects.count()],
|
||||
'modelswitch': [
|
||||
ModelSwitch.PRETTY_NAME,
|
||||
ModelSwitch._meta.verbose_name,
|
||||
ModelSwitch.objects.count()
|
||||
],
|
||||
'constructorswitch': [
|
||||
ConstructorSwitch.PRETTY_NAME,
|
||||
ConstructorSwitch._meta.verbose_name,
|
||||
ConstructorSwitch.objects.count()
|
||||
],
|
||||
},
|
||||
'Actions effectuées sur la base':
|
||||
_("Actions performed"):
|
||||
{
|
||||
'revision': ["Nombre d'actions", Revision.objects.count()],
|
||||
'revision': [_("Number of actions"), Revision.objects.count()],
|
||||
},
|
||||
}
|
||||
return render(request, 'logs/stats_models.html', {'stats_list': stats})
|
||||
|
@ -408,35 +416,35 @@ def stats_users(request):
|
|||
de moyens de paiements par user, de banque par user,
|
||||
de bannissement par user, etc"""
|
||||
stats = {
|
||||
'Utilisateur': {
|
||||
'Machines': User.objects.annotate(
|
||||
_("User"): {
|
||||
_("Machines"): User.objects.annotate(
|
||||
num=Count('machine')
|
||||
).order_by('-num')[:10],
|
||||
'Facture': User.objects.annotate(
|
||||
_("Invoice"): User.objects.annotate(
|
||||
num=Count('facture')
|
||||
).order_by('-num')[:10],
|
||||
'Bannissement': User.objects.annotate(
|
||||
_("Ban"): User.objects.annotate(
|
||||
num=Count('ban')
|
||||
).order_by('-num')[:10],
|
||||
'Accès gracieux': User.objects.annotate(
|
||||
_("Whitelist"): User.objects.annotate(
|
||||
num=Count('whitelist')
|
||||
).order_by('-num')[:10],
|
||||
'Droits': User.objects.annotate(
|
||||
_("Rights"): User.objects.annotate(
|
||||
num=Count('groups')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
'Etablissement': {
|
||||
'Utilisateur': School.objects.annotate(
|
||||
_("School"): {
|
||||
_("User"): School.objects.annotate(
|
||||
num=Count('user')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
'Moyen de paiement': {
|
||||
'Utilisateur': Paiement.objects.annotate(
|
||||
_("Payment method"): {
|
||||
_("User"): Paiement.objects.annotate(
|
||||
num=Count('facture')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
'Banque': {
|
||||
'Utilisateur': Banque.objects.annotate(
|
||||
_("Bank"): {
|
||||
_("User"): Banque.objects.annotate(
|
||||
num=Count('facture')
|
||||
).order_by('-num')[:10],
|
||||
},
|
||||
|
@ -451,8 +459,8 @@ def stats_actions(request):
|
|||
utilisateurs.
|
||||
Affiche le nombre de modifications aggrégées par utilisateurs"""
|
||||
stats = {
|
||||
'Utilisateur': {
|
||||
'Action': User.objects.annotate(
|
||||
_("User"): {
|
||||
_("Action"): User.objects.annotate(
|
||||
num=Count('revision')
|
||||
).order_by('-num')[:40],
|
||||
},
|
||||
|
@ -489,14 +497,14 @@ def history(request, application, object_name, object_id):
|
|||
try:
|
||||
instance = model.get_instance(**kwargs)
|
||||
except model.DoesNotExist:
|
||||
messages.error(request, _("No entry found."))
|
||||
messages.error(request, _("Nonexistent entry."))
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
can, msg = instance.can_view(request.user)
|
||||
if not can:
|
||||
messages.error(request, msg or _("You cannot acces to this menu"))
|
||||
messages.error(request, msg or _("You don't have the right to access this menu."))
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
|
@ -513,3 +521,4 @@ def history(request, application, object_name, object_id):
|
|||
're2o/history.html',
|
||||
{'reversions': reversions, 'object': instance}
|
||||
)
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
Here are defined some functions to check acl on the application.
|
||||
"""
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
def can_view(user):
|
||||
|
@ -38,4 +39,6 @@ def can_view(user):
|
|||
viewing is granted and msg is a message (can be None).
|
||||
"""
|
||||
can = user.has_module_perms('machines')
|
||||
return can, None if can else "Vous ne pouvez pas voir cette application."
|
||||
return can, None if can else _("You don't have the right to view this"
|
||||
" application.")
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ from .models import (
|
|||
SshFp,
|
||||
Nas,
|
||||
Service,
|
||||
Role,
|
||||
OuverturePort,
|
||||
Ipv6List,
|
||||
OuverturePortList,
|
||||
|
@ -146,6 +147,11 @@ class ServiceAdmin(VersionAdmin):
|
|||
""" Admin view of a ServiceAdmin object """
|
||||
list_display = ('service_type', 'min_time_regen', 'regular_time_regen')
|
||||
|
||||
class RoleAdmin(VersionAdmin):
|
||||
""" Admin view of a RoleAdmin object """
|
||||
pass
|
||||
|
||||
|
||||
|
||||
admin.site.register(Machine, MachineAdmin)
|
||||
admin.site.register(MachineType, MachineTypeAdmin)
|
||||
|
@ -162,6 +168,7 @@ admin.site.register(IpList, IpListAdmin)
|
|||
admin.site.register(Interface, InterfaceAdmin)
|
||||
admin.site.register(Domain, DomainAdmin)
|
||||
admin.site.register(Service, ServiceAdmin)
|
||||
admin.site.register(Role, RoleAdmin)
|
||||
admin.site.register(Vlan, VlanAdmin)
|
||||
admin.site.register(Ipv6List, Ipv6ListAdmin)
|
||||
admin.site.register(Nas, NasAdmin)
|
||||
|
|
|
@ -37,6 +37,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.forms import ModelForm, Form
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from re2o.field_permissions import FieldPermissionFormMixin
|
||||
from re2o.mixins import FormRevMixin
|
||||
|
@ -53,6 +54,7 @@ from .models import (
|
|||
Txt,
|
||||
DName,
|
||||
Ns,
|
||||
Role,
|
||||
Service,
|
||||
Vlan,
|
||||
Srv,
|
||||
|
@ -73,7 +75,7 @@ class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(EditMachineForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Nom de la machine'
|
||||
self.fields['name'].label = _("Machine name")
|
||||
|
||||
|
||||
class NewMachineForm(EditMachineForm):
|
||||
|
@ -92,12 +94,11 @@ class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
|||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
user = kwargs.get('user')
|
||||
super(EditInterfaceForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['mac_address'].label = 'Adresse mac'
|
||||
self.fields['type'].label = 'Type de machine'
|
||||
self.fields['type'].empty_label = "Séléctionner un type de machine"
|
||||
self.fields['mac_address'].label = _("MAC address")
|
||||
self.fields['type'].label = _("Machine type")
|
||||
self.fields['type'].empty_label = _("Select a machine type")
|
||||
if "ipv4" in self.fields:
|
||||
self.fields['ipv4'].empty_label = ("Assignation automatique de "
|
||||
"l'ipv4")
|
||||
self.fields['ipv4'].empty_label = _("Automatic IPv4 assignment")
|
||||
self.fields['ipv4'].queryset = IpList.objects.filter(
|
||||
interface__isnull=True
|
||||
)
|
||||
|
@ -168,7 +169,7 @@ class DelAliasForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs objets alias"""
|
||||
alias = forms.ModelMultipleChoiceField(
|
||||
queryset=Domain.objects.all(),
|
||||
label="Alias actuels",
|
||||
label=_("Current aliases"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -189,15 +190,15 @@ class MachineTypeForm(FormRevMixin, ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(MachineTypeForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['type'].label = 'Type de machine à ajouter'
|
||||
self.fields['ip_type'].label = "Type d'ip relié"
|
||||
self.fields['type'].label = _("Machine type to add")
|
||||
self.fields['ip_type'].label = _("Related IP type")
|
||||
|
||||
|
||||
class DelMachineTypeForm(FormRevMixin, Form):
|
||||
"""Suppression d'un ou plusieurs machinetype"""
|
||||
machinetypes = forms.ModelMultipleChoiceField(
|
||||
queryset=MachineType.objects.none(),
|
||||
label="Types de machines actuelles",
|
||||
label=_("Current machine types"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -215,20 +216,21 @@ class IpTypeForm(FormRevMixin, ModelForm):
|
|||
stop après creation"""
|
||||
class Meta:
|
||||
model = IpType
|
||||
fields = ['type', 'extension', 'need_infra', 'domaine_ip_start',
|
||||
'domaine_ip_stop', 'prefix_v6', 'vlan', 'ouverture_ports']
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(IpTypeForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['type'].label = 'Type ip à ajouter'
|
||||
self.fields['type'].label = _("IP type to add")
|
||||
|
||||
|
||||
class EditIpTypeForm(IpTypeForm):
|
||||
"""Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait
|
||||
synchroniser les objets iplist"""
|
||||
class Meta(IpTypeForm.Meta):
|
||||
fields = ['extension', 'type', 'need_infra', 'prefix_v6', 'vlan',
|
||||
fields = ['extension', 'type', 'need_infra', 'domaine_ip_network', 'domaine_ip_netmask',
|
||||
'prefix_v6', 'prefix_v6_length',
|
||||
'vlan', 'reverse_v4', 'reverse_v6',
|
||||
'ouverture_ports']
|
||||
|
||||
|
||||
|
@ -236,7 +238,7 @@ class DelIpTypeForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs iptype"""
|
||||
iptypes = forms.ModelMultipleChoiceField(
|
||||
queryset=IpType.objects.none(),
|
||||
label="Types d'ip actuelles",
|
||||
label=_("Current IP types"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -258,17 +260,17 @@ class ExtensionForm(FormRevMixin, ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ExtensionForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['name'].label = 'Extension à ajouter'
|
||||
self.fields['origin'].label = 'Enregistrement A origin'
|
||||
self.fields['origin_v6'].label = 'Enregistrement AAAA origin'
|
||||
self.fields['soa'].label = 'En-tête SOA à utiliser'
|
||||
self.fields['name'].label = _("Extension to add")
|
||||
self.fields['origin'].label = _("A record origin")
|
||||
self.fields['origin_v6'].label = _("AAAA record origin")
|
||||
self.fields['soa'].label = _("SOA record to use")
|
||||
|
||||
|
||||
class DelExtensionForm(FormRevMixin, Form):
|
||||
"""Suppression d'une ou plusieurs extensions"""
|
||||
extensions = forms.ModelMultipleChoiceField(
|
||||
queryset=Extension.objects.none(),
|
||||
label="Extensions actuelles",
|
||||
label=_("Current extensions"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -307,7 +309,7 @@ class DelSOAForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs SOA"""
|
||||
soa = forms.ModelMultipleChoiceField(
|
||||
queryset=SOA.objects.none(),
|
||||
label="SOA actuels",
|
||||
label=_("Current SOA records"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -338,7 +340,7 @@ class DelMxForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs MX"""
|
||||
mx = forms.ModelMultipleChoiceField(
|
||||
queryset=Mx.objects.none(),
|
||||
label="MX actuels",
|
||||
label=_("Current MX records"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -371,7 +373,7 @@ class DelNsForm(FormRevMixin, Form):
|
|||
"""Suppresion d'un ou plusieurs NS"""
|
||||
ns = forms.ModelMultipleChoiceField(
|
||||
queryset=Ns.objects.none(),
|
||||
label="Enregistrements NS actuels",
|
||||
label=_("Current NS records"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -399,7 +401,7 @@ class DelTxtForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs TXT"""
|
||||
txt = forms.ModelMultipleChoiceField(
|
||||
queryset=Txt.objects.none(),
|
||||
label="Enregistrements Txt actuels",
|
||||
label=_("Current TXT records"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -427,7 +429,7 @@ class DelDNameForm(FormRevMixin, Form):
|
|||
"""Delete a set of DNAME entries"""
|
||||
dnames = forms.ModelMultipleChoiceField(
|
||||
queryset=Txt.objects.none(),
|
||||
label="Existing DNAME entries",
|
||||
label=_("Current DNAME records"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -455,7 +457,7 @@ class DelSrvForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs Srv"""
|
||||
srv = forms.ModelMultipleChoiceField(
|
||||
queryset=Srv.objects.none(),
|
||||
label="Enregistrements Srv actuels",
|
||||
label=_("Current SRV records"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -484,7 +486,7 @@ class DelNasForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs nas"""
|
||||
nas = forms.ModelMultipleChoiceField(
|
||||
queryset=Nas.objects.none(),
|
||||
label="Enregistrements Nas actuels",
|
||||
label=_("Current NAS devices"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -497,6 +499,38 @@ class DelNasForm(FormRevMixin, Form):
|
|||
self.fields['nas'].queryset = Nas.objects.all()
|
||||
|
||||
|
||||
class RoleForm(FormRevMixin, ModelForm):
|
||||
"""Add and edit role."""
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(RoleForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['servers'].queryset = (Interface.objects.all()
|
||||
.select_related(
|
||||
'domain__extension'
|
||||
))
|
||||
|
||||
|
||||
class DelRoleForm(FormRevMixin, Form):
|
||||
"""Deletion of one or several roles."""
|
||||
role = forms.ModelMultipleChoiceField(
|
||||
queryset=Role.objects.none(),
|
||||
label=_("Current roles"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
instances = kwargs.pop('instances', None)
|
||||
super(DelRoleForm, self).__init__(*args, **kwargs)
|
||||
if instances:
|
||||
self.fields['role'].queryset = instances
|
||||
else:
|
||||
self.fields['role'].queryset = Role.objects.all()
|
||||
|
||||
|
||||
class ServiceForm(FormRevMixin, ModelForm):
|
||||
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
|
||||
class Meta:
|
||||
|
@ -525,7 +559,7 @@ class DelServiceForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs service"""
|
||||
service = forms.ModelMultipleChoiceField(
|
||||
queryset=Service.objects.none(),
|
||||
label="Services actuels",
|
||||
label=_("Current services"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -553,7 +587,7 @@ class DelVlanForm(FormRevMixin, Form):
|
|||
"""Suppression d'un ou plusieurs vlans"""
|
||||
vlan = forms.ModelMultipleChoiceField(
|
||||
queryset=Vlan.objects.none(),
|
||||
label="Vlan actuels",
|
||||
label=_("Current VLANs"),
|
||||
widget=forms.CheckboxSelectMultiple
|
||||
)
|
||||
|
||||
|
@ -611,3 +645,4 @@ class SshFpForm(FormRevMixin, ModelForm):
|
|||
prefix=prefix,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
|
BIN
machines/locale/fr/LC_MESSAGES/django.mo
Normal file
BIN
machines/locale/fr/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
1748
machines/locale/fr/LC_MESSAGES/django.po
Normal file
1748
machines/locale/fr/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load diff
27
machines/migrations/0086_role.py
Normal file
27
machines/migrations/0086_role.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-23 14:07
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import re2o.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0085_sshfingerprint'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Role',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role_type', models.CharField(max_length=255, unique=True)),
|
||||
('servers', models.ManyToManyField(to='machines.Interface')),
|
||||
('specific_role', models.CharField(blank=True, choices=[('dhcp-server', 'DHCP server'), ('switch-conf-server', 'Switches configuration server'), ('dns-recursif-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', 'Gatewaw')], max_length=32, null=True))
|
||||
],
|
||||
options={'permissions': (('view_role', 'Can view a role.'),), 'verbose_name': 'Server role'},
|
||||
bases=(re2o.mixins.RevMixin, re2o.mixins.AclMixin, models.Model),
|
||||
),
|
||||
]
|
25
machines/migrations/0087_dnssec.py
Normal file
25
machines/migrations/0087_dnssec.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-06-25 15:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0086_role'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='iptype',
|
||||
name='dnssec_reverse_v4',
|
||||
field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv4'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='iptype',
|
||||
name='dnssec_reverse_v6',
|
||||
field=models.BooleanField(default=False, help_text='Activer DNSSEC sur le reverse DNS IPv6'),
|
||||
),
|
||||
]
|
21
machines/migrations/0088_iptype_prefix_v6_length.py
Normal file
21
machines/migrations/0088_iptype_prefix_v6_length.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-07-16 18:46
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0087_dnssec'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='iptype',
|
||||
name='prefix_v6_length',
|
||||
field=models.IntegerField(default=64, validators=[django.core.validators.MaxValueValidator(128), django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
]
|
21
machines/migrations/0089_auto_20180805_1148.py
Normal file
21
machines/migrations/0089_auto_20180805_1148.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-05 09:48
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import macaddress.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0088_iptype_prefix_v6_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='interface',
|
||||
name='mac_address',
|
||||
field=macaddress.fields.MACAddressField(integer=False, max_length=17),
|
||||
),
|
||||
]
|
20
machines/migrations/0090_auto_20180805_1459.py
Normal file
20
machines/migrations/0090_auto_20180805_1459.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-05 12:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0089_auto_20180805_1148'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ipv6list',
|
||||
name='ipv6',
|
||||
field=models.GenericIPAddressField(protocol='IPv6'),
|
||||
),
|
||||
]
|
26
machines/migrations/0091_auto_20180806_2310.py
Normal file
26
machines/migrations/0091_auto_20180806_2310.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-06 21:10
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0090_auto_20180805_1459'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='iptype',
|
||||
name='domaine_ip_netmask',
|
||||
field=models.IntegerField(default=24, help_text='Netmask for the ipv4 range domain', validators=[django.core.validators.MaxValueValidator(31), django.core.validators.MinValueValidator(8)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='iptype',
|
||||
name='domaine_ip_network',
|
||||
field=models.GenericIPAddressField(blank=True, help_text='Network containing the ipv4 range domain ip start/stop. Optional', null=True, protocol='IPv4'),
|
||||
),
|
||||
]
|
25
machines/migrations/0092_auto_20180807_0926.py
Normal file
25
machines/migrations/0092_auto_20180807_0926.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-07 07:26
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0091_auto_20180806_2310'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='iptype',
|
||||
old_name='dnssec_reverse_v4',
|
||||
new_name='reverse_v4',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='iptype',
|
||||
old_name='dnssec_reverse_v6',
|
||||
new_name='reverse_v6',
|
||||
),
|
||||
]
|
26
machines/migrations/0093_auto_20180807_1115.py
Normal file
26
machines/migrations/0093_auto_20180807_1115.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-07 09:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0092_auto_20180807_0926'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='mx',
|
||||
name='name',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='machines.Domain'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='mx',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(),
|
||||
),
|
||||
]
|
221
machines/migrations/0094_auto_20180815_1918.py
Normal file
221
machines/migrations/0094_auto_20180815_1918.py
Normal file
|
@ -0,0 +1,221 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-08-15 17:18
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0093_auto_20180807_1115'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='dname',
|
||||
options={'permissions': (('view_dname', 'Can view a DNAME record object'),), 'verbose_name': 'DNAME record', 'verbose_name_plural': 'DNAME records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='domain',
|
||||
options={'permissions': (('view_domain', 'Can view a domain object'),), 'verbose_name': 'domain', 'verbose_name_plural': 'domains'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='extension',
|
||||
options={'permissions': (('view_extension', 'Can view an extension object'), ('use_all_extension', 'Can use all extensions')), 'verbose_name': 'DNS extension', 'verbose_name_plural': 'DNS extensions'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='interface',
|
||||
options={'permissions': (('view_interface', 'Can view an interface object'), ('change_interface_machine', 'Can change the owner of an interface')), 'verbose_name': 'interface', 'verbose_name_plural': 'interfaces'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='iplist',
|
||||
options={'permissions': (('view_iplist', 'Can view an IPv4 addresses list object'),), 'verbose_name': 'IPv4 addresses list', 'verbose_name_plural': 'IPv4 addresses lists'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='iptype',
|
||||
options={'permissions': (('view_iptype', 'Can view an IP type object'), ('use_all_iptype', 'Can use all IP types')), 'verbose_name': 'IP type', 'verbose_name_plural': 'IP types'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ipv6list',
|
||||
options={'permissions': (('view_ipv6list', 'Can view an IPv6 addresses list object'), ('change_ipv6list_slaac_ip', 'Can change the SLAAC value of an IPv6 addresses list')), 'verbose_name': 'IPv6 addresses list', 'verbose_name_plural': 'IPv6 addresses lists'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='machine',
|
||||
options={'permissions': (('view_machine', 'Can view a machine object'), ('change_machine_user', 'Can change the user of a machine')), 'verbose_name': 'machine', 'verbose_name_plural': 'machines'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='machinetype',
|
||||
options={'permissions': (('view_machinetype', 'Can view a machine type object'), ('use_all_machinetype', 'Can use all machine types')), 'verbose_name': 'machine type', 'verbose_name_plural': 'machine types'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='mx',
|
||||
options={'permissions': (('view_mx', 'Can view an MX record object'),), 'verbose_name': 'MX record', 'verbose_name_plural': 'MX records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='nas',
|
||||
options={'permissions': (('view_nas', 'Can view a NAS device object'),), 'verbose_name': 'NAS device', 'verbose_name_plural': 'NAS devices'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ns',
|
||||
options={'permissions': (('view_ns', 'Can view an NS record object'),), 'verbose_name': 'NS record', 'verbose_name_plural': 'NS records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ouvertureport',
|
||||
options={'verbose_name': 'ports openings'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='ouvertureportlist',
|
||||
options={'permissions': (('view_ouvertureportlist', 'Can view a ports opening list object'),), 'verbose_name': 'ports opening list', 'verbose_name_plural': 'ports opening lists'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='role',
|
||||
options={'permissions': (('view_role', 'Can view a role object'),), 'verbose_name': 'server role', 'verbose_name_plural': 'server roles'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='service',
|
||||
options={'permissions': (('view_service', 'Can view a service object'),), 'verbose_name': 'service to generate (DHCP, DNS, ...)', 'verbose_name_plural': 'services to generate (DHCP, DNS, ...)'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='service_link',
|
||||
options={'permissions': (('view_service_link', 'Can view a service server link object'),), 'verbose_name': 'link between service and server', 'verbose_name_plural': 'links between service and server'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='soa',
|
||||
options={'permissions': (('view_soa', 'Can view an SOA record object'),), 'verbose_name': 'SOA record', 'verbose_name_plural': 'SOA records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='srv',
|
||||
options={'permissions': (('view_srv', 'Can view an SRV record object'),), 'verbose_name': 'SRV record', 'verbose_name_plural': 'SRV records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='sshfp',
|
||||
options={'permissions': (('view_sshfp', 'Can view an SSHFP record object'),), 'verbose_name': 'SSHFP record', 'verbose_name_plural': 'SSHFP records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='txt',
|
||||
options={'permissions': (('view_txt', 'Can view a TXT record object'),), 'verbose_name': 'TXT record', 'verbose_name_plural': 'TXT records'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='vlan',
|
||||
options={'permissions': (('view_vlan', 'Can view a VLAN object'),), 'verbose_name': 'VLAN', 'verbose_name_plural': 'VLANs'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='domain',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Mandatory and unique, must not contain dots.', max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='extension',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Zone name, must begin with a dot (.example.org)', max_length=255, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='extension',
|
||||
name='origin',
|
||||
field=models.ForeignKey(blank=True, help_text='A record associated with the zone', null=True, on_delete=django.db.models.deletion.PROTECT, to='machines.IpList'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='extension',
|
||||
name='origin_v6',
|
||||
field=models.GenericIPAddressField(blank=True, help_text='AAAA record associated with the zone', null=True, protocol='IPv6'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='iptype',
|
||||
name='domaine_ip_netmask',
|
||||
field=models.IntegerField(default=24, help_text="Netmask for the domain's IPv4 range", validators=[django.core.validators.MaxValueValidator(31), django.core.validators.MinValueValidator(8)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='iptype',
|
||||
name='domaine_ip_network',
|
||||
field=models.GenericIPAddressField(blank=True, help_text="Network containing the domain's IPv4 range (optional)", null=True, protocol='IPv4'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='iptype',
|
||||
name='reverse_v4',
|
||||
field=models.BooleanField(default=False, help_text='Enable reverse DNS for IPv4'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='iptype',
|
||||
name='reverse_v6',
|
||||
field=models.BooleanField(default=False, help_text='Enable reverse DNS for IPv6'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='machine',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, help_text='Optional', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='ouvertureportlist',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Name of the ports configuration', max_length=255),
|
||||
),
|
||||
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'), ('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),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='min_time_regen',
|
||||
field=models.DurationField(default=datetime.timedelta(0, 60), help_text='Minimal time before regeneration of the service.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='regular_time_regen',
|
||||
field=models.DurationField(default=datetime.timedelta(0, 3600), help_text='Maximal time before regeneration of the service.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='soa',
|
||||
name='expire',
|
||||
field=models.PositiveIntegerField(default=3600000, help_text='Seconds before the secondary DNS stop answering requests in case of primary DNS timeout'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='soa',
|
||||
name='mail',
|
||||
field=models.EmailField(help_text='Contact email address for the zone', max_length=254),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='soa',
|
||||
name='refresh',
|
||||
field=models.PositiveIntegerField(default=86400, help_text='Seconds before the secondary DNS have to ask the primary DNS serial to detect a modification'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='soa',
|
||||
name='retry',
|
||||
field=models.PositiveIntegerField(default=7200, help_text='Seconds before the secondary DNS ask the serial again in case of a primary DNS timeout'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='soa',
|
||||
name='ttl',
|
||||
field=models.PositiveIntegerField(default=172800, help_text='Time to Live'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='srv',
|
||||
name='port',
|
||||
field=models.PositiveIntegerField(help_text='TCP/UDP port', validators=[django.core.validators.MaxValueValidator(65535)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='srv',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Priority of the target server (positive integer value, the lower it is, the more the server will be used if available)', validators=[django.core.validators.MaxValueValidator(65535)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='srv',
|
||||
name='target',
|
||||
field=models.ForeignKey(help_text='Target server', on_delete=django.db.models.deletion.PROTECT, to='machines.Domain'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='srv',
|
||||
name='ttl',
|
||||
field=models.PositiveIntegerField(default=172800, help_text='Time to Live'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='srv',
|
||||
name='weight',
|
||||
field=models.PositiveIntegerField(default=0, help_text='Relative weight for records with the same priority (integer value between 0 and 65535)', validators=[django.core.validators.MaxValueValidator(65535)]),
|
||||
),
|
||||
]
|
File diff suppressed because it is too large
Load diff
|
@ -23,12 +23,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
{% load logs_extra %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alias</th>
|
||||
<th>{% trans "Aliases" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -22,12 +22,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Target zone</th>
|
||||
<th>Record</th>
|
||||
<th>{% trans "Target zone" %}</th>
|
||||
<th>{% trans "Record" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -43,6 +44,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
</table>
|
||||
|
||||
|
|
|
@ -25,17 +25,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load design %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Extension</th>
|
||||
<th>Droit infra pour utiliser ?</th>
|
||||
<th>Enregistrement SOA</th>
|
||||
<th>Enregistrement A origin</th>
|
||||
<th>{% trans "Extension" %}</th>
|
||||
<th>{% trans "'infra' right required" %}</th>
|
||||
<th>{% trans "SOA record" %}</th>
|
||||
<th>{% trans "A record origin" %}</th>
|
||||
{% if ipv6_enabled %}
|
||||
<th>Enregistrement AAAA origin</th>
|
||||
<th>{% trans "AAAA record origin" %}</th>
|
||||
{% endif %}
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -44,7 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<td>{{ extension.name }}</td>
|
||||
<td>{{ extension.need_infra|tick }}</td>
|
||||
<td>{{ extension.soa}}</td>
|
||||
<td>{{ extension.soa }}</td>
|
||||
<td>{{ extension.origin }}</td>
|
||||
{% if ipv6_enabled %}
|
||||
<td>{{ extension.origin_v6 }}</td>
|
||||
|
@ -59,3 +60,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -26,18 +26,20 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type d'ip</th>
|
||||
<th>Extension</th>
|
||||
<th>Nécessite l'autorisation infra</th>
|
||||
<th>Plage ipv4</th>
|
||||
<th>Préfixe v6</th>
|
||||
<th>Sur vlan</th>
|
||||
<th>Ouverture ports par défault</th>
|
||||
<th></th>
|
||||
<th>{% trans "IP type" %}</th>
|
||||
<th>{% trans "Extension" %}</th>
|
||||
<th>{% trans "'infra' right required" %}</th>
|
||||
<th>{% trans "IPv4 range" %}</th>
|
||||
<th>{% trans "v6 prefix" %}</th>
|
||||
<th>{% trans "DNSSEC reverse v4/v6" %}</th>
|
||||
<th>{% trans "On VLAN(s)" %}</th>
|
||||
<th>{% trans "Default ports opening" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -46,8 +48,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ type.type }}</td>
|
||||
<td>{{ type.extension }}</td>
|
||||
<td>{{ type.need_infra|tick }}</td>
|
||||
<td>{{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}</td>
|
||||
<td>{{ type.prefix_v6 }}</td>
|
||||
<td>{{ type.domaine_ip_start }}-{{ type.domaine_ip_stop }}{% if type.ip_network %}<b><u> on </b></u>{{ type.ip_network }}{% endif %}</td>
|
||||
<td>{{ type.prefix_v6 }}/{{ type.prefix_v6_length }}</td>
|
||||
<td>{{ type.reverse_v4|tick }}/{{ type.reverse_v6|tick }}</td>
|
||||
<td>{{ type.vlan }}</td>
|
||||
<td>{{ type.ouverture_ports }}</td>
|
||||
<td class="text-right">
|
||||
|
@ -60,3 +63,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -24,12 +24,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ipv6</th>
|
||||
<th>Slaac</th>
|
||||
<th>{% trans "IPv6 addresses" %}</th>
|
||||
<th>{% trans "SLAAC" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="table-responsive">
|
||||
{% if machines_list.paginator %}
|
||||
|
@ -39,23 +40,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<col width="144px">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<th>{% include "buttons/sort.html" with prefix='machine' col='name' text='Nom DNS' %}</th>
|
||||
<th>Type</th>
|
||||
<th>MAC</th>
|
||||
<th>IP</th>
|
||||
<th>Actions</th>
|
||||
{% trans "DNS name" as tr_dns_name %}
|
||||
<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>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
<tbody>
|
||||
{% for machine in machines_list %}
|
||||
<tr class="info">
|
||||
<td colspan="4">
|
||||
<b>{{ machine.name|default:'<i>Pas de nom</i>' }}</b> <i class="fa-angle-right"></i>
|
||||
<a href="{% url 'users:profil' userid=machine.user.id %}" title="Voir le profil">
|
||||
{% trans "No name" as tr_no_name %}
|
||||
{% trans "View the profile" as tr_view_the_profile %}
|
||||
<b>{{ machine.name|default:tr_no_name }}</b> <i class="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>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% can_create Interface machine.id %}
|
||||
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %}
|
||||
{% trans "Create an interface" as tr_create_an_interface %}
|
||||
{% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc=tr_create_an_interface %}
|
||||
{% acl_end %}
|
||||
{% history_button machine %}
|
||||
{% can_delete machine %}
|
||||
|
@ -68,8 +73,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>
|
||||
{% if interface.domain.related_domain.all %}
|
||||
{{ interface.domain }}
|
||||
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseDomain_{{interface.id}}" aria-expanded="true" aria-controls="collapseDomain_{{interface.id}}">
|
||||
Afficher les alias
|
||||
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseDomain_{{ interface.id }}" aria-expanded="true" aria-controls="collapseDomain_{{ interface.id }}">
|
||||
{% trans "Display the aliases" %}
|
||||
</button>
|
||||
{% else %}
|
||||
{{ interface.domain }}
|
||||
|
@ -86,8 +91,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<br>
|
||||
{% if ipv6_enabled and interface.ipv6 != 'None'%}
|
||||
<b>IPv6</b>
|
||||
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseIpv6_{{interface.id}}" aria-expanded="true" aria-controls="collapseIpv6_{{interface.id}}">
|
||||
Afficher l'IPV6
|
||||
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#collapseIpv6_{{ interface.id }}" aria-expanded="true" aria-controls="collapseIpv6_{{ interface.id }}">
|
||||
{% trans "Display the IPv6 address" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
@ -97,39 +102,44 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-edit"></i> <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu pull-right" aria-labelledby="editioninterface">
|
||||
<ul class="dropdown-menu" aria-labelledby="editioninterface">
|
||||
{% can_edit interface %}
|
||||
<li>
|
||||
<a href="{% url 'machines:edit-interface' interface.id %}">
|
||||
<i class="fa fa-edit"></i> Editer
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans " Edit"%}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
{% can_create Domain interface.id %}
|
||||
<li>
|
||||
<a href="{% url 'machines:index-alias' interface.id %}">
|
||||
<i class="fa fa-edit"></i> Gerer les alias
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans " Manage the aliases" %}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
{% can_create Ipv6List interface.id %}
|
||||
<li>
|
||||
<a href="{% url 'machines:index-ipv6' interface.id %}">
|
||||
<i class="fa fa-edit"></i> Gerer les ipv6
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans " Manage the IPv6 addresses" %}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
{% can_create SshFp interface.machine.id %}
|
||||
<li>
|
||||
<a href="{% url 'machines:index-sshfp' interface.machine.id %}">
|
||||
<i class="fa fa-edit"></i> Manage the SSH fingerprints
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans " Manage the SSH fingerprints" %}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
{% can_create OuverturePortList %}
|
||||
<li>
|
||||
<a href="{% url 'machines:port-config' interface.id%}">
|
||||
<i class="fa fa-edit"></i> Gerer la configuration des ports
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans " Manage the ports configuration" %}
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
|
@ -142,7 +152,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% if ipv6_enabled and interface.ipv6 != 'None'%}
|
||||
<tr>
|
||||
<td colspan=5 style="border-top: none; padding: 1px;">
|
||||
|
@ -150,16 +159,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<ul class="list-group" style="margin-bottom: 0px;">
|
||||
{% for ipv6 in interface.ipv6.all %}
|
||||
<li class="list-group-item col-xs-6 col-sm-6 col-md-6" style="border: none;">
|
||||
{{ipv6}}
|
||||
{{ ipv6 }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<tr>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if interface.domain.related_domain.all %}
|
||||
<tr>
|
||||
<td colspan=5 style="border-top: none; padding: 1px;">
|
||||
|
@ -176,7 +183,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<tr>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<tr>
|
||||
|
@ -184,19 +191,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</thead>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
$("#machines_table").ready( function() {
|
||||
var alias_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.domain.related_domain.all %}$("#collapseDomain_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}];
|
||||
var alias_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.domain.related_domain.all %}$("#collapseDomain_{{ interface.id }}"), {% endif %}{% endfor %}{% endfor %}];
|
||||
for (var i=0 ; i<alias_div.length ; i++) {
|
||||
alias_div[i].collapse('hide');
|
||||
}
|
||||
} );
|
||||
$("#machines_table").ready( function() {
|
||||
var ipv6_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.ipv6.all %}$("#collapseIpv6_{{interface.id}}"), {% endif %}{% endfor %}{% endfor %}];
|
||||
var ipv6_div = [{% for machine in machines_list %}{% for interface in machine.interface_set.all %}{% if interface.ipv6.all %}$("#collapseIpv6_{{ interface.id }}"), {% endif %}{% endfor %}{% endfor %}];
|
||||
for (var i=0 ; i<ipv6_div.length ; i++) {
|
||||
ipv6_div[i].collapse('hide');
|
||||
}
|
||||
|
@ -207,3 +213,4 @@ $("#machines_table").ready( function() {
|
|||
{% include "pagination.html" with list=machines_list %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -24,12 +24,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type de machine</th>
|
||||
<th>Type d'ip correspondant</th>
|
||||
<th>{% trans "Machine type" %}</th>
|
||||
<th>{% trans "Matching IP type" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -24,14 +24,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone concernée</th>
|
||||
<th>Priorité</th>
|
||||
<th>Enregistrement</th>
|
||||
<th></th>
|
||||
<th>{% trans "Concerned zone" %}</th>
|
||||
<th>{% trans "Priority" %}</th>
|
||||
<th>{% trans "Record" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -50,4 +50,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
|
|
|
@ -25,15 +25,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load design %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Type du nas</th>
|
||||
<th>Type de machine reliées au nas</th>
|
||||
<th>Mode d'accès</th>
|
||||
<th>Autocapture mac</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "NAS device type" %}</th>
|
||||
<th>{% trans "Machine type linked to the NAS device" %}</th>
|
||||
<th>{% trans "Access mode" %}</th>
|
||||
<th>{% trans "MAC address auto capture" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -24,13 +24,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone concernée</th>
|
||||
<th>Interface autoritaire de la zone</th>
|
||||
<th></th>
|
||||
<th>{% trans "Concerned zone" %}</th>
|
||||
<th>{% trans "Authoritarian interface for the concerned zone" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -48,4 +48,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
|
|
54
machines/templates/machines/aff_role.html
Normal file
54
machines/templates/machines/aff_role.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% 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 "Role name" %}</th>
|
||||
<th>{% trans "Specific role" %}</th>
|
||||
<th>{% trans "Servers" %}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for role in role_list %}
|
||||
<tr>
|
||||
<td>{{ role.role_type }}</td>
|
||||
<td>{{ role.specific_role }}</td>
|
||||
<td>{% for serv in role.servers.all %}{{ serv }}, {% endfor %}</td>
|
||||
<td class="text-right">
|
||||
{% can_edit role %}
|
||||
{% include 'buttons/edit.html' with href='machines:edit-role' id=role.id %}
|
||||
{% acl_end %}
|
||||
{% history_button role %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
@ -23,15 +23,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load design %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom du service</th>
|
||||
<th>Serveur</th>
|
||||
<th>Dernière régénération</th>
|
||||
<th>Régénération nécessaire</th>
|
||||
<th>Régénération activée</th>
|
||||
<th>{% trans "Service name" %}</th>
|
||||
<th>{% trans "Server" %}</th>
|
||||
<th>{% trans "Last regeneration" %}</th>
|
||||
<th>{% trans "Regeneration required" %}</th>
|
||||
<th>{% trans "Regeneration activated" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for server in servers_list %}
|
||||
|
@ -39,8 +40,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ server.service }}</td>
|
||||
<td>{{ server.server }}</td>
|
||||
<td>{{ server.last_regen }}</td>
|
||||
<td>{{ server.asked_regen| tick }}</td>
|
||||
<td>{{ server.need_regen | tick }}</td>
|
||||
<td>{{ server.asked_regen|tick }}</td>
|
||||
<td>{{ server.need_regen|tick }}</td>
|
||||
<td class="text-right">
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom du service</th>
|
||||
<th>Temps minimum avant nouvelle régénération</th>
|
||||
<th>Temps avant nouvelle génération obligatoire (max)</th>
|
||||
<th>Serveurs inclus</th>
|
||||
<th></th>
|
||||
<th>{% trans "Service name" %}</th>
|
||||
<th>{% trans "Minimal time before regeneration" %}</th>
|
||||
<th>{% trans "Maximal time before regeneration" %}</th>
|
||||
<th>{% trans "Included servers" %}</th>
|
||||
<th>{% trans "Ask for regeneration" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -42,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td>{{ service.min_time_regen }}</td>
|
||||
<td>{{ service.regular_time_regen }}</td>
|
||||
<td>{% for serv in service.servers.all %}{{ serv }}, {% endfor %}</td>
|
||||
<td><a role="button" class="btn btn-danger" href="{% url 'machines:regen-service' service.id %}"><i class="fa fa-sync"></i></a></td>
|
||||
<td class="text-right">
|
||||
{% can_edit service %}
|
||||
{% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %}
|
||||
|
|
|
@ -24,17 +24,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th>Mail</th>
|
||||
<th>Refresh</th>
|
||||
<th>Retry</th>
|
||||
<th>Expire</th>
|
||||
<th>TTL</th>
|
||||
<th></th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Mail" %}</th>
|
||||
<th>{% trans "Refresh" %}</th>
|
||||
<th>{% trans "Retry" %}</th>
|
||||
<th>{% trans "Expire" %}</th>
|
||||
<th>{% trans "TTL" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -56,4 +56,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
|
|
|
@ -24,19 +24,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>Protocole</th>
|
||||
<th>Extension</th>
|
||||
<th>TTL</th>
|
||||
<th>Priorité</th>
|
||||
<th>Poids</th>
|
||||
<th>Port</th>
|
||||
<th>Cible</th>
|
||||
<th></th>
|
||||
<th>{% trans "Service" %}</th>
|
||||
<th>{% trans "Protocol" %}</th>
|
||||
<th>{% trans "Extension" %}</th>
|
||||
<th>{% trans "TTL" %}</th>
|
||||
<th>{% trans "Priority" %}</th>
|
||||
<th>{% trans "Weight" %}</th>
|
||||
<th>{% trans "Port" %}</th>
|
||||
<th>{% trans "Target" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -60,4 +60,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
|
|
|
@ -21,15 +21,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
{% load logs_extra %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped long_text">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="long_text">SSH public key</th>
|
||||
<th>Algorithm used</th>
|
||||
<th>Comment</th>
|
||||
<th class="long_text">{% trans "SSH public key" %}</th>
|
||||
<th>{% trans "Algorithm used" %}</th>
|
||||
<th>{% trans "Comment" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -42,10 +43,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% can_edit sshfp %}
|
||||
{% include 'buttons/edit.html' with href='machines:edit-sshfp' id=sshfp.id %}
|
||||
{% acl_end %}
|
||||
{% history_button sshfp %}
|
||||
{% can_delete sshfp %}
|
||||
{% include 'buttons/suppr.html' with href='machines:del-sshfp' id=sshfp.id %}
|
||||
{% acl_end %}
|
||||
{% history_button sshfp %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -24,13 +24,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Zone concernée</th>
|
||||
<th>Enregistrement</th>
|
||||
<th></th>
|
||||
<th>{% trans "Concerned zone" %}</th>
|
||||
<th>{% trans "Record" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -48,4 +48,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
||||
|
|
|
@ -24,15 +24,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
{% load logs_extra %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Nom</th>
|
||||
<th>Commentaire</th>
|
||||
<th>Ranges ip</th>
|
||||
<th>{% trans "ID" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Comment" %}</th>
|
||||
<th>{% trans "IP ranges" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -52,3 +53,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -24,17 +24,20 @@ 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 "Creation and editing of machines" %}{% 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 object {{ objet_name }} ( {{ objet }} )?{% endblocktrans %}</h4>
|
||||
{% trans "Confirm" as tr_confirm %}
|
||||
{% bootstrap_button tr_confirm button_type="submit" icon="trash" %}
|
||||
</form>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,8 +24,9 @@ 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 "Machines" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% bootstrap_form_errors port_list %}
|
||||
|
@ -46,10 +47,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</div>
|
||||
|
||||
<p>
|
||||
<input class="btn btn-primary btn-sm" role="button" value="Ajouter un port" id="add_one">
|
||||
{% trans "Add a port" as value %}
|
||||
<input class="btn btn-primary btn-sm" role="button" value=value id="add_one">
|
||||
</p>
|
||||
|
||||
{% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %}
|
||||
{% trans "Create or edit" as tr_create_or_edit %}
|
||||
{% bootstrap_button tr_create_or_edit button_type="submit" icon="star" %}
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
var template = `{{ports.empty_form}}`;
|
||||
|
@ -67,3 +69,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -24,11 +24,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Machines{% endblock %}
|
||||
{% block title %}{% trans "Machines" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Machines</h2>
|
||||
<h2>{% trans "Machines" %}</h2>
|
||||
{% include "machines/aff_machines.html" with machines_list=machines_list %}
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -24,13 +24,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% endcomment %}
|
||||
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Machines{% endblock %}
|
||||
{% block title %}{% trans "Machines" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Liste des alias de l'interface</h2>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-alias' interface_id %}"><i class="fa fa-plus"></i> Ajouter un alias</a>
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-alias' interface_id %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs alias</a>
|
||||
<h2>{% trans "List of the aliases of the interface" %}</h2>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-alias' interface_id %}"><i 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 %}
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -28,57 +28,60 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Machines{% endblock %}
|
||||
{% block title %}{% trans "Machines" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Liste des extensions</h2>
|
||||
<h2>{% trans "List of extensions" %}</h2>
|
||||
{% can_create Extension %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-extension' %}"><i class="fa fa-plus"></i> Ajouter une extension</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-extension' %}"><i class="fa fa-plus"></i>{% trans " Add an extension" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-extension' %}"><i class="fa fa-trash"></i> Supprimer une ou plusieurs extensions</a>
|
||||
<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 %}
|
||||
|
||||
<h2>Liste des enregistrements SOA</h2>
|
||||
<h2>{% trans "List of SOA records" %}</h2>
|
||||
{% can_create SOA %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement SOA</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-soa' %}"><i class="fa fa-plus"></i>{% trans " Add an SOA record" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-soa' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement SOA</a>
|
||||
<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 %}
|
||||
<h2>Liste des enregistrements MX</h2>
|
||||
|
||||
<h2>{% trans "List of MX records" %}</h2>
|
||||
{% can_create Mx %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement MX</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-mx' %}"><i class="fa fa-plus"></i>{% trans " Add an MX record" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-mx' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement MX</a>
|
||||
<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 %}
|
||||
<h2>Liste des enregistrements NS</h2>
|
||||
|
||||
<h2>{% trans "List of NS records" %}</h2>
|
||||
{% can_create Ns %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-ns' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement NS</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-ns' %}"><i class="fa fa-plus"></i>{% trans " Add an NS record" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-ns' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement NS</a>
|
||||
<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 %}
|
||||
<h2>Liste des enregistrements TXT</h2>
|
||||
|
||||
<h2>{% trans "List of TXT records" %}</h2>
|
||||
{% can_create Txt %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-txt' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement TXT</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-txt' %}"><i class="fa fa-plus"></i>{% trans " Add a TXT record" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-txt' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement TXT</a>
|
||||
<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 %}
|
||||
|
||||
<h2>DNAME records</h2>
|
||||
<h2>{% trans "List of DNAME records" %}</h2>
|
||||
{% can_create DName %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-dname' %}">
|
||||
<i class="fa fa-plus"></i> {% trans "Add a DNAME record" %}
|
||||
<i class="fa fa-plus"></i> {% trans " Add a DNAME record" %}
|
||||
</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-dname' %}">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete DNAME records" %}
|
||||
<i class="fa fa-trash"></i> {% trans " Delete one or several DNAME records" %}
|
||||
</a>
|
||||
{% include "machines/aff_dname.html" with dname_list=dname_list %}
|
||||
|
||||
<h2>Liste des enregistrements SRV</h2>
|
||||
<h2>{% trans "List of SRV records" %}</h2>
|
||||
{% can_create Srv %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-srv' %}"><i class="fa fa-plus"></i> Ajouter un enregistrement SRV</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-srv' %}"><i class="fa fa-plus"></i>{% trans " Add an SRV record" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-srv' %}"><i class="fa fa-trash"></i> Supprimer un enregistrement SRV</a>
|
||||
<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 %}
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -26,15 +26,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% load bootstrap3 %}
|
||||
|
||||
{% load acl %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Ip{% endblock %}
|
||||
{% block title %}{% trans "Machines" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Liste des types d'ip</h2>
|
||||
<h2>{% trans "List of IP types" %}</h2>
|
||||
{% can_create IpType %}
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-iptype' %}"><i class="fa fa-plus"></i> Ajouter un type d'ip</a>
|
||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'machines:add-iptype' %}"><i class="fa fa-plus"></i>{% trans " Add an IP type" %}</a>
|
||||
{% acl_end %}
|
||||
<a class="btn btn-danger btn-sm" role="button" href="{% url 'machines:del-iptype' %}"><i class="fa fa-trash"></i> Supprimer un ou plusieurs types d'ip</a>
|
||||
<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 %}
|
||||
<br />
|
||||
<br />
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue