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

Release : 2.6.1

This commit is contained in:
Hugo LEVY-FALK 2018-08-28 13:46:05 +02:00
commit ef4e430eba
240 changed files with 12666 additions and 3917 deletions

View file

@ -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
```

View file

@ -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.

View file

@ -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),

View file

@ -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,11 +543,21 @@ 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
class DNSZonesView(generics.ListAPIView):
"""Exposes the detailed information about each extension (hostnames,
"""Exposes the detailed information about each extension (hostnames,
IPs, DNS records, etc.) in order to build the DNS zone files.
"""
queryset = (machines.Extension.objects
@ -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

View file

@ -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.")

View file

@ -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)

View file

@ -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

File diff suppressed because it is too large Load diff

View 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)'),
),
]

View 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'},
),
]

View 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'),
),
]

View file

@ -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')

View file

@ -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

View file

@ -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}
))

View file

@ -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()
}
)

View file

@ -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))

View file

@ -50,7 +50,7 @@ def accept_payment(request, factureid):
if invoice.valid:
messages.success(
request,
_("The payment of %(amount)shas been accepted.") % {
_("The payment of %(amount)swas 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")

View file

@ -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'
}

View file

@ -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 %}">

View file

@ -26,25 +26,23 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load i18n %}
{% load logs_extra %}
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Bank" %}</th>
<th></th>
</tr>
</thead>
{% for banque in banque_list %}
<table class="table table-striped">
<thead>
<tr>
<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>
{% acl_end %}
{% history_button banque %}
</td>
<th>{% trans "Bank" %}</th>
<th></th>
</tr>
{% endfor %}
</table>
</thead>
{% for banque in banque_list %}
<tr>
<td>{{ banque.name }}</td>
<td class="text-right">
{% can_edit banque %}
{% include 'buttons/edit.html' with href='cotisations:edit-banque' id=banque.id %}
{% acl_end %}
{% history_button banque %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -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>
{% acl_else %}
<li>{% trans "Controlled invoice" %}</li>
{% acl_end %}
{% can_delete facture %}
<li>
<a href="{% url 'cotisations:del-facture' facture.id %}">
<i class="fa fa-trash"></i> {% trans "Delete" %}
</a>
</li>
{% acl_end %}
<li>
{% history_button facture text=True html_class=False%}
</li>
</ul>
</div>
{% can_edit facture %}
{% include 'buttons/edit.html' with href='cotisations:edit-facture' id=facture.id %}
{% acl_else %}
{% trans "Controlled invoice" %}
{% acl_end %}
{% can_delete facture %}
{% include 'buttons/suppr.html' with href='cotisations:del-facture' id=facture.id %}
{% acl_end %}
{% 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>

View 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>

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View 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, nhé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

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View 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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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

View file

@ -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]+)$',

View file

@ -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()

View file

@ -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.")
)

View file

@ -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,16 +109,10 @@ def new_facture(request, user, userid):
creation=True
)
if request.user.is_class_club:
article_formset = formset_factory(SelectClubArticleForm)(
request.POST or None,
form_kwargs={'user': request.user}
)
else:
article_formset = formset_factory(SelectUserArticleForm)(
request.POST or None,
form_kwargs={'user': request.user}
)
article_formset = formset_factory(SelectArticleForm)(
request.POST or None,
form_kwargs={'user': request.user, 'target_user': user}
)
if invoice_form.is_valid() and article_formset.is_valid():
new_invoice_instance = invoice_form.save(commit=False)
@ -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)

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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.")

Binary file not shown.

View 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."

View file

@ -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>
@ -32,11 +32,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</tr>
</thead>
{% for key, stat in stats.1.items %}
<tr>
{% for item in stat %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
<tr>
{% for item in stat %}
<td>{{ item }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
{% endfor %}
{% endfor %}

View file

@ -28,39 +28,43 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load logs_extra %}
{% load acl %}
{% load i18n %}
<table class="table table-striped">
<thead>
<table class="table table-striped">
<thead>
<tr>
<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>
{% for revision in revisions_list %}
{% for reversion in revision.version_set.all %}
<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></th>
<td>{{ reversion.object|truncatechars:20 }}</td>
<td>{{ reversion.object|classname }}</td>
<td>{{ revision.user }}</td>
<td>{{ revision.date_created }}</td>
<td>{{ revision.comment }}</td>
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' revision.id %}">
<i class="fa fa-times"></i>
{% trans "Cancel" %}
</a>
</td>
{% acl_end %}
</tr>
</thead>
{% for revision in revisions_list %}
{% for reversion in revision.version_set.all %}
<tr>
<td>{{ reversion.object|truncatechars:20 }}</td>
<td>{{ reversion.object|classname }}</td>
<td>{{ revision.user }}</td>
<td>{{ revision.date_created }}</td>
<td>{{ revision.comment }}</td>
{% can_edit_history %}
<td>
<a class="btn btn-danger btn-sm" role="button" href="{% url 'logs:revert-action' revision.id %}">
<i class="fa fa-times"></i>
Annuler
</a>
</td>
{% acl_end %}
</tr>
{% endfor %}
{% endfor %}
</table>
{% endfor %}
</table>
{% if revisions_list.paginator %}
{% include "pagination.html" with list=revisions_list %}
{% endif %}

View file

@ -22,20 +22,23 @@ 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>
<thead>
<tr>
<th>Type d'objet</th>
<th>Nombre d'entrée stockées</th>
</tr>
</thead>
{% for key, stat in stats.items %}
<tr>
<td>{{ stat.0 }}</td>
<td>{{ stat.1 }}</td>
</tr>
{% endfor %}
<h4>{% blocktrans %}Statistics of the set {{ key }}{% endblocktrans %}</h4>
<thead>
<tr>
<th>{% trans "Object type" %}</th>
<th>{% trans "Number of stored entries" %}</th>
</tr>
</thead>
{% for key, stat in stats.items %}
<tr>
<td>{{ stat.0 }}</td>
<td>{{ stat.1 }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
{% endfor %}

View file

@ -22,24 +22,27 @@ 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>
<tr>
<th>{{ key_dict }}</th>
<th>Nombre de {{ key }} par {{ key_dict }}</th>
<th>Rang</th>
</tr>
</thead>
{% for stat in stats %}
<tr>
<td>{{ stat|truncatechars:25 }}</td>
<td>{{ stat.num }}</td>
<td>{{ forloop.counter }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
{% endfor %}
<table class="table table-striped">
<thead>
<h4>{% blocktrans %}Statistics per {{ key_dict }} of {{ key }}{% endblocktrans %}</h4>
<tr>
<th>{{ key_dict }}</th>
<th>{% blocktrans %}Number of {{ key }} per {{ key_dict }}{% endblocktrans %}</th>
<th>{% trans "Rank" %}</th>
</tr>
</thead>
{% for stat in stats %}
<tr>
<td>{{ stat|truncatechars:25 }}</td>
<td>{{ stat.num }}</td>
<td>{{ forloop.counter }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
{% endfor %}

View file

@ -28,122 +28,132 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load logs_extra %}
{% load acl %}
<table class="table table-striped">
<thead>
<tr>
<th>{% include "buttons/sort.html" with prefix='sum' col='date' text='Date' %}</th>
<th>Modification</th>
<th></th>
</tr>
</thead>
{% load i18n %}
<table class="table table-striped">
<thead>
<tr>
{% 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>
{% for v in versions_list %}
{% if v.version.content_type.model == 'ban' %}
<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>
(<i>
{% if v.version.object.raison == '' %}
Aucune raison
{% trans "No reason" %}
{% else %}
{{ v.version.object.raison }}
{% endif %}
</i>)
</td>
{% can_edit_history %}
{% 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 %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'whitelist' %}
<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 %}
</i>)
</td>
{% can_edit_history%}
{% 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 %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'user' %}
<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>)
{% endif %}
{% if v.comment != '' %}
(<i>{{ v.comment }}</i>)
{% endif %}
</td>
{% can_edit_history %}
{% 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 %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'vente' %}
<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>
{% if v.version.object.iscotisation %}
(<i>+{{ v.version.object.duration }} mois</i>)
{% endif %}
{% 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>{% blocktrans with duration=v.version.object.duration %}+{{ duration }} months{% endblocktrans %}</i>)
{% endif %}
{% endif %}
{% endwith %}
</td>
{% can_edit_history %}
{% 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 %}
{% acl_end %}
</tr>
{% elif v.version.content_type.model == 'interface' %}
<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>)
{% endif %}
{% if v.comment != '' %}
(<i>{{ v.comment }}</i>)
{% endif %}
</td>
{% can_edit_history %}
{% 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 %}
{% acl_end %}
</tr>
{% endif %}
{% endfor %}
</table>
</table>
{% if versions_list.paginator %}
{% include "pagination.html" with list=versions_list %}
{% endif %}

View file

@ -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 />
<br />
<br />
<br />
{% endblock %}

View file

@ -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>
{% include "logs/aff_summary.html" with versions_list=versions_list %}
<br />
<br />
<br />
{% endblock %}
<h2>{% trans "Actions performed" %}</h2>
{% include "logs/aff_summary.html" with versions_list=versions_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -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 %}

View file

@ -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>
{% include "logs/aff_stats_general.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}
<h2>{% trans "General statistics" %}</h2>
{% include "logs/aff_stats_general.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -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>
{% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %}
<br />
<br />
<br />
{% endblock %}
<h2>{% trans "Actions performed" %}</h2>
{% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -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>
{% include "logs/aff_stats_models.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}
<h2>{% trans "Database statistics" %}</h2>
{% include "logs/aff_stats_models.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -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>
{% include "logs/aff_stats_users.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}
<h2>{% trans "Statistics about users" %}</h2>
{% include "logs/aff_stats_users.html" with stats_list=stats_list %}
<br />
<br />
<br />
{% endblock %}

View file

@ -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):

View file

@ -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}
)

View file

@ -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.")

View file

@ -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)

View file

@ -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
)

Binary file not shown.

File diff suppressed because it is too large Load diff

View 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),
),
]

View 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'),
),
]

View 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)]),
),
]

View 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),
),
]

View 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'),
),
]

View 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'),
),
]

View 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',
),
]

View 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(),
),
]

View 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

View file

@ -23,25 +23,26 @@ 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>
{% for alias in alias_list %}
<tr>
<td>{{ alias }}</td>
<td class="text-right">
{% can_edit alias %}
{% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %}
{% acl_end %}
{% history_button alias %}
</td>
</tr>
<tr>
<td>{{ alias }}</td>
<td class="text-right">
{% can_edit alias %}
{% include 'buttons/edit.html' with href='machines:edit-alias' id=alias.id %}
{% acl_end %}
{% history_button alias %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -22,16 +22,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>Target zone</th>
<th>Record</th>
<th></th>
</tr>
</thead>
{% for dname in dname_list %}
<table class="table table-striped">
<thead>
<tr>
<th>{% trans "Target zone" %}</th>
<th>{% trans "Record" %}</th>
<th></th>
</tr>
</thead>
{% for dname in dname_list %}
<tr>
<td>{{ dname.zone }}</td>
<td>{{ dname.dns_entry }}</td>
@ -39,10 +40,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% can_edit dname %}
{% include 'buttons/edit.html' with href='machines:edit-dname' id=dname.id %}
{% acl_end %}
{% history_button dname %}
{% history_button dname %}
</td>
</tr>
{% endfor %}
</table>
{% endfor %}
</table>

View file

@ -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>

View file

@ -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>

View file

@ -24,29 +24,30 @@ 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>
</thead>
{% for ipv6 in ipv6_list %}
<tr>
<td>{{ ipv6.ipv6 }}</td>
<td>{{ ipv6.slaac_ip }}</td>
<td class="text-right">
{% can_edit ipv6 %}
{% include 'buttons/edit.html' with href='machines:edit-ipv6list' id=ipv6.id %}
{% acl_end %}
{% can_delete ipv6 %}
{% include 'buttons/suppr.html' with href='machines:del-ipv6list' id=ipv6.id %}
{% acl_end %}
{% history_button ipv6 %}
</td>
</tr>
<tr>
<td>{{ ipv6.ipv6 }}</td>
<td>{{ ipv6.slaac_ip }}</td>
<td class="text-right">
{% can_edit ipv6 %}
{% include 'buttons/edit.html' with href='machines:edit-ipv6list' id=ipv6.id %}
{% acl_end %}
{% can_delete ipv6 %}
{% include 'buttons/suppr.html' with href='machines:del-ipv6list' id=ipv6.id %}
{% acl_end %}
{% history_button ipv6 %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -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 %}
{% 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 }}
@ -77,7 +82,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
</td>
<td>
{{ interface.type }}
</td>
</td>
<td>
{{ interface.mac_address }}
</td>
@ -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>
</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,61 +152,57 @@ 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;">
<div class="collapse in" id="collapseIpv6_{{interface.id}}">
<ul class="list-group" style="margin-bottom: 0px;">
{% for ipv6 in interface.ipv6.all %}
{% 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 %}
{% endfor %}
</ul>
</div>
</td>
<tr>
{% endif %}
{% if interface.domain.related_domain.all %}
<tr>
<td colspan=5 style="border-top: none; padding: 1px;">
<div class="collapse in" id="collapseDomain_{{interface.id}}">
<ul class="list-group" style="margin-bottom: 0px;">
{% for al in interface.domain.related_domain.all %}
<li class="list-group-item col-xs-6 col-sm-4 col-md-3" style="border: none;">
<a href="http://{{ al }}">
{{ al }}
<i class="fa fa-share"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
</td>
<tr>
{% endif %}
{% endfor %}
<tr>
<td colspan="8"></td>
</tr>
{% endfor %}
</tr>
{% endif %}
{% if interface.domain.related_domain.all %}
<tr>
<td colspan=5 style="border-top: none; padding: 1px;">
<div class="collapse in" id="collapseDomain_{{interface.id}}">
<ul class="list-group" style="margin-bottom: 0px;">
{% for al in interface.domain.related_domain.all %}
<li class="list-group-item col-xs-6 col-sm-4 col-md-3" style="border: none;">
<a href="http://{{ al }}">
{{ al }}
<i class="fa fa-share"></i>
</a>
</li>
{% endfor %}
</ul>
</div>
</td>
</tr>
{% endif %}
{% endfor %}
<tr>
<td colspan="8"></td>
</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>

View file

@ -24,26 +24,27 @@ 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>
{% for type in machinetype_list %}
<tr>
<td>{{ type.type }}</td>
<td>{{ type.ip_type }}</td>
<td class="text-right">
{% can_edit type %}
{% include 'buttons/edit.html' with href='machines:edit-machinetype' id=type.id %}
{% acl_end %}
{% history_button type %}
</td>
</tr>
<tr>
<td>{{ type.type }}</td>
<td>{{ type.ip_type }}</td>
<td class="text-right">
{% can_edit type %}
{% include 'buttons/edit.html' with href='machines:edit-machinetype' id=type.id %}
{% acl_end %}
{% history_button type %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,30 +24,29 @@ 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>
{% for mx in mx_list %}
<tr>
<td>{{ mx.zone }}</td>
<td>{{ mx.priority }}</td>
<td>{{ mx.name }}</td>
<td class="text-right">
{% can_edit mx %}
{% include 'buttons/edit.html' with href='machines:edit-mx' id=mx.id %}
{% acl_end %}
{% history_button mx %}
</td>
</tr>
<tr>
<td>{{ mx.zone }}</td>
<td>{{ mx.priority }}</td>
<td>{{ mx.name }}</td>
<td class="text-right">
{% can_edit mx %}
{% include 'buttons/edit.html' with href='machines:edit-mx' id=mx.id %}
{% acl_end %}
{% history_button mx %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -25,32 +25,33 @@ 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>
{% for nas in nas_list %}
<tr>
<td>{{ nas.name }}</td>
<td>{{ nas.nas_type }}</td>
<td>{{ nas.machine_type }}</td>
<td>{{ nas.port_access_mode }}</td>
<td>{{ nas.autocapture_mac|tick }}</td>
<td class="text-right">
{% can_edit nas %}
{% include 'buttons/edit.html' with href='machines:edit-nas' id=nas.id %}
{% acl_end %}
{% history_button nas %}
</td>
</tr>
<tr>
<td>{{ nas.name }}</td>
<td>{{ nas.nas_type }}</td>
<td>{{ nas.machine_type }}</td>
<td>{{ nas.port_access_mode }}</td>
<td>{{ nas.autocapture_mac|tick }}</td>
<td class="text-right">
{% can_edit nas %}
{% include 'buttons/edit.html' with href='machines:edit-nas' id=nas.id %}
{% acl_end %}
{% history_button nas %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,28 +24,27 @@ 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>
{% for ns in ns_list %}
<tr>
<td>{{ ns.zone }}</td>
<td>{{ ns.ns }}</td>
<td class="text-right">
{% can_edit ns %}
{% include 'buttons/edit.html' with href='machines:edit-ns' id=ns.id %}
{% acl_end %}
{% history_button ns %}
</td>
</tr>
<tr>
<td>{{ ns.zone }}</td>
<td>{{ ns.ns }}</td>
<td class="text-right">
{% can_edit ns %}
{% include 'buttons/edit.html' with href='machines:edit-ns' id=ns.id %}
{% acl_end %}
{% history_button ns %}
</td>
</tr>
{% endfor %}
</table>

View 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>

View file

@ -23,27 +23,28 @@ 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 %}
<tr>
<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 class="text-right">
</td>
</tr>
<tr>
<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 class="text-right">
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,31 +24,33 @@ 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>
{% for service in service_list %}
<tr>
<td>{{ service.service_type }}</td>
<td>{{ service.min_time_regen }}</td>
<td>{{ service.regular_time_regen }}</td>
<td>{% for serv in service.servers.all %}{{ serv }}, {% endfor %}</td>
<td class="text-right">
{% can_edit service %}
{% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %}
{% acl_end %}
{% history_button service %}
</td>
</tr>
<tr>
<td>{{ service.service_type }}</td>
<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 %}
{% acl_end %}
{% history_button service %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,36 +24,35 @@ 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>
{% for soa in soa_list %}
<tr>
<td>{{ soa.name }}</td>
<td>{{ soa.mail }}</td>
<td>{{ soa.refresh }}</td>
<td>{{ soa.retry }}</td>
<td>{{ soa.expire }}</td>
<td>{{ soa.ttl }}</td>
<td class="text-right">
{% can_edit soa %}
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %}
{% acl_end %}
{% history_button soa %}
</td>
</tr>
<tr>
<td>{{ soa.name }}</td>
<td>{{ soa.mail }}</td>
<td>{{ soa.refresh }}</td>
<td>{{ soa.retry }}</td>
<td>{{ soa.expire }}</td>
<td>{{ soa.ttl }}</td>
<td class="text-right">
{% can_edit soa %}
{% include 'buttons/edit.html' with href='machines:edit-soa' id=soa.id %}
{% acl_end %}
{% history_button soa %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,40 +24,39 @@ 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>
{% for srv in srv_list %}
<tr>
<td>{{ srv.service }}</td>
<td>{{ srv.protocole }}</td>
<td>{{ srv.extension }}</td>
<td>{{ srv.ttl }}</td>
<td>{{ srv.priority }}</td>
<td>{{ srv.weight }}</td>
<td>{{ srv.port }}</td>
<td>{{ srv.target }}</td>
<td class="text-right">
{% can_edit srv %}
{% include 'buttons/edit.html' with href='machines:edit-srv' id=srv.id %}
{% acl_end %}
{% history_button srv %}
</td>
</tr>
<tr>
<td>{{ srv.service }}</td>
<td>{{ srv.protocole }}</td>
<td>{{ srv.extension }}</td>
<td>{{ srv.ttl }}</td>
<td>{{ srv.priority }}</td>
<td>{{ srv.weight }}</td>
<td>{{ srv.port }}</td>
<td>{{ srv.target }}</td>
<td class="text-right">
{% can_edit srv %}
{% include 'buttons/edit.html' with href='machines:edit-srv' id=srv.id %}
{% acl_end %}
{% history_button srv %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -21,33 +21,34 @@ 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>
{% for sshfp in sshfp_list %}
<tr>
<td class="long_text">{{ sshfp.pub_key_entry }}</td>
<td>{{ sshfp.algo }}</td>
<td>{{ sshfp.comment }}</td>
<td class="text-right">
{% can_edit sshfp %}
{% include 'buttons/edit.html' with href='machines:edit-sshfp' id=sshfp.id %}
{% acl_end %}
{% can_delete sshfp %}
{% include 'buttons/suppr.html' with href='machines:del-sshfp' id=sshfp.id %}
{% acl_end %}
{% history_button sshfp %}
</td>
</tr>
<tr>
<td class="long_text">{{ sshfp.pub_key_entry }}</td>
<td>{{ sshfp.algo }}</td>
<td>{{ sshfp.comment }}</td>
<td class="text-right">
{% 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 %}
</td>
</tr>
{% endfor %}
</table>
</div>

View file

@ -24,28 +24,27 @@ 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>
{% for txt in txt_list %}
<tr>
<td>{{ txt.zone }}</td>
<td>{{ txt.dns_entry }}</td>
<td class="text-right">
{% can_edit txt %}
{% include 'buttons/edit.html' with href='machines:edit-txt' id=txt.id %}
{% acl_end %}
{% history_button txt %}
</td>
</tr>
<tr>
<td>{{ txt.zone }}</td>
<td>{{ txt.dns_entry }}</td>
<td class="text-right">
{% can_edit txt %}
{% include 'buttons/edit.html' with href='machines:edit-txt' id=txt.id %}
{% acl_end %}
{% history_button txt %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -24,31 +24,33 @@ 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>
{% for vlan in vlan_list %}
<tr>
<td>{{ vlan.vlan_id }}</td>
<td>{{ vlan.name }}</td>
<td>{{ vlan.comment }}</td>
<td>{% for range in vlan.iptype_set.all %}{{ range }}, {% endfor%}</td>
<td class="text-right">
{% can_edit vlan %}
{% include 'buttons/edit.html' with href='machines:edit-vlan' id=vlan.id %}
{% acl_end %}
{% history_button vlan %}
</td>
</tr>
<tr>
<td>{{ vlan.vlan_id }}</td>
<td>{{ vlan.name }}</td>
<td>{{ vlan.comment }}</td>
<td>{% for range in vlan.iptype_set.all %}{{ range }}, {% endfor%}</td>
<td class="text-right">
{% can_edit vlan %}
{% include 'buttons/edit.html' with href='machines:edit-vlan' id=vlan.id %}
{% acl_end %}
{% history_button vlan %}
</td>
</tr>
{% endfor %}
</table>
</div>

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 />

View file

@ -24,16 +24,17 @@ 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>
{% include "machines/aff_alias.html" with alias_list=alias_list %}
<br />
<br />
<br />
<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 />
<br />
{% endblock %}

View file

@ -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 />

View file

@ -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