Commit feddc3f6 authored by chirac's avatar chirac

Conflicts fix switch_conf_json

# Conflicts:
#   re2o/templatetags/acl.py
#   re2o/views.py
#   topologie/templates/topologie/aff_port.html
parents eefa0b4a 12b6f74a
## MR 160: Datepicker
Install libjs-jquery libjs-jquery-ui libjs-jquery-timepicker libjs-bootstrap javascript-common
```
```bash
apt-get -y install \
libjs-jquery \
libjs-jquery-ui \
......@@ -10,12 +10,12 @@ apt-get -y install \
javascript-common
```
Enable javascript-common conf
```
```bash
a2enconf javascript-common
```
Delete old jquery files :
```
```bash
rm -r static_files/js/jquery-ui-*
rm static_files/js/jquery-2.2.4.min.js
rm static/css/jquery-ui-timepicker-addon.css
......@@ -42,6 +42,7 @@ Refactored install_re2o.sh script.
```
install_re2o.sh help
```
* The installation templates (LDIF files and `re2o/settings_locale.example.py`) have been changed to use `example.net` instead of `example.org` (more neutral and generic)
......@@ -75,7 +76,6 @@ OPTIONAL_APPS = (
```
## MR 177: Add django-debug-toolbar support
Add the possibility to enable `django-debug-toolbar` in debug mode. First install the APT package:
......@@ -94,3 +94,29 @@ If you to restrict the IP which can see the debug, use the `INTERNAL_IPS` option
```
INTERNAL_IPS = ["10.0.0.1", "10.0.0.2"]
```
## MR 145: Fix #117 : Use unix_name instead of name for ldap groups
Fix a mixing between unix_name and name for groups
After this modification you need to:
* Double-check your defined groups' unix-name only contain small letters
* Run the following commands to rebuild your ldap's groups:
```shell
python3 manage.py ldap_rebuild
```
* You may need to force your nslcd cache to be reloaded on some servers (else you will have to wait for the cache to be refreshed):
```bash
sudo nslcd -i groups
```
## MR 174 : Fix online payment + allow users to pay their subscription
Add the possibility to use custom payment methods. There is also a boolean field on the
Payments allowing every user to use some kinds of payment. You have to add the rights `cotisations.use_every_payment` and `cotisations.buy_every_article`
to the staff members so they can use every type of payment to buy anything.
Don't forget to run migrations, several settings previously in the `preferences` app ar now
in their own Payment models.
To have a closer look on how the payments works, please go to the wiki.
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......@@ -190,6 +191,13 @@ class MxSerializer(NamespacedHMSerializer):
fields = ('zone', 'priority', 'name', 'api_url')
class DNameSerializer(NamespacedHMSerializer):
"""Serialize `machines.models.DName` objects.
"""
class Meta:
model = machines.DName
fields = ('zone', 'alias', 'api_url')
class NsSerializer(NamespacedHMSerializer):
"""Serialize `machines.models.Ns` objects.
"""
......
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......
# -*- mode: python; 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.
......@@ -51,6 +52,7 @@ router.register_viewset(r'machines/extension', views.ExtensionViewSet)
router.register_viewset(r'machines/mx', views.MxViewSet)
router.register_viewset(r'machines/ns', views.NsViewSet)
router.register_viewset(r'machines/txt', views.TxtViewSet)
router.register_viewset(r'machines/dname', views.DNameViewSet)
router.register_viewset(r'machines/srv', views.SrvViewSet)
router.register_viewset(r'machines/interface', views.InterfaceViewSet)
router.register_viewset(r'machines/ipv6list', views.Ipv6ListViewSet)
......
# -*- mode: python; 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.
......@@ -162,6 +163,12 @@ class TxtViewSet(viewsets.ReadOnlyModelViewSet):
queryset = machines.Txt.objects.all()
serializer_class = serializers.TxtSerializer
class DNameViewSet(viewsets.ReadOnlyModelViewSet):
"""Exposes list and details of `machines.models.DName` objects.
"""
queryset = machines.DName.objects.all()
serializer_class = serializers.DNameSerializer
class SrvViewSet(viewsets.ReadOnlyModelViewSet):
"""Exposes list and details of `machines.models.Srv` objects.
......
......@@ -5,6 +5,7 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# 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
......@@ -41,75 +42,49 @@ 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.shortcuts import get_object_or_404
from preferences.models import OptionalUser
from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin
from .models import Article, Paiement, Facture, Banque
from .payment_methods import balance
class NewFactureForm(FormRevMixin, ModelForm):
class FactureForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
"""
Form used to create a new invoice by using a payment method, a bank and a
cheque number.
Form used to manage and create an invoice and its fields.
"""
def __init__(self, *args, **kwargs):
def __init__(self, *args, creation=False, **kwargs):
user = kwargs['user']
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
# TODO : remove the use of cheque and banque and paiement
# for something more generic or at least in English
self.fields['cheque'].required = False
self.fields['banque'].required = False
self.fields['cheque'].label = _("Cheque number")
self.fields['banque'].empty_label = _("Not specified")
super(FactureForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['paiement'].empty_label = \
_("Select a payment method")
paiement_list = Paiement.objects.filter(type_paiement=1)
if paiement_list:
self.fields['paiement'].widget\
.attrs['data-cheque'] = paiement_list.first().id
self.fields['paiement'].queryset = Paiement.find_allowed_payments(user)
if not creation:
self.fields['user'].label = _("Member")
self.fields['user'].empty_label = \
_("Select the proprietary member")
self.fields['valid'].label = _("Validated invoice")
else:
self.fields = {'paiement': self.fields['paiement']}
class Meta:
model = Facture
fields = ['paiement', 'banque', 'cheque']
fields = '__all__'
def clean(self):
cleaned_data = super(NewFactureForm, self).clean()
cleaned_data = super(FactureForm, self).clean()
paiement = cleaned_data.get('paiement')
cheque = cleaned_data.get('cheque')
banque = cleaned_data.get('banque')
if not paiement:
raise forms.ValidationError(
_("A payment method must be specified.")
)
elif paiement.type_paiement == 'check' and not (cheque and banque):
raise forms.ValidationError(
_("A cheque number and a bank must be specified.")
)
return cleaned_data
class CreditSoldeForm(NewFactureForm):
"""
Form used to make some operations on the user's balance if the option is
activated.
"""
class Meta(NewFactureForm.Meta):
model = Facture
fields = ['paiement', 'banque', 'cheque']
def __init__(self, *args, **kwargs):
super(CreditSoldeForm, self).__init__(*args, **kwargs)
# TODO : change solde to balance
self.fields['paiement'].queryset = Paiement.objects.exclude(
moyen='solde'
).exclude(moyen='Solde')
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
class SelectUserArticleForm(
FormRevMixin, Form):
class SelectUserArticleForm(FormRevMixin, Form):
"""
Form used to select an article during the creation of an invoice for a
member.
......@@ -127,6 +102,11 @@ class SelectUserArticleForm(
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):
"""
......@@ -146,6 +126,10 @@ class SelectClubArticleForm(Form):
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):
......@@ -167,26 +151,6 @@ class NewFactureFormPdf(Form):
)
# TODO : change Facture to Invoice
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
"""
Form used to edit an invoice and its fields : payment method, bank,
user associated, ...
"""
class Meta(NewFactureForm.Meta):
# TODO : change Facture to Invoice
model = Facture
fields = '__all__'
def __init__(self, *args, **kwargs):
# TODO : change Facture to Invoice
super(EditFactureForm, self).__init__(*args, **kwargs)
self.fields['user'].label = _("Member")
self.fields['user'].empty_label = \
_("Select the proprietary member")
self.fields['valid'].label = _("Validated invoice")
class ArticleForm(FormRevMixin, ModelForm):
"""
Form used to create an article.
......@@ -231,17 +195,12 @@ class PaiementForm(FormRevMixin, ModelForm):
class Meta:
model = Paiement
# TODO : change moyen to method and type_paiement to payment_type
fields = ['moyen', 'type_paiement']
fields = ['moyen', 'available_for_everyone']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(PaiementForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields['moyen'].label = _("Payment method name")
self.fields['type_paiement'].label = _("Payment type")
self.fields['type_paiement'].help_text = \
_("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.")
# TODO : change paiement to payment
......@@ -304,56 +263,6 @@ class DelBanqueForm(FormRevMixin, Form):
self.fields['banques'].queryset = Banque.objects.all()
# TODO : change facture to Invoice
class NewFactureSoldeForm(NewFactureForm):
"""
Form used to create an invoice
"""
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(NewFactureSoldeForm, self).__init__(
*args,
prefix=prefix,
**kwargs
)
self.fields['cheque'].required = False
self.fields['banque'].required = False
self.fields['cheque'].label = _('Cheque number')
self.fields['banque'].empty_label = _("Not specified")
self.fields['paiement'].empty_label = \
_("Select a payment method")
# TODO : change paiement to payment
paiement_list = Paiement.objects.filter(type_paiement=1)
if paiement_list:
self.fields['paiement'].widget\
.attrs['data-cheque'] = paiement_list.first().id
class Meta:
# TODO : change facture to invoice
model = Facture
# TODO : change paiement to payment and baque to bank
fields = ['paiement', 'banque']
def clean(self):
cleaned_data = super(NewFactureSoldeForm, self).clean()
# TODO : change paiement to payment
paiement = cleaned_data.get("paiement")
cheque = cleaned_data.get("cheque")
# TODO : change banque to bank
banque = cleaned_data.get("banque")
# TODO : change paiement to payment
if not paiement:
raise forms.ValidationError(
_("A payment method must be specified.")
)
# TODO : change paiement and banque to payment and bank
elif paiement.type_paiement == "check" and not (cheque and banque):
raise forms.ValidationError(
_("A cheque number and a bank must be specified.")
)
return cleaned_data
# TODO : Better name and docstring
class RechargeForm(FormRevMixin, Form):
"""
......@@ -364,34 +273,31 @@ class RechargeForm(FormRevMixin, Form):
min_value=0.01,
validators=[]
)
payment = forms.ModelChoiceField(
queryset=Paiement.objects.none(),
label=_l("Payment method")
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
def __init__(self, *args, user=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)
def clean_value(self):
def clean(self):
"""
Returns a cleaned vlaue from the received form by validating
Returns a cleaned value from the received form by validating
the value is well inside the possible limits
"""
value = self.cleaned_data['value']
if value < OptionalUser.get_cached_value('min_online_payment'):
raise forms.ValidationError(
_("Requested amount is too small. Minimum amount possible : \
%(min_online_amount)s €.") % {
'min_online_amount': OptionalUser.get_cached_value(
'min_online_payment'
)
}
)
if value + self.user.solde > \
OptionalUser.get_cached_value('max_solde'):
balance_method = get_object_or_404(balance.PaymentMethod)
if balance_method.maximum_balance is not None and \
value + self.user.solde > balance_method.maximum_balance:
raise forms.ValidationError(
_("Requested amount is too high. Your balance can't exceed \
%(max_online_balance)s €.") % {
'max_online_balance': OptionalUser.get_cached_value(
'max_solde'
)
'max_online_balance': balance_method.maximum_balance
}
)
return value
return self.cleaned_data
# -*- coding: utf-8 -*-
# Generated by Django 1.10.7 on 2018-07-02 18:56
from __future__ import unicode_literals
import re2o.aes_field
import cotisations.payment_methods.mixins
from django.db import migrations, models
import django.db.models.deletion
def add_cheque(apps, schema_editor):
ChequePayment = apps.get_model('cotisations', 'ChequePayment')
Payment = apps.get_model('cotisations', 'Paiement')
for p in Payment.objects.filter(type_paiement=1):
cheque = ChequePayment()
cheque.payment = p
cheque.save()
def add_comnpay(apps, schema_editor):
ComnpayPayment = apps.get_model('cotisations', 'ComnpayPayment')
Payment = apps.get_model('cotisations', 'Paiement')
AssoOption = apps.get_model('preferences', 'AssoOption')
options, _created = AssoOption.objects.get_or_create()
try:
payment = Payment.objects.get(
moyen='Rechargement en ligne'
)
except Payment.DoesNotExist:
return
comnpay = ComnpayPayment()
comnpay.payment_user = options.payment_id
comnpay.payment = payment
comnpay.save()
payment.moyen = "ComnPay"
payment.save()
def add_solde(apps, schema_editor):
OptionalUser = apps.get_model('preferences', 'OptionalUser')
options, _created = OptionalUser.objects.get_or_create()
Payment = apps.get_model('cotisations', 'Paiement')
BalancePayment = apps.get_model('cotisations', 'BalancePayment')
try:
solde = Payment.objects.get(moyen="solde")
except Payment.DoesNotExist:
return
balance = BalancePayment()
balance.payment = solde
balance.minimum_balance = options.solde_negatif
balance.maximum_balance = options.max_solde
solde.is_balance = True
balance.save()
solde.save()
class Migration(migrations.Migration):
dependencies = [
('preferences', '0044_remove_payment_pass'),
('cotisations', '0029_auto_20180414_2056'),
]
operations = [
migrations.AlterModelOptions(
name='paiement',
options={'permissions': (('view_paiement', "Can see a payement's details"), ('use_every_payment', 'Can use every payement')), 'verbose_name': 'Payment method', 'verbose_name_plural': 'Payment methods'},
),
migrations.AlterModelOptions(
name='article',
options={'permissions': (('view_article', "Can see an article's details"), ('buy_every_article', 'Can buy every_article')), 'verbose_name': 'Article', 'verbose_name_plural': 'Articles'},
),
migrations.AddField(
model_name='paiement',
name='available_for_everyone',
field=models.BooleanField(default=False, verbose_name='Is available for every user'),
),
migrations.AddField(
model_name='paiement',
name='is_balance',
field=models.BooleanField(default=False, editable=False, help_text='There should be only one balance payment method.', verbose_name='Is user balance', validators=[cotisations.models.check_no_balance]),
),
migrations.AddField(
model_name='article',
name='available_for_everyone',
field=models.BooleanField(default=False, verbose_name='Is available for every user'),
),
migrations.CreateModel(
name='ChequePayment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')),
],
bases=(cotisations.payment_methods.mixins.PaymentMethodMixin, models.Model),
options={'verbose_name': 'Cheque'},
),
migrations.CreateModel(
name='ComnpayPayment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('payment_credential', models.CharField(blank=True, default='', max_length=255, verbose_name='ComNpay VAD Number')),
('payment_pass', re2o.aes_field.AESEncryptedField(blank=True, max_length=255, null=True, verbose_name='ComNpay Secret Key')),
('payment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')),
('minimum_payment', models.DecimalField(decimal_places=2, default=1, help_text='The minimal amount of money you have to use when paying with ComNpay', max_digits=5, verbose_name='Minimum payment')),
],
bases=(cotisations.payment_methods.mixins.PaymentMethodMixin, models.Model),
options={'verbose_name': 'ComNpay'},
),
migrations.CreateModel(
name='BalancePayment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('minimum_balance', models.DecimalField(decimal_places=2, default=0, help_text='The minimal amount of money allowed for the balance at the end of a payment. You can specify negative amount.', max_digits=5, verbose_name='Minimum balance')),
('payment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')),
('maximum_balance', models.DecimalField(decimal_places=2, default=50, help_text='The maximal amount of money allowed for the balance.', max_digits=5, verbose_name='Maximum balance', null=True, blank=True)),
('credit_balance_allowed', models.BooleanField(default=False, verbose_name='Allow user to credit their balance')),
],
bases=(cotisations.payment_methods.mixins.PaymentMethodMixin, models.Model),
options={'verbose_name': 'User Balance'},
),
migrations.RunPython(add_comnpay),
migrations.RunPython(add_cheque),
migrations.RunPython(add_solde),
migrations.RemoveField(
model_name='paiement',
name='type_paiement',
),
]
......@@ -6,6 +6,7 @@
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Goulven Kermarec
# Copyright © 2017 Augustin Lemesle
# 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
......@@ -42,11 +43,17 @@ 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.urls import reverse
from django.shortcuts import redirect
from django.contrib import messages
from machines.models import regen
from re2o.field_permissions import FieldPermissionModelMixin
from re2o.mixins import AclMixin, RevMixin