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
......@@ -21,430 +21,711 @@ msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-05-10 15:21-0500\n"
"POT-Creation-Date: 2018-08-18 13:17+0200\n"
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
"Last-Translator: Maël Kervella <dev@maelkervella.eu>\n"
"Language-Team: \n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\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:45
msgid "You don't have the rights to see this application."
msgstr "Vous n'avez pas les droits de voir cette application."
msgid "You don't have the right to view this application."
msgstr "Vous n'avez pas le droit de voir cette application."
#: forms.py:63 forms.py:321
msgid "Cheque number"
msgstr "Numéro de chèque"
#: forms.py:64 forms.py:322
msgid "Not specified"
msgstr "Non renseigné"
#: forms.py:66 forms.py:324
#: forms.py:63 forms.py:274
msgid "Select a payment method"
msgstr "Sélectionnez un moyen de paiement"
#: forms.py:83 forms.py:347
msgid "A payment method must be specified."
msgstr "Un moyen de paiement doit être renseigné."
#: forms.py:87 forms.py:352
msgid "A cheque number and a bank must be specified."
msgstr "Un numéro de chèqe et une banque doivent être renseignés."
#: forms.py:184
#: forms.py:66 models.py:510
msgid "Member"
msgstr "Adhérent"
#: forms.py:186
#: forms.py:68
msgid "Select the proprietary member"
msgstr "Sélectionnez l'adhérent propriétaire"
#: forms.py:187
#: forms.py:69
msgid "Validated invoice"
msgstr "Facture validée"
#: forms.py:201
#: forms.py:82
msgid "A payment method must be specified."
msgstr "Un moyen de paiement doit être renseigné."
#: forms.py:96 forms.py:120 templates/cotisations/aff_article.html:33
#: templates/cotisations/facture.html:61
msgid "Article"
msgstr "Article"
#: forms.py:100 forms.py:124 templates/cotisations/edit_facture.html:46
msgid "Quantity"
msgstr "Quantité"
#: forms.py:154
msgid "Article name"
msgstr "Nom de l'article"
#: forms.py:239
#: forms.py:164 templates/cotisations/sidebar.html:50
msgid "Available articles"
msgstr "Articles disponibles"
#: forms.py:192
msgid "Payment method name"
msgstr "Nom du moyen de paiement"
#: forms.py:240
msgid "Payment type"
msgstr "Type de paiement"
#: forms.py:242
msgid ""
"The payement type is used for specific behaviour. The \"cheque\" "
"type means a cheque number and a bank name may be added when "
"using this payment method."
msgstr ""
"Le type de paiement est utilisé pour des comportements spécifiques. Le type "
"\"chèque\" permet de spécifier un numéro de chèque et une banque lors de "
"l'utilisation de cette méthode."
#: forms.py:204
msgid "Available payment methods"
msgstr "Moyens de paiement disponibles"
#: forms.py:282
#: forms.py:230
msgid "Bank name"
msgstr "Nom de la banque"
#: forms.py:380
#, python-format
msgid ""
"Requested amount is too small. Minimum amount possible : "
"%(min_online_amount)s €."
msgstr ""
"Montant demandé est trop faible. Montant minimal possible : "
"%(min_online_amount)s €"
#: forms.py:242
msgid "Available banks"
msgstr "Banques disponibles"
#: forms.py:261
msgid "Amount"
msgstr "Montant"
#: forms.py:267 templates/cotisations/aff_cotisations.html:44
#: templates/cotisations/aff_custom_invoice.html:42
#: templates/cotisations/control.html:66
msgid "Payment method"
msgstr "Moyen de paiement"
#: forms.py:390
#: forms.py:287
#, python-format
msgid ""
"Requested amount is too high. Your balance can't exceed "
"%(max_online_balance)s €."
msgstr ""
"Montant demandé trop grand. Votre solde ne peut excéder "
"%(max_online_balance)s €"
"Le montant demandé trop grand. Votre solde ne peut excéder "
"%(max_online_balance)s €."
#: models.py:60 templates/cotisations/aff_cotisations.html:48
#: templates/cotisations/aff_custom_invoice.html:46
#: templates/cotisations/control.html:70