Commit ef4e430e authored by Levy--Falk Hugo's avatar Levy--Falk Hugo

Release : 2.6.1

parents ea7ea032 c25a4981
......@@ -120,3 +120,33 @@ Don't forget to run migrations, several settings previously in the `preferences`
in their own Payment models.
To have a closer look on how the payments works, please go to the wiki.
## MR 182: Add role models
Adds the Role model.
You need to ensure that your database character set is utf-8.
```sql
ALTER DATABASE re2o CHARACTER SET utf8;
```
## MR 247: Fix des comptes mails
Fix several issues with email accounts, you need to collect the static files.
```bash
./manage.py collectstatic
```
## MR 203 Add custom invoices
The custom invoices are now stored in database. You need to migrate your database :
```bash
python3 manage.py migrate
```
On some database engines (postgreSQL) you also need to update the id sequences:
```bash
python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell
```
......@@ -338,6 +338,7 @@ class OptionalMachineSerializer(NamespacedHMSerializer):
class OptionalTopologieSerializer(NamespacedHMSerializer):
"""Serialize `preferences.models.OptionalTopologie` objects.
"""
class Meta:
model = preferences.OptionalTopologie
fields = ('radius_general_policy', 'vlan_decision_ok',
......@@ -469,10 +470,10 @@ class SwitchPortSerializer(NamespacedHMSerializer):
class Meta:
model = topologie.Port
fields = ('switch', 'port', 'room', 'machine_interface', 'related',
'radius', 'vlan_force', 'details', 'api_url')
'custom_profile', 'state', 'details', 'api_url')
extra_kwargs = {
'related': {'view_name': 'switchport-detail'},
'api_url': {'view_name': 'switchport-detail'}
'api_url': {'view_name': 'switchport-detail'},
}
......@@ -484,6 +485,18 @@ class RoomSerializer(NamespacedHMSerializer):
fields = ('name', 'details', 'api_url')
class PortProfileSerializer(NamespacedHMSerializer):
vlan_untagged = VlanSerializer(read_only=True)
class Meta:
model = topologie.PortProfile
fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged',
'radius_type', 'radius_mode', 'speed', 'mac_limit',
'flow_control', 'dhcp_snooping', 'dhcpv6_snooping',
'arp_protect', 'ra_guard', 'loop_protect', 'vlan_untagged',
'vlan_tagged')
# USERS
......@@ -534,11 +547,20 @@ class AdherentSerializer(NamespacedHMSerializer):
fields = ('name', 'surname', 'pseudo', 'email', 'local_email_redirect',
'local_email_enabled', 'school', 'shell', 'comment',
'state', 'registered', 'telephone', 'room', 'solde',
'access', 'end_access', 'uid', 'api_url')
'access', 'end_access', 'uid', 'api_url','gid')
extra_kwargs = {
'shell': {'view_name': 'shell-detail'}
}
class HomeCreationSerializer(NamespacedHMSerializer):
"""Serialize 'users.models.User' minimal infos to create home
"""
uid = serializers.IntegerField(source='uid_number')
gid = serializers.IntegerField(source='gid_number')
class Meta:
model = users.User
fields = ('pseudo', 'uid', 'gid')
class ServiceUserSerializer(NamespacedHMSerializer):
"""Serialize `users.models.ServiceUser` objects.
......@@ -599,7 +621,7 @@ class WhitelistSerializer(NamespacedHMSerializer):
class EMailAddressSerializer(NamespacedHMSerializer):
"""Serialize `users.models.EMailAddress` objects.
"""
user = serializers.CharField(source='user.pseudo', read_only=True)
class Meta:
model = users.EMailAddress
fields = ('user', 'local_part', 'complete_email_address', 'api_url')
......@@ -635,8 +657,41 @@ class LocalEmailUsersSerializer(NamespacedHMSerializer):
class Meta:
model = users.User
fields = ('local_email_enabled', 'local_email_redirect',
'email_address')
'email_address', 'email')
#Firewall
class FirewallPortListSerializer(serializers.ModelSerializer):
class Meta:
model = machines.OuverturePort
fields = ('begin', 'end', 'protocole', 'io', 'show_port')
class FirewallOuverturePortListSerializer(serializers.ModelSerializer):
tcp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
udp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
tcp_ports_out = FirewallPortListSerializer(many=True, read_only=True)
udp_ports_out = FirewallPortListSerializer(many=True, read_only=True)
class Meta:
model = machines.OuverturePortList
fields = ('tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', 'udp_ports_out')
class SubnetPortsOpenSerializer(serializers.ModelSerializer):
ouverture_ports = FirewallOuverturePortListSerializer(read_only=True)
class Meta:
model = machines.IpType
fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'complete_prefixv6', 'ouverture_ports')
class InterfacePortsOpenSerializer(serializers.ModelSerializer):
port_lists = FirewallOuverturePortListSerializer(read_only=True, many=True)
ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True)
ipv6 = Ipv6ListSerializer(many=True, read_only=True)
class Meta:
model = machines.Interface
fields = ('port_lists', 'ipv4', 'ipv6')
# DHCP
......@@ -679,7 +734,7 @@ class NSRecordSerializer(NsSerializer):
"""Serialize `machines.models.Ns` objects with the data needed to
generate a NS DNS record.
"""
target = serializers.CharField(source='ns.name', read_only=True)
target = serializers.CharField(source='ns', read_only=True)
class Meta(NsSerializer.Meta):
fields = ('target',)
......@@ -689,7 +744,7 @@ class MXRecordSerializer(MxSerializer):
"""Serialize `machines.models.Mx` objects with the data needed to
generate a MX DNS record.
"""
target = serializers.CharField(source='name.name', read_only=True)
target = serializers.CharField(source='name', read_only=True)
class Meta(MxSerializer.Meta):
fields = ('target', 'priority')
......@@ -761,13 +816,12 @@ class CNAMERecordSerializer(serializers.ModelSerializer):
"""Serialize `machines.models.Domain` objects with the data needed to
generate a CNAME DNS record.
"""
alias = serializers.CharField(source='cname.name', read_only=True)
alias = serializers.CharField(source='cname', read_only=True)
hostname = serializers.CharField(source='name', read_only=True)
extension = serializers.CharField(source='extension.name', read_only=True)
class Meta:
model = machines.Domain
fields = ('alias', 'hostname', 'extension')
fields = ('alias', 'hostname')
class DNSZonesSerializer(serializers.ModelSerializer):
......@@ -792,6 +846,25 @@ class DNSZonesSerializer(serializers.ModelSerializer):
'aaaa_records', 'cname_records', 'sshfp_records')
class DNSReverseZonesSerializer(serializers.ModelSerializer):
"""Serialize the data about DNS Zones.
"""
soa = SOARecordSerializer(source='extension.soa')
extension = serializers.CharField(source='extension.name', read_only=True)
cidrs = serializers.ListField(child=serializers.CharField(), source='ip_set_cidrs_as_str', read_only=True)
ns_records = NSRecordSerializer(many=True, source='extension.ns_set')
mx_records = MXRecordSerializer(many=True, source='extension.mx_set')
txt_records = TXTRecordSerializer(many=True, source='extension.txt_set')
ptr_records = ARecordSerializer(many=True, source='get_associated_ptr_records')
ptr_v6_records = AAAARecordSerializer(many=True, source='get_associated_ptr_v6_records')
class Meta:
model = machines.IpType
fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records',
'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs',
'prefix_v6', 'prefix_v6_length')
# MAILING
......@@ -799,7 +872,7 @@ class MailingMemberSerializer(UserSerializer):
"""Serialize the data about a mailing member.
"""
class Meta(UserSerializer.Meta):
fields = ('name', 'pseudo', 'email')
fields = ('name', 'pseudo', 'get_mail')
class MailingSerializer(ClubSerializer):
"""Serialize the data about a mailing.
......
......@@ -81,10 +81,12 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet)
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
router.register_viewset(r'topologie/building', views.BuildingViewSet)
router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
router.register_viewset(r'topologie/room', views.RoomViewSet)
router.register(r'topologie/portprofile', views.PortProfileViewSet)
# USERS
router.register_viewset(r'users/user', views.UserViewSet)
router.register_viewset(r'users/homecreation', views.HomeCreationViewSet)
router.register_viewset(r'users/club', views.ClubViewSet)
router.register_viewset(r'users/adherent', views.AdherentViewSet)
router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet)
......@@ -100,8 +102,12 @@ router.register_viewset(r'services/regen', views.ServiceRegenViewSet, base_name=
router.register_view(r'dhcp/hostmacip', views.HostMacIpView),
# LOCAL EMAILS
router.register_view(r'localemail/users', views.LocalEmailUsersView),
# Firewall
router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView),
router.register_view(r'firewall/interface-ports', views.InterfacePortsOpenView),
# DNS
router.register_view(r'dns/zones', views.DNSZonesView),
router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView),
# MAILING
router.register_view(r'mailing/standard', views.StandardMailingView),
router.register_view(r'mailing/club', views.ClubMailingView),
......
......@@ -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
......
......@@ -42,4 +42,5 @@ def can_view(user):
if can:
return can, None
else:
return can, _("You don't have the rights to see this application.")
return can, _("You don't have the right to view this application.")
......@@ -30,6 +30,7 @@ from django.contrib import admin
from reversion.admin import VersionAdmin
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
from .models import CustomInvoice
class FactureAdmin(VersionAdmin):
......@@ -37,6 +38,11 @@ class FactureAdmin(VersionAdmin):
pass
class CustomInvoiceAdmin(VersionAdmin):
"""Admin class for custom invoices."""
pass
class VenteAdmin(VersionAdmin):
"""Class admin d'une vente, tous les champs (facture related)"""
pass
......@@ -69,3 +75,4 @@ admin.site.register(Banque, BanqueAdmin)
admin.site.register(Paiement, PaiementAdmin)
admin.site.register(Vente, VenteAdmin)
admin.site.register(Cotisation, CotisationAdmin)
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
......@@ -40,13 +40,13 @@ from django import forms
from django.db.models import Q
from django.forms import ModelForm, Form
from django.core.validators import MinValueValidator
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy as _l
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
from .models import Article, Paiement, Facture, Banque
from .models import Article, Paiement, Facture, Banque, CustomInvoice
from .payment_methods import balance
......@@ -84,71 +84,36 @@ class FactureForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
return cleaned_data
class SelectUserArticleForm(FormRevMixin, Form):
class SelectArticleForm(FormRevMixin, Form):
"""
Form used to select an article during the creation of an invoice for a
member.
"""
article = forms.ModelChoiceField(
queryset=Article.objects.filter(
Q(type_user='All') | Q(type_user='Adherent')
),
label=_l("Article"),
queryset=Article.objects.none(),
label=_("Article"),
required=True
)
quantity = forms.IntegerField(
label=_l("Quantity"),
label=_("Quantity"),
validators=[MinValueValidator(1)],
required=True
)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(SelectUserArticleForm, self).__init__(*args, **kwargs)
self.fields['article'].queryset = Article.find_allowed_articles(user)
class SelectClubArticleForm(Form):
"""
Form used to select an article during the creation of an invoice for a
club.
"""
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)
target_user = kwargs.pop('target_user')
super(SelectArticleForm, self).__init__(*args, **kwargs)
self.fields['article'].queryset = Article.find_allowed_articles(user, target_user)
# TODO : change Facture to Invoice
class NewFactureFormPdf(Form):
class CustomInvoiceForm(FormRevMixin, ModelForm):
"""
Form used to create a custom PDF invoice.
Form used to create a custom 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
This diff is collapsed.
# -*- 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)'),
),
]
# -*- 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(