mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 06:32:26 +00:00
Release : 2.7
This commit is contained in:
commit
a8dbe4621f
265 changed files with 11067 additions and 9165 deletions
52
.gitignore
vendored
52
.gitignore
vendored
|
@ -1,8 +1,48 @@
|
||||||
settings_local.py
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
*.swp
|
*.swp
|
||||||
*.pyc
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# PyCharm project settings
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Django statics
|
||||||
|
static_files/
|
||||||
|
static/logo/
|
||||||
|
|
||||||
|
# re2o specific
|
||||||
|
settings_local.py
|
||||||
re2o.png
|
re2o.png
|
||||||
__pycache__/*
|
media/
|
||||||
static_files/*
|
|
||||||
static/logo/*
|
|
||||||
media/*
|
|
||||||
|
|
28
CHANGELOG.md
28
CHANGELOG.md
|
@ -150,3 +150,31 @@ On some database engines (postgreSQL) you also need to update the id sequences:
|
||||||
```bash
|
```bash
|
||||||
python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell
|
python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## MR 296: Frontend changes
|
||||||
|
|
||||||
|
Install fonts-font-awesome
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt-get -y install fonts-font-awesome
|
||||||
|
```
|
||||||
|
|
||||||
|
Collec new statics
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 manage.py collectstatic
|
||||||
|
```
|
||||||
|
|
||||||
|
## MR 391: Document templates and subscription vouchers
|
||||||
|
|
||||||
|
Re2o can now use templates for generated invoices. To load default templates run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install update
|
||||||
|
```
|
||||||
|
|
||||||
|
Be carefull, you need the proper rights to edit a DocumentTemplate.
|
||||||
|
|
||||||
|
Re2o now sends subscription voucher when an invoice is controlled. It uses one
|
||||||
|
of the templates. You also need to set the name of the president of your association
|
||||||
|
to be set in your settings.
|
||||||
|
|
32
README.md
32
README.md
|
@ -1,10 +1,10 @@
|
||||||
# Re2o
|
# Re2o
|
||||||
|
|
||||||
Gnu public license v2.0
|
GNU public license v2.0
|
||||||
|
|
||||||
## Avant propos
|
## Avant propos
|
||||||
|
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
Re2o est un logiciel d'administration développé initialement au rezometz. Il
|
||||||
se veut agnostique au réseau considéré, de manière à être installable en
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
quelques clics.
|
quelques clics.
|
||||||
|
|
||||||
|
@ -31,15 +31,15 @@ Pour cela :
|
||||||
|
|
||||||
## Fonctionnement général
|
## Fonctionnement général
|
||||||
|
|
||||||
Re2o est séparé entre les models, qui sont visible sur le schéma des
|
Re2o est séparé entre les models, qui sont visibles sur le schéma des
|
||||||
dépendances. Il s'agit en réalité des tables sql, et les fields etant les
|
dépendances. Il s'agit en réalité des tables sql, et les fields étant les
|
||||||
colonnes.
|
colonnes.
|
||||||
Ceci dit il n'est jamais nécessaire de toucher directement au sql, django
|
Ceci dit il n'est jamais nécessaire de toucher directement au sql, django
|
||||||
procédant automatiquement à tout cela.
|
procédant automatiquement à tout cela.
|
||||||
On crée donc différents models (user, right pour les droits des users,
|
On crée donc différents models (user, right pour les droits des users,
|
||||||
interfaces, IpList pour l'ensemble des adresses ip, etc)
|
interfaces, IpList pour l'ensemble des adresses ip, etc)
|
||||||
|
|
||||||
Du coté des forms, il s'agit des formulaire d'édition des models. Il
|
Du coté des forms, il s'agit des formulaires d'édition des models. Il
|
||||||
s'agit de ModelForms django, qui héritent des models très simplement, voir la
|
s'agit de ModelForms django, qui héritent des models très simplement, voir la
|
||||||
documentation django models forms.
|
documentation django models forms.
|
||||||
|
|
||||||
|
@ -56,12 +56,20 @@ d'accéder à ces vues, utilisé par re2o-tools.
|
||||||
|
|
||||||
# Requète en base de donnée
|
# Requète en base de donnée
|
||||||
|
|
||||||
Pour avoir un shell, il suffit de lancer '''python3 manage.py shell'''
|
Pour avoir un shell, lancer :
|
||||||
Pour charger des objets, example avec User, faire :
|
```.bash
|
||||||
''' from users.models import User'''
|
python3 manage.py shell
|
||||||
Pour charger les objets django, il suffit de faire User.objects.all()
|
```
|
||||||
|
|
||||||
|
Pour charger des objets (exemple avec User), faire :
|
||||||
|
```.python
|
||||||
|
from users.models import User
|
||||||
|
```
|
||||||
|
|
||||||
|
Pour charger les objets django, il suffit de faire `User.objects.all()`
|
||||||
pour tous les users par exemple.
|
pour tous les users par exemple.
|
||||||
Il est ensuite aisé de faire des requètes, par exemple
|
Il est ensuite aisé de faire des requêtes, par exemple
|
||||||
User.objects.filter(pseudo='test')
|
`User.objects.filter(pseudo='test')`
|
||||||
Des exemples et la documentation complète sur les requètes django sont
|
|
||||||
|
Des exemples et la documentation complète sur les requêtes django sont
|
||||||
disponible sur le site officiel.
|
disponible sur le site officiel.
|
||||||
|
|
|
@ -26,9 +26,9 @@ done.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
|
||||||
def _create_api_permission():
|
def _create_api_permission():
|
||||||
|
@ -71,4 +71,5 @@ def can_view(user):
|
||||||
'codename': settings.API_PERMISSION_CODENAME
|
'codename': settings.API_PERMISSION_CODENAME
|
||||||
}
|
}
|
||||||
can = user.has_perm('%(app_label)s.%(codename)s' % kwargs)
|
can = user.has_perm('%(app_label)s.%(codename)s' % kwargs)
|
||||||
return can, None if can else _("You cannot see this application.")
|
return can, None if can else _("You don't have the right to see this"
|
||||||
|
" application.")
|
||||||
|
|
|
@ -26,12 +26,14 @@ import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.authentication import TokenAuthentication
|
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
|
from rest_framework.authentication import TokenAuthentication
|
||||||
|
|
||||||
|
|
||||||
class ExpiringTokenAuthentication(TokenAuthentication):
|
class ExpiringTokenAuthentication(TokenAuthentication):
|
||||||
"""Authenticate a user if the provided token is valid and not expired.
|
"""Authenticate a user if the provided token is valid and not expired.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def authenticate_credentials(self, key):
|
def authenticate_credentials(self, key):
|
||||||
"""See base class. Add the verification the token is not expired.
|
"""See base class. Add the verification the token is not expired.
|
||||||
"""
|
"""
|
||||||
|
@ -44,6 +46,6 @@ class ExpiringTokenAuthentication(TokenAuthentication):
|
||||||
)
|
)
|
||||||
utc_now = datetime.datetime.now(datetime.timezone.utc)
|
utc_now = datetime.datetime.now(datetime.timezone.utc)
|
||||||
if token.created < utc_now - token_duration:
|
if token.created < utc_now - token_duration:
|
||||||
raise exceptions.AuthenticationFailed(_('Token has expired'))
|
raise exceptions.AuthenticationFailed(_("The token has expired."))
|
||||||
|
|
||||||
return (token.user, token)
|
return token.user, token
|
||||||
|
|
40
api/locale/fr/LC_MESSAGES/django.po
Normal file
40
api/locale/fr/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# 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: 2019-01-08 23:06+0100\n"
|
||||||
|
"PO-Revision-Date: 2019-01-07 01:37+0100\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"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: acl.py:74
|
||||||
|
msgid "You don't have the right to see this application."
|
||||||
|
msgstr "Vous n'avez pas le droit de voir cette application."
|
||||||
|
|
||||||
|
#: authentication.py:49
|
||||||
|
msgid "The token has expired."
|
||||||
|
msgstr "Le jeton a expiré."
|
|
@ -24,8 +24,6 @@
|
||||||
|
|
||||||
from rest_framework import permissions, exceptions
|
from rest_framework import permissions, exceptions
|
||||||
|
|
||||||
from re2o.acl import can_create, can_edit, can_delete, can_view_all
|
|
||||||
|
|
||||||
from . import acl
|
from . import acl
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,7 +78,8 @@ class ACLPermission(permissions.BasePermission):
|
||||||
See the wiki for the syntax of this attribute.
|
See the wiki for the syntax of this attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_required_permissions(self, method, view):
|
@staticmethod
|
||||||
|
def get_required_permissions(method, view):
|
||||||
"""Build the list of permissions required for the request to be
|
"""Build the list of permissions required for the request to be
|
||||||
accepted.
|
accepted.
|
||||||
|
|
||||||
|
@ -209,7 +208,8 @@ class AutodetectACLPermission(permissions.BasePermission):
|
||||||
|
|
||||||
return [perm(obj) for perm in self.perms_obj_map[method]]
|
return [perm(obj) for perm in self.perms_obj_map[method]]
|
||||||
|
|
||||||
def _queryset(self, view):
|
@staticmethod
|
||||||
|
def _queryset(view):
|
||||||
return _get_param_in_view(view, 'queryset')
|
return _get_param_in_view(view, 'queryset')
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
def has_permission(self, request, view):
|
||||||
|
@ -282,4 +282,3 @@ class AutodetectACLPermission(permissions.BasePermission):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -24,12 +24,12 @@
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url
|
||||||
from django.core.urlresolvers import NoReverseMatch
|
from django.core.urlresolvers import NoReverseMatch
|
||||||
from rest_framework import views
|
from rest_framework import views
|
||||||
from rest_framework.routers import DefaultRouter
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
from rest_framework.schemas import SchemaGenerator
|
from rest_framework.schemas import SchemaGenerator
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
|
||||||
|
@ -64,7 +64,8 @@ class AllViewsRouter(DefaultRouter):
|
||||||
name = self.get_default_name(pattern)
|
name = self.get_default_name(pattern)
|
||||||
self.view_registry.append((pattern, view, name))
|
self.view_registry.append((pattern, view, name))
|
||||||
|
|
||||||
def get_default_name(self, pattern):
|
@staticmethod
|
||||||
|
def get_default_name(pattern):
|
||||||
"""Returns the name to use for the route if none was specified.
|
"""Returns the name to use for the route if none was specified.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -113,7 +114,8 @@ class AllViewsRouter(DefaultRouter):
|
||||||
_ignore_model_permissions = True
|
_ignore_model_permissions = True
|
||||||
renderer_classes = view_renderers
|
renderer_classes = view_renderers
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
@staticmethod
|
||||||
|
def get(request, *args, **kwargs):
|
||||||
if request.accepted_renderer.media_type in schema_media_types:
|
if request.accepted_renderer.media_type in schema_media_types:
|
||||||
# Return a schema response.
|
# Return a schema response.
|
||||||
schema = schema_generator.get_schema(request)
|
schema = schema_generator.get_schema(request)
|
||||||
|
|
|
@ -30,7 +30,6 @@ import preferences.models as preferences
|
||||||
import topologie.models as topologie
|
import topologie.models as topologie
|
||||||
import users.models as users
|
import users.models as users
|
||||||
|
|
||||||
|
|
||||||
# The namespace used for the API. It must match the namespace used in the
|
# The namespace used for the API. It must match the namespace used in the
|
||||||
# urlpatterns to include the API URLs.
|
# urlpatterns to include the API URLs.
|
||||||
API_NAMESPACE = 'api'
|
API_NAMESPACE = 'api'
|
||||||
|
@ -40,6 +39,7 @@ class NamespacedHRField(serializers.HyperlinkedRelatedField):
|
||||||
"""A `rest_framework.serializers.HyperlinkedRelatedField` subclass to
|
"""A `rest_framework.serializers.HyperlinkedRelatedField` subclass to
|
||||||
automatically prefix view names with the API namespace.
|
automatically prefix view names with the API namespace.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, view_name=None, **kwargs):
|
def __init__(self, view_name=None, **kwargs):
|
||||||
if view_name is not None:
|
if view_name is not None:
|
||||||
view_name = '%s:%s' % (API_NAMESPACE, view_name)
|
view_name = '%s:%s' % (API_NAMESPACE, view_name)
|
||||||
|
@ -50,6 +50,7 @@ class NamespacedHIField(serializers.HyperlinkedIdentityField):
|
||||||
"""A `rest_framework.serializers.HyperlinkedIdentityField` subclass to
|
"""A `rest_framework.serializers.HyperlinkedIdentityField` subclass to
|
||||||
automatically prefix view names with teh API namespace.
|
automatically prefix view names with teh API namespace.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, view_name=None, **kwargs):
|
def __init__(self, view_name=None, **kwargs):
|
||||||
if view_name is not None:
|
if view_name is not None:
|
||||||
view_name = '%s:%s' % (API_NAMESPACE, view_name)
|
view_name = '%s:%s' % (API_NAMESPACE, view_name)
|
||||||
|
@ -70,24 +71,33 @@ class NamespacedHMSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class FactureSerializer(NamespacedHMSerializer):
|
class FactureSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `cotisations.models.Facture` objects.
|
"""Serialize `cotisations.models.Facture` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Facture
|
model = cotisations.Facture
|
||||||
fields = ('user', 'paiement', 'banque', 'cheque', 'date', 'valid',
|
fields = ('user', 'paiement', 'banque', 'cheque', 'date', 'valid',
|
||||||
'control', 'prix_total', 'name', 'api_url')
|
'control', 'prix_total', 'name', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
|
class BaseInvoiceSerializer(NamespacedHMSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = cotisations.BaseInvoice
|
||||||
|
fields = ('__all__')
|
||||||
|
|
||||||
class VenteSerializer(NamespacedHMSerializer):
|
class VenteSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `cotisations.models.Vente` objects.
|
"""Serialize `cotisations.models.Vente` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Vente
|
model = cotisations.Vente
|
||||||
fields = ('facture', 'number', 'name', 'prix', 'duration',
|
fields = ('facture',
|
||||||
|
'number', 'name', 'prix', 'duration',
|
||||||
'type_cotisation', 'prix_total', 'api_url')
|
'type_cotisation', 'prix_total', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
class ArticleSerializer(NamespacedHMSerializer):
|
class ArticleSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `cotisations.models.Article` objects.
|
"""Serialize `cotisations.models.Article` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Article
|
model = cotisations.Article
|
||||||
fields = ('name', 'prix', 'duration', 'type_user',
|
fields = ('name', 'prix', 'duration', 'type_user',
|
||||||
|
@ -97,6 +107,7 @@ class ArticleSerializer(NamespacedHMSerializer):
|
||||||
class BanqueSerializer(NamespacedHMSerializer):
|
class BanqueSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `cotisations.models.Banque` objects.
|
"""Serialize `cotisations.models.Banque` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Banque
|
model = cotisations.Banque
|
||||||
fields = ('name', 'api_url')
|
fields = ('name', 'api_url')
|
||||||
|
@ -105,14 +116,16 @@ class BanqueSerializer(NamespacedHMSerializer):
|
||||||
class PaiementSerializer(NamespacedHMSerializer):
|
class PaiementSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `cotisations.models.Paiement` objects.
|
"""Serialize `cotisations.models.Paiement` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Paiement
|
model = cotisations.Paiement
|
||||||
fields = ('moyen', 'type_paiement', 'api_url')
|
fields = ('moyen', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
class CotisationSerializer(NamespacedHMSerializer):
|
class CotisationSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `cotisations.models.Cotisation` objects.
|
"""Serialize `cotisations.models.Cotisation` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Cotisation
|
model = cotisations.Cotisation
|
||||||
fields = ('vente', 'type_cotisation', 'date_start', 'date_end',
|
fields = ('vente', 'type_cotisation', 'date_start', 'date_end',
|
||||||
|
@ -125,6 +138,7 @@ class CotisationSerializer(NamespacedHMSerializer):
|
||||||
class MachineSerializer(NamespacedHMSerializer):
|
class MachineSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Machine` objects.
|
"""Serialize `machines.models.Machine` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Machine
|
model = machines.Machine
|
||||||
fields = ('user', 'name', 'active', 'api_url')
|
fields = ('user', 'name', 'active', 'api_url')
|
||||||
|
@ -133,6 +147,7 @@ class MachineSerializer(NamespacedHMSerializer):
|
||||||
class MachineTypeSerializer(NamespacedHMSerializer):
|
class MachineTypeSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.MachineType` objects.
|
"""Serialize `machines.models.MachineType` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.MachineType
|
model = machines.MachineType
|
||||||
fields = ('type', 'ip_type', 'api_url')
|
fields = ('type', 'ip_type', 'api_url')
|
||||||
|
@ -141,6 +156,7 @@ class MachineTypeSerializer(NamespacedHMSerializer):
|
||||||
class IpTypeSerializer(NamespacedHMSerializer):
|
class IpTypeSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.IpType` objects.
|
"""Serialize `machines.models.IpType` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.IpType
|
model = machines.IpType
|
||||||
fields = ('type', 'extension', 'need_infra', 'domaine_ip_start',
|
fields = ('type', 'extension', 'need_infra', 'domaine_ip_start',
|
||||||
|
@ -151,14 +167,17 @@ class IpTypeSerializer(NamespacedHMSerializer):
|
||||||
class VlanSerializer(NamespacedHMSerializer):
|
class VlanSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Vlan` objects.
|
"""Serialize `machines.models.Vlan` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Vlan
|
model = machines.Vlan
|
||||||
fields = ('vlan_id', 'name', 'comment', 'api_url')
|
fields = ('vlan_id', 'name', 'comment', 'arp_protect', 'dhcp_snooping',
|
||||||
|
'dhcpv6_snooping', 'igmp', 'mld', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
class NasSerializer(NamespacedHMSerializer):
|
class NasSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Nas` objects.
|
"""Serialize `machines.models.Nas` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Nas
|
model = machines.Nas
|
||||||
fields = ('name', 'nas_type', 'machine_type', 'port_access_mode',
|
fields = ('name', 'nas_type', 'machine_type', 'port_access_mode',
|
||||||
|
@ -168,6 +187,7 @@ class NasSerializer(NamespacedHMSerializer):
|
||||||
class SOASerializer(NamespacedHMSerializer):
|
class SOASerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.SOA` objects.
|
"""Serialize `machines.models.SOA` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.SOA
|
model = machines.SOA
|
||||||
fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl',
|
fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl',
|
||||||
|
@ -177,6 +197,7 @@ class SOASerializer(NamespacedHMSerializer):
|
||||||
class ExtensionSerializer(NamespacedHMSerializer):
|
class ExtensionSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Extension` objects.
|
"""Serialize `machines.models.Extension` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Extension
|
model = machines.Extension
|
||||||
fields = ('name', 'need_infra', 'origin', 'origin_v6', 'soa',
|
fields = ('name', 'need_infra', 'origin', 'origin_v6', 'soa',
|
||||||
|
@ -186,6 +207,7 @@ class ExtensionSerializer(NamespacedHMSerializer):
|
||||||
class MxSerializer(NamespacedHMSerializer):
|
class MxSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Mx` objects.
|
"""Serialize `machines.models.Mx` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Mx
|
model = machines.Mx
|
||||||
fields = ('zone', 'priority', 'name', 'api_url')
|
fields = ('zone', 'priority', 'name', 'api_url')
|
||||||
|
@ -194,13 +216,16 @@ class MxSerializer(NamespacedHMSerializer):
|
||||||
class DNameSerializer(NamespacedHMSerializer):
|
class DNameSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.DName` objects.
|
"""Serialize `machines.models.DName` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.DName
|
model = machines.DName
|
||||||
fields = ('zone', 'alias', 'api_url')
|
fields = ('zone', 'alias', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
class NsSerializer(NamespacedHMSerializer):
|
class NsSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Ns` objects.
|
"""Serialize `machines.models.Ns` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Ns
|
model = machines.Ns
|
||||||
fields = ('zone', 'ns', 'api_url')
|
fields = ('zone', 'ns', 'api_url')
|
||||||
|
@ -209,6 +234,7 @@ class NsSerializer(NamespacedHMSerializer):
|
||||||
class TxtSerializer(NamespacedHMSerializer):
|
class TxtSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Txt` objects.
|
"""Serialize `machines.models.Txt` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Txt
|
model = machines.Txt
|
||||||
fields = ('zone', 'field1', 'field2', 'api_url')
|
fields = ('zone', 'field1', 'field2', 'api_url')
|
||||||
|
@ -217,14 +243,17 @@ class TxtSerializer(NamespacedHMSerializer):
|
||||||
class SrvSerializer(NamespacedHMSerializer):
|
class SrvSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Srv` objects.
|
"""Serialize `machines.models.Srv` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Srv
|
model = machines.Srv
|
||||||
fields = ('service', 'protocole', 'extension', 'ttl', 'priority',
|
fields = ('service', 'protocole', 'extension', 'ttl', 'priority',
|
||||||
'weight', 'port', 'target', 'api_url')
|
'weight', 'port', 'target', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
class SshFpSerializer(NamespacedHMSerializer):
|
class SshFpSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.SSHFP` objects.
|
"""Serialize `machines.models.SSHFP` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.SshFp
|
model = machines.SshFp
|
||||||
field = ('machine', 'pub_key_entry', 'algo', 'comment', 'api_url')
|
field = ('machine', 'pub_key_entry', 'algo', 'comment', 'api_url')
|
||||||
|
@ -245,6 +274,7 @@ class InterfaceSerializer(NamespacedHMSerializer):
|
||||||
class Ipv6ListSerializer(NamespacedHMSerializer):
|
class Ipv6ListSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Ipv6List` objects.
|
"""Serialize `machines.models.Ipv6List` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Ipv6List
|
model = machines.Ipv6List
|
||||||
fields = ('ipv6', 'interface', 'slaac_ip', 'api_url')
|
fields = ('ipv6', 'interface', 'slaac_ip', 'api_url')
|
||||||
|
@ -253,6 +283,7 @@ class Ipv6ListSerializer(NamespacedHMSerializer):
|
||||||
class DomainSerializer(NamespacedHMSerializer):
|
class DomainSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Domain` objects.
|
"""Serialize `machines.models.Domain` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Domain
|
model = machines.Domain
|
||||||
fields = ('interface_parent', 'name', 'extension', 'cname',
|
fields = ('interface_parent', 'name', 'extension', 'cname',
|
||||||
|
@ -262,6 +293,7 @@ class DomainSerializer(NamespacedHMSerializer):
|
||||||
class IpListSerializer(NamespacedHMSerializer):
|
class IpListSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.IpList` objects.
|
"""Serialize `machines.models.IpList` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.IpList
|
model = machines.IpList
|
||||||
fields = ('ipv4', 'ip_type', 'need_infra', 'api_url')
|
fields = ('ipv4', 'ip_type', 'need_infra', 'api_url')
|
||||||
|
@ -270,6 +302,7 @@ class IpListSerializer(NamespacedHMSerializer):
|
||||||
class ServiceSerializer(NamespacedHMSerializer):
|
class ServiceSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Service` objects.
|
"""Serialize `machines.models.Service` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Service
|
model = machines.Service
|
||||||
fields = ('service_type', 'min_time_regen', 'regular_time_regen',
|
fields = ('service_type', 'min_time_regen', 'regular_time_regen',
|
||||||
|
@ -279,6 +312,7 @@ class ServiceSerializer(NamespacedHMSerializer):
|
||||||
class ServiceLinkSerializer(NamespacedHMSerializer):
|
class ServiceLinkSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.Service_link` objects.
|
"""Serialize `machines.models.Service_link` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Service_link
|
model = machines.Service_link
|
||||||
fields = ('service', 'server', 'last_regen', 'asked_regen',
|
fields = ('service', 'server', 'last_regen', 'asked_regen',
|
||||||
|
@ -305,11 +339,22 @@ class OuverturePortListSerializer(NamespacedHMSerializer):
|
||||||
class OuverturePortSerializer(NamespacedHMSerializer):
|
class OuverturePortSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `machines.models.OuverturePort` objects.
|
"""Serialize `machines.models.OuverturePort` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.OuverturePort
|
model = machines.OuverturePort
|
||||||
fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url')
|
fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
|
class RoleSerializer(NamespacedHMSerializer):
|
||||||
|
"""Serialize `machines.models.OuverturePort` objects.
|
||||||
|
"""
|
||||||
|
servers = InterfaceSerializer(read_only=True, many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = machines.Role
|
||||||
|
fields = ('role_type', 'servers', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
# PREFERENCES
|
# PREFERENCES
|
||||||
|
|
||||||
|
|
||||||
|
@ -317,17 +362,21 @@ class OptionalUserSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.OptionalUser` objects.
|
"""Serialize `preferences.models.OptionalUser` objects.
|
||||||
"""
|
"""
|
||||||
tel_mandatory = serializers.BooleanField(source='is_tel_mandatory')
|
tel_mandatory = serializers.BooleanField(source='is_tel_mandatory')
|
||||||
|
shell_default = serializers.StringRelatedField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.OptionalUser
|
model = preferences.OptionalUser
|
||||||
fields = ('tel_mandatory', 'user_solde', 'solde_negatif', 'max_solde',
|
fields = ('tel_mandatory', 'gpg_fingerprint',
|
||||||
'min_online_payment', 'gpg_fingerprint',
|
'all_can_create_club', 'self_adhesion', 'shell_default',
|
||||||
'all_can_create_club', 'self_adhesion', 'shell_default')
|
'self_change_shell', 'local_email_accounts_enabled', 'local_email_domain',
|
||||||
|
'max_email_address',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OptionalMachineSerializer(NamespacedHMSerializer):
|
class OptionalMachineSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.OptionalMachine` objects.
|
"""Serialize `preferences.models.OptionalMachine` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.OptionalMachine
|
model = preferences.OptionalMachine
|
||||||
fields = ('password_machine', 'max_lambdauser_interfaces',
|
fields = ('password_machine', 'max_lambdauser_interfaces',
|
||||||
|
@ -338,27 +387,45 @@ class OptionalMachineSerializer(NamespacedHMSerializer):
|
||||||
class OptionalTopologieSerializer(NamespacedHMSerializer):
|
class OptionalTopologieSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.OptionalTopologie` objects.
|
"""Serialize `preferences.models.OptionalTopologie` objects.
|
||||||
"""
|
"""
|
||||||
|
switchs_management_interface_ip = serializers.CharField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.OptionalTopologie
|
model = preferences.OptionalTopologie
|
||||||
fields = ('radius_general_policy', 'vlan_decision_ok',
|
fields = ('switchs_ip_type', 'switchs_web_management',
|
||||||
'vlan_decision_nok')
|
'switchs_web_management_ssl', 'switchs_rest_management',
|
||||||
|
'switchs_management_utils', 'switchs_management_interface_ip',
|
||||||
|
'provision_switchs_enabled', 'switchs_provision', 'switchs_management_sftp_creds')
|
||||||
|
|
||||||
|
|
||||||
|
class RadiusOptionSerializer(NamespacedHMSerializer):
|
||||||
|
"""Serialize `preferences.models.RadiusOption` objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = preferences.RadiusOption
|
||||||
|
fields = ('radius_general_policy', 'unknown_machine',
|
||||||
|
'unknown_machine_vlan', 'unknown_port',
|
||||||
|
'unknown_port_vlan', 'unknown_room', 'unknown_room_vlan',
|
||||||
|
'non_member', 'non_member_vlan', 'banned', 'banned_vlan',
|
||||||
|
'vlan_decision_ok')
|
||||||
|
|
||||||
|
|
||||||
class GeneralOptionSerializer(NamespacedHMSerializer):
|
class GeneralOptionSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.GeneralOption` objects.
|
"""Serialize `preferences.models.GeneralOption` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.GeneralOption
|
model = preferences.GeneralOption
|
||||||
fields = ('general_message', 'search_display_page',
|
fields = ('general_message_fr', 'general_message_en',
|
||||||
'pagination_number', 'pagination_large_number',
|
'search_display_page', 'pagination_number',
|
||||||
'req_expire_hrs', 'site_name', 'email_from', 'GTU_sum_up',
|
'pagination_large_number', 'req_expire_hrs',
|
||||||
'GTU')
|
'site_name', 'main_site_url', 'email_from',
|
||||||
|
'GTU_sum_up', 'GTU')
|
||||||
|
|
||||||
class HomeServiceSerializer(NamespacedHMSerializer):
|
class HomeServiceSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.Service` objects.
|
"""Serialize `preferences.models.Service` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.Service
|
model = preferences.Service
|
||||||
fields = ('name', 'url', 'description', 'image', 'api_url')
|
fields = ('name', 'url', 'description', 'image', 'api_url')
|
||||||
|
@ -370,16 +437,17 @@ class HomeServiceSerializer(NamespacedHMSerializer):
|
||||||
class AssoOptionSerializer(NamespacedHMSerializer):
|
class AssoOptionSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.AssoOption` objects.
|
"""Serialize `preferences.models.AssoOption` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.AssoOption
|
model = preferences.AssoOption
|
||||||
fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact',
|
fields = ('name', 'siret', 'adresse1', 'adresse2', 'contact',
|
||||||
'telephone', 'pseudo', 'utilisateur_asso', 'payment',
|
'telephone', 'pseudo', 'utilisateur_asso', 'description')
|
||||||
'payment_id', 'payment_pass', 'description')
|
|
||||||
|
|
||||||
|
|
||||||
class HomeOptionSerializer(NamespacedHMSerializer):
|
class HomeOptionSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.HomeOption` objects.
|
"""Serialize `preferences.models.HomeOption` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.HomeOption
|
model = preferences.HomeOption
|
||||||
fields = ('facebook_url', 'twitter_url', 'twitter_account_name')
|
fields = ('facebook_url', 'twitter_url', 'twitter_account_name')
|
||||||
|
@ -388,18 +456,19 @@ class HomeOptionSerializer(NamespacedHMSerializer):
|
||||||
class MailMessageOptionSerializer(NamespacedHMSerializer):
|
class MailMessageOptionSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `preferences.models.MailMessageOption` objects.
|
"""Serialize `preferences.models.MailMessageOption` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.MailMessageOption
|
model = preferences.MailMessageOption
|
||||||
fields = ('welcome_mail_fr', 'welcome_mail_en')
|
fields = ('welcome_mail_fr', 'welcome_mail_en')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# TOPOLOGIE
|
# TOPOLOGIE
|
||||||
|
|
||||||
|
|
||||||
class StackSerializer(NamespacedHMSerializer):
|
class StackSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.Stack` objects
|
"""Serialize `topologie.models.Stack` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.Stack
|
model = topologie.Stack
|
||||||
fields = ('name', 'stack_id', 'details', 'member_id_min',
|
fields = ('name', 'stack_id', 'details', 'member_id_min',
|
||||||
|
@ -409,6 +478,7 @@ class StackSerializer(NamespacedHMSerializer):
|
||||||
class AccessPointSerializer(NamespacedHMSerializer):
|
class AccessPointSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.AccessPoint` objects
|
"""Serialize `topologie.models.AccessPoint` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.AccessPoint
|
model = topologie.AccessPoint
|
||||||
fields = ('user', 'name', 'active', 'location', 'api_url')
|
fields = ('user', 'name', 'active', 'location', 'api_url')
|
||||||
|
@ -418,6 +488,7 @@ class SwitchSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.Switch` objects
|
"""Serialize `topologie.models.Switch` objects
|
||||||
"""
|
"""
|
||||||
port_amount = serializers.IntegerField(source='number')
|
port_amount = serializers.IntegerField(source='number')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.Switch
|
model = topologie.Switch
|
||||||
fields = ('user', 'name', 'active', 'port_amount', 'stack',
|
fields = ('user', 'name', 'active', 'port_amount', 'stack',
|
||||||
|
@ -427,6 +498,7 @@ class SwitchSerializer(NamespacedHMSerializer):
|
||||||
class ServerSerializer(NamespacedHMSerializer):
|
class ServerSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.Server` objects
|
"""Serialize `topologie.models.Server` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.Server
|
model = topologie.Server
|
||||||
fields = ('user', 'name', 'active', 'api_url')
|
fields = ('user', 'name', 'active', 'api_url')
|
||||||
|
@ -435,6 +507,7 @@ class ServerSerializer(NamespacedHMSerializer):
|
||||||
class ModelSwitchSerializer(NamespacedHMSerializer):
|
class ModelSwitchSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.ModelSwitch` objects
|
"""Serialize `topologie.models.ModelSwitch` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.ModelSwitch
|
model = topologie.ModelSwitch
|
||||||
fields = ('reference', 'constructor', 'api_url')
|
fields = ('reference', 'constructor', 'api_url')
|
||||||
|
@ -443,6 +516,7 @@ class ModelSwitchSerializer(NamespacedHMSerializer):
|
||||||
class ConstructorSwitchSerializer(NamespacedHMSerializer):
|
class ConstructorSwitchSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.ConstructorSwitch` objects
|
"""Serialize `topologie.models.ConstructorSwitch` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.ConstructorSwitch
|
model = topologie.ConstructorSwitch
|
||||||
fields = ('name', 'api_url')
|
fields = ('name', 'api_url')
|
||||||
|
@ -451,6 +525,7 @@ class ConstructorSwitchSerializer(NamespacedHMSerializer):
|
||||||
class SwitchBaySerializer(NamespacedHMSerializer):
|
class SwitchBaySerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.SwitchBay` objects
|
"""Serialize `topologie.models.SwitchBay` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.SwitchBay
|
model = topologie.SwitchBay
|
||||||
fields = ('name', 'building', 'info', 'api_url')
|
fields = ('name', 'building', 'info', 'api_url')
|
||||||
|
@ -459,6 +534,7 @@ class SwitchBaySerializer(NamespacedHMSerializer):
|
||||||
class BuildingSerializer(NamespacedHMSerializer):
|
class BuildingSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.Building` objects
|
"""Serialize `topologie.models.Building` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.Building
|
model = topologie.Building
|
||||||
fields = ('name', 'api_url')
|
fields = ('name', 'api_url')
|
||||||
|
@ -467,19 +543,34 @@ class BuildingSerializer(NamespacedHMSerializer):
|
||||||
class SwitchPortSerializer(NamespacedHMSerializer):
|
class SwitchPortSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.Port` objects
|
"""Serialize `topologie.models.Port` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
get_port_profile = NamespacedHIField(view_name='portprofile-detail', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.Port
|
model = topologie.Port
|
||||||
fields = ('switch', 'port', 'room', 'machine_interface', 'related',
|
fields = ('switch', 'port', 'room', 'machine_interface', 'related',
|
||||||
'custom_profile', 'state', 'details', 'api_url')
|
'custom_profile', 'state', 'get_port_profile', 'details', 'api_url')
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'related': {'view_name': 'switchport-detail'},
|
'related': {'view_name': 'switchport-detail'},
|
||||||
'api_url': {'view_name': 'switchport-detail'},
|
'api_url': {'view_name': 'switchport-detail'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class PortProfileSerializer(NamespacedHMSerializer):
|
||||||
|
"""Serialize `topologie.models.Room` objects
|
||||||
|
"""
|
||||||
|
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', 'dhcpv6_snooping', 'arp_protect',
|
||||||
|
'ra_guard', 'loop_protect', 'api_url')
|
||||||
|
|
||||||
|
|
||||||
class RoomSerializer(NamespacedHMSerializer):
|
class RoomSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `topologie.models.Room` objects
|
"""Serialize `topologie.models.Room` objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = topologie.Room
|
model = topologie.Room
|
||||||
fields = ('name', 'details', 'api_url')
|
fields = ('name', 'details', 'api_url')
|
||||||
|
@ -552,9 +643,9 @@ class AdherentSerializer(NamespacedHMSerializer):
|
||||||
'shell': {'view_name': 'shell-detail'}
|
'shell': {'view_name': 'shell-detail'}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomeCreationSerializer(NamespacedHMSerializer):
|
|
||||||
"""Serialize 'users.models.User' minimal infos to create home
|
class BasicUserSerializer(NamespacedHMSerializer):
|
||||||
"""
|
"""Serialize 'users.models.User' minimal infos"""
|
||||||
uid = serializers.IntegerField(source='uid_number')
|
uid = serializers.IntegerField(source='uid_number')
|
||||||
gid = serializers.IntegerField(source='gid_number')
|
gid = serializers.IntegerField(source='gid_number')
|
||||||
|
|
||||||
|
@ -562,9 +653,11 @@ class HomeCreationSerializer(NamespacedHMSerializer):
|
||||||
model = users.User
|
model = users.User
|
||||||
fields = ('pseudo', 'uid', 'gid')
|
fields = ('pseudo', 'uid', 'gid')
|
||||||
|
|
||||||
|
|
||||||
class ServiceUserSerializer(NamespacedHMSerializer):
|
class ServiceUserSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `users.models.ServiceUser` objects.
|
"""Serialize `users.models.ServiceUser` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = users.ServiceUser
|
model = users.ServiceUser
|
||||||
fields = ('pseudo', 'access_group', 'comment', 'api_url')
|
fields = ('pseudo', 'access_group', 'comment', 'api_url')
|
||||||
|
@ -573,6 +666,7 @@ class ServiceUserSerializer(NamespacedHMSerializer):
|
||||||
class SchoolSerializer(NamespacedHMSerializer):
|
class SchoolSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `users.models.School` objects.
|
"""Serialize `users.models.School` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = users.School
|
model = users.School
|
||||||
fields = ('name', 'api_url')
|
fields = ('name', 'api_url')
|
||||||
|
@ -581,6 +675,7 @@ class SchoolSerializer(NamespacedHMSerializer):
|
||||||
class ListRightSerializer(NamespacedHMSerializer):
|
class ListRightSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `users.models.ListRight` objects.
|
"""Serialize `users.models.ListRight` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = users.ListRight
|
model = users.ListRight
|
||||||
fields = ('unix_name', 'gid', 'critical', 'details', 'api_url')
|
fields = ('unix_name', 'gid', 'critical', 'details', 'api_url')
|
||||||
|
@ -589,6 +684,7 @@ class ListRightSerializer(NamespacedHMSerializer):
|
||||||
class ShellSerializer(NamespacedHMSerializer):
|
class ShellSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `users.models.ListShell` objects.
|
"""Serialize `users.models.ListShell` objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = users.ListShell
|
model = users.ListShell
|
||||||
fields = ('shell', 'api_url')
|
fields = ('shell', 'api_url')
|
||||||
|
@ -622,6 +718,7 @@ class EMailAddressSerializer(NamespacedHMSerializer):
|
||||||
"""Serialize `users.models.EMailAddress` objects.
|
"""Serialize `users.models.EMailAddress` objects.
|
||||||
"""
|
"""
|
||||||
user = serializers.CharField(source='user.pseudo', read_only=True)
|
user = serializers.CharField(source='user.pseudo', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = users.EMailAddress
|
model = users.EMailAddress
|
||||||
fields = ('user', 'local_part', 'complete_email_address', 'api_url')
|
fields = ('user', 'local_part', 'complete_email_address', 'api_url')
|
||||||
|
@ -644,6 +741,90 @@ class ServiceRegenSerializer(NamespacedHMSerializer):
|
||||||
'api_url': {'view_name': 'serviceregen-detail'}
|
'api_url': {'view_name': 'serviceregen-detail'}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Switches et ports
|
||||||
|
|
||||||
|
class InterfaceVlanSerializer(NamespacedHMSerializer):
|
||||||
|
domain = serializers.CharField(read_only=True)
|
||||||
|
ipv4 = serializers.CharField(read_only=True)
|
||||||
|
ipv6 = Ipv6ListSerializer(read_only=True, many=True)
|
||||||
|
vlan_id = serializers.IntegerField(source='type.ip_type.vlan.vlan_id', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = machines.Interface
|
||||||
|
fields = ('ipv4', 'ipv6', 'domain', 'vlan_id')
|
||||||
|
|
||||||
|
class InterfaceRoleSerializer(NamespacedHMSerializer):
|
||||||
|
interface = InterfaceVlanSerializer(source='machine.interface_set', read_only=True, many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = machines.Interface
|
||||||
|
fields = ('interface',)
|
||||||
|
|
||||||
|
|
||||||
|
class RoleSerializer(NamespacedHMSerializer):
|
||||||
|
"""Serialize `machines.models.OuverturePort` objects.
|
||||||
|
"""
|
||||||
|
servers = InterfaceRoleSerializer(read_only=True, many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = machines.Role
|
||||||
|
fields = ('role_type', 'servers', 'specific_role')
|
||||||
|
|
||||||
|
|
||||||
|
class VlanPortSerializer(NamespacedHMSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = machines.Vlan
|
||||||
|
fields = ('vlan_id', 'name')
|
||||||
|
|
||||||
|
|
||||||
|
class ProfilSerializer(NamespacedHMSerializer):
|
||||||
|
vlan_untagged = VlanSerializer(read_only=True)
|
||||||
|
vlan_tagged = VlanPortSerializer(read_only=True, many=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')
|
||||||
|
|
||||||
|
|
||||||
|
class ModelSwitchSerializer(NamespacedHMSerializer):
|
||||||
|
constructor = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = topologie.ModelSwitch
|
||||||
|
fields = ('reference', 'firmware', 'constructor')
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchBaySerializer(NamespacedHMSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = topologie.SwitchBay
|
||||||
|
fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
class PortsSerializer(NamespacedHMSerializer):
|
||||||
|
"""Serialize `machines.models.Ipv6List` objects.
|
||||||
|
"""
|
||||||
|
get_port_profile = ProfilSerializer(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = topologie.Port
|
||||||
|
fields = ('state', 'port', 'pretty_name', 'get_port_profile')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SwitchPortSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialize the data about the switches"""
|
||||||
|
ports = PortsSerializer(many=True, read_only=True)
|
||||||
|
model = ModelSwitchSerializer(read_only=True)
|
||||||
|
switchbay = SwitchBaySerializer(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = topologie.Switch
|
||||||
|
fields = ('short_name', 'model', 'switchbay', 'ports', 'ipv4', 'ipv6',
|
||||||
|
'interfaces_subnet', 'interfaces6_subnet', 'automatic_provision', 'rest_enabled',
|
||||||
|
'web_management_enabled', 'get_radius_key_value', 'get_management_cred_value',
|
||||||
|
'list_modules')
|
||||||
|
|
||||||
# LOCAL EMAILS
|
# LOCAL EMAILS
|
||||||
|
|
||||||
|
@ -667,6 +848,7 @@ class FirewallPortListSerializer(serializers.ModelSerializer):
|
||||||
model = machines.OuverturePort
|
model = machines.OuverturePort
|
||||||
fields = ('begin', 'end', 'protocole', 'io', 'show_port')
|
fields = ('begin', 'end', 'protocole', 'io', 'show_port')
|
||||||
|
|
||||||
|
|
||||||
class FirewallOuverturePortListSerializer(serializers.ModelSerializer):
|
class FirewallOuverturePortListSerializer(serializers.ModelSerializer):
|
||||||
tcp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
|
tcp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
|
||||||
udp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
|
udp_ports_in = FirewallPortListSerializer(many=True, read_only=True)
|
||||||
|
@ -677,6 +859,7 @@ class FirewallOuverturePortListSerializer(serializers.ModelSerializer):
|
||||||
model = machines.OuverturePortList
|
model = machines.OuverturePortList
|
||||||
fields = ('tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', 'udp_ports_out')
|
fields = ('tcp_ports_in', 'udp_ports_in', 'tcp_ports_out', 'udp_ports_out')
|
||||||
|
|
||||||
|
|
||||||
class SubnetPortsOpenSerializer(serializers.ModelSerializer):
|
class SubnetPortsOpenSerializer(serializers.ModelSerializer):
|
||||||
ouverture_ports = FirewallOuverturePortListSerializer(read_only=True)
|
ouverture_ports = FirewallOuverturePortListSerializer(read_only=True)
|
||||||
|
|
||||||
|
@ -684,6 +867,7 @@ class SubnetPortsOpenSerializer(serializers.ModelSerializer):
|
||||||
model = machines.IpType
|
model = machines.IpType
|
||||||
fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'complete_prefixv6', 'ouverture_ports')
|
fields = ('type', 'domaine_ip_start', 'domaine_ip_stop', 'complete_prefixv6', 'ouverture_ports')
|
||||||
|
|
||||||
|
|
||||||
class InterfacePortsOpenSerializer(serializers.ModelSerializer):
|
class InterfacePortsOpenSerializer(serializers.ModelSerializer):
|
||||||
port_lists = FirewallOuverturePortListSerializer(read_only=True, many=True)
|
port_lists = FirewallOuverturePortListSerializer(read_only=True, many=True)
|
||||||
ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True)
|
ipv4 = serializers.CharField(source='ipv4.ipv4', read_only=True)
|
||||||
|
@ -693,6 +877,7 @@ class InterfacePortsOpenSerializer(serializers.ModelSerializer):
|
||||||
model = machines.Interface
|
model = machines.Interface
|
||||||
fields = ('port_lists', 'ipv4', 'ipv6')
|
fields = ('port_lists', 'ipv4', 'ipv6')
|
||||||
|
|
||||||
|
|
||||||
# DHCP
|
# DHCP
|
||||||
|
|
||||||
|
|
||||||
|
@ -717,6 +902,7 @@ class SOARecordSerializer(SOASerializer):
|
||||||
"""Serialize `machines.models.SOA` objects with the data needed to
|
"""Serialize `machines.models.SOA` objects with the data needed to
|
||||||
generate a SOA DNS record.
|
generate a SOA DNS record.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.SOA
|
model = machines.SOA
|
||||||
fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl')
|
fields = ('name', 'mail', 'refresh', 'retry', 'expire', 'ttl')
|
||||||
|
@ -726,6 +912,7 @@ class OriginV4RecordSerializer(IpListSerializer):
|
||||||
"""Serialize `machines.models.IpList` objects with the data needed to
|
"""Serialize `machines.models.IpList` objects with the data needed to
|
||||||
generate an IPv4 Origin DNS record.
|
generate an IPv4 Origin DNS record.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta(IpListSerializer.Meta):
|
class Meta(IpListSerializer.Meta):
|
||||||
fields = ('ipv4',)
|
fields = ('ipv4',)
|
||||||
|
|
||||||
|
@ -754,6 +941,7 @@ class TXTRecordSerializer(TxtSerializer):
|
||||||
"""Serialize `machines.models.Txt` objects with the data needed to
|
"""Serialize `machines.models.Txt` objects with the data needed to
|
||||||
generate a TXT DNS record.
|
generate a TXT DNS record.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta(TxtSerializer.Meta):
|
class Meta(TxtSerializer.Meta):
|
||||||
fields = ('field1', 'field2')
|
fields = ('field1', 'field2')
|
||||||
|
|
||||||
|
@ -772,6 +960,7 @@ class SSHFPRecordSerializer(SshFpSerializer):
|
||||||
"""Serialize `machines.models.SshFp` objects with the data needed to
|
"""Serialize `machines.models.SshFp` objects with the data needed to
|
||||||
generate a SSHFP DNS record.
|
generate a SSHFP DNS record.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta(SshFpSerializer.Meta):
|
class Meta(SshFpSerializer.Meta):
|
||||||
fields = ('algo_id', 'hash')
|
fields = ('algo_id', 'hash')
|
||||||
|
|
||||||
|
@ -823,6 +1012,17 @@ class CNAMERecordSerializer(serializers.ModelSerializer):
|
||||||
model = machines.Domain
|
model = machines.Domain
|
||||||
fields = ('alias', 'hostname')
|
fields = ('alias', 'hostname')
|
||||||
|
|
||||||
|
class DNAMERecordSerializer(serializers.ModelSerializer):
|
||||||
|
"""Serialize `machines.models.Domain` objects with the data needed to
|
||||||
|
generate a DNAME DNS record.
|
||||||
|
"""
|
||||||
|
alias = serializers.CharField(read_only=True)
|
||||||
|
zone = serializers.CharField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = machines.DName
|
||||||
|
fields = ('alias', 'zone')
|
||||||
|
|
||||||
|
|
||||||
class DNSZonesSerializer(serializers.ModelSerializer):
|
class DNSZonesSerializer(serializers.ModelSerializer):
|
||||||
"""Serialize the data about DNS Zones.
|
"""Serialize the data about DNS Zones.
|
||||||
|
@ -837,13 +1037,33 @@ class DNSZonesSerializer(serializers.ModelSerializer):
|
||||||
a_records = ARecordSerializer(many=True, source='get_associated_a_records')
|
a_records = ARecordSerializer(many=True, source='get_associated_a_records')
|
||||||
aaaa_records = AAAARecordSerializer(many=True, source='get_associated_aaaa_records')
|
aaaa_records = AAAARecordSerializer(many=True, source='get_associated_aaaa_records')
|
||||||
cname_records = CNAMERecordSerializer(many=True, source='get_associated_cname_records')
|
cname_records = CNAMERecordSerializer(many=True, source='get_associated_cname_records')
|
||||||
|
dname_records = DNAMERecordSerializer(many=True, source='get_associated_dname_records')
|
||||||
sshfp_records = SSHFPInterfaceSerializer(many=True, source='get_associated_sshfp_records')
|
sshfp_records = SSHFPInterfaceSerializer(many=True, source='get_associated_sshfp_records')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.Extension
|
model = machines.Extension
|
||||||
fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6',
|
fields = ('name', 'soa', 'ns_records', 'originv4', 'originv6',
|
||||||
'mx_records', 'txt_records', 'srv_records', 'a_records',
|
'mx_records', 'txt_records', 'srv_records', 'a_records',
|
||||||
'aaaa_records', 'cname_records', 'sshfp_records')
|
'aaaa_records', 'cname_records', 'dname_records', 'sshfp_records')
|
||||||
|
#REMINDER
|
||||||
|
|
||||||
|
|
||||||
|
class ReminderUsersSerializer(UserSerializer):
|
||||||
|
"""Serialize the data about a mailing member.
|
||||||
|
"""
|
||||||
|
class Meta(UserSerializer.Meta):
|
||||||
|
fields = ('get_full_name', 'get_mail')
|
||||||
|
|
||||||
|
|
||||||
|
class ReminderSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Serialize the data about a reminder
|
||||||
|
"""
|
||||||
|
users_to_remind = ReminderUsersSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = preferences.Reminder
|
||||||
|
fields = ('days','message','users_to_remind')
|
||||||
|
|
||||||
|
|
||||||
class DNSReverseZonesSerializer(serializers.ModelSerializer):
|
class DNSReverseZonesSerializer(serializers.ModelSerializer):
|
||||||
|
@ -858,22 +1078,24 @@ class DNSReverseZonesSerializer(serializers.ModelSerializer):
|
||||||
ptr_records = ARecordSerializer(many=True, source='get_associated_ptr_records')
|
ptr_records = ARecordSerializer(many=True, source='get_associated_ptr_records')
|
||||||
ptr_v6_records = AAAARecordSerializer(many=True, source='get_associated_ptr_v6_records')
|
ptr_v6_records = AAAARecordSerializer(many=True, source='get_associated_ptr_v6_records')
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = machines.IpType
|
model = machines.IpType
|
||||||
fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records',
|
fields = ('type', 'extension', 'soa', 'ns_records', 'mx_records',
|
||||||
'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs',
|
'txt_records', 'ptr_records', 'ptr_v6_records', 'cidrs',
|
||||||
'prefix_v6', 'prefix_v6_length')
|
'prefix_v6', 'prefix_v6_length')
|
||||||
|
|
||||||
|
|
||||||
# MAILING
|
# MAILING
|
||||||
|
|
||||||
|
|
||||||
class MailingMemberSerializer(UserSerializer):
|
class MailingMemberSerializer(UserSerializer):
|
||||||
"""Serialize the data about a mailing member.
|
"""Serialize the data about a mailing member.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta(UserSerializer.Meta):
|
class Meta(UserSerializer.Meta):
|
||||||
fields = ('name', 'pseudo', 'get_mail')
|
fields = ('name', 'pseudo', 'get_mail')
|
||||||
|
|
||||||
|
|
||||||
class MailingSerializer(ClubSerializer):
|
class MailingSerializer(ClubSerializer):
|
||||||
"""Serialize the data about a mailing.
|
"""Serialize the data about a mailing.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -21,10 +21,11 @@
|
||||||
"""Defines the test suite for the API
|
"""Defines the test suite for the API
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import datetime
|
import datetime
|
||||||
from rest_framework.test import APITestCase
|
import json
|
||||||
|
|
||||||
from requests import codes
|
from requests import codes
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
import cotisations.models as cotisations
|
import cotisations.models as cotisations
|
||||||
import machines.models as machines
|
import machines.models as machines
|
||||||
|
@ -676,6 +677,7 @@ class APIEndpointsTestCase(APITestCase):
|
||||||
formats=[None, 'json', 'api'],
|
formats=[None, 'json', 'api'],
|
||||||
assert_more=assert_more)
|
assert_more=assert_more)
|
||||||
|
|
||||||
|
|
||||||
class APIPaginationTestCase(APITestCase):
|
class APIPaginationTestCase(APITestCase):
|
||||||
"""Test case to check that the pagination is used on all endpoints that
|
"""Test case to check that the pagination is used on all endpoints that
|
||||||
should use it.
|
should use it.
|
||||||
|
@ -756,7 +758,7 @@ class APIPaginationTestCase(APITestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
cls.superuser.delete()
|
cls.superuser.delete()
|
||||||
super().tearDownClass()
|
super(APIPaginationTestCase, self).tearDownClass()
|
||||||
|
|
||||||
def test_pagination(self):
|
def test_pagination(self):
|
||||||
"""Tests that every endpoint is using the pagination correctly.
|
"""Tests that every endpoint is using the pagination correctly.
|
||||||
|
@ -776,4 +778,3 @@ class APIPaginationTestCase(APITestCase):
|
||||||
assert 'previous' in res_json.keys()
|
assert 'previous' in res_json.keys()
|
||||||
assert 'results' in res_json.keys()
|
assert 'results' in res_json.keys()
|
||||||
assert not len('results') > 100
|
assert not len('results') > 100
|
||||||
|
|
||||||
|
|
18
api/urls.py
18
api/urls.py
|
@ -32,7 +32,6 @@ from django.conf.urls import url, include
|
||||||
from . import views
|
from . import views
|
||||||
from .routers import AllViewsRouter
|
from .routers import AllViewsRouter
|
||||||
|
|
||||||
|
|
||||||
router = AllViewsRouter()
|
router = AllViewsRouter()
|
||||||
# COTISATIONS
|
# COTISATIONS
|
||||||
router.register_viewset(r'cotisations/facture', views.FactureViewSet)
|
router.register_viewset(r'cotisations/facture', views.FactureViewSet)
|
||||||
|
@ -63,10 +62,12 @@ router.register_viewset(r'machines/service', views.ServiceViewSet)
|
||||||
router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink')
|
router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink')
|
||||||
router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet)
|
router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet)
|
||||||
router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet)
|
router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet)
|
||||||
|
router.register_viewset(r'machines/role', views.RoleViewSet)
|
||||||
# PREFERENCES
|
# PREFERENCES
|
||||||
router.register_view(r'preferences/optionaluser', views.OptionalUserView),
|
router.register_view(r'preferences/optionaluser', views.OptionalUserView),
|
||||||
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
|
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
|
||||||
router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView),
|
router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView),
|
||||||
|
router.register_view(r'preferences/radiusoption', views.RadiusOptionView),
|
||||||
router.register_view(r'preferences/generaloption', views.GeneralOptionView),
|
router.register_view(r'preferences/generaloption', views.GeneralOptionView),
|
||||||
router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'),
|
router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'),
|
||||||
router.register_view(r'preferences/assooption', views.AssoOptionView),
|
router.register_view(r'preferences/assooption', views.AssoOptionView),
|
||||||
|
@ -81,12 +82,15 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet)
|
||||||
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
|
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
|
||||||
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
|
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
|
||||||
router.register_viewset(r'topologie/building', views.BuildingViewSet)
|
router.register_viewset(r'topologie/building', views.BuildingViewSet)
|
||||||
router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
|
router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
|
||||||
|
router.register_viewset(r'topologie/portprofile', views.PortProfileViewSet, base_name='portprofile')
|
||||||
router.register_viewset(r'topologie/room', views.RoomViewSet)
|
router.register_viewset(r'topologie/room', views.RoomViewSet)
|
||||||
router.register(r'topologie/portprofile', views.PortProfileViewSet)
|
router.register(r'topologie/portprofile', views.PortProfileViewSet)
|
||||||
# USERS
|
# USERS
|
||||||
router.register_viewset(r'users/user', views.UserViewSet)
|
router.register_viewset(r'users/user', views.UserViewSet, base_name='user')
|
||||||
router.register_viewset(r'users/homecreation', views.HomeCreationViewSet)
|
router.register_viewset(r'users/homecreation', views.HomeCreationViewSet, base_name='homecreation')
|
||||||
|
router.register_viewset(r'users/normaluser', views.NormalUserViewSet, base_name='normaluser')
|
||||||
|
router.register_viewset(r'users/criticaluser', views.CriticalUserViewSet, base_name='criticaluser')
|
||||||
router.register_viewset(r'users/club', views.ClubViewSet)
|
router.register_viewset(r'users/club', views.ClubViewSet)
|
||||||
router.register_viewset(r'users/adherent', views.AdherentViewSet)
|
router.register_viewset(r'users/adherent', views.AdherentViewSet)
|
||||||
router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet)
|
router.register_viewset(r'users/serviceuser', views.ServiceUserViewSet)
|
||||||
|
@ -105,6 +109,11 @@ router.register_view(r'localemail/users', views.LocalEmailUsersView),
|
||||||
# Firewall
|
# Firewall
|
||||||
router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView),
|
router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView),
|
||||||
router.register_view(r'firewall/interface-ports', views.InterfacePortsOpenView),
|
router.register_view(r'firewall/interface-ports', views.InterfacePortsOpenView),
|
||||||
|
# Switches config
|
||||||
|
router.register_view(r'switchs/ports-config', views.SwitchPortView),
|
||||||
|
router.register_view(r'switchs/role', views.RoleView),
|
||||||
|
# Reminder
|
||||||
|
router.register_view(r'reminder/get-users', views.ReminderView),
|
||||||
# DNS
|
# DNS
|
||||||
router.register_view(r'dns/zones', views.DNSZonesView),
|
router.register_view(r'dns/zones', views.DNSZonesView),
|
||||||
router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView),
|
router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView),
|
||||||
|
@ -114,7 +123,6 @@ router.register_view(r'mailing/club', views.ClubMailingView),
|
||||||
# TOKEN AUTHENTICATION
|
# TOKEN AUTHENTICATION
|
||||||
router.register_view(r'token-auth', views.ObtainExpiringAuthToken)
|
router.register_view(r'token-auth', views.ObtainExpiringAuthToken)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^', include(router.urls)),
|
url(r'^', include(router.urls)),
|
||||||
]
|
]
|
||||||
|
|
94
api/views.py
94
api/views.py
|
@ -29,10 +29,11 @@ the response (JSON or other), the CSRF exempting, ...
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from rest_framework.authtoken.views import ObtainAuthToken
|
from django.db.models import Q
|
||||||
from rest_framework.authtoken.models import Token
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import viewsets, generics, views
|
from rest_framework import viewsets, generics, views
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
from rest_framework.authtoken.views import ObtainAuthToken
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
import cotisations.models as cotisations
|
import cotisations.models as cotisations
|
||||||
import machines.models as machines
|
import machines.models as machines
|
||||||
|
@ -40,7 +41,6 @@ import preferences.models as preferences
|
||||||
import topologie.models as topologie
|
import topologie.models as topologie
|
||||||
import users.models as users
|
import users.models as users
|
||||||
from re2o.utils import all_active_interfaces, all_has_access
|
from re2o.utils import all_active_interfaces, all_has_access
|
||||||
|
|
||||||
from . import serializers
|
from . import serializers
|
||||||
from .pagination import PageSizedPagination
|
from .pagination import PageSizedPagination
|
||||||
from .permissions import ACLPermission
|
from .permissions import ACLPermission
|
||||||
|
@ -55,6 +55,12 @@ class FactureViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = cotisations.Facture.objects.all()
|
queryset = cotisations.Facture.objects.all()
|
||||||
serializer_class = serializers.FactureSerializer
|
serializer_class = serializers.FactureSerializer
|
||||||
|
|
||||||
|
class FactureViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""Exposes list and details of `cotisations.models.Facture` objects.
|
||||||
|
"""
|
||||||
|
queryset = cotisations.BaseInvoice.objects.all()
|
||||||
|
serializer_class = serializers.BaseInvoiceSerializer
|
||||||
|
|
||||||
|
|
||||||
class VenteViewSet(viewsets.ReadOnlyModelViewSet):
|
class VenteViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Exposes list and details of `cotisations.models.Vente` objects.
|
"""Exposes list and details of `cotisations.models.Vente` objects.
|
||||||
|
@ -163,6 +169,7 @@ class TxtViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = machines.Txt.objects.all()
|
queryset = machines.Txt.objects.all()
|
||||||
serializer_class = serializers.TxtSerializer
|
serializer_class = serializers.TxtSerializer
|
||||||
|
|
||||||
|
|
||||||
class DNameViewSet(viewsets.ReadOnlyModelViewSet):
|
class DNameViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Exposes list and details of `machines.models.DName` objects.
|
"""Exposes list and details of `machines.models.DName` objects.
|
||||||
"""
|
"""
|
||||||
|
@ -241,6 +248,13 @@ class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = serializers.OuverturePortSerializer
|
serializer_class = serializers.OuverturePortSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class RoleViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""Exposes list and details of `machines.models.Machine` objects.
|
||||||
|
"""
|
||||||
|
queryset = machines.Role.objects.all()
|
||||||
|
serializer_class = serializers.RoleSerializer
|
||||||
|
|
||||||
|
|
||||||
# PREFERENCES
|
# PREFERENCES
|
||||||
# Those views differ a bit because there is only one object
|
# Those views differ a bit because there is only one object
|
||||||
# to display, so we don't bother with the listing part
|
# to display, so we don't bother with the listing part
|
||||||
|
@ -278,6 +292,17 @@ class OptionalTopologieView(generics.RetrieveAPIView):
|
||||||
return preferences.OptionalTopologie.objects.first()
|
return preferences.OptionalTopologie.objects.first()
|
||||||
|
|
||||||
|
|
||||||
|
class RadiusOptionView(generics.RetrieveAPIView):
|
||||||
|
"""Exposes details of `preferences.models.OptionalTopologie` settings.
|
||||||
|
"""
|
||||||
|
permission_classes = (ACLPermission,)
|
||||||
|
perms_map = {'GET': [preferences.RadiusOption.can_view_all]}
|
||||||
|
serializer_class = serializers.RadiusOptionSerializer
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return preferences.RadiusOption.objects.first()
|
||||||
|
|
||||||
|
|
||||||
class GeneralOptionView(generics.RetrieveAPIView):
|
class GeneralOptionView(generics.RetrieveAPIView):
|
||||||
"""Exposes details of `preferences.models.GeneralOption` settings.
|
"""Exposes details of `preferences.models.GeneralOption` settings.
|
||||||
"""
|
"""
|
||||||
|
@ -396,6 +421,13 @@ class SwitchPortViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = serializers.SwitchPortSerializer
|
serializer_class = serializers.SwitchPortSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PortProfileViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""Exposes list and details of `topologie.models.PortProfile` objects.
|
||||||
|
"""
|
||||||
|
queryset = topologie.PortProfile.objects.all()
|
||||||
|
serializer_class = serializers.PortProfileSerializer
|
||||||
|
|
||||||
|
|
||||||
class RoomViewSet(viewsets.ReadOnlyModelViewSet):
|
class RoomViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Exposes list and details of `topologie.models.Room` objects.
|
"""Exposes list and details of `topologie.models.Room` objects.
|
||||||
"""
|
"""
|
||||||
|
@ -409,6 +441,7 @@ class PortProfileViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = topologie.PortProfile.objects.all()
|
queryset = topologie.PortProfile.objects.all()
|
||||||
serializer_class = serializers.PortProfileSerializer
|
serializer_class = serializers.PortProfileSerializer
|
||||||
|
|
||||||
|
|
||||||
# USER
|
# USER
|
||||||
|
|
||||||
|
|
||||||
|
@ -418,11 +451,25 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = users.User.objects.all()
|
queryset = users.User.objects.all()
|
||||||
serializer_class = serializers.UserSerializer
|
serializer_class = serializers.UserSerializer
|
||||||
|
|
||||||
|
|
||||||
class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet):
|
class HomeCreationViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Exposes infos of `users.models.Users` objects to create homes.
|
"""Exposes infos of `users.models.Users` objects to create homes.
|
||||||
"""
|
"""
|
||||||
queryset = users.User.objects.all()
|
queryset = users.User.objects.exclude(Q(state=users.User.STATE_DISABLED) | Q(state=users.User.STATE_NOT_YET_ACTIVE))
|
||||||
serializer_class = serializers.HomeCreationSerializer
|
serializer_class = serializers.BasicUserSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class NormalUserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""Exposes infos of `users.models.Users`without specific rights objects."""
|
||||||
|
queryset = users.User.objects.exclude(groups__listright__critical=True).distinct()
|
||||||
|
serializer_class = serializers.BasicUserSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class CriticalUserViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""Exposes infos of `users.models.Users`without specific rights objects."""
|
||||||
|
queryset = users.User.objects.filter(groups__listright__critical=True).distinct()
|
||||||
|
serializer_class = serializers.BasicUserSerializer
|
||||||
|
|
||||||
|
|
||||||
class ClubViewSet(viewsets.ReadOnlyModelViewSet):
|
class ClubViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
"""Exposes list and details of `users.models.Club` objects.
|
"""Exposes list and details of `users.models.Club` objects.
|
||||||
|
@ -514,6 +561,31 @@ class ServiceRegenViewSet(viewsets.ModelViewSet):
|
||||||
queryset = queryset.filter(server__domain__name__iexact=hostname)
|
queryset = queryset.filter(server__domain__name__iexact=hostname)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
# Config des switches
|
||||||
|
|
||||||
|
class SwitchPortView(generics.ListAPIView):
|
||||||
|
"""Output each port of a switch, to be serialized with
|
||||||
|
additionnal informations (profiles etc)
|
||||||
|
"""
|
||||||
|
queryset = topologie.Switch.objects.all().select_related("switchbay").select_related("model__constructor").prefetch_related("ports__custom_profile__vlan_tagged").prefetch_related("ports__custom_profile__vlan_untagged").prefetch_related("ports__machine_interface__domain__extension").prefetch_related("ports__room")
|
||||||
|
|
||||||
|
serializer_class = serializers.SwitchPortSerializer
|
||||||
|
|
||||||
|
# Rappel fin adhésion
|
||||||
|
|
||||||
|
class ReminderView(generics.ListAPIView):
|
||||||
|
"""Output for users to remind an end of their subscription.
|
||||||
|
"""
|
||||||
|
queryset = preferences.Reminder.objects.all()
|
||||||
|
serializer_class = serializers.ReminderSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class RoleView(generics.ListAPIView):
|
||||||
|
"""Output of roles for each server
|
||||||
|
"""
|
||||||
|
queryset = machines.Role.objects.all().prefetch_related('servers')
|
||||||
|
serializer_class = serializers.RoleSerializer
|
||||||
|
|
||||||
|
|
||||||
# LOCAL EMAILS
|
# LOCAL EMAILS
|
||||||
|
|
||||||
|
@ -539,9 +611,11 @@ class HostMacIpView(generics.ListAPIView):
|
||||||
"""Exposes the associations between hostname, mac address and IPv4 in
|
"""Exposes the associations between hostname, mac address and IPv4 in
|
||||||
order to build the DHCP lease files.
|
order to build the DHCP lease files.
|
||||||
"""
|
"""
|
||||||
queryset = all_active_interfaces()
|
|
||||||
serializer_class = serializers.HostMacIpSerializer
|
serializer_class = serializers.HostMacIpSerializer
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return all_active_interfaces()
|
||||||
|
|
||||||
|
|
||||||
# Firewall
|
# Firewall
|
||||||
|
|
||||||
|
@ -549,10 +623,12 @@ class SubnetPortsOpenView(generics.ListAPIView):
|
||||||
queryset = machines.IpType.objects.all()
|
queryset = machines.IpType.objects.all()
|
||||||
serializer_class = serializers.SubnetPortsOpenSerializer
|
serializer_class = serializers.SubnetPortsOpenSerializer
|
||||||
|
|
||||||
|
|
||||||
class InterfacePortsOpenView(generics.ListAPIView):
|
class InterfacePortsOpenView(generics.ListAPIView):
|
||||||
queryset = machines.Interface.objects.filter(port_lists__isnull=False).distinct()
|
queryset = machines.Interface.objects.filter(port_lists__isnull=False).distinct()
|
||||||
serializer_class = serializers.InterfacePortsOpenSerializer
|
serializer_class = serializers.InterfacePortsOpenSerializer
|
||||||
|
|
||||||
|
|
||||||
# DNS
|
# DNS
|
||||||
|
|
||||||
|
|
||||||
|
@ -570,6 +646,7 @@ class DNSZonesView(generics.ListAPIView):
|
||||||
.all())
|
.all())
|
||||||
serializer_class = serializers.DNSZonesSerializer
|
serializer_class = serializers.DNSZonesSerializer
|
||||||
|
|
||||||
|
|
||||||
class DNSReverseZonesView(generics.ListAPIView):
|
class DNSReverseZonesView(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.
|
IPs, DNS records, etc.) in order to build the DNS zone files.
|
||||||
|
@ -578,8 +655,6 @@ class DNSReverseZonesView(generics.ListAPIView):
|
||||||
serializer_class = serializers.DNSReverseZonesSerializer
|
serializer_class = serializers.DNSReverseZonesSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# MAILING
|
# MAILING
|
||||||
|
|
||||||
|
|
||||||
|
@ -617,6 +692,7 @@ class ObtainExpiringAuthToken(ObtainAuthToken):
|
||||||
`rest_framework.auth_token.views.ObtainAuthToken` view except that the
|
`rest_framework.auth_token.views.ObtainAuthToken` view except that the
|
||||||
expiration time is send along with the token as an addtional information.
|
expiration time is send along with the token as an addtional information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
|
@ -14,4 +14,7 @@ libjs-jquery
|
||||||
libjs-jquery-ui
|
libjs-jquery-ui
|
||||||
libjs-jquery-timepicker
|
libjs-jquery-timepicker
|
||||||
libjs-bootstrap
|
libjs-bootstrap
|
||||||
|
fonts-font-awesome
|
||||||
graphviz
|
graphviz
|
||||||
|
git
|
||||||
|
gettext
|
||||||
|
|
|
@ -30,7 +30,7 @@ from django.contrib import admin
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
|
from .models import Facture, Article, Banque, Paiement, Cotisation, Vente
|
||||||
from .models import CustomInvoice
|
from .models import CustomInvoice, CostEstimate
|
||||||
|
|
||||||
|
|
||||||
class FactureAdmin(VersionAdmin):
|
class FactureAdmin(VersionAdmin):
|
||||||
|
@ -38,6 +38,11 @@ class FactureAdmin(VersionAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CostEstimateAdmin(VersionAdmin):
|
||||||
|
"""Admin class for cost estimates."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CustomInvoiceAdmin(VersionAdmin):
|
class CustomInvoiceAdmin(VersionAdmin):
|
||||||
"""Admin class for custom invoices."""
|
"""Admin class for custom invoices."""
|
||||||
pass
|
pass
|
||||||
|
@ -76,3 +81,4 @@ admin.site.register(Paiement, PaiementAdmin)
|
||||||
admin.site.register(Vente, VenteAdmin)
|
admin.site.register(Vente, VenteAdmin)
|
||||||
admin.site.register(Cotisation, CotisationAdmin)
|
admin.site.register(Cotisation, CotisationAdmin)
|
||||||
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
|
admin.site.register(CustomInvoice, CustomInvoiceAdmin)
|
||||||
|
admin.site.register(CostEstimate, CostEstimateAdmin)
|
||||||
|
|
|
@ -46,7 +46,10 @@ from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from re2o.field_permissions import FieldPermissionFormMixin
|
from re2o.field_permissions import FieldPermissionFormMixin
|
||||||
from re2o.mixins import FormRevMixin
|
from re2o.mixins import FormRevMixin
|
||||||
from .models import Article, Paiement, Facture, Banque, CustomInvoice
|
from .models import (
|
||||||
|
Article, Paiement, Facture, Banque,
|
||||||
|
CustomInvoice, Vente, CostEstimate,
|
||||||
|
)
|
||||||
from .payment_methods import balance
|
from .payment_methods import balance
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,9 +105,46 @@ class SelectArticleForm(FormRevMixin, Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
user = kwargs.pop('user')
|
user = kwargs.pop('user')
|
||||||
target_user = kwargs.pop('target_user')
|
target_user = kwargs.pop('target_user', None)
|
||||||
super(SelectArticleForm, self).__init__(*args, **kwargs)
|
super(SelectArticleForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['article'].queryset = Article.find_allowed_articles(user, target_user)
|
self.fields['article'].queryset = Article.find_allowed_articles(
|
||||||
|
user, target_user)
|
||||||
|
|
||||||
|
|
||||||
|
class DiscountForm(Form):
|
||||||
|
"""
|
||||||
|
Form used in oder to create a discount on an invoice.
|
||||||
|
"""
|
||||||
|
is_relative = forms.BooleanField(
|
||||||
|
label=_("Discount is on percentage."),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
discount = forms.DecimalField(
|
||||||
|
label=_("Discount"),
|
||||||
|
max_value=100,
|
||||||
|
min_value=0,
|
||||||
|
max_digits=5,
|
||||||
|
decimal_places=2,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_to_invoice(self, invoice):
|
||||||
|
invoice_price = invoice.prix_total()
|
||||||
|
discount = self.cleaned_data['discount']
|
||||||
|
is_relative = self.cleaned_data['is_relative']
|
||||||
|
if is_relative:
|
||||||
|
amount = discount/100 * invoice_price
|
||||||
|
else:
|
||||||
|
amount = discount
|
||||||
|
if amount:
|
||||||
|
name = _("{}% discount") if is_relative else _("{}€ discount")
|
||||||
|
name = name.format(discount)
|
||||||
|
Vente.objects.create(
|
||||||
|
facture=invoice,
|
||||||
|
name=name,
|
||||||
|
prix=-amount,
|
||||||
|
number=1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CustomInvoiceForm(FormRevMixin, ModelForm):
|
class CustomInvoiceForm(FormRevMixin, ModelForm):
|
||||||
|
@ -116,6 +156,15 @@ class CustomInvoiceForm(FormRevMixin, ModelForm):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class CostEstimateForm(FormRevMixin, ModelForm):
|
||||||
|
"""
|
||||||
|
Form used to create a cost estimate.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = CostEstimate
|
||||||
|
exclude = ['paid', 'final_invoice']
|
||||||
|
|
||||||
|
|
||||||
class ArticleForm(FormRevMixin, ModelForm):
|
class ArticleForm(FormRevMixin, ModelForm):
|
||||||
"""
|
"""
|
||||||
Form used to create an article.
|
Form used to create an article.
|
||||||
|
@ -233,7 +282,7 @@ class RechargeForm(FormRevMixin, Form):
|
||||||
"""
|
"""
|
||||||
Form used to refill a user's balance
|
Form used to refill a user's balance
|
||||||
"""
|
"""
|
||||||
value = forms.FloatField(
|
value = forms.DecimalField(
|
||||||
label=_("Amount"),
|
label=_("Amount"),
|
||||||
min_value=0.01,
|
min_value=0.01,
|
||||||
validators=[]
|
validators=[]
|
||||||
|
@ -248,7 +297,8 @@ class RechargeForm(FormRevMixin, Form):
|
||||||
super(RechargeForm, self).__init__(*args, **kwargs)
|
super(RechargeForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['payment'].empty_label = \
|
self.fields['payment'].empty_label = \
|
||||||
_("Select a payment method")
|
_("Select a payment method")
|
||||||
self.fields['payment'].queryset = Paiement.find_allowed_payments(user_source).exclude(is_balance=True)
|
self.fields['payment'].queryset = Paiement.find_allowed_payments(
|
||||||
|
user_source).exclude(is_balance=True)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""
|
"""
|
||||||
|
@ -260,10 +310,9 @@ class RechargeForm(FormRevMixin, Form):
|
||||||
if balance_method.maximum_balance is not None and \
|
if balance_method.maximum_balance is not None and \
|
||||||
value + self.user.solde > balance_method.maximum_balance:
|
value + self.user.solde > balance_method.maximum_balance:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("Requested amount is too high. Your balance can't exceed \
|
_("Requested amount is too high. Your balance can't exceed"
|
||||||
%(max_online_balance)s €.") % {
|
" %(max_online_balance)s €.") % {
|
||||||
'max_online_balance': balance_method.maximum_balance
|
'max_online_balance': balance_method.maximum_balance
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return self.cleaned_data
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -21,7 +21,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 2.5\n"
|
"Project-Id-Version: 2.5\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-08-18 13:17+0200\n"
|
"POT-Creation-Date: 2019-01-12 16:50+0100\n"
|
||||||
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
|
"PO-Revision-Date: 2018-03-31 16:09+0002\n"
|
||||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||||
"Language: fr_FR\n"
|
"Language: fr_FR\n"
|
||||||
|
@ -33,79 +33,98 @@ msgstr ""
|
||||||
msgid "You don't have the right to view this application."
|
msgid "You don't have the right to view this application."
|
||||||
msgstr "Vous n'avez pas le droit de voir cette application."
|
msgstr "Vous n'avez pas le droit de voir cette application."
|
||||||
|
|
||||||
#: forms.py:63 forms.py:274
|
#: forms.py:66 forms.py:299
|
||||||
msgid "Select a payment method"
|
msgid "Select a payment method"
|
||||||
msgstr "Sélectionnez un moyen de paiement"
|
msgstr "Sélectionnez un moyen de paiement"
|
||||||
|
|
||||||
#: forms.py:66 models.py:510
|
#: forms.py:69 models.py:579
|
||||||
msgid "Member"
|
msgid "Member"
|
||||||
msgstr "Adhérent"
|
msgstr "Adhérent"
|
||||||
|
|
||||||
#: forms.py:68
|
#: forms.py:71
|
||||||
msgid "Select the proprietary member"
|
msgid "Select the proprietary member"
|
||||||
msgstr "Sélectionnez l'adhérent propriétaire"
|
msgstr "Sélectionnez l'adhérent propriétaire"
|
||||||
|
|
||||||
#: forms.py:69
|
#: forms.py:72
|
||||||
msgid "Validated invoice"
|
msgid "Validated invoice"
|
||||||
msgstr "Facture validée"
|
msgstr "Facture validée"
|
||||||
|
|
||||||
#: forms.py:82
|
#: forms.py:85
|
||||||
msgid "A payment method must be specified."
|
msgid "A payment method must be specified."
|
||||||
msgstr "Un moyen de paiement doit être renseigné."
|
msgstr "Un moyen de paiement doit être renseigné."
|
||||||
|
|
||||||
#: forms.py:96 forms.py:120 templates/cotisations/aff_article.html:33
|
#: forms.py:97 templates/cotisations/aff_article.html:33
|
||||||
#: templates/cotisations/facture.html:61
|
#: templates/cotisations/facture.html:67
|
||||||
msgid "Article"
|
msgid "Article"
|
||||||
msgstr "Article"
|
msgstr "Article"
|
||||||
|
|
||||||
#: forms.py:100 forms.py:124 templates/cotisations/edit_facture.html:46
|
#: forms.py:101 templates/cotisations/edit_facture.html:50
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr "Quantité"
|
msgstr "Quantité"
|
||||||
|
|
||||||
#: forms.py:154
|
#: forms.py:119
|
||||||
|
msgid "Discount is on percentage."
|
||||||
|
msgstr "La réduction est en pourcentage."
|
||||||
|
|
||||||
|
#: forms.py:123 templates/cotisations/facture.html:78
|
||||||
|
msgid "Discount"
|
||||||
|
msgstr "Réduction"
|
||||||
|
|
||||||
|
#: forms.py:140
|
||||||
|
#, python-format
|
||||||
|
msgid "{}% discount"
|
||||||
|
msgstr "{}% de réduction"
|
||||||
|
|
||||||
|
#: forms.py:140
|
||||||
|
msgid "{}€ discount"
|
||||||
|
msgstr "{}€ de réduction"
|
||||||
|
|
||||||
|
#: forms.py:179
|
||||||
msgid "Article name"
|
msgid "Article name"
|
||||||
msgstr "Nom de l'article"
|
msgstr "Nom de l'article"
|
||||||
|
|
||||||
#: forms.py:164 templates/cotisations/sidebar.html:50
|
#: forms.py:189 templates/cotisations/sidebar.html:55
|
||||||
msgid "Available articles"
|
msgid "Available articles"
|
||||||
msgstr "Articles disponibles"
|
msgstr "Articles disponibles"
|
||||||
|
|
||||||
#: forms.py:192
|
#: forms.py:217
|
||||||
msgid "Payment method name"
|
msgid "Payment method name"
|
||||||
msgstr "Nom du moyen de paiement"
|
msgstr "Nom du moyen de paiement"
|
||||||
|
|
||||||
#: forms.py:204
|
#: forms.py:229
|
||||||
msgid "Available payment methods"
|
msgid "Available payment methods"
|
||||||
msgstr "Moyens de paiement disponibles"
|
msgstr "Moyens de paiement disponibles"
|
||||||
|
|
||||||
#: forms.py:230
|
#: forms.py:255
|
||||||
msgid "Bank name"
|
msgid "Bank name"
|
||||||
msgstr "Nom de la banque"
|
msgstr "Nom de la banque"
|
||||||
|
|
||||||
#: forms.py:242
|
#: forms.py:267
|
||||||
msgid "Available banks"
|
msgid "Available banks"
|
||||||
msgstr "Banques disponibles"
|
msgstr "Banques disponibles"
|
||||||
|
|
||||||
#: forms.py:261
|
#: forms.py:286
|
||||||
msgid "Amount"
|
msgid "Amount"
|
||||||
msgstr "Montant"
|
msgstr "Montant"
|
||||||
|
|
||||||
#: forms.py:267 templates/cotisations/aff_cotisations.html:44
|
#: forms.py:292 templates/cotisations/aff_cost_estimate.html:42
|
||||||
|
#: templates/cotisations/aff_cotisations.html:44
|
||||||
#: templates/cotisations/aff_custom_invoice.html:42
|
#: templates/cotisations/aff_custom_invoice.html:42
|
||||||
#: templates/cotisations/control.html:66
|
#: templates/cotisations/control.html:66
|
||||||
msgid "Payment method"
|
msgid "Payment method"
|
||||||
msgstr "Moyen de paiement"
|
msgstr "Moyen de paiement"
|
||||||
|
|
||||||
#: forms.py:287
|
#: forms.py:313
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Requested amount is too high. Your balance can't exceed "
|
"Requested amount is too high. Your balance can't exceed "
|
||||||
"%(max_online_balance)s €."
|
"%(max_online_balance)s €."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Le montant demandé trop grand. Votre solde ne peut excéder "
|
"Le montant demandé est trop grand. Votre solde ne peut excéder "
|
||||||
"%(max_online_balance)s €."
|
"%(max_online_balance)s €."
|
||||||
|
|
||||||
#: models.py:60 templates/cotisations/aff_cotisations.html:48
|
#: models.py:60 templates/cotisations/aff_cost_estimate.html:46
|
||||||
|
#: templates/cotisations/aff_cotisations.html:48
|
||||||
#: templates/cotisations/aff_custom_invoice.html:46
|
#: templates/cotisations/aff_custom_invoice.html:46
|
||||||
#: templates/cotisations/control.html:70
|
#: templates/cotisations/control.html:70
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
|
@ -133,9 +152,9 @@ msgstr "Peut voir un objet facture"
|
||||||
|
|
||||||
#: models.py:158
|
#: models.py:158
|
||||||
msgid "Can edit all the previous invoices"
|
msgid "Can edit all the previous invoices"
|
||||||
msgstr "Peut modifier toutes les factures existantes"
|
msgstr "Peut modifier toutes les factures précédentes"
|
||||||
|
|
||||||
#: models.py:160 models.py:305
|
#: models.py:160 models.py:373
|
||||||
msgid "invoice"
|
msgid "invoice"
|
||||||
msgstr "facture"
|
msgstr "facture"
|
||||||
|
|
||||||
|
@ -156,128 +175,149 @@ msgid ""
|
||||||
"You don't have the right to edit an invoice already controlled or "
|
"You don't have the right to edit an invoice already controlled or "
|
||||||
"invalidated."
|
"invalidated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'avez pas le droit de modifier une facture précedemment contrôlée ou "
|
"Vous n'avez pas le droit de modifier une facture précédemment contrôlée ou "
|
||||||
"invalidée."
|
"invalidée."
|
||||||
|
|
||||||
#: models.py:184
|
#: models.py:184
|
||||||
msgid "You don't have the right to delete an invoice."
|
msgid "You don't have the right to delete an invoice."
|
||||||
msgstr "Vous n'avez pas le droit de supprimer une facture."
|
msgstr "Vous n'avez pas le droit de supprimer une facture."
|
||||||
|
|
||||||
#: models.py:186
|
#: models.py:187
|
||||||
msgid "You don't have the right to delete this user's invoices."
|
msgid "You don't have the right to delete this user's invoices."
|
||||||
msgstr "Vous n'avez pas le droit de supprimer les factures de cet utilisateur."
|
msgstr "Vous n'avez pas le droit de supprimer les factures de cet utilisateur."
|
||||||
|
|
||||||
#: models.py:189
|
#: models.py:191
|
||||||
msgid ""
|
msgid ""
|
||||||
"You don't have the right to delete an invoice already controlled or "
|
"You don't have the right to delete an invoice already controlled or "
|
||||||
"invalidated."
|
"invalidated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'avez pas le droit de supprimer une facture précedement contrôlée ou "
|
"Vous n'avez pas le droit de supprimer une facture précédemment contrôlée ou "
|
||||||
"invalidée."
|
"invalidée."
|
||||||
|
|
||||||
#: models.py:197
|
#: models.py:199
|
||||||
msgid "You don't have the right to view someone else's invoices history."
|
msgid "You don't have the right to view someone else's invoices history."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'avez pas le droit de voir l'historique des factures d'un autre "
|
"Vous n'avez pas le droit de voir l'historique des factures d'un autre "
|
||||||
"utilisateur."
|
"utilisateur."
|
||||||
|
|
||||||
#: models.py:200
|
#: models.py:202
|
||||||
msgid "The invoice has been invalidated."
|
msgid "The invoice has been invalidated."
|
||||||
msgstr "La facture a été invalidée."
|
msgstr "La facture a été invalidée."
|
||||||
|
|
||||||
#: models.py:210
|
#: models.py:214
|
||||||
msgid "You don't have the right to edit the \"controlled\" state."
|
msgid "You don't have the right to edit the \"controlled\" state."
|
||||||
msgstr "Vous n'avez pas le droit de modifier le statut \"contrôlé\"."
|
msgstr "Vous n'avez pas le droit de modifier le statut \"contrôlé\"."
|
||||||
|
|
||||||
#: models.py:224
|
#: models.py:228
|
||||||
msgid "There are no payment method which you can use."
|
msgid "There are no payment method which you can use."
|
||||||
msgstr "Il n'y a pas de moyen de paiement que vous puissiez utiliser."
|
msgstr "Il n'y a pas de moyen de paiement que vous puissiez utiliser."
|
||||||
|
|
||||||
#: models.py:226
|
#: models.py:230
|
||||||
msgid "There are no article that you can buy."
|
msgid "There are no article that you can buy."
|
||||||
msgstr "Il n'y a pas d'article que vous puissiez acheter."
|
msgstr "Il n'y a pas d'article que vous puissiez acheter."
|
||||||
|
|
||||||
#: models.py:261
|
#: models.py:272
|
||||||
msgid "Can view a custom invoice object"
|
msgid "Can view a custom invoice object"
|
||||||
msgstr "Peut voir un objet facture personnalisée"
|
msgstr "Peut voir un objet facture personnalisée"
|
||||||
|
|
||||||
#: models.py:265 templates/cotisations/aff_custom_invoice.html:36
|
#: models.py:276 templates/cotisations/aff_cost_estimate.html:36
|
||||||
|
#: templates/cotisations/aff_custom_invoice.html:36
|
||||||
msgid "Recipient"
|
msgid "Recipient"
|
||||||
msgstr "Destinataire"
|
msgstr "Destinataire"
|
||||||
|
|
||||||
#: models.py:269 templates/cotisations/aff_paiement.html:33
|
#: models.py:280 templates/cotisations/aff_paiement.html:33
|
||||||
msgid "Payment type"
|
msgid "Payment type"
|
||||||
msgstr "Type de paiement"
|
msgstr "Type de paiement"
|
||||||
|
|
||||||
#: models.py:273
|
#: models.py:284
|
||||||
msgid "Address"
|
msgid "Address"
|
||||||
msgstr "Adresse"
|
msgstr "Adresse"
|
||||||
|
|
||||||
#: models.py:276 templates/cotisations/aff_custom_invoice.html:54
|
#: models.py:287 templates/cotisations/aff_custom_invoice.html:54
|
||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr "Payé"
|
msgstr "Payé"
|
||||||
|
|
||||||
#: models.py:296 models.py:516 models.py:764
|
#: models.py:291
|
||||||
|
msgid "Remark"
|
||||||
|
msgstr "Remarque"
|
||||||
|
|
||||||
|
#: models.py:300
|
||||||
|
msgid "Can view a cost estimate object"
|
||||||
|
msgstr "Peut voir un objet devis"
|
||||||
|
|
||||||
|
#: models.py:303
|
||||||
|
msgid "Period of validity"
|
||||||
|
msgstr "Période de validité"
|
||||||
|
|
||||||
|
#: models.py:340
|
||||||
|
msgid "You don't have the right to delete a cost estimate."
|
||||||
|
msgstr "Vous n'avez pas le droit de supprimer un devis."
|
||||||
|
|
||||||
|
#: models.py:343
|
||||||
|
msgid "The cost estimate has an invoice and can't be deleted."
|
||||||
|
msgstr "Le devis a une facture et ne peut pas être supprimé."
|
||||||
|
|
||||||
|
#: models.py:364 models.py:585 models.py:852
|
||||||
msgid "Connection"
|
msgid "Connection"
|
||||||
msgstr "Connexion"
|
msgstr "Connexion"
|
||||||
|
|
||||||
#: models.py:297 models.py:517 models.py:765
|
#: models.py:365 models.py:586 models.py:853
|
||||||
msgid "Membership"
|
msgid "Membership"
|
||||||
msgstr "Adhésion"
|
msgstr "Adhésion"
|
||||||
|
|
||||||
#: models.py:298 models.py:512 models.py:518 models.py:766
|
#: models.py:366 models.py:581 models.py:587 models.py:854
|
||||||
msgid "Both of them"
|
msgid "Both of them"
|
||||||
msgstr "Les deux"
|
msgstr "Les deux"
|
||||||
|
|
||||||
#: models.py:310
|
#: models.py:378
|
||||||
msgid "amount"
|
msgid "amount"
|
||||||
msgstr "montant"
|
msgstr "montant"
|
||||||
|
|
||||||
#: models.py:315
|
#: models.py:383
|
||||||
msgid "article"
|
msgid "article"
|
||||||
msgstr "article"
|
msgstr "article"
|
||||||
|
|
||||||
#: models.py:322
|
#: models.py:390
|
||||||
msgid "price"
|
msgid "price"
|
||||||
msgstr "prix"
|
msgstr "prix"
|
||||||
|
|
||||||
#: models.py:327 models.py:535
|
#: models.py:395 models.py:604
|
||||||
msgid "duration (in months)"
|
msgid "duration (in months)"
|
||||||
msgstr "durée (en mois)"
|
msgstr "durée (en mois)"
|
||||||
|
|
||||||
#: models.py:335 models.py:549 models.py:780
|
#: models.py:403 models.py:618 models.py:868
|
||||||
msgid "subscription type"
|
msgid "subscription type"
|
||||||
msgstr "type de cotisation"
|
msgstr "type de cotisation"
|
||||||
|
|
||||||
#: models.py:340
|
#: models.py:408
|
||||||
msgid "Can view a purchase object"
|
msgid "Can view a purchase object"
|
||||||
msgstr "Peut voir un objet achat"
|
msgstr "Peut voir un objet achat"
|
||||||
|
|
||||||
#: models.py:341
|
#: models.py:409
|
||||||
msgid "Can edit all the previous purchases"
|
msgid "Can edit all the previous purchases"
|
||||||
msgstr "Peut modifier tous les achats précédents"
|
msgstr "Peut modifier tous les achats précédents"
|
||||||
|
|
||||||
#: models.py:343 models.py:774
|
#: models.py:411 models.py:862
|
||||||
msgid "purchase"
|
msgid "purchase"
|
||||||
msgstr "achat"
|
msgstr "achat"
|
||||||
|
|
||||||
#: models.py:344
|
#: models.py:412
|
||||||
msgid "purchases"
|
msgid "purchases"
|
||||||
msgstr "achats"
|
msgstr "achats"
|
||||||
|
|
||||||
#: models.py:411 models.py:573
|
#: models.py:479 models.py:642
|
||||||
msgid "Duration must be specified for a subscription."
|
msgid "Duration must be specified for a subscription."
|
||||||
msgstr "La durée de la cotisation doit être indiquée."
|
msgstr "La durée de la cotisation doit être indiquée."
|
||||||
|
|
||||||
#: models.py:418
|
#: models.py:486
|
||||||
msgid "You don't have the right to edit the purchases."
|
msgid "You don't have the right to edit the purchases."
|
||||||
msgstr "Vous n'avez pas le droit de modifier les achats."
|
msgstr "Vous n'avez pas le droit de modifier les achats."
|
||||||
|
|
||||||
#: models.py:423
|
#: models.py:491
|
||||||
msgid "You don't have the right to edit this user's purchases."
|
msgid "You don't have the right to edit this user's purchases."
|
||||||
msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur."
|
msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur."
|
||||||
|
|
||||||
#: models.py:427
|
#: models.py:495
|
||||||
msgid ""
|
msgid ""
|
||||||
"You don't have the right to edit a purchase already controlled or "
|
"You don't have the right to edit a purchase already controlled or "
|
||||||
"invalidated."
|
"invalidated."
|
||||||
|
@ -285,150 +325,150 @@ msgstr ""
|
||||||
"Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou "
|
"Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou "
|
||||||
"invalidé."
|
"invalidé."
|
||||||
|
|
||||||
#: models.py:434
|
#: models.py:502
|
||||||
msgid "You don't have the right to delete a purchase."
|
msgid "You don't have the right to delete a purchase."
|
||||||
msgstr "Vous n'avez pas le droit de supprimer un achat."
|
msgstr "Vous n'avez pas le droit de supprimer un achat."
|
||||||
|
|
||||||
#: models.py:436
|
#: models.py:504
|
||||||
msgid "You don't have the right to delete this user's purchases."
|
msgid "You don't have the right to delete this user's purchases."
|
||||||
msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur."
|
msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur."
|
||||||
|
|
||||||
#: models.py:439
|
#: models.py:507
|
||||||
msgid ""
|
msgid ""
|
||||||
"You don't have the right to delete a purchase already controlled or "
|
"You don't have the right to delete a purchase already controlled or "
|
||||||
"invalidated."
|
"invalidated."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'avez pas le droit de supprimer un achat précédement contrôlé ou "
|
"Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou "
|
||||||
"invalidé."
|
"invalidé."
|
||||||
|
|
||||||
#: models.py:447
|
#: models.py:515
|
||||||
msgid "You don't have the right to view someone else's purchase history."
|
msgid "You don't have the right to view someone else's purchase history."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'avez pas le droit de voir l'historique des achats d'un autre "
|
"Vous n'avez pas le droit de voir l'historique des achats d'un autre "
|
||||||
"utilisateur."
|
"utilisateur."
|
||||||
|
|
||||||
#: models.py:511
|
#: models.py:580
|
||||||
msgid "Club"
|
msgid "Club"
|
||||||
msgstr "Club"
|
msgstr "Club"
|
||||||
|
|
||||||
#: models.py:523
|
#: models.py:592
|
||||||
msgid "designation"
|
msgid "designation"
|
||||||
msgstr "désignation"
|
msgstr "désignation"
|
||||||
|
|
||||||
#: models.py:529
|
#: models.py:598
|
||||||
msgid "unit price"
|
msgid "unit price"
|
||||||
msgstr "prix unitaire"
|
msgstr "prix unitaire"
|
||||||
|
|
||||||
#: models.py:541
|
#: models.py:610
|
||||||
msgid "type of users concerned"
|
msgid "type of users concerned"
|
||||||
msgstr "type d'utilisateurs concernés"
|
msgstr "type d'utilisateurs concernés"
|
||||||
|
|
||||||
#: models.py:553 models.py:649
|
#: models.py:622 models.py:733
|
||||||
msgid "is available for every user"
|
msgid "is available for every user"
|
||||||
msgstr "est disponible pour chaque utilisateur"
|
msgstr "est disponible pour chaque utilisateur"
|
||||||
|
|
||||||
#: models.py:560
|
#: models.py:629
|
||||||
msgid "Can view an article object"
|
msgid "Can view an article object"
|
||||||
msgstr "Peut voir un objet article"
|
msgstr "Peut voir un objet article"
|
||||||
|
|
||||||
#: models.py:561
|
#: models.py:630
|
||||||
msgid "Can buy every article"
|
msgid "Can buy every article"
|
||||||
msgstr "Peut acheter chaque article"
|
msgstr "Peut acheter chaque article"
|
||||||
|
|
||||||
#: models.py:569
|
#: models.py:638
|
||||||
msgid "Balance is a reserved article name."
|
msgid "Balance is a reserved article name."
|
||||||
msgstr "Solde est un nom d'article réservé."
|
msgstr "Solde est un nom d'article réservé."
|
||||||
|
|
||||||
#: models.py:594
|
#: models.py:663
|
||||||
msgid "You can't buy this article."
|
msgid "You can't buy this article."
|
||||||
msgstr "Vous ne pouvez pas acheter cet article."
|
msgstr "Vous ne pouvez pas acheter cet article."
|
||||||
|
|
||||||
#: models.py:624
|
#: models.py:708
|
||||||
msgid "Can view a bank object"
|
msgid "Can view a bank object"
|
||||||
msgstr "Peut voir un objet banque"
|
msgstr "Peut voir un objet banque"
|
||||||
|
|
||||||
#: models.py:626
|
#: models.py:710
|
||||||
msgid "bank"
|
msgid "bank"
|
||||||
msgstr "banque"
|
msgstr "banque"
|
||||||
|
|
||||||
#: models.py:627
|
#: models.py:711
|
||||||
msgid "banks"
|
msgid "banks"
|
||||||
msgstr "banques"
|
msgstr "banques"
|
||||||
|
|
||||||
#: models.py:645
|
#: models.py:729
|
||||||
msgid "method"
|
msgid "method"
|
||||||
msgstr "moyen"
|
msgstr "moyen"
|
||||||
|
|
||||||
#: models.py:654
|
#: models.py:738
|
||||||
msgid "is user balance"
|
msgid "is user balance"
|
||||||
msgstr "est solde utilisateur"
|
msgstr "est solde utilisateur"
|
||||||
|
|
||||||
#: models.py:655
|
#: models.py:739
|
||||||
msgid "There should be only one balance payment method."
|
msgid "There should be only one balance payment method."
|
||||||
msgstr "Il ne devrait y avoir qu'un moyen de paiement solde."
|
msgstr "Il ne devrait y avoir qu'un moyen de paiement solde."
|
||||||
|
|
||||||
#: models.py:661
|
#: models.py:745
|
||||||
msgid "Can view a payment method object"
|
msgid "Can view a payment method object"
|
||||||
msgstr "Peut voir un objet moyen de paiement"
|
msgstr "Peut voir un objet moyen de paiement"
|
||||||
|
|
||||||
#: models.py:662
|
#: models.py:746
|
||||||
msgid "Can use every payment method"
|
msgid "Can use every payment method"
|
||||||
msgstr "Peut utiliser chaque moyen de paiement"
|
msgstr "Peut utiliser chaque moyen de paiement"
|
||||||
|
|
||||||
#: models.py:664
|
#: models.py:748
|
||||||
msgid "payment method"
|
msgid "payment method"
|
||||||
msgstr "moyen de paiement"
|
msgstr "moyen de paiement"
|
||||||
|
|
||||||
#: models.py:665
|
#: models.py:749
|
||||||
msgid "payment methods"
|
msgid "payment methods"
|
||||||
msgstr "moyens de paiement"
|
msgstr "moyens de paiement"
|
||||||
|
|
||||||
#: models.py:699 payment_methods/comnpay/views.py:63
|
#: models.py:787 payment_methods/comnpay/views.py:63
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The subscription of %(member_name)s was extended to %(end_date)s."
|
msgid "The subscription of %(member_name)s was extended to %(end_date)s."
|
||||||
msgstr "La cotisation de %(member_name)s a été étendue au %(end_date)s."
|
msgstr "La cotisation de %(member_name)s a été étendue au %(end_date)s."
|
||||||
|
|
||||||
#: models.py:709
|
#: models.py:797
|
||||||
msgid "The invoice was created."
|
msgid "The invoice was created."
|
||||||
msgstr "La facture a été créée."
|
msgstr "La facture a été créée."
|
||||||
|
|
||||||
#: models.py:730
|
#: models.py:818
|
||||||
msgid "You can't use this payment method."
|
msgid "You can't use this payment method."
|
||||||
msgstr "Vous ne pouvez pas utiliser ce moyen de paiement."
|
msgstr "Vous ne pouvez pas utiliser ce moyen de paiement."
|
||||||
|
|
||||||
#: models.py:748
|
#: models.py:836
|
||||||
msgid "No custom payment method."
|
msgid "No custom payment method."
|
||||||
msgstr "Pas de moyen de paiement personnalisé."
|
msgstr "Pas de moyen de paiement personnalisé."
|
||||||
|
|
||||||
#: models.py:783
|
#: models.py:871
|
||||||
msgid "start date"
|
msgid "start date"
|
||||||
msgstr "date de début"
|
msgstr "date de début"
|
||||||
|
|
||||||
#: models.py:786
|
#: models.py:874
|
||||||
msgid "end date"
|
msgid "end date"
|
||||||
msgstr "date de fin"
|
msgstr "date de fin"
|
||||||
|
|
||||||
#: models.py:791
|
#: models.py:879
|
||||||
msgid "Can view a subscription object"
|
msgid "Can view a subscription object"
|
||||||
msgstr "Peut voir un objet cotisation"
|
msgstr "Peut voir un objet cotisation"
|
||||||
|
|
||||||
#: models.py:792
|
#: models.py:880
|
||||||
msgid "Can edit the previous subscriptions"
|
msgid "Can edit the previous subscriptions"
|
||||||
msgstr "Peut modifier les cotisations précédentes"
|
msgstr "Peut modifier les cotisations précédentes"
|
||||||
|
|
||||||
#: models.py:794
|
#: models.py:882
|
||||||
msgid "subscription"
|
msgid "subscription"
|
||||||
msgstr "cotisation"
|
msgstr "cotisation"
|
||||||
|
|
||||||
#: models.py:795
|
#: models.py:883
|
||||||
msgid "subscriptions"
|
msgid "subscriptions"
|
||||||
msgstr "cotisations"
|
msgstr "cotisations"
|
||||||
|
|
||||||
#: models.py:799
|
#: models.py:887
|
||||||
msgid "You don't have the right to edit a subscription."
|
msgid "You don't have the right to edit a subscription."
|
||||||
msgstr "Vous n'avez pas le droit de modifier une cotisation."
|
msgstr "Vous n'avez pas le droit de modifier une cotisation."
|
||||||
|
|
||||||
#: models.py:803
|
#: models.py:891
|
||||||
msgid ""
|
msgid ""
|
||||||
"You don't have the right to edit a subscription already controlled or "
|
"You don't have the right to edit a subscription already controlled or "
|
||||||
"invalidated."
|
"invalidated."
|
||||||
|
@ -436,11 +476,11 @@ msgstr ""
|
||||||
"Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée "
|
"Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée "
|
||||||
"ou invalidée."
|
"ou invalidée."
|
||||||
|
|
||||||
#: models.py:810
|
#: models.py:898
|
||||||
msgid "You don't have the right to delete a subscription."
|
msgid "You don't have the right to delete a subscription."
|
||||||
msgstr "Vous n'avez pas le droit de supprimer une cotisation."
|
msgstr "Vous n'avez pas le droit de supprimer une cotisation."
|
||||||
|
|
||||||
#: models.py:813
|
#: models.py:901
|
||||||
msgid ""
|
msgid ""
|
||||||
"You don't have the right to delete a subscription already controlled or "
|
"You don't have the right to delete a subscription already controlled or "
|
||||||
"invalidated."
|
"invalidated."
|
||||||
|
@ -448,7 +488,7 @@ msgstr ""
|
||||||
"Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée "
|
"Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée "
|
||||||
"ou invalidée."
|
"ou invalidée."
|
||||||
|
|
||||||
#: models.py:821
|
#: models.py:909
|
||||||
msgid "You don't have the right to view someone else's subscription history."
|
msgid "You don't have the right to view someone else's subscription history."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Vous n'avez pas le droit de voir l'historique des cotisations d'un autre "
|
"Vous n'avez pas le droit de voir l'historique des cotisations d'un autre "
|
||||||
|
@ -482,11 +522,11 @@ msgstr "Le montant maximal d'argent autorisé pour le solde."
|
||||||
msgid "Allow user to credit their balance"
|
msgid "Allow user to credit their balance"
|
||||||
msgstr "Autorise l'utilisateur à créditer son solde"
|
msgstr "Autorise l'utilisateur à créditer son solde"
|
||||||
|
|
||||||
#: payment_methods/balance/models.py:81 payment_methods/balance/models.py:112
|
#: payment_methods/balance/models.py:79 payment_methods/balance/models.py:110
|
||||||
msgid "Your balance is too low for this operation."
|
msgid "Your balance is too low for this operation."
|
||||||
msgstr "Votre solde est trop bas pour cette opération."
|
msgstr "Votre solde est trop bas pour cette opération."
|
||||||
|
|
||||||
#: payment_methods/balance/models.py:99 validators.py:20
|
#: payment_methods/balance/models.py:97 validators.py:20
|
||||||
msgid "There is already a payment method for user balance."
|
msgid "There is already a payment method for user balance."
|
||||||
msgstr "Il y a déjà un moyen de paiement pour le solde utilisateur."
|
msgstr "Il y a déjà un moyen de paiement pour le solde utilisateur."
|
||||||
|
|
||||||
|
@ -523,11 +563,11 @@ msgstr ""
|
||||||
msgid "Production mode enabled (production URL, instead of homologation)"
|
msgid "Production mode enabled (production URL, instead of homologation)"
|
||||||
msgstr "Mode production activé (URL de production, au lieu d'homologation)"
|
msgstr "Mode production activé (URL de production, au lieu d'homologation)"
|
||||||
|
|
||||||
#: payment_methods/comnpay/models.py:104
|
#: payment_methods/comnpay/models.py:102
|
||||||
msgid "Pay invoice number "
|
msgid "Pay invoice number "
|
||||||
msgstr "Payer la facture numéro "
|
msgstr "Payer la facture numéro "
|
||||||
|
|
||||||
#: payment_methods/comnpay/models.py:116
|
#: payment_methods/comnpay/models.py:114
|
||||||
msgid ""
|
msgid ""
|
||||||
"In order to pay your invoice with ComNpay, the price must be greater than {} "
|
"In order to pay your invoice with ComNpay, the price must be greater than {} "
|
||||||
"€."
|
"€."
|
||||||
|
@ -559,6 +599,30 @@ msgstr ""
|
||||||
msgid "no"
|
msgid "no"
|
||||||
msgstr "non"
|
msgstr "non"
|
||||||
|
|
||||||
|
#: payment_methods/note_kfet/forms.py:32
|
||||||
|
msgid "pseudo note"
|
||||||
|
msgstr "pseudo note"
|
||||||
|
|
||||||
|
#: payment_methods/note_kfet/forms.py:35
|
||||||
|
msgid "Password"
|
||||||
|
msgstr "Mot de passe"
|
||||||
|
|
||||||
|
#: payment_methods/note_kfet/models.py:40
|
||||||
|
msgid "NoteKfet"
|
||||||
|
msgstr "NoteKfet"
|
||||||
|
|
||||||
|
#: payment_methods/note_kfet/models.py:50
|
||||||
|
msgid "server"
|
||||||
|
msgstr "serveur"
|
||||||
|
|
||||||
|
#: payment_methods/note_kfet/views.py:60
|
||||||
|
msgid "Unknown error."
|
||||||
|
msgstr "Erreur inconnue."
|
||||||
|
|
||||||
|
#: payment_methods/note_kfet/views.py:88
|
||||||
|
msgid "The payment with note was done."
|
||||||
|
msgstr "Le paiement par note a été effectué."
|
||||||
|
|
||||||
#: templates/cotisations/aff_article.html:34
|
#: templates/cotisations/aff_article.html:34
|
||||||
msgid "Price"
|
msgid "Price"
|
||||||
msgstr "Prix"
|
msgstr "Prix"
|
||||||
|
@ -579,34 +643,47 @@ msgstr "Utilisateurs concernés"
|
||||||
msgid "Available for everyone"
|
msgid "Available for everyone"
|
||||||
msgstr "Disponible pour tous"
|
msgstr "Disponible pour tous"
|
||||||
|
|
||||||
#: templates/cotisations/aff_article.html:52
|
|
||||||
#: templates/cotisations/aff_paiement.html:48
|
|
||||||
#: templates/cotisations/control.html:107 views.py:483 views.py:570
|
|
||||||
#: views.py:650
|
|
||||||
msgid "Edit"
|
|
||||||
msgstr "Modifier"
|
|
||||||
|
|
||||||
#: templates/cotisations/aff_banque.html:32
|
#: templates/cotisations/aff_banque.html:32
|
||||||
msgid "Bank"
|
msgid "Bank"
|
||||||
msgstr "Banque"
|
msgstr "Banque"
|
||||||
|
|
||||||
#: templates/cotisations/aff_cotisations.html:38
|
#: templates/cotisations/aff_cost_estimate.html:39
|
||||||
msgid "User"
|
|
||||||
msgstr "Utilisateur"
|
|
||||||
|
|
||||||
#: templates/cotisations/aff_cotisations.html:41
|
#: templates/cotisations/aff_cotisations.html:41
|
||||||
#: templates/cotisations/aff_custom_invoice.html:39
|
#: templates/cotisations/aff_custom_invoice.html:39
|
||||||
#: templates/cotisations/control.html:63
|
#: templates/cotisations/control.html:63
|
||||||
#: templates/cotisations/edit_facture.html:45
|
#: templates/cotisations/edit_facture.html:49
|
||||||
msgid "Designation"
|
msgid "Designation"
|
||||||
msgstr "Désignation"
|
msgstr "Désignation"
|
||||||
|
|
||||||
|
#: templates/cotisations/aff_cost_estimate.html:40
|
||||||
#: templates/cotisations/aff_cotisations.html:42
|
#: templates/cotisations/aff_cotisations.html:42
|
||||||
#: templates/cotisations/aff_custom_invoice.html:40
|
#: templates/cotisations/aff_custom_invoice.html:40
|
||||||
#: templates/cotisations/control.html:64
|
#: templates/cotisations/control.html:64
|
||||||
msgid "Total price"
|
msgid "Total price"
|
||||||
msgstr "Prix total"
|
msgstr "Prix total"
|
||||||
|
|
||||||
|
#: templates/cotisations/aff_cost_estimate.html:50
|
||||||
|
msgid "Validity"
|
||||||
|
msgstr "Validité"
|
||||||
|
|
||||||
|
#: templates/cotisations/aff_cost_estimate.html:54
|
||||||
|
msgid "Cost estimate ID"
|
||||||
|
msgstr "ID devis"
|
||||||
|
|
||||||
|
#: templates/cotisations/aff_cost_estimate.html:58
|
||||||
|
msgid "Invoice created"
|
||||||
|
msgstr "Facture créée"
|
||||||
|
|
||||||
|
#: templates/cotisations/aff_cost_estimate.html:91
|
||||||
|
#: templates/cotisations/aff_cotisations.html:81
|
||||||
|
#: templates/cotisations/aff_custom_invoice.html:79
|
||||||
|
msgid "PDF"
|
||||||
|
msgstr "PDF"
|
||||||
|
|
||||||
|
#: templates/cotisations/aff_cotisations.html:38
|
||||||
|
msgid "User"
|
||||||
|
msgstr "Utilisateur"
|
||||||
|
|
||||||
#: templates/cotisations/aff_cotisations.html:52
|
#: templates/cotisations/aff_cotisations.html:52
|
||||||
#: templates/cotisations/aff_custom_invoice.html:50
|
#: templates/cotisations/aff_custom_invoice.html:50
|
||||||
#: templates/cotisations/control.html:56
|
#: templates/cotisations/control.html:56
|
||||||
|
@ -617,11 +694,6 @@ msgstr "ID facture"
|
||||||
msgid "Controlled invoice"
|
msgid "Controlled invoice"
|
||||||
msgstr "Facture contrôlée"
|
msgstr "Facture contrôlée"
|
||||||
|
|
||||||
#: templates/cotisations/aff_cotisations.html:81
|
|
||||||
#: templates/cotisations/aff_custom_invoice.html:79
|
|
||||||
msgid "PDF"
|
|
||||||
msgstr "PDF"
|
|
||||||
|
|
||||||
#: templates/cotisations/aff_cotisations.html:84
|
#: templates/cotisations/aff_cotisations.html:84
|
||||||
msgid "Invalidated invoice"
|
msgid "Invalidated invoice"
|
||||||
msgstr "Facture invalidée"
|
msgstr "Facture invalidée"
|
||||||
|
@ -666,6 +738,11 @@ msgstr "Validé"
|
||||||
msgid "Controlled"
|
msgid "Controlled"
|
||||||
msgstr "Contrôlé"
|
msgstr "Contrôlé"
|
||||||
|
|
||||||
|
#: templates/cotisations/control.html:107 views.py:642 views.py:729
|
||||||
|
#: views.py:809
|
||||||
|
msgid "Edit"
|
||||||
|
msgstr "Modifier"
|
||||||
|
|
||||||
#: templates/cotisations/delete.html:29
|
#: templates/cotisations/delete.html:29
|
||||||
msgid "Deletion of subscriptions"
|
msgid "Deletion of subscriptions"
|
||||||
msgstr "Suppression de cotisations"
|
msgstr "Suppression de cotisations"
|
||||||
|
@ -676,11 +753,12 @@ msgid ""
|
||||||
"Warning: are you sure you really want to delete this %(object_name)s object "
|
"Warning: are you sure you really want to delete this %(object_name)s object "
|
||||||
"( %(objet)s )?"
|
"( %(objet)s )?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"\tAttention: voulez-vous vraiment supprimer cet objet %(object_name)s "
|
"Attention: voulez-vous vraiment supprimer cet objet %(object_name)s "
|
||||||
"( %(objet)s ) ?"
|
"( %(objet)s ) ?"
|
||||||
|
|
||||||
#: templates/cotisations/delete.html:38
|
#: templates/cotisations/delete.html:38
|
||||||
#: templates/cotisations/edit_facture.html:60
|
#: templates/cotisations/edit_facture.html:64 views.py:178 views.py:228
|
||||||
|
#: views.py:280
|
||||||
msgid "Confirm"
|
msgid "Confirm"
|
||||||
msgstr "Confirmer"
|
msgstr "Confirmer"
|
||||||
|
|
||||||
|
@ -689,18 +767,19 @@ msgstr "Confirmer"
|
||||||
msgid "Creation and editing of invoices"
|
msgid "Creation and editing of invoices"
|
||||||
msgstr "Création et modification de factures"
|
msgstr "Création et modification de factures"
|
||||||
|
|
||||||
#: templates/cotisations/edit_facture.html:38
|
#: templates/cotisations/edit_facture.html:41
|
||||||
msgid "Edit the invoice"
|
msgid "Edit invoice"
|
||||||
msgstr "Modifier la facture"
|
msgstr "Modifier la facture"
|
||||||
|
|
||||||
#: templates/cotisations/edit_facture.html:41
|
#: templates/cotisations/edit_facture.html:45
|
||||||
#: templates/cotisations/facture.html:56
|
#: templates/cotisations/facture.html:62
|
||||||
msgid "Invoice's articles"
|
#: templates/cotisations/index_article.html:30
|
||||||
msgstr "Articles de la facture"
|
msgid "Articles"
|
||||||
|
msgstr "Articles"
|
||||||
|
|
||||||
#: templates/cotisations/facture.html:37
|
#: templates/cotisations/facture.html:37
|
||||||
msgid "New invoice"
|
msgid "Buy"
|
||||||
msgstr "Nouvelle facture"
|
msgstr "Acheter"
|
||||||
|
|
||||||
#: templates/cotisations/facture.html:40
|
#: templates/cotisations/facture.html:40
|
||||||
#, python-format
|
#, python-format
|
||||||
|
@ -712,11 +791,11 @@ msgstr "Solde maximum autorisé : %(max_balance)s €"
|
||||||
msgid "Current balance: %(balance)s €"
|
msgid "Current balance: %(balance)s €"
|
||||||
msgstr "Solde actuel : %(balance)s €"
|
msgstr "Solde actuel : %(balance)s €"
|
||||||
|
|
||||||
#: templates/cotisations/facture.html:70
|
#: templates/cotisations/facture.html:76
|
||||||
msgid "Add an article"
|
msgid "Add an extra article"
|
||||||
msgstr "Ajouter un article"
|
msgstr "Ajouter un article supplémentaire"
|
||||||
|
|
||||||
#: templates/cotisations/facture.html:72
|
#: templates/cotisations/facture.html:82
|
||||||
msgid "Total price: <span id=\"total_price\">0,00</span> €"
|
msgid "Total price: <span id=\"total_price\">0,00</span> €"
|
||||||
msgstr "Prix total : <span id=\"total_price\">0,00</span> €"
|
msgstr "Prix total : <span id=\"total_price\">0,00</span> €"
|
||||||
|
|
||||||
|
@ -728,12 +807,8 @@ msgstr "Factures"
|
||||||
msgid "Subscriptions"
|
msgid "Subscriptions"
|
||||||
msgstr "Cotisations"
|
msgstr "Cotisations"
|
||||||
|
|
||||||
#: templates/cotisations/index_article.html:30
|
|
||||||
msgid "Articles"
|
|
||||||
msgstr "Articles"
|
|
||||||
|
|
||||||
#: templates/cotisations/index_article.html:33
|
#: templates/cotisations/index_article.html:33
|
||||||
msgid "Article types list"
|
msgid "List of article types"
|
||||||
msgstr "Liste des types d'article"
|
msgstr "Liste des types d'article"
|
||||||
|
|
||||||
#: templates/cotisations/index_article.html:36
|
#: templates/cotisations/index_article.html:36
|
||||||
|
@ -745,12 +820,12 @@ msgid "Delete one or several article types"
|
||||||
msgstr "Supprimer un ou plusieurs types d'article"
|
msgstr "Supprimer un ou plusieurs types d'article"
|
||||||
|
|
||||||
#: templates/cotisations/index_banque.html:30
|
#: templates/cotisations/index_banque.html:30
|
||||||
#: templates/cotisations/sidebar.html:55
|
#: templates/cotisations/sidebar.html:60
|
||||||
msgid "Banks"
|
msgid "Banks"
|
||||||
msgstr "Banques"
|
msgstr "Banques"
|
||||||
|
|
||||||
#: templates/cotisations/index_banque.html:33
|
#: templates/cotisations/index_banque.html:33
|
||||||
msgid "Banks list"
|
msgid "List of banks"
|
||||||
msgstr "Liste des banques"
|
msgstr "Liste des banques"
|
||||||
|
|
||||||
#: templates/cotisations/index_banque.html:36
|
#: templates/cotisations/index_banque.html:36
|
||||||
|
@ -761,17 +836,26 @@ msgstr "Ajouter une banque"
|
||||||
msgid "Delete one or several banks"
|
msgid "Delete one or several banks"
|
||||||
msgstr "Supprimer une ou plusieurs banques"
|
msgstr "Supprimer une ou plusieurs banques"
|
||||||
|
|
||||||
|
#: templates/cotisations/index_cost_estimate.html:28
|
||||||
|
#: templates/cotisations/sidebar.html:50
|
||||||
|
msgid "Cost estimates"
|
||||||
|
msgstr "Devis"
|
||||||
|
|
||||||
|
#: templates/cotisations/index_cost_estimate.html:31
|
||||||
|
msgid "List of cost estimates"
|
||||||
|
msgstr "Liste des devis"
|
||||||
|
|
||||||
#: templates/cotisations/index_custom_invoice.html:28
|
#: templates/cotisations/index_custom_invoice.html:28
|
||||||
#: templates/cotisations/sidebar.html:45
|
#: templates/cotisations/sidebar.html:45
|
||||||
msgid "Custom invoices"
|
msgid "Custom invoices"
|
||||||
msgstr "Factures personnalisées"
|
msgstr "Factures personnalisées"
|
||||||
|
|
||||||
#: templates/cotisations/index_custom_invoice.html:31
|
#: templates/cotisations/index_custom_invoice.html:31
|
||||||
msgid "Custom invoices list"
|
msgid "List of custom invoices"
|
||||||
msgstr "Liste des factures personalisées"
|
msgstr "Liste des factures personnalisées"
|
||||||
|
|
||||||
#: templates/cotisations/index_paiement.html:30
|
#: templates/cotisations/index_paiement.html:30
|
||||||
#: templates/cotisations/sidebar.html:60
|
#: templates/cotisations/sidebar.html:65
|
||||||
msgid "Payment methods"
|
msgid "Payment methods"
|
||||||
msgstr "Moyens de paiement"
|
msgstr "Moyens de paiement"
|
||||||
|
|
||||||
|
@ -794,9 +878,9 @@ msgstr "Rechargement de solde"
|
||||||
#: templates/cotisations/payment.html:34
|
#: templates/cotisations/payment.html:34
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Pay %(amount)s €"
|
msgid "Pay %(amount)s €"
|
||||||
msgstr "Recharger de %(amount)s €"
|
msgstr "Payer %(amount)s €"
|
||||||
|
|
||||||
#: templates/cotisations/payment.html:42 views.py:870
|
#: templates/cotisations/payment.html:42 views.py:1049
|
||||||
msgid "Pay"
|
msgid "Pay"
|
||||||
msgstr "Payer"
|
msgstr "Payer"
|
||||||
|
|
||||||
|
@ -808,84 +892,104 @@ msgstr "Créer une facture"
|
||||||
msgid "Control the invoices"
|
msgid "Control the invoices"
|
||||||
msgstr "Contrôler les factures"
|
msgstr "Contrôler les factures"
|
||||||
|
|
||||||
#: views.py:167
|
#: views.py:164
|
||||||
msgid "You need to choose at least one article."
|
msgid "You need to choose at least one article."
|
||||||
msgstr "Vous devez choisir au moins un article."
|
msgstr "Vous devez choisir au moins un article."
|
||||||
|
|
||||||
#: views.py:181 views.py:235
|
#: views.py:222
|
||||||
msgid "Create"
|
msgid "The cost estimate was created."
|
||||||
msgstr "Créer"
|
msgstr "Le devis a été créé."
|
||||||
|
|
||||||
#: views.py:228
|
#: views.py:232 views.py:534
|
||||||
|
msgid "Cost estimate"
|
||||||
|
msgstr "Devis"
|
||||||
|
|
||||||
|
#: views.py:274
|
||||||
msgid "The custom invoice was created."
|
msgid "The custom invoice was created."
|
||||||
msgstr "La facture personnalisée a été créée."
|
msgstr "La facture personnalisée a été créée."
|
||||||
|
|
||||||
#: views.py:316 views.py:370
|
#: views.py:363 views.py:466
|
||||||
msgid "The invoice was edited."
|
msgid "The invoice was edited."
|
||||||
msgstr "La facture a été modifiée."
|
msgstr "La facture a été modifiée."
|
||||||
|
|
||||||
#: views.py:336 views.py:430
|
#: views.py:383 views.py:589
|
||||||
msgid "The invoice was deleted."
|
msgid "The invoice was deleted."
|
||||||
msgstr "La facture a été supprimée."
|
msgstr "La facture a été supprimée."
|
||||||
|
|
||||||
#: views.py:341 views.py:435
|
#: views.py:388 views.py:594
|
||||||
msgid "Invoice"
|
msgid "Invoice"
|
||||||
msgstr "Facture"
|
msgstr "Facture"
|
||||||
|
|
||||||
#: views.py:456
|
#: views.py:417
|
||||||
|
msgid "The cost estimate was edited."
|
||||||
|
msgstr "Le devis a été modifié."
|
||||||
|
|
||||||
|
#: views.py:424
|
||||||
|
msgid "Edit cost estimate"
|
||||||
|
msgstr "Modifier le devis"
|
||||||
|
|
||||||
|
#: views.py:436
|
||||||
|
msgid "An invoice was successfully created from your cost estimate."
|
||||||
|
msgstr "Une facture a bien été créée à partir de votre devis."
|
||||||
|
|
||||||
|
#: views.py:529
|
||||||
|
msgid "The cost estimate was deleted."
|
||||||
|
msgstr "Le devis a été supprimé."
|
||||||
|
|
||||||
|
#: views.py:615
|
||||||
msgid "The article was created."
|
msgid "The article was created."
|
||||||
msgstr "L'article a été créé."
|
msgstr "L'article a été créé."
|
||||||
|
|
||||||
#: views.py:461 views.py:534 views.py:627
|
#: views.py:620 views.py:693 views.py:786
|
||||||
msgid "Add"
|
msgid "Add"
|
||||||
msgstr "Ajouter"
|
msgstr "Ajouter"
|
||||||
|
|
||||||
#: views.py:462
|
#: views.py:621
|
||||||
msgid "New article"
|
msgid "New article"
|
||||||
msgstr "Nouvel article"
|
msgstr "Nouvel article"
|
||||||
|
|
||||||
#: views.py:478
|
#: views.py:637
|
||||||
msgid "The article was edited."
|
msgid "The article was edited."
|
||||||
msgstr "L'article a été modifié."
|
msgstr "L'article a été modifié."
|
||||||
|
|
||||||
#: views.py:484
|
#: views.py:643
|
||||||
msgid "Edit article"
|
msgid "Edit article"
|
||||||
msgstr "Modifier l'article"
|
msgstr "Modifier l'article"
|
||||||
|
|
||||||
#: views.py:500
|
#: views.py:659
|
||||||
msgid "The articles were deleted."
|
msgid "The articles were deleted."
|
||||||
msgstr "Les articles ont été supprimés."
|
msgstr "Les articles ont été supprimés."
|
||||||
|
|
||||||
#: views.py:505 views.py:605 views.py:685
|
#: views.py:664 views.py:764 views.py:844
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr "Supprimer"
|
msgstr "Supprimer"
|
||||||
|
|
||||||
#: views.py:506
|
#: views.py:665
|
||||||
msgid "Delete article"
|
msgid "Delete article"
|
||||||
msgstr "Supprimer l'article"
|
msgstr "Supprimer l'article"
|
||||||
|
|
||||||
#: views.py:528
|
#: views.py:687
|
||||||
msgid "The payment method was created."
|
msgid "The payment method was created."
|
||||||
msgstr "Le moyen de paiment a été créé."
|
msgstr "Le moyen de paiment a été créé."
|
||||||
|
|
||||||
#: views.py:535
|
#: views.py:694
|
||||||
msgid "New payment method"
|
msgid "New payment method"
|
||||||
msgstr "Nouveau moyen de paiement"
|
msgstr "Nouveau moyen de paiement"
|
||||||
|
|
||||||
#: views.py:564
|
#: views.py:723
|
||||||
msgid "The payment method was edited."
|
msgid "The payment method was edited."
|
||||||
msgstr "Le moyen de paiment a été modifié."
|
msgstr "Le moyen de paiment a été modifié."
|
||||||
|
|
||||||
#: views.py:571
|
#: views.py:730
|
||||||
msgid "Edit payment method"
|
msgid "Edit payment method"
|
||||||
msgstr "Modifier le moyen de paiement"
|
msgstr "Modifier le moyen de paiement"
|
||||||
|
|
||||||
#: views.py:590
|
#: views.py:749
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The payment method %(method_name)s was deleted."
|
msgid "The payment method %(method_name)s was deleted."
|
||||||
msgstr "Le moyen de paiement %(method_name)s a été supprimé."
|
msgstr "Le moyen de paiement %(method_name)s a été supprimé."
|
||||||
|
|
||||||
#: views.py:597
|
#: views.py:756
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The payment method %(method_name)s can't be deleted "
|
"The payment method %(method_name)s can't be deleted "
|
||||||
|
@ -894,52 +998,51 @@ msgstr ""
|
||||||
"Le moyen de paiement %(method_name)s ne peut pas être supprimé car il y a "
|
"Le moyen de paiement %(method_name)s ne peut pas être supprimé car il y a "
|
||||||
"des factures qui l'utilisent."
|
"des factures qui l'utilisent."
|
||||||
|
|
||||||
#: views.py:606
|
#: views.py:765
|
||||||
msgid "Delete payment method"
|
msgid "Delete payment method"
|
||||||
msgstr "Supprimer le moyen de paiement"
|
msgstr "Supprimer le moyen de paiement"
|
||||||
|
|
||||||
#: views.py:622
|
#: views.py:781
|
||||||
msgid "The bank was created."
|
msgid "The bank was created."
|
||||||
msgstr "La banque a été créée."
|
msgstr "La banque a été créée."
|
||||||
|
|
||||||
#: views.py:628
|
#: views.py:787
|
||||||
msgid "New bank"
|
msgid "New bank"
|
||||||
msgstr "Nouvelle banque"
|
msgstr "Nouvelle banque"
|
||||||
|
|
||||||
#: views.py:645
|
#: views.py:804
|
||||||
msgid "The bank was edited."
|
msgid "The bank was edited."
|
||||||
msgstr "La banque a été modifiée."
|
msgstr "La banque a été modifiée."
|
||||||
|
|
||||||
#: views.py:651
|
#: views.py:810
|
||||||
msgid "Edit bank"
|
msgid "Edit bank"
|
||||||
msgstr "Modifier la banque"
|
msgstr "Modifier la banque"
|
||||||
|
|
||||||
#: views.py:670
|
#: views.py:829
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "The bank %(bank_name)s was deleted."
|
msgid "The bank %(bank_name)s was deleted."
|
||||||
msgstr "La banque %(bank_name)s a été supprimée."
|
msgstr "La banque %(bank_name)s a été supprimée."
|
||||||
|
|
||||||
#: views.py:677
|
#: views.py:836
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The bank %(bank_name)s can't be deleted because there "
|
"The bank %(bank_name)s can't be deleted because there are invoices using it."
|
||||||
"are invoices using it."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La banque %(bank_name)s ne peut pas être supprimée car il y a des factures "
|
"La banque %(bank_name)s ne peut pas être supprimée car il y a des factures "
|
||||||
"qui l'utilisent."
|
"qui l'utilisent."
|
||||||
|
|
||||||
#: views.py:686
|
#: views.py:845
|
||||||
msgid "Delete bank"
|
msgid "Delete bank"
|
||||||
msgstr "Supprimer la banque"
|
msgstr "Supprimer la banque"
|
||||||
|
|
||||||
#: views.py:722
|
#: views.py:881
|
||||||
msgid "Your changes have been properly taken into account."
|
msgid "Your changes have been properly taken into account."
|
||||||
msgstr "Vos modifications ont correctement été prises en compte."
|
msgstr "Vos modifications ont correctement été prises en compte."
|
||||||
|
|
||||||
#: views.py:834
|
#: views.py:1016
|
||||||
msgid "You are not allowed to credit your balance."
|
msgid "You are not allowed to credit your balance."
|
||||||
msgstr "Vous n'êtes pas autorisés à créditer votre solde."
|
msgstr "Vous n'êtes pas autorisés à créditer votre solde."
|
||||||
|
|
||||||
#: views.py:869
|
#: views.py:1048
|
||||||
msgid "Refill your balance"
|
msgid "Refill your balance"
|
||||||
msgstr "Recharger votre solde"
|
msgstr "Recharger votre solde"
|
||||||
|
|
|
@ -30,7 +30,9 @@ def update_rights(apps, schema_editor):
|
||||||
create_permissions(app)
|
create_permissions(app)
|
||||||
app.models_module = False
|
app.models_module = False
|
||||||
|
|
||||||
former = Permission.objects.get(codename='change_facture_pdf')
|
ContentType = apps.get_model("contenttypes", "ContentType")
|
||||||
|
content_type = ContentType.objects.get_for_model(Permission)
|
||||||
|
former, created = Permission.objects.get_or_create(codename='change_facture_pdf', content_type=content_type)
|
||||||
new_1 = Permission.objects.get(codename='add_custominvoice')
|
new_1 = Permission.objects.get(codename='add_custominvoice')
|
||||||
new_2 = Permission.objects.get(codename='change_custominvoice')
|
new_2 = Permission.objects.get(codename='change_custominvoice')
|
||||||
new_3 = Permission.objects.get(codename='view_custominvoice')
|
new_3 = Permission.objects.get(codename='view_custominvoice')
|
||||||
|
|
20
cotisations/migrations/0034_auto_20180831_1532.py
Normal file
20
cotisations/migrations/0034_auto_20180831_1532.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-08-31 13:32
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0033_auto_20180818_1319'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='facture',
|
||||||
|
name='valid',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='validated'),
|
||||||
|
),
|
||||||
|
]
|
31
cotisations/migrations/0035_notepayment.py
Normal file
31
cotisations/migrations/0035_notepayment.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-09-01 11:27
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import cotisations.payment_methods.mixins
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0034_auto_20180831_1532'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NotePayment',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('server', models.CharField(max_length=255, verbose_name='server')),
|
||||||
|
('port', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('id_note', models.PositiveIntegerField(blank=True, null=True)),
|
||||||
|
('payment', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method', to='cotisations.Paiement')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'NoteKfet',
|
||||||
|
},
|
||||||
|
bases=(cotisations.payment_methods.mixins.PaymentMethodMixin, models.Model),
|
||||||
|
),
|
||||||
|
]
|
20
cotisations/migrations/0036_custominvoice_remark.py
Normal file
20
cotisations/migrations/0036_custominvoice_remark.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-12-29 14:22
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0035_notepayment'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='custominvoice',
|
||||||
|
name='remark',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='Remark'),
|
||||||
|
),
|
||||||
|
]
|
28
cotisations/migrations/0037_costestimate.py
Normal file
28
cotisations/migrations/0037_costestimate.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-12-29 21:03
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0036_custominvoice_remark'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CostEstimate',
|
||||||
|
fields=[
|
||||||
|
('custominvoice_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='cotisations.CustomInvoice')),
|
||||||
|
('validity', models.DurationField(verbose_name='Period of validity')),
|
||||||
|
('final_invoice', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='origin_cost_estimate', to='cotisations.CustomInvoice')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'permissions': (('view_costestimate', 'Can view a cost estimate object'),),
|
||||||
|
},
|
||||||
|
bases=('cotisations.custominvoice',),
|
||||||
|
),
|
||||||
|
]
|
31
cotisations/migrations/0038_auto_20181231_1657.py
Normal file
31
cotisations/migrations/0038_auto_20181231_1657.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-12-31 22:57
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0037_costestimate'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='costestimate',
|
||||||
|
name='final_invoice',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='origin_cost_estimate', to='cotisations.CustomInvoice'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='costestimate',
|
||||||
|
name='validity',
|
||||||
|
field=models.DurationField(help_text='DD HH:MM:SS', verbose_name='Period of validity'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='custominvoice',
|
||||||
|
name='paid',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Paid'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -46,11 +46,14 @@ from django.urls import reverse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|
||||||
|
from preferences.models import CotisationsOption
|
||||||
from machines.models import regen
|
from machines.models import regen
|
||||||
from re2o.field_permissions import FieldPermissionModelMixin
|
from re2o.field_permissions import FieldPermissionModelMixin
|
||||||
from re2o.mixins import AclMixin, RevMixin
|
from re2o.mixins import AclMixin, RevMixin
|
||||||
|
|
||||||
from cotisations.utils import find_payment_method
|
from cotisations.utils import (
|
||||||
|
find_payment_method, send_mail_invoice, send_mail_voucher
|
||||||
|
)
|
||||||
from cotisations.validators import check_no_balance
|
from cotisations.validators import check_no_balance
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,7 +86,7 @@ class BaseInvoice(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
).aggregate(
|
).aggregate(
|
||||||
total=models.Sum(
|
total=models.Sum(
|
||||||
models.F('prix')*models.F('number'),
|
models.F('prix')*models.F('number'),
|
||||||
output_field=models.FloatField()
|
output_field=models.DecimalField()
|
||||||
)
|
)
|
||||||
)['total'] or 0
|
)['total'] or 0
|
||||||
|
|
||||||
|
@ -137,7 +140,7 @@ class Facture(BaseInvoice):
|
||||||
)
|
)
|
||||||
# TODO : change name to validity for clarity
|
# TODO : change name to validity for clarity
|
||||||
valid = models.BooleanField(
|
valid = models.BooleanField(
|
||||||
default=True,
|
default=False,
|
||||||
verbose_name=_("validated")
|
verbose_name=_("validated")
|
||||||
)
|
)
|
||||||
# TODO : changed name to controlled for clarity
|
# TODO : changed name to controlled for clarity
|
||||||
|
@ -182,24 +185,28 @@ class Facture(BaseInvoice):
|
||||||
def can_delete(self, user_request, *args, **kwargs):
|
def can_delete(self, user_request, *args, **kwargs):
|
||||||
if not user_request.has_perm('cotisations.delete_facture'):
|
if not user_request.has_perm('cotisations.delete_facture'):
|
||||||
return False, _("You don't have the right to delete an invoice.")
|
return False, _("You don't have the right to delete an invoice.")
|
||||||
if not self.user.can_edit(user_request, *args, **kwargs)[0]:
|
elif not user_request.has_perm('cotisations.change_all_facture') and \
|
||||||
|
not self.user.can_edit(user_request, *args, **kwargs)[0]:
|
||||||
return False, _("You don't have the right to delete this user's "
|
return False, _("You don't have the right to delete this user's "
|
||||||
"invoices.")
|
"invoices.")
|
||||||
if self.control or not self.valid:
|
elif not user_request.has_perm('cotisations.change_all_facture') and \
|
||||||
|
(self.control or not self.valid):
|
||||||
return False, _("You don't have the right to delete an invoice "
|
return False, _("You don't have the right to delete an invoice "
|
||||||
"already controlled or invalidated.")
|
"already controlled or invalidated.")
|
||||||
else:
|
else:
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
def can_view(self, user_request, *_args, **_kwargs):
|
def can_view(self, user_request, *_args, **_kwargs):
|
||||||
if not user_request.has_perm('cotisations.view_facture') and \
|
if not user_request.has_perm('cotisations.view_facture'):
|
||||||
self.user != user_request:
|
if self.user != user_request:
|
||||||
return False, _("You don't have the right to view someone else's "
|
return False, _("You don't have the right to view someone else's "
|
||||||
"invoices history.")
|
"invoices history.")
|
||||||
elif not self.valid:
|
elif not self.valid:
|
||||||
return False, _("The invoice has been invalidated.")
|
return False, _("The invoice has been invalidated.")
|
||||||
else:
|
else:
|
||||||
return True, None
|
return True, None
|
||||||
|
else:
|
||||||
|
return True, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def can_change_control(user_request, *_args, **_kwargs):
|
def can_change_control(user_request, *_args, **_kwargs):
|
||||||
|
@ -231,6 +238,31 @@ class Facture(BaseInvoice):
|
||||||
self.field_permissions = {
|
self.field_permissions = {
|
||||||
'control': self.can_change_control,
|
'control': self.can_change_control,
|
||||||
}
|
}
|
||||||
|
self.__original_valid = self.valid
|
||||||
|
self.__original_control = self.control
|
||||||
|
|
||||||
|
def get_subscription(self):
|
||||||
|
"""Returns every subscription associated with this invoice."""
|
||||||
|
return Cotisation.objects.filter(
|
||||||
|
vente__in=self.vente_set.filter(
|
||||||
|
Q(type_cotisation='All') |
|
||||||
|
Q(type_cotisation='Adhesion')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def is_subscription(self):
|
||||||
|
"""Returns True if this invoice contains at least one subscribtion."""
|
||||||
|
return bool(self.get_subscription())
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super(Facture, self).save(*args, **kwargs)
|
||||||
|
if not self.__original_valid and self.valid:
|
||||||
|
send_mail_invoice(self)
|
||||||
|
if self.is_subscription() \
|
||||||
|
and not self.__original_control \
|
||||||
|
and self.control \
|
||||||
|
and CotisationsOption.get_cached_value('send_voucher_mail'):
|
||||||
|
send_mail_voucher(self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.user) + ' ' + str(self.date)
|
return str(self.user) + ' ' + str(self.date)
|
||||||
|
@ -242,7 +274,9 @@ def facture_post_save(**kwargs):
|
||||||
Synchronise the LDAP user after an invoice has been saved.
|
Synchronise the LDAP user after an invoice has been saved.
|
||||||
"""
|
"""
|
||||||
facture = kwargs['instance']
|
facture = kwargs['instance']
|
||||||
|
if facture.valid:
|
||||||
user = facture.user
|
user = facture.user
|
||||||
|
user.set_active()
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
|
||||||
|
@ -273,8 +307,65 @@ class CustomInvoice(BaseInvoice):
|
||||||
verbose_name=_("Address")
|
verbose_name=_("Address")
|
||||||
)
|
)
|
||||||
paid = models.BooleanField(
|
paid = models.BooleanField(
|
||||||
verbose_name=_("Paid")
|
verbose_name=_("Paid"),
|
||||||
|
default=False
|
||||||
)
|
)
|
||||||
|
remark = models.TextField(
|
||||||
|
verbose_name=_("Remark"),
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CostEstimate(CustomInvoice):
|
||||||
|
class Meta:
|
||||||
|
permissions = (
|
||||||
|
('view_costestimate', _("Can view a cost estimate object")),
|
||||||
|
)
|
||||||
|
validity = models.DurationField(
|
||||||
|
verbose_name=_("Period of validity"),
|
||||||
|
help_text="DD HH:MM:SS"
|
||||||
|
)
|
||||||
|
final_invoice = models.ForeignKey(
|
||||||
|
CustomInvoice,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="origin_cost_estimate",
|
||||||
|
primary_key=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_invoice(self):
|
||||||
|
"""Create a CustomInvoice from the CostEstimate."""
|
||||||
|
if self.final_invoice is not None:
|
||||||
|
return self.final_invoice
|
||||||
|
invoice = CustomInvoice()
|
||||||
|
invoice.recipient = self.recipient
|
||||||
|
invoice.payment = self.payment
|
||||||
|
invoice.address = self.address
|
||||||
|
invoice.paid = False
|
||||||
|
invoice.remark = self.remark
|
||||||
|
invoice.date = timezone.now()
|
||||||
|
invoice.save()
|
||||||
|
self.final_invoice = invoice
|
||||||
|
self.save()
|
||||||
|
for sale in self.vente_set.all():
|
||||||
|
Vente.objects.create(
|
||||||
|
facture=invoice,
|
||||||
|
name=sale.name,
|
||||||
|
prix=sale.prix,
|
||||||
|
number=sale.number,
|
||||||
|
)
|
||||||
|
return invoice
|
||||||
|
|
||||||
|
def can_delete(self, user_request, *args, **kwargs):
|
||||||
|
if not user_request.has_perm('cotisations.delete_costestimate'):
|
||||||
|
return False, _("You don't have the right "
|
||||||
|
"to delete a cost estimate.")
|
||||||
|
if self.final_invoice is not None:
|
||||||
|
return False, _("The cost estimate has an "
|
||||||
|
"invoice and can't be deleted.")
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
# TODO : change Vente to Purchase
|
# TODO : change Vente to Purchase
|
||||||
|
@ -471,8 +562,9 @@ def vente_post_save(**kwargs):
|
||||||
if purchase.type_cotisation:
|
if purchase.type_cotisation:
|
||||||
purchase.create_cotis()
|
purchase.create_cotis()
|
||||||
purchase.cotisation.save()
|
purchase.cotisation.save()
|
||||||
user = purchase.facture.user
|
user = purchase.facture.facture.user
|
||||||
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
|
user.set_active()
|
||||||
|
user.ldap_sync(base=True, access_refresh=True, mac_refresh=False)
|
||||||
|
|
||||||
|
|
||||||
# TODO : change vente to purchase
|
# TODO : change vente to purchase
|
||||||
|
@ -602,7 +694,9 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
user: The user requesting articles.
|
user: The user requesting articles.
|
||||||
target_user: The user to sell articles
|
target_user: The user to sell articles
|
||||||
"""
|
"""
|
||||||
if target_user.is_class_club:
|
if target_user is None:
|
||||||
|
objects_pool = cls.objects.filter(Q(type_user='All'))
|
||||||
|
elif target_user.is_class_club:
|
||||||
objects_pool = cls.objects.filter(
|
objects_pool = cls.objects.filter(
|
||||||
Q(type_user='All') | Q(type_user='Club')
|
Q(type_user='All') | Q(type_user='Club')
|
||||||
)
|
)
|
||||||
|
@ -610,6 +704,10 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
objects_pool = cls.objects.filter(
|
objects_pool = cls.objects.filter(
|
||||||
Q(type_user='All') | Q(type_user='Adherent')
|
Q(type_user='All') | Q(type_user='Adherent')
|
||||||
)
|
)
|
||||||
|
if target_user is not None and not target_user.is_adherent():
|
||||||
|
objects_pool = objects_pool.filter(
|
||||||
|
Q(type_cotisation='All') | Q(type_cotisation='Adhesion')
|
||||||
|
)
|
||||||
if user.has_perm('cotisations.buy_every_article'):
|
if user.has_perm('cotisations.buy_every_article'):
|
||||||
return objects_pool
|
return objects_pool
|
||||||
return objects_pool.filter(available_for_everyone=True)
|
return objects_pool.filter(available_for_everyone=True)
|
||||||
|
@ -700,6 +798,10 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
||||||
if payment_method is not None and use_payment_method:
|
if payment_method is not None and use_payment_method:
|
||||||
return payment_method.end_payment(invoice, request)
|
return payment_method.end_payment(invoice, request)
|
||||||
|
|
||||||
|
# So make this invoice valid, trigger send mail
|
||||||
|
invoice.valid = True
|
||||||
|
invoice.save()
|
||||||
|
|
||||||
# In case a cotisation was bought, inform the user, the
|
# In case a cotisation was bought, inform the user, the
|
||||||
# cotisation time has been extended too
|
# cotisation time has been extended too
|
||||||
if any(sell.type_cotisation for sell in invoice.vente_set.all()):
|
if any(sell.type_cotisation for sell in invoice.vente_set.all()):
|
||||||
|
@ -856,4 +958,3 @@ def cotisation_post_delete(**_kwargs):
|
||||||
"""
|
"""
|
||||||
regen('mac_ip_list')
|
regen('mac_ip_list')
|
||||||
regen('mailing')
|
regen('mailing')
|
||||||
|
|
||||||
|
|
|
@ -127,10 +127,11 @@ method to your model, where `form` is an instance of
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from . import comnpay, cheque, balance, urls
|
from . import comnpay, cheque, balance, note_kfet, urls
|
||||||
|
|
||||||
PAYMENT_METHODS = [
|
PAYMENT_METHODS = [
|
||||||
comnpay,
|
comnpay,
|
||||||
cheque,
|
cheque,
|
||||||
balance,
|
balance,
|
||||||
|
note_kfet
|
||||||
]
|
]
|
||||||
|
|
|
@ -73,9 +73,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
user = invoice.user
|
user = invoice.user
|
||||||
total_price = invoice.prix_total()
|
total_price = invoice.prix_total()
|
||||||
if float(user.solde) - float(total_price) < self.minimum_balance:
|
if user.solde - total_price < self.minimum_balance:
|
||||||
invoice.valid = False
|
|
||||||
invoice.save()
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
_("Your balance is too low for this operation.")
|
_("Your balance is too low for this operation.")
|
||||||
|
@ -108,7 +106,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
|
||||||
balance.
|
balance.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
float(user.solde) - float(price) >= self.minimum_balance,
|
user.solde - price >= self.minimum_balance,
|
||||||
_("Your balance is too low for this operation.")
|
_("Your balance is too low for this operation.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,6 @@ class ChequePayment(PaymentMethodMixin, models.Model):
|
||||||
"""Invalidates the invoice then redirect the user towards a view asking
|
"""Invalidates the invoice then redirect the user towards a view asking
|
||||||
for informations to add to the invoice before validating it.
|
for informations to add to the invoice before validating it.
|
||||||
"""
|
"""
|
||||||
invoice.valid = False
|
|
||||||
invoice.save()
|
|
||||||
return redirect(reverse(
|
return redirect(reverse(
|
||||||
'cotisations:cheque:validate',
|
'cotisations:cheque:validate',
|
||||||
kwargs={'invoice_pk': invoice.pk}
|
kwargs={'invoice_pk': invoice.pk}
|
||||||
|
|
|
@ -81,8 +81,6 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
|
||||||
a facture id, the price and the secret transaction data stored in
|
a facture id, the price and the secret transaction data stored in
|
||||||
the preferences.
|
the preferences.
|
||||||
"""
|
"""
|
||||||
invoice.valid = False
|
|
||||||
invoice.save()
|
|
||||||
host = request.get_host()
|
host = request.get_host()
|
||||||
p = Transaction(
|
p = Transaction(
|
||||||
str(self.payment_credential),
|
str(self.payment_credential),
|
||||||
|
|
|
@ -62,13 +62,13 @@ def accept_payment(request, factureid):
|
||||||
request,
|
request,
|
||||||
_("The subscription of %(member_name)s was extended to"
|
_("The subscription of %(member_name)s was extended to"
|
||||||
" %(end_date)s.") % {
|
" %(end_date)s.") % {
|
||||||
'member_name': request.user.pseudo,
|
'member_name': invoice.user.pseudo,
|
||||||
'end_date': request.user.end_adhesion()
|
'end_date': invoice.user.end_adhesion()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return redirect(reverse(
|
return redirect(reverse(
|
||||||
'users:profil',
|
'users:profil',
|
||||||
kwargs={'userid': request.user.id}
|
kwargs={'userid': invoice.user.id}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
26
cotisations/payment_methods/note_kfet/__init__.py
Normal file
26
cotisations/payment_methods/note_kfet/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- 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.
|
||||||
|
#
|
||||||
|
# Copyright © 2018 Gabriel Detraz, Pierre-Antoine Comby
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""
|
||||||
|
This module contains a method to pay online using comnpay.
|
||||||
|
"""
|
||||||
|
from . import models, urls
|
||||||
|
NAME = "NOTE"
|
||||||
|
PaymentMethod = models.NotePayment
|
38
cotisations/payment_methods/note_kfet/forms.py
Normal file
38
cotisations/payment_methods/note_kfet/forms.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- 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.
|
||||||
|
#
|
||||||
|
# Copyright © 2018 Pierre-Antoine Comby
|
||||||
|
# Copyright © 2018 Gabriel Detraz
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from cotisations.utils import find_payment_method
|
||||||
|
|
||||||
|
class NoteCredentialForm(forms.Form):
|
||||||
|
"""A special form to get credential to connect to a NoteKfet2015 server throught his API
|
||||||
|
object.
|
||||||
|
"""
|
||||||
|
login = forms.CharField(
|
||||||
|
label=_("pseudo note")
|
||||||
|
)
|
||||||
|
password = forms.CharField(
|
||||||
|
label=_("Password"),
|
||||||
|
widget=forms.PasswordInput
|
||||||
|
)
|
||||||
|
|
65
cotisations/payment_methods/note_kfet/models.py
Normal file
65
cotisations/payment_methods/note_kfet/models.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# -*- 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.
|
||||||
|
#
|
||||||
|
# Copyright © 2018 Pierre-Antoine Comby
|
||||||
|
# Copyright © 2018 Gabriel Detraz
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from django.db import models
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
from cotisations.models import Paiement
|
||||||
|
from cotisations.payment_methods.mixins import PaymentMethodMixin
|
||||||
|
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
|
||||||
|
|
||||||
|
class NotePayment(PaymentMethodMixin, models.Model):
|
||||||
|
"""
|
||||||
|
The model allowing you to pay with NoteKfet2015.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("NoteKfet")
|
||||||
|
|
||||||
|
payment = models.OneToOneField(
|
||||||
|
Paiement,
|
||||||
|
on_delete = models.CASCADE,
|
||||||
|
related_name = 'payment_method',
|
||||||
|
editable = False
|
||||||
|
)
|
||||||
|
server = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name=_("server")
|
||||||
|
)
|
||||||
|
port = models.PositiveIntegerField(
|
||||||
|
blank = True,
|
||||||
|
null = True
|
||||||
|
)
|
||||||
|
id_note = models.PositiveIntegerField(
|
||||||
|
blank = True,
|
||||||
|
null = True
|
||||||
|
)
|
||||||
|
|
||||||
|
def end_payment(self, invoice, request):
|
||||||
|
return redirect(reverse(
|
||||||
|
'cotisations:note_kfet:note_payment',
|
||||||
|
kwargs={'factureid': invoice.id}
|
||||||
|
))
|
74
cotisations/payment_methods/note_kfet/note.py
Executable file
74
cotisations/payment_methods/note_kfet/note.py
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
# Codé par PAC , forké de 20-100
|
||||||
|
|
||||||
|
""" Module pour dialoguer avec la NoteKfet2015 """
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import ssl
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def get_response(socket):
|
||||||
|
length_str = b''
|
||||||
|
char = socket.recv(1)
|
||||||
|
while char != b'\n':
|
||||||
|
length_str += char
|
||||||
|
char = socket.recv(1)
|
||||||
|
total = int(length_str)
|
||||||
|
return json.loads(socket.recv(total).decode('utf-8'))
|
||||||
|
|
||||||
|
def connect(server, port):
|
||||||
|
sock = socket.socket()
|
||||||
|
try:
|
||||||
|
# On établit la connexion sur port 4242
|
||||||
|
sock.connect((server, port))
|
||||||
|
# On passe en SSL
|
||||||
|
sock = ssl.wrap_socket(sock)
|
||||||
|
# On fait un hello
|
||||||
|
sock.send(b'["hello", "manual"]')
|
||||||
|
retcode = get_response(sock)
|
||||||
|
except:
|
||||||
|
# Si on a foiré quelque part, c'est que le serveur est down
|
||||||
|
return (False, sock, "Serveur indisponible")
|
||||||
|
return (True, sock, "")
|
||||||
|
|
||||||
|
def login(server, port, username, password, masque = [[], [], True]):
|
||||||
|
result, sock, err = connect(server, port)
|
||||||
|
if not result:
|
||||||
|
return (False, None, err)
|
||||||
|
try:
|
||||||
|
commande = ["login", [username, password, "bdd", masque]]
|
||||||
|
sock.send(json.dumps(commande).encode("utf-8"))
|
||||||
|
response = get_response(sock)
|
||||||
|
retcode = response['retcode']
|
||||||
|
if retcode == 0:
|
||||||
|
return (True, sock, "")
|
||||||
|
elif retcode == 5:
|
||||||
|
return (False, sock, "Login incorrect")
|
||||||
|
else:
|
||||||
|
return (False, sock, "Erreur inconnue " + str(retcode))
|
||||||
|
except:
|
||||||
|
# Si on a foiré quelque part, c'est que le serveur est down
|
||||||
|
return (False, sock, "Erreur de communication avec le serveur")
|
||||||
|
|
||||||
|
|
||||||
|
def don(sock, montant, id_note, facture):
|
||||||
|
"""
|
||||||
|
Faire faire un don à l'id_note
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sock.send(json.dumps(["dons", [[id_note], round(montant*100), "Facture : id=%s, designation=%s" % (facture.id, facture.name())]]).encode("utf-8"))
|
||||||
|
response = get_response(sock)
|
||||||
|
retcode = response['retcode']
|
||||||
|
transaction_retcode = response["msg"][0][0]
|
||||||
|
if 0 < retcode < 100 or 200 <= retcode or 0 < transaction_retcode < 100 or 200 <= transaction_retcode:
|
||||||
|
return (False, "Transaction échouée. (Solde trop négatif ?)")
|
||||||
|
elif retcode == 0:
|
||||||
|
return (True, "")
|
||||||
|
else:
|
||||||
|
return (False, "Erreur inconnue " + str(retcode))
|
||||||
|
except:
|
||||||
|
return (False, "Erreur de communication avec le serveur")
|
30
cotisations/payment_methods/note_kfet/urls.py
Normal file
30
cotisations/payment_methods/note_kfet/urls.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- 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.
|
||||||
|
#
|
||||||
|
# Copyright © 2018 Gabriel Detraz
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from django.conf.urls import url
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(
|
||||||
|
r'^note_payment/(?P<factureid>[0-9]+)$',
|
||||||
|
views.note_payment,
|
||||||
|
name='note_payment'
|
||||||
|
),
|
||||||
|
]
|
97
cotisations/payment_methods/note_kfet/views.py
Normal file
97
cotisations/payment_methods/note_kfet/views.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# -*- 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.
|
||||||
|
#
|
||||||
|
# Copyright © 2018 Gabriel Detraz
|
||||||
|
# Copyright © 2018 Pierre-Antoine Comby
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
"""Payment
|
||||||
|
|
||||||
|
Here are the views needed by comnpay
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.shortcuts import redirect, get_object_or_404
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.utils.datastructures import MultiValueDictKeyError
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
|
|
||||||
|
from cotisations.models import Facture
|
||||||
|
from cotisations.utils import find_payment_method
|
||||||
|
from .models import NotePayment
|
||||||
|
from re2o.views import form
|
||||||
|
from re2o.acl import (
|
||||||
|
can_create,
|
||||||
|
can_edit
|
||||||
|
)
|
||||||
|
from .note import login, don
|
||||||
|
from .forms import NoteCredentialForm
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_edit(Facture)
|
||||||
|
def note_payment(request, facture, factureid):
|
||||||
|
"""
|
||||||
|
Build a request to start the negociation with NoteKfet by using
|
||||||
|
a facture id, the price and the login/password data stored in
|
||||||
|
the preferences.
|
||||||
|
"""
|
||||||
|
user = facture.user
|
||||||
|
payment_method = find_payment_method(facture.paiement)
|
||||||
|
if not payment_method or not isinstance(payment_method, NotePayment):
|
||||||
|
messages.error(request, _("Unknown error."))
|
||||||
|
return redirect(reverse(
|
||||||
|
'users:profil',
|
||||||
|
kwargs={'userid': user.id}
|
||||||
|
))
|
||||||
|
noteform = NoteCredentialForm(request.POST or None)
|
||||||
|
if noteform.is_valid():
|
||||||
|
pseudo = noteform.cleaned_data['login']
|
||||||
|
password = noteform.cleaned_data['password']
|
||||||
|
result, sock, err = login(payment_method.server, payment_method.port, pseudo, password)
|
||||||
|
if not result:
|
||||||
|
messages.error(request, err)
|
||||||
|
return form(
|
||||||
|
{'form': noteform, 'amount': facture.prix_total()},
|
||||||
|
"cotisations/payment.html",
|
||||||
|
request
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result, err = don(sock, facture.prix_total(), payment_method.id_note, facture)
|
||||||
|
if not result:
|
||||||
|
messages.error(request, err)
|
||||||
|
return form(
|
||||||
|
{'form': noteform, 'amount': facture.prix_total()},
|
||||||
|
"cotisations/payment.html",
|
||||||
|
request
|
||||||
|
)
|
||||||
|
facture.valid = True
|
||||||
|
facture.save()
|
||||||
|
messages.success(request, _("The payment with note was done."))
|
||||||
|
return redirect(reverse(
|
||||||
|
'users:profil',
|
||||||
|
kwargs={'userid': user.id}
|
||||||
|
))
|
||||||
|
return form(
|
||||||
|
{'form': noteform, 'amount': facture.prix_total()},
|
||||||
|
"cotisations/payment.html",
|
||||||
|
request
|
||||||
|
)
|
|
@ -19,9 +19,10 @@
|
||||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
# 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 02110-1301 USA.
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from . import comnpay, cheque
|
from . import comnpay, cheque, note_kfet
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^comnpay/', include(comnpay.urls, namespace='comnpay')),
|
url(r'^comnpay/', include(comnpay.urls, namespace='comnpay')),
|
||||||
url(r'^cheque/', include(cheque.urls, namespace='cheque')),
|
url(r'^cheque/', include(cheque.urls, namespace='cheque')),
|
||||||
|
url(r'^note_kfet/', include(note_kfet.urls, namespace='note_kfet')),
|
||||||
]
|
]
|
||||||
|
|
|
@ -49,9 +49,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ article.available_for_everyone | tick }}</td>
|
<td>{{ article.available_for_everyone | tick }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% can_edit article %}
|
{% can_edit article %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" title="{% trans "Edit" %}" href="{% url 'cotisations:edit-article' article.id %}">
|
{% include 'buttons/edit.html' with href='cotisations:edit-article' id=article.id %}
|
||||||
<i class="fa fa-edit"></i>
|
|
||||||
</a>
|
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% history_button article %}
|
{% history_button article %}
|
||||||
</td>
|
</td>
|
||||||
|
|
101
cotisations/templates/cotisations/aff_cost_estimate.html
Normal file
101
cotisations/templates/cotisations/aff_cost_estimate.html
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
{% 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 cost_estimate_list.paginator %}
|
||||||
|
{% include 'pagination.html' with list=cost_estimate_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_recip %}
|
||||||
|
</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 "Validity" as tr_validity %}
|
||||||
|
{% include 'buttons/sort.html' with prefix='invoice' col='validity' text=tr_validity %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Cost estimate ID" as tr_estimate_id %}
|
||||||
|
{% include 'buttons/sort.html' with prefix='invoice' col='id' text=tr_estimate_id %}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
{% trans "Invoice created" as tr_invoice_created%}
|
||||||
|
{% include 'buttons/sort.html' with prefix='invoice' col='paid' text=tr_invoice_created %}
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% for estimate in cost_estimate_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ estimate.recipient }}</td>
|
||||||
|
<td>{{ estimate.name }}</td>
|
||||||
|
<td>{{ estimate.prix_total }}</td>
|
||||||
|
<td>{{ estimate.payment }}</td>
|
||||||
|
<td>{{ estimate.date }}</td>
|
||||||
|
<td>{{ estimate.validity }}</td>
|
||||||
|
<td>{{ estimate.id }}</td>
|
||||||
|
<td>
|
||||||
|
{% if estimate.final_invoice %}
|
||||||
|
<a href="{% url 'cotisations:edit-custom-invoice' estimate.final_invoice.pk %}"><i style="color: #1ECA18;" class="fa fa-check"></i></a>
|
||||||
|
{% else %}
|
||||||
|
<i style="color: #D10115;" class="fa fa-times"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% can_edit estimate %}
|
||||||
|
{% include 'buttons/edit.html' with href='cotisations:edit-cost-estimate' id=estimate.id %}
|
||||||
|
{% acl_end %}
|
||||||
|
{% history_button estimate %}
|
||||||
|
{% include 'buttons/suppr.html' with href='cotisations:del-cost-estimate' id=estimate.id %}
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:cost-estimate-to-invoice' estimate.id %}">
|
||||||
|
<i class="fa fa-file"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:cost-estimate-pdf' estimate.id %}">
|
||||||
|
<i class="fa fa-file-pdf-o"></i> {% trans "PDF" %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if custom_invoice_list.paginator %}
|
||||||
|
{% include 'pagination.html' with list=custom_invoice_list %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
|
@ -78,11 +78,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>
|
<td>
|
||||||
{% if facture.valid %}
|
{% if facture.valid %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:facture-pdf' facture.id %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:facture-pdf' facture.id %}">
|
||||||
<i class="fa fa-file-pdf"></i> {% trans "PDF" %}
|
<i class="fa fa-file-pdf-o"></i> {% trans "PDF" %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="text-danger">{% trans "Invalidated invoice" %}</i>
|
<i class="text-danger">{% trans "Invalidated invoice" %}</i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if facture.control and facture.is_subscription %}
|
||||||
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:voucher-pdf' facture.id %}">
|
||||||
|
<i class="fa fa-file-pdf-o"></i> {% trans "Voucher" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Recipient" as tr_recip %}
|
{% trans "Recipient" as tr_recip %}
|
||||||
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_user %}
|
{% include 'buttons/sort.html' with prefix='invoice' col='user' text=tr_recip %}
|
||||||
</th>
|
</th>
|
||||||
<th>{% trans "Designation" %}</th>
|
<th>{% trans "Designation" %}</th>
|
||||||
<th>{% trans "Total price" %}</th>
|
<th>{% trans "Total price" %}</th>
|
||||||
|
@ -76,7 +76,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% history_button invoice %}
|
{% history_button invoice %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:custom-invoice-pdf' invoice.id %}">
|
<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" %}
|
<i class="fa fa-file-pdf-o"></i> {% trans "PDF" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -45,9 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% can_edit paiement %}
|
{% can_edit paiement %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" title="{% trans "Edit" %}" href="{% url 'cotisations:edit-paiement' paiement.id %}">
|
{% include 'buttons/edit.html' with href='cotisations:edit-paiement' id=paiement.id %}
|
||||||
<i class="fa fa-edit"></i>
|
|
||||||
</a>
|
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% history_button paiement %}
|
{% history_button paiement %}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -105,7 +105,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% trans "Edit" as tr_edit %}
|
{% trans "Edit" as tr_edit %}
|
||||||
{% bootstrap_button tr_edit button_type='submit' icon='star' %}
|
{% bootstrap_button tr_edit button_type='submit' icon='ok' button_class='btn-success' %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "machines/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% blocktrans %}Warning: are you sure you really want to delete this {{ object_name }} object ( {{ objet }} )?{% endblocktrans %}
|
{% blocktrans %}Warning: are you sure you really want to delete this {{ object_name }} object ( {{ objet }} )?{% endblocktrans %}
|
||||||
</h4>
|
</h4>
|
||||||
{% trans "Confirm" as tr_confirm %}
|
{% trans "Confirm" as tr_confirm %}
|
||||||
{% bootstrap_button tr_confirm button_type='submit' icon='trash' %}
|
{% bootstrap_button tr_confirm button_type='submit' icon='trash' button_class='btn-danger' %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -35,10 +35,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<h3>{% trans "Edit the invoice" %}</h3>
|
{% if title %}
|
||||||
|
<h3>{{title}}</h3>
|
||||||
|
{% else %}
|
||||||
|
<h3>{% trans "Edit invoice" %}</h3>
|
||||||
|
{% endif %}
|
||||||
{% massive_bootstrap_form factureform 'user' %}
|
{% massive_bootstrap_form factureform 'user' %}
|
||||||
{{ venteform.management_form }}
|
{{ venteform.management_form }}
|
||||||
<h3>{% trans "Invoice's articles" %}</h3>
|
<h3>{% trans "Articles" %}</h3>
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -58,7 +62,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% trans "Confirm" as tr_confirm %}
|
{% trans "Confirm" as tr_confirm %}
|
||||||
{% bootstrap_button tr_confirm button_type='submit' icon='star' %}
|
{% bootstrap_button tr_confirm button_type='submit' icon='ok' button_class='btn-success' %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
Bonjour {{name}} !
|
||||||
|
|
||||||
|
Nous vous informons que votre cotisation auprès de {{asso_name}} a été acceptée. Vous voilà donc membre de l'association.
|
||||||
|
|
||||||
|
Vous trouverez en pièce jointe un reçu.
|
||||||
|
|
||||||
|
Pour nous faire part de toute remarque, suggestion ou problème vous pouvez nous envoyer un mail à {{asso_email}}.
|
||||||
|
|
||||||
|
À bientôt,
|
||||||
|
L'équipe de {{asso_name}}.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Your subscription to {{asso_name}} has just been accepted. You are now a full member of {{asso_name}}.
|
||||||
|
|
||||||
|
You will find with this email a subscription voucher.
|
||||||
|
|
||||||
|
For any information, suggestion or problem, you can contact us via email at
|
||||||
|
{{asso_email}}.
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
The {{asso_name}} team.
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -34,7 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% if title %}
|
{% if title %}
|
||||||
<h3>{{ title }}</h3>
|
<h3>{{ title }}</h3>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3>{% trans "New invoice" %}</h3>
|
<h3>{% trans "Buy" %}</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if max_balance %}
|
{% if max_balance %}
|
||||||
<h4>{% blocktrans %}Maximum allowed balance: {{ max_balance }} €{% endblocktrans %}</h4>
|
<h4>{% blocktrans %}Maximum allowed balance: {{ max_balance }} €{% endblocktrans %}</h4>
|
||||||
|
@ -44,6 +44,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% blocktrans %}Current balance: {{ balance }} €{% endblocktrans %}
|
{% blocktrans %}Current balance: {{ balance }} €{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if factureform %}
|
||||||
|
{% bootstrap_form_errors factureform %}
|
||||||
|
{% endif %}
|
||||||
|
{% if discount_form %}
|
||||||
|
{% bootstrap_form_errors discount_form %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form class="form" method="post">
|
<form class="form" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -53,7 +59,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<div id="paymentMethod"></div>
|
<div id="paymentMethod"></div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if articlesformset %}
|
{% if articlesformset %}
|
||||||
<h3>{% trans "Invoice's articles" %}</h3>
|
<h3>{% trans "Articles" %}</h3>
|
||||||
<div id="form_set" class="form-group">
|
<div id="form_set" class="form-group">
|
||||||
{{ articlesformset.management_form }}
|
{{ articlesformset.management_form }}
|
||||||
{% for articlesform in articlesformset.forms %}
|
{% for articlesform in articlesformset.forms %}
|
||||||
|
@ -67,12 +73,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<input class="btn btn-primary btn-sm" role="button" value="{% trans "Add an article"%}" id="add_one">
|
<input class="btn btn-primary btn-block" role="button" value="{% trans "Add an extra article"%}" id="add_one">
|
||||||
|
{% if discount_form %}
|
||||||
|
<h3>{% trans "Discount" %}</h3>
|
||||||
|
{% bootstrap_form discount_form %}
|
||||||
|
{% endif %}
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans %}Total price: <span id="total_price">0,00</span> €{% endblocktrans %}
|
{% blocktrans %}Total price: <span id="total_price">0,00</span> €{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% bootstrap_button action_name button_type='submit' icon='star' %}
|
{% bootstrap_button action_name button_type='submit' icon='ok' button_class='btn-success' %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if articlesformset or payment_method%}
|
{% if articlesformset or payment_method%}
|
||||||
|
@ -119,6 +129,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
'id_form-' + i.toString() + '-quantity').value;
|
'id_form-' + i.toString() + '-quantity').value;
|
||||||
price += article_price * quantity;
|
price += article_price * quantity;
|
||||||
}
|
}
|
||||||
|
{% if discount_form %}
|
||||||
|
var relative_discount = document.getElementById('id_is_relative').checked;
|
||||||
|
var discount = document.getElementById('id_discount').value;
|
||||||
|
if(relative_discount) {
|
||||||
|
discount = discount/100 * price;
|
||||||
|
}
|
||||||
|
price -= discount;
|
||||||
|
{% endif %}
|
||||||
document.getElementById('total_price').innerHTML =
|
document.getElementById('total_price').innerHTML =
|
||||||
price.toFixed(2).toString().replace('.', ',');
|
price.toFixed(2).toString().replace('.', ',');
|
||||||
}
|
}
|
||||||
|
@ -148,6 +166,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
for (i = 0; i < product_count; ++i){
|
for (i = 0; i < product_count; ++i){
|
||||||
add_listenner_for_id(i);
|
add_listenner_for_id(i);
|
||||||
}
|
}
|
||||||
|
document.getElementById('id_discount')
|
||||||
|
.addEventListener('change', update_price, true);
|
||||||
|
document.getElementById('id_is_relative')
|
||||||
|
.addEventListener('click', update_price, true);
|
||||||
update_price();
|
update_price();
|
||||||
});
|
});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -75,8 +75,12 @@
|
||||||
{\bf Pour :} {{recipient_name|safe}} & {\bf Date :} {{DATE}} \\
|
{\bf Pour :} {{recipient_name|safe}} & {\bf Date :} {{DATE}} \\
|
||||||
{\bf Adresse :} {% if address is None %}Aucune adresse renseignée{% else %}{{address}}{% endif %} & \\
|
{\bf Adresse :} {% if address is None %}Aucune adresse renseignée{% else %}{{address}}{% endif %} & \\
|
||||||
{% if fid is not None %}
|
{% if fid is not None %}
|
||||||
|
{% if is_estimate %}
|
||||||
|
{\bf Devis n\textsuperscript{o} :} {{ fid }} & \\
|
||||||
|
{% else %}
|
||||||
{\bf Facture n\textsuperscript{o} :} {{ fid }} & \\
|
{\bf Facture n\textsuperscript{o} :} {{ fid }} & \\
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
\end{tabular*}
|
\end{tabular*}
|
||||||
\\
|
\\
|
||||||
|
|
||||||
|
@ -104,12 +108,30 @@
|
||||||
\begin{tabular}{|l|r|}
|
\begin{tabular}{|l|r|}
|
||||||
\hline
|
\hline
|
||||||
\textbf{Total} & {{total|floatformat:2}} \euro \\
|
\textbf{Total} & {{total|floatformat:2}} \euro \\
|
||||||
|
{% if not is_estimate %}
|
||||||
\textbf{Votre règlement} & {% if paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro \\
|
\textbf{Votre règlement} & {% if paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro \\
|
||||||
\doublehline
|
\doublehline
|
||||||
\textbf{À PAYER} & {% if not paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro\\
|
\textbf{À PAYER} & {% if not paid %}{{total|floatformat:2}}{% else %} 00,00 {% endif %} \euro\\
|
||||||
|
{% endif %}
|
||||||
\hline
|
\hline
|
||||||
\end{tabular}
|
\end{tabular}
|
||||||
|
|
||||||
|
\vspace{1cm}
|
||||||
|
\begin{tabularx}{\textwidth}{r X}
|
||||||
|
\hline
|
||||||
|
\textbf{Moyen de paiement} & {{payment_method|default:"Non spécifié"}} \\
|
||||||
|
\hline
|
||||||
|
{% if remark %}
|
||||||
|
\textbf{Remarque} & {{remark|safe}} \\
|
||||||
|
\hline
|
||||||
|
{% endif %}
|
||||||
|
{% if end_validity %}
|
||||||
|
\textbf{Validité} & Jusqu'au {{end_validity}} \\
|
||||||
|
\hline
|
||||||
|
{% endif %}
|
||||||
|
\end{tabularx}
|
||||||
|
|
||||||
|
|
||||||
\vfill
|
\vfill
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block title %}{% trans "Articles" %}{% endblock %}
|
{% block title %}{% trans "Articles" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Article types list" %}</h2>
|
<h2>{% trans "List of article types" %}</h2>
|
||||||
{% can_create Article %}
|
{% can_create Article %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-article' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-article' %}">
|
||||||
<i class="fa fa-cart-plus"></i> {% trans "Add an article type" %}
|
<i class="fa fa-cart-plus"></i> {% trans "Add an article type" %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block title %}{% trans "Banks" %}{% endblock %}
|
{% block title %}{% trans "Banks" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Banks list" %}</h2>
|
<h2>{% trans "List of banks" %}</h2>
|
||||||
{% can_create Banque %}
|
{% can_create Banque %}
|
||||||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-banque' %}">
|
<a class="btn btn-primary btn-sm" role="button" href="{% url 'cotisations:add-banque' %}">
|
||||||
<i class="fa fa-cart-plus"></i> {% trans "Add a bank" %}
|
<i class="fa fa-cart-plus"></i> {% trans "Add a bank" %}
|
||||||
|
|
36
cotisations/templates/cotisations/index_cost_estimate.html
Normal file
36
cotisations/templates/cotisations/index_cost_estimate.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
|
{% comment %}
|
||||||
|
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||||
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
quelques clics.
|
||||||
|
|
||||||
|
Copyright © 2017 Gabriel Détraz
|
||||||
|
Copyright © 2017 Goulven Kermarec
|
||||||
|
Copyright © 2017 Augustin Lemesle
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
{% endcomment %}
|
||||||
|
{% load acl %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Cost estimates" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>{% trans "List of cost estimates" %}</h2>
|
||||||
|
{% can_create CostEstimate %}
|
||||||
|
{% include 'buttons/add.html' with href='cotisations:new-cost-estimate'%}
|
||||||
|
{% acl_end %}
|
||||||
|
{% include 'cotisations/aff_cost_estimate.html' %}
|
||||||
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -28,9 +28,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% block title %}{% trans "Custom invoices" %}{% endblock %}
|
{% block title %}{% trans "Custom invoices" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Custom invoices list" %}</h2>
|
<h2>{% trans "List of custom invoices" %}</h2>
|
||||||
{% can_create CustomInvoice %}
|
{% can_create CustomInvoice %}
|
||||||
{% include "buttons/add.html" with href='cotisations:new-custom-invoice'%}
|
{% include 'buttons/add.html' with href='cotisations:new-custom-invoice'%}
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% include 'cotisations/aff_custom_invoice.html' with custom_invoice_list=custom_invoice_list %}
|
{% include 'cotisations/aff_custom_invoice.html' with custom_invoice_list=custom_invoice_list %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "cotisations/sidebar.html" %}
|
{% extends 'cotisations/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -40,7 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% bootstrap_form form %}
|
{% bootstrap_form form %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% trans "Pay" as tr_pay %}
|
{% trans "Pay" as tr_pay %}
|
||||||
{% bootstrap_button tr_pay button_type='submit' icon='piggy-bank' %}
|
{% bootstrap_button tr_pay button_type='submit' icon='piggy-bank' button_class='btn-success' %}
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends 'base.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -28,35 +28,40 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
{% can_create CustomInvoice %}
|
{% can_create CustomInvoice %}
|
||||||
<a class="list-group-item list-group-item-success" href="{% url "cotisations:new-custom-invoice" %}">
|
<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" %}
|
<i class="fa fa-plus"></i> {% trans "Create an invoice" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item list-group-item-warning" href="{% url "cotisations:control" %}">
|
<a class="list-group-item list-group-item-warning" href="{% url 'cotisations:control' %}">
|
||||||
<i class="fa fa-eye"></i> {% trans "Control the invoices" %}
|
<i class="fa fa-eye"></i> {% trans "Control the invoices" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% can_view_all Facture %}
|
{% can_view_all Facture %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index' %}">
|
||||||
<i class="fa fa-list-ul"></i> {% trans "Invoices" %}
|
<i class="fa fa-list-ul"></i> {% trans "Invoices" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% can_view_all CustomInvoice %}
|
{% can_view_all CustomInvoice %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-custom-invoice" %}">
|
<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" %}
|
<i class="fa fa-list-ul"></i> {% trans "Custom invoices" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
|
{% can_view_all CostEstimate %}
|
||||||
|
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-cost-estimate' %}">
|
||||||
|
<i class="fa fa-list-ul"></i> {% trans "Cost estimates" %}
|
||||||
|
</a>
|
||||||
|
{% acl_end %}
|
||||||
{% can_view_all Article %}
|
{% can_view_all Article %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-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" %}
|
<i class="fa fa-list-ul"></i> {% trans "Available articles" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% can_view_all Banque %}
|
{% can_view_all Banque %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-banque" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-banque' %}">
|
||||||
<i class="fa fa-list-ul"></i> {% trans "Banks" %}
|
<i class="fa fa-list-ul"></i> {% trans "Banks" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% can_view_all Paiement %}
|
{% can_view_all Paiement %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "cotisations:index-paiement" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'cotisations:index-paiement' %}">
|
||||||
<i class="fa fa-list-ul"></i> {% trans "Payment methods" %}
|
<i class="fa fa-list-ul"></i> {% trans "Payment methods" %}
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
|
|
87
cotisations/templates/cotisations/voucher.tex
Normal file
87
cotisations/templates/cotisations/voucher.tex
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
{% load i18n %}
|
||||||
|
{% language 'fr' %}
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% Invoice Template
|
||||||
|
% LaTeX Template
|
||||||
|
% Version 1.0 (3/11/12)
|
||||||
|
%% This template has been downloaded from:
|
||||||
|
% http://www.LaTeXTemplates.com
|
||||||
|
%
|
||||||
|
% Original author:
|
||||||
|
% Trey Hunner (http://www.treyhunner.com/)
|
||||||
|
%
|
||||||
|
% License:
|
||||||
|
% CC BY-NC-SA 3.0 (http://creativecommons.org/licenses/by-nc-sa/3.0/)
|
||||||
|
%
|
||||||
|
% Important note:
|
||||||
|
% This template requires the invoice.cls file to be in the same directory as
|
||||||
|
% the .tex file. The invoice.cls file provides the style used for structuring the
|
||||||
|
% document.
|
||||||
|
%
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
% DOCUMENT CONFIGURATION
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
\documentclass[12pt]{article} % Use the custom invoice class (invoice.cls)
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[letterpaper,hmargin=0.79in,vmargin=0.79in]{geometry}
|
||||||
|
\usepackage{longtable}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\usepackage{tabularx}
|
||||||
|
\usepackage{eurosym}
|
||||||
|
\usepackage{multicol}
|
||||||
|
|
||||||
|
\pagestyle{empty} % No page numbers
|
||||||
|
|
||||||
|
\linespread{1.5}
|
||||||
|
|
||||||
|
\newcommand{\doublehline}{\noalign{\hrule height 1pt}}
|
||||||
|
\setlength{\parindent}{0cm}
|
||||||
|
|
||||||
|
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
% HEADING SECTION
|
||||||
|
%----------------------------------------------------------------------------------------
|
||||||
|
\begin{center}
|
||||||
|
{\Huge\bf Reçu d'adhésion \\ {{asso_name|safe}} } % Company providing the invoice
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\bigskip
|
||||||
|
\hrule
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
|
||||||
|
Je sousigné, {{pres_name|safe}}, déclare par la présente avoir reçu le bulletin d'adhésion de:
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\setlength{\tabcolsep}{10pt} % Make table columns tighter, usefull for postionning
|
||||||
|
\begin{tabular}{r l r l}
|
||||||
|
{\bf Prénom :}~ & {{firstname|safe}} & {% if phone %}{\bf Téléphone :}~ & {{phone}}{% else %} & {% endif %} \\
|
||||||
|
{\bf Nom :}~ & {{lastname|safe}} & {\bf Mail :}~ & {{email|safe}} \\
|
||||||
|
\end{tabular}
|
||||||
|
\end{center}
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
ainsi que sa cotisation.
|
||||||
|
|
||||||
|
Le postulant, déclare reconnaître l'objet de l'association, et en a accepté les statuts ainsi que le règlement intérieur qui sont mis à sa disposition dans les locaux de l'association. L'adhésion du membre sus-nommé est ainsi validée. Ce reçu confirme la qualité de membre du postulant, et ouvre droit à la participation à l'assemblée générale de l'association jusqu'au {{date_end|date:"d F Y"}}.
|
||||||
|
|
||||||
|
\bigskip
|
||||||
|
|
||||||
|
Validé électroniquement par {{pres_name|safe}}, le {{date_begin|date:"d/m/Y"}}.
|
||||||
|
|
||||||
|
\vfill
|
||||||
|
\hrule
|
||||||
|
\smallskip
|
||||||
|
\footnotesize
|
||||||
|
Les informations recueillies sont nécessaires pour votre adhésion. Conformément à la loi "Informatique et Libertés" du 6 janvier 1978, vous disposez d'un droit d'accès et de rectification aux données personnelles vous concernant. Pour l'exercer, adressez-vous au secrétariat de l'association.
|
||||||
|
|
||||||
|
|
||||||
|
\end{document}
|
||||||
|
{% endlanguage %}
|
|
@ -1,4 +1,4 @@
|
||||||
# coding: utf-8
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
# 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
|
# se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
# quelques clics.
|
# quelques clics.
|
||||||
|
@ -31,11 +31,16 @@ from subprocess import Popen, PIPE
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.template import Context
|
from django.template import Context
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from re2o.mixins import AclMixin, RevMixin
|
||||||
|
from preferences.models import CotisationsOption
|
||||||
|
|
||||||
|
|
||||||
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
|
TEMP_PREFIX = getattr(settings, 'TEX_TEMP_PREFIX', 'render_tex-')
|
||||||
|
@ -48,15 +53,40 @@ def render_invoice(_request, ctx={}):
|
||||||
Render an invoice using some available information such as the current
|
Render an invoice using some available information such as the current
|
||||||
date, the user, the articles, the prices, ...
|
date, the user, the articles, the prices, ...
|
||||||
"""
|
"""
|
||||||
|
options, _ = CotisationsOption.objects.get_or_create()
|
||||||
|
is_estimate = ctx.get('is_estimate', False)
|
||||||
filename = '_'.join([
|
filename = '_'.join([
|
||||||
'invoice',
|
'cost_estimate' if is_estimate else 'invoice',
|
||||||
slugify(ctx.get('asso_name', "")),
|
slugify(ctx.get('asso_name', "")),
|
||||||
slugify(ctx.get('recipient_name', "")),
|
slugify(ctx.get('recipient_name', "")),
|
||||||
str(ctx.get('DATE', datetime.now()).year),
|
str(ctx.get('DATE', datetime.now()).year),
|
||||||
str(ctx.get('DATE', datetime.now()).month),
|
str(ctx.get('DATE', datetime.now()).month),
|
||||||
str(ctx.get('DATE', datetime.now()).day),
|
str(ctx.get('DATE', datetime.now()).day),
|
||||||
])
|
])
|
||||||
r = render_tex(_request, 'cotisations/factures.tex', ctx)
|
templatename = options.invoice_template.template.name.split('/')[-1]
|
||||||
|
r = render_tex(_request, templatename, ctx)
|
||||||
|
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
||||||
|
name=filename
|
||||||
|
)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def render_voucher(_request, ctx={}):
|
||||||
|
"""
|
||||||
|
Render a subscribtion voucher.
|
||||||
|
"""
|
||||||
|
options, _ = CotisationsOption.objects.get_or_create()
|
||||||
|
filename = '_'.join([
|
||||||
|
'voucher',
|
||||||
|
slugify(ctx.get('asso_name', "")),
|
||||||
|
slugify(ctx.get('firstname', "")),
|
||||||
|
slugify(ctx.get('lastname', "")),
|
||||||
|
str(ctx.get('date_begin', datetime.now()).year),
|
||||||
|
str(ctx.get('date_begin', datetime.now()).month),
|
||||||
|
str(ctx.get('date_begin', datetime.now()).day),
|
||||||
|
])
|
||||||
|
templatename = options.voucher_template.template.name.split('/')[-1]
|
||||||
|
r = render_tex(_request, templatename, ctx)
|
||||||
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
r['Content-Disposition'] = 'attachment; filename="{name}.pdf"'.format(
|
||||||
name=filename
|
name=filename
|
||||||
)
|
)
|
||||||
|
@ -81,10 +111,11 @@ def create_pdf(template, ctx={}):
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tempdir:
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
|
with open("/var/www/re2o/out.log", "w") as f:
|
||||||
process = Popen(
|
process = Popen(
|
||||||
['pdflatex', '-output-directory', tempdir],
|
['pdflatex', '-output-directory', tempdir],
|
||||||
stdin=PIPE,
|
stdin=PIPE,
|
||||||
stdout=PIPE,
|
stdout=f,#PIPE,
|
||||||
)
|
)
|
||||||
process.communicate(rendered_tpl)
|
process.communicate(rendered_tpl)
|
||||||
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
|
with open(os.path.join(tempdir, 'texput.pdf'), 'rb') as f:
|
||||||
|
@ -93,6 +124,20 @@ def create_pdf(template, ctx={}):
|
||||||
return pdf
|
return pdf
|
||||||
|
|
||||||
|
|
||||||
|
def escape_chars(string):
|
||||||
|
"""Escape the '%' and the '€' signs to avoid messing with LaTeX"""
|
||||||
|
if not isinstance(string, str):
|
||||||
|
return string
|
||||||
|
mapping = (
|
||||||
|
('€', r'\euro'),
|
||||||
|
('%', r'\%'),
|
||||||
|
)
|
||||||
|
r = str(string)
|
||||||
|
for k, v in mapping:
|
||||||
|
r = r.replace(k, v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
def render_tex(_request, template, ctx={}):
|
def render_tex(_request, template, ctx={}):
|
||||||
"""Creates a PDF from a LaTex templates using pdflatex.
|
"""Creates a PDF from a LaTex templates using pdflatex.
|
||||||
|
|
||||||
|
|
|
@ -51,11 +51,46 @@ urlpatterns = [
|
||||||
views.facture_pdf,
|
views.facture_pdf,
|
||||||
name='facture-pdf'
|
name='facture-pdf'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r'^voucher_pdf/(?P<factureid>[0-9]+)$',
|
||||||
|
views.voucher_pdf,
|
||||||
|
name='voucher-pdf'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^new_cost_estimate/$',
|
||||||
|
views.new_cost_estimate,
|
||||||
|
name='new-cost-estimate'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^index_cost_estimate/$',
|
||||||
|
views.index_cost_estimate,
|
||||||
|
name='index-cost-estimate'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^cost_estimate_pdf/(?P<costestimateid>[0-9]+)$',
|
||||||
|
views.cost_estimate_pdf,
|
||||||
|
name='cost-estimate-pdf',
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r'^index_custom_invoice/$',
|
r'^index_custom_invoice/$',
|
||||||
views.index_custom_invoice,
|
views.index_custom_invoice,
|
||||||
name='index-custom-invoice'
|
name='index-custom-invoice'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
r'^edit_cost_estimate/(?P<costestimateid>[0-9]+)$',
|
||||||
|
views.edit_cost_estimate,
|
||||||
|
name='edit-cost-estimate'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^cost_estimate_to_invoice/(?P<costestimateid>[0-9]+)$',
|
||||||
|
views.cost_estimate_to_invoice,
|
||||||
|
name='cost-estimate-to-invoice'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^del_cost_estimate/(?P<costestimateid>[0-9]+)$',
|
||||||
|
views.del_cost_estimate,
|
||||||
|
name='del-cost-estimate'
|
||||||
|
),
|
||||||
url(
|
url(
|
||||||
r'^new_custom_invoice/$',
|
r'^new_custom_invoice/$',
|
||||||
views.new_custom_invoice,
|
views.new_custom_invoice,
|
||||||
|
|
|
@ -25,7 +25,7 @@ from django.template.loader import get_template
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
|
|
||||||
from .tex import create_pdf
|
from .tex import create_pdf
|
||||||
from preferences.models import AssoOption, GeneralOption
|
from preferences.models import AssoOption, GeneralOption, CotisationsOption
|
||||||
from re2o.settings import LOGO_PATH
|
from re2o.settings import LOGO_PATH
|
||||||
from re2o import settings
|
from re2o import settings
|
||||||
|
|
||||||
|
@ -89,7 +89,42 @@ def send_mail_invoice(invoice):
|
||||||
'Votre facture / Your invoice',
|
'Votre facture / Your invoice',
|
||||||
template.render(ctx),
|
template.render(ctx),
|
||||||
GeneralOption.get_cached_value('email_from'),
|
GeneralOption.get_cached_value('email_from'),
|
||||||
[invoice.user.email],
|
[invoice.user.get_mail],
|
||||||
attachments=[('invoice.pdf', pdf, 'application/pdf')]
|
attachments=[('invoice.pdf', pdf, 'application/pdf')]
|
||||||
)
|
)
|
||||||
mail.send()
|
mail.send()
|
||||||
|
|
||||||
|
|
||||||
|
def send_mail_voucher(invoice):
|
||||||
|
"""Creates a voucher from an invoice and sends it by email to the client"""
|
||||||
|
ctx = {
|
||||||
|
'asso_name': AssoOption.get_cached_value('name'),
|
||||||
|
'pres_name': AssoOption.get_cached_value('pres_name'),
|
||||||
|
'firstname': invoice.user.name,
|
||||||
|
'lastname': invoice.user.surname,
|
||||||
|
'email': invoice.user.email,
|
||||||
|
'phone': invoice.user.telephone,
|
||||||
|
'date_end': invoice.get_subscription().latest('date_end').date_end,
|
||||||
|
'date_begin': invoice.get_subscription().earliest('date_start').date_start
|
||||||
|
}
|
||||||
|
templatename = CotisationsOption.get_cached_value('voucher_template').template.name.split('/')[-1]
|
||||||
|
pdf = create_pdf(templatename, ctx)
|
||||||
|
template = get_template('cotisations/email_subscription_accepted')
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
'name': "{} {}".format(
|
||||||
|
invoice.user.name,
|
||||||
|
invoice.user.surname
|
||||||
|
),
|
||||||
|
'asso_email': AssoOption.get_cached_value('contact'),
|
||||||
|
'asso_name': AssoOption.get_cached_value('name')
|
||||||
|
}
|
||||||
|
|
||||||
|
mail = EmailMessage(
|
||||||
|
'Votre reçu / Your voucher',
|
||||||
|
template.render(ctx),
|
||||||
|
GeneralOption.get_cached_value('email_from'),
|
||||||
|
[invoice.user.get_mail],
|
||||||
|
attachments=[('voucher.pdf', pdf, 'application/pdf')]
|
||||||
|
)
|
||||||
|
mail.send()
|
||||||
|
|
|
@ -47,7 +47,10 @@ from users.models import User
|
||||||
from re2o.settings import LOGO_PATH
|
from re2o.settings import LOGO_PATH
|
||||||
from re2o import settings
|
from re2o import settings
|
||||||
from re2o.views import form
|
from re2o.views import form
|
||||||
from re2o.utils import SortTable, re2o_paginator
|
from re2o.base import (
|
||||||
|
SortTable,
|
||||||
|
re2o_paginator,
|
||||||
|
)
|
||||||
from re2o.acl import (
|
from re2o.acl import (
|
||||||
can_create,
|
can_create,
|
||||||
can_edit,
|
can_edit,
|
||||||
|
@ -65,7 +68,8 @@ from .models import (
|
||||||
Paiement,
|
Paiement,
|
||||||
Banque,
|
Banque,
|
||||||
CustomInvoice,
|
CustomInvoice,
|
||||||
BaseInvoice
|
BaseInvoice,
|
||||||
|
CostEstimate,
|
||||||
)
|
)
|
||||||
from .forms import (
|
from .forms import (
|
||||||
FactureForm,
|
FactureForm,
|
||||||
|
@ -77,11 +81,13 @@ from .forms import (
|
||||||
DelBanqueForm,
|
DelBanqueForm,
|
||||||
SelectArticleForm,
|
SelectArticleForm,
|
||||||
RechargeForm,
|
RechargeForm,
|
||||||
CustomInvoiceForm
|
CustomInvoiceForm,
|
||||||
|
DiscountForm,
|
||||||
|
CostEstimateForm,
|
||||||
)
|
)
|
||||||
from .tex import render_invoice
|
from .tex import render_invoice, render_voucher, escape_chars
|
||||||
from .payment_methods.forms import payment_method_factory
|
from .payment_methods.forms import payment_method_factory
|
||||||
from .utils import find_payment_method, send_mail_invoice
|
from .utils import find_payment_method
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -148,8 +154,6 @@ def new_facture(request, user, userid):
|
||||||
p.facture = new_invoice_instance
|
p.facture = new_invoice_instance
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
send_mail_invoice(new_invoice_instance)
|
|
||||||
|
|
||||||
return new_invoice_instance.paiement.end_payment(
|
return new_invoice_instance.paiement.end_payment(
|
||||||
new_invoice_instance,
|
new_invoice_instance,
|
||||||
request
|
request
|
||||||
|
@ -171,13 +175,65 @@ def new_facture(request, user, userid):
|
||||||
'articlesformset': article_formset,
|
'articlesformset': article_formset,
|
||||||
'articlelist': article_list,
|
'articlelist': article_list,
|
||||||
'balance': balance,
|
'balance': balance,
|
||||||
'action_name': _('Create'),
|
'action_name': _('Confirm'),
|
||||||
},
|
},
|
||||||
'cotisations/facture.html', request
|
'cotisations/facture.html', request
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO : change facture to invoice
|
@login_required
|
||||||
|
@can_create(CostEstimate)
|
||||||
|
def new_cost_estimate(request):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# The template needs the list of articles (for the JS part)
|
||||||
|
articles = Article.objects.filter(
|
||||||
|
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||||
|
)
|
||||||
|
# Building the invocie form and the article formset
|
||||||
|
cost_estimate_form = CostEstimateForm(request.POST or None)
|
||||||
|
|
||||||
|
articles_formset = formset_factory(SelectArticleForm)(
|
||||||
|
request.POST or None,
|
||||||
|
form_kwargs={'user': request.user}
|
||||||
|
)
|
||||||
|
discount_form = DiscountForm(request.POST or None)
|
||||||
|
|
||||||
|
if cost_estimate_form.is_valid() and articles_formset.is_valid() and discount_form.is_valid():
|
||||||
|
cost_estimate_instance = cost_estimate_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=cost_estimate_instance,
|
||||||
|
name=article.name,
|
||||||
|
prix=article.prix,
|
||||||
|
type_cotisation=article.type_cotisation,
|
||||||
|
duration=article.duration,
|
||||||
|
number=quantity
|
||||||
|
)
|
||||||
|
discount_form.apply_to_invoice(cost_estimate_instance)
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("The cost estimate was created.")
|
||||||
|
)
|
||||||
|
return redirect(reverse('cotisations:index-cost-estimate'))
|
||||||
|
|
||||||
|
return form({
|
||||||
|
'factureform': cost_estimate_form,
|
||||||
|
'action_name': _("Confirm"),
|
||||||
|
'articlesformset': articles_formset,
|
||||||
|
'articlelist': articles,
|
||||||
|
'discount_form': discount_form,
|
||||||
|
'title': _("Cost estimate"),
|
||||||
|
}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_create(CustomInvoice)
|
@can_create(CustomInvoice)
|
||||||
def new_custom_invoice(request):
|
def new_custom_invoice(request):
|
||||||
|
@ -193,12 +249,13 @@ def new_custom_invoice(request):
|
||||||
# Building the invocie form and the article formset
|
# Building the invocie form and the article formset
|
||||||
invoice_form = CustomInvoiceForm(request.POST or None)
|
invoice_form = CustomInvoiceForm(request.POST or None)
|
||||||
|
|
||||||
article_formset = formset_factory(SelectArticleForm)(
|
articles_formset = formset_factory(SelectArticleForm)(
|
||||||
request.POST or None,
|
request.POST or None,
|
||||||
form_kwargs={'user': request.user, 'target_user': user}
|
form_kwargs={'user': request.user}
|
||||||
)
|
)
|
||||||
|
discount_form = DiscountForm(request.POST or None)
|
||||||
|
|
||||||
if invoice_form.is_valid() and articles_formset.is_valid():
|
if invoice_form.is_valid() and articles_formset.is_valid() and discount_form.is_valid():
|
||||||
new_invoice_instance = invoice_form.save()
|
new_invoice_instance = invoice_form.save()
|
||||||
for art_item in articles_formset:
|
for art_item in articles_formset:
|
||||||
if art_item.cleaned_data:
|
if art_item.cleaned_data:
|
||||||
|
@ -212,18 +269,19 @@ def new_custom_invoice(request):
|
||||||
duration=article.duration,
|
duration=article.duration,
|
||||||
number=quantity
|
number=quantity
|
||||||
)
|
)
|
||||||
|
discount_form.apply_to_invoice(new_invoice_instance)
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_("The custom invoice was created.")
|
_("The custom invoice was created.")
|
||||||
)
|
)
|
||||||
return redirect(reverse('cotisations:index-custom-invoice'))
|
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||||
|
|
||||||
|
|
||||||
return form({
|
return form({
|
||||||
'factureform': invoice_form,
|
'factureform': invoice_form,
|
||||||
'action_name': _("Create"),
|
'action_name': _("Confirm"),
|
||||||
'articlesformset': articles_formset,
|
'articlesformset': articles_formset,
|
||||||
'articlelist': articles
|
'articlelist': articles,
|
||||||
|
'discount_form': discount_form
|
||||||
}, 'cotisations/facture.html', request)
|
}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
|
@ -266,7 +324,8 @@ def facture_pdf(request, facture, **_kwargs):
|
||||||
'siret': AssoOption.get_cached_value('siret'),
|
'siret': AssoOption.get_cached_value('siret'),
|
||||||
'email': AssoOption.get_cached_value('contact'),
|
'email': AssoOption.get_cached_value('contact'),
|
||||||
'phone': AssoOption.get_cached_value('telephone'),
|
'phone': AssoOption.get_cached_value('telephone'),
|
||||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH),
|
||||||
|
'payment_method': facture.paiement.moyen,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -331,6 +390,55 @@ def del_facture(request, facture, **_kwargs):
|
||||||
}, 'cotisations/delete.html', request)
|
}, 'cotisations/delete.html', request)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_edit(CostEstimate)
|
||||||
|
def edit_cost_estimate(request, invoice, **kwargs):
|
||||||
|
# Building the invocie form and the article formset
|
||||||
|
invoice_form = CostEstimateForm(
|
||||||
|
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 cost estimate was edited.")
|
||||||
|
)
|
||||||
|
return redirect(reverse('cotisations:index-cost-estimate'))
|
||||||
|
|
||||||
|
return form({
|
||||||
|
'factureform': invoice_form,
|
||||||
|
'venteform': purchase_form,
|
||||||
|
'title': _("Edit cost estimate")
|
||||||
|
}, 'cotisations/edit_facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_edit(CostEstimate)
|
||||||
|
@can_create(CustomInvoice)
|
||||||
|
def cost_estimate_to_invoice(request, cost_estimate, **_kwargs):
|
||||||
|
"""Create a custom invoice from a cos estimate"""
|
||||||
|
cost_estimate.create_invoice()
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("An invoice was successfully created from your cost estimate.")
|
||||||
|
)
|
||||||
|
return redirect(reverse('cotisations:index-custom-invoice'))
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_edit(CustomInvoice)
|
@can_edit(CustomInvoice)
|
||||||
def edit_custom_invoice(request, invoice, **kwargs):
|
def edit_custom_invoice(request, invoice, **kwargs):
|
||||||
|
@ -367,22 +475,21 @@ def edit_custom_invoice(request, invoice, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view(CustomInvoice)
|
@can_view(CostEstimate)
|
||||||
def custom_invoice_pdf(request, invoice, **_kwargs):
|
def cost_estimate_pdf(request, invoice, **_kwargs):
|
||||||
"""
|
"""
|
||||||
View used to generate a PDF file from an existing invoice in database
|
View used to generate a PDF file from an existing cost estimate in database
|
||||||
Creates a line for each Purchase (thus article sold) and generate the
|
Creates a line for each Purchase (thus article sold) and generate the
|
||||||
invoice with the total price, the payment method, the address and the
|
invoice with the total price, the payment method, the address and the
|
||||||
legal information for the user.
|
legal information for the user.
|
||||||
"""
|
"""
|
||||||
# TODO : change vente to purchase
|
|
||||||
purchases_objects = Vente.objects.all().filter(facture=invoice)
|
purchases_objects = Vente.objects.all().filter(facture=invoice)
|
||||||
# Get the article list and build an list out of it
|
# Get the article list and build an list out of it
|
||||||
# contiaining (article_name, article_price, quantity, total_price)
|
# contiaining (article_name, article_price, quantity, total_price)
|
||||||
purchases_info = []
|
purchases_info = []
|
||||||
for purchase in purchases_objects:
|
for purchase in purchases_objects:
|
||||||
purchases_info.append({
|
purchases_info.append({
|
||||||
'name': purchase.name,
|
'name': escape_chars(purchase.name),
|
||||||
'price': purchase.prix,
|
'price': purchase.prix,
|
||||||
'quantity': purchase.number,
|
'quantity': purchase.number,
|
||||||
'total_price': purchase.prix_total
|
'total_price': purchase.prix_total
|
||||||
|
@ -401,11 +508,74 @@ def custom_invoice_pdf(request, invoice, **_kwargs):
|
||||||
'siret': AssoOption.get_cached_value('siret'),
|
'siret': AssoOption.get_cached_value('siret'),
|
||||||
'email': AssoOption.get_cached_value('contact'),
|
'email': AssoOption.get_cached_value('contact'),
|
||||||
'phone': AssoOption.get_cached_value('telephone'),
|
'phone': AssoOption.get_cached_value('telephone'),
|
||||||
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH)
|
'tpl_path': os.path.join(settings.BASE_DIR, LOGO_PATH),
|
||||||
|
'payment_method': invoice.payment,
|
||||||
|
'remark': invoice.remark,
|
||||||
|
'end_validity': invoice.date + invoice.validity,
|
||||||
|
'is_estimate': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_delete(CostEstimate)
|
||||||
|
def del_cost_estimate(request, estimate, **_kwargs):
|
||||||
|
"""
|
||||||
|
View used to delete an existing invocie.
|
||||||
|
"""
|
||||||
|
if request.method == "POST":
|
||||||
|
estimate.delete()
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
_("The cost estimate was deleted.")
|
||||||
|
)
|
||||||
|
return redirect(reverse('cotisations:index-cost-estimate'))
|
||||||
|
return form({
|
||||||
|
'objet': estimate,
|
||||||
|
'objet_name': _("Cost estimate")
|
||||||
|
}, 'cotisations/delete.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': escape_chars(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),
|
||||||
|
'payment_method': invoice.payment,
|
||||||
|
'remark': invoice.remark,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# TODO : change facture to invoice
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_delete(CustomInvoice)
|
@can_delete(CustomInvoice)
|
||||||
def del_custom_invoice(request, invoice, **_kwargs):
|
def del_custom_invoice(request, invoice, **_kwargs):
|
||||||
|
@ -663,8 +833,8 @@ def del_banque(request, instances):
|
||||||
except ProtectedError:
|
except ProtectedError:
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
_("The bank %(bank_name)s can't be deleted \
|
_("The bank %(bank_name)s can't be deleted because there"
|
||||||
because there are invoices using it.") % {
|
" are invoices using it.") % {
|
||||||
'bank_name': bank_del
|
'bank_name': bank_del
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -756,12 +926,36 @@ def index_banque(request):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_view_all(CustomInvoice)
|
||||||
|
def index_cost_estimate(request):
|
||||||
|
"""View used to display every custom invoice."""
|
||||||
|
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
||||||
|
cost_estimate_list = CostEstimate.objects.prefetch_related('vente_set')
|
||||||
|
cost_estimate_list = SortTable.sort(
|
||||||
|
cost_estimate_list,
|
||||||
|
request.GET.get('col'),
|
||||||
|
request.GET.get('order'),
|
||||||
|
SortTable.COTISATIONS_CUSTOM
|
||||||
|
)
|
||||||
|
cost_estimate_list = re2o_paginator(
|
||||||
|
request,
|
||||||
|
cost_estimate_list,
|
||||||
|
pagination_number,
|
||||||
|
)
|
||||||
|
return render(request, 'cotisations/index_cost_estimate.html', {
|
||||||
|
'cost_estimate_list': cost_estimate_list
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_all(CustomInvoice)
|
@can_view_all(CustomInvoice)
|
||||||
def index_custom_invoice(request):
|
def index_custom_invoice(request):
|
||||||
"""View used to display every custom invoice."""
|
"""View used to display every custom invoice."""
|
||||||
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
pagination_number = GeneralOption.get_cached_value('pagination_number')
|
||||||
custom_invoice_list = CustomInvoice.objects.prefetch_related('vente_set')
|
cost_estimate_ids = [i for i, in CostEstimate.objects.values_list('id')]
|
||||||
|
custom_invoice_list = CustomInvoice.objects.prefetch_related(
|
||||||
|
'vente_set').exclude(id__in=cost_estimate_ids)
|
||||||
custom_invoice_list = SortTable.sort(
|
custom_invoice_list = SortTable.sort(
|
||||||
custom_invoice_list,
|
custom_invoice_list,
|
||||||
request.GET.get('col'),
|
request.GET.get('col'),
|
||||||
|
@ -827,7 +1021,8 @@ def credit_solde(request, user, **_kwargs):
|
||||||
kwargs={'userid': user.id}
|
kwargs={'userid': user.id}
|
||||||
))
|
))
|
||||||
|
|
||||||
refill_form = RechargeForm(request.POST or None, user=user, user_source=request.user)
|
refill_form = RechargeForm(
|
||||||
|
request.POST or None, user=user, user_source=request.user)
|
||||||
if refill_form.is_valid():
|
if refill_form.is_valid():
|
||||||
price = refill_form.cleaned_data['value']
|
price = refill_form.cleaned_data['value']
|
||||||
invoice = Facture(user=user)
|
invoice = Facture(user=user)
|
||||||
|
@ -839,7 +1034,6 @@ def credit_solde(request, user, **_kwargs):
|
||||||
else:
|
else:
|
||||||
price_ok = True
|
price_ok = True
|
||||||
if price_ok:
|
if price_ok:
|
||||||
invoice.valid = True
|
|
||||||
invoice.save()
|
invoice.save()
|
||||||
Vente.objects.create(
|
Vente.objects.create(
|
||||||
facture=invoice,
|
facture=invoice,
|
||||||
|
@ -848,8 +1042,6 @@ def credit_solde(request, user, **_kwargs):
|
||||||
number=1
|
number=1
|
||||||
)
|
)
|
||||||
|
|
||||||
send_mail_invoice(invoice)
|
|
||||||
|
|
||||||
return invoice.paiement.end_payment(invoice, request)
|
return invoice.paiement.end_payment(invoice, request)
|
||||||
p = get_object_or_404(Paiement, is_balance=True)
|
p = get_object_or_404(Paiement, is_balance=True)
|
||||||
return form({
|
return form({
|
||||||
|
@ -857,6 +1049,32 @@ def credit_solde(request, user, **_kwargs):
|
||||||
'balance': user.solde,
|
'balance': user.solde,
|
||||||
'title': _("Refill your balance"),
|
'title': _("Refill your balance"),
|
||||||
'action_name': _("Pay"),
|
'action_name': _("Pay"),
|
||||||
'max_balance': p.payment_method.maximum_balance,
|
'max_balance': find_payment_method(p).maximum_balance,
|
||||||
}, 'cotisations/facture.html', request)
|
}, 'cotisations/facture.html', request)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@can_view(Facture)
|
||||||
|
def voucher_pdf(request, invoice, **_kwargs):
|
||||||
|
"""
|
||||||
|
View used to generate a PDF file from a controlled invoice
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
if not invoice.control:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
_("Could not find a voucher for that invoice.")
|
||||||
|
)
|
||||||
|
return redirect(reverse('cotisations:index'))
|
||||||
|
return render_voucher(request, {
|
||||||
|
'asso_name': AssoOption.get_cached_value('name'),
|
||||||
|
'pres_name': AssoOption.get_cached_value('pres_name'),
|
||||||
|
'firstname': invoice.user.name,
|
||||||
|
'lastname': invoice.user.surname,
|
||||||
|
'email': invoice.user.email,
|
||||||
|
'phone': invoice.user.telephone,
|
||||||
|
'date_end': invoice.get_subscription().latest('date_end').date_end,
|
||||||
|
'date_begin': invoice.date
|
||||||
|
})
|
||||||
|
|
|
@ -38,6 +38,7 @@ Inspiré du travail de Daniel Stan au Crans
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
import radiusd # Module magique freeradius (radiusd.py is dummy)
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
@ -57,14 +58,9 @@ application = get_wsgi_application()
|
||||||
from machines.models import Interface, IpList, Nas, Domain
|
from machines.models import Interface, IpList, Nas, Domain
|
||||||
from topologie.models import Port, Switch
|
from topologie.models import Port, Switch
|
||||||
from users.models import User
|
from users.models import User
|
||||||
from preferences.models import OptionalTopologie
|
from preferences.models import RadiusOption
|
||||||
|
|
||||||
|
|
||||||
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)
|
#: Serveur radius de test (pas la prod)
|
||||||
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
|
TEST_SERVER = bool(os.getenv('DBG_FREERADIUS', False))
|
||||||
|
|
||||||
|
@ -81,7 +77,7 @@ class RadiusdHandler(logging.Handler):
|
||||||
rad_sig = radiusd.L_INFO
|
rad_sig = radiusd.L_INFO
|
||||||
else:
|
else:
|
||||||
rad_sig = radiusd.L_DBG
|
rad_sig = radiusd.L_DBG
|
||||||
radiusd.radlog(rad_sig, record.msg)
|
radiusd.radlog(rad_sig, record.msg.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
# Initialisation d'un logger (pour logguer unifié)
|
# Initialisation d'un logger (pour logguer unifié)
|
||||||
|
@ -122,7 +118,8 @@ def radius_event(fun):
|
||||||
return fun(data)
|
return fun(data)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error('Failed %r on data %r' % (err, auth_data))
|
logger.error('Failed %r on data %r' % (err, auth_data))
|
||||||
raise
|
logger.debug('Function %r, Traceback: %s' % (fun, repr(traceback.format_stack())))
|
||||||
|
return radiusd.RLM_MODULE_FAIL
|
||||||
|
|
||||||
return new_f
|
return new_f
|
||||||
|
|
||||||
|
@ -194,12 +191,12 @@ def post_auth(data):
|
||||||
nas_instance = find_nas_from_request(nas)
|
nas_instance = find_nas_from_request(nas)
|
||||||
# Toutes les reuquètes non proxifiées
|
# Toutes les reuquètes non proxifiées
|
||||||
if not nas_instance:
|
if not nas_instance:
|
||||||
logger.info(u"Requète proxifiée, nas inconnu".encode('utf-8'))
|
logger.info(u"Requete proxifiee, nas inconnu".encode('utf-8'))
|
||||||
return radiusd.RLM_MODULE_OK
|
return radiusd.RLM_MODULE_OK
|
||||||
nas_type = Nas.objects.filter(nas_type=nas_instance.type).first()
|
nas_type = Nas.objects.filter(nas_type=nas_instance.type).first()
|
||||||
if not nas_type:
|
if not nas_type:
|
||||||
logger.info(
|
logger.info(
|
||||||
u"Type de nas non enregistré dans la bdd!".encode('utf-8')
|
u"Type de nas non enregistre dans la bdd!".encode('utf-8')
|
||||||
)
|
)
|
||||||
return radiusd.RLM_MODULE_OK
|
return radiusd.RLM_MODULE_OK
|
||||||
|
|
||||||
|
@ -227,9 +224,10 @@ def post_auth(data):
|
||||||
# On récupère le numéro du port sur l'output de freeradius.
|
# On récupère le numéro du port sur l'output de freeradius.
|
||||||
# La ligne suivante fonctionne pour cisco, HP et Juniper
|
# La ligne suivante fonctionne pour cisco, HP et Juniper
|
||||||
port = port.split(".")[0].split('/')[-1][-2:]
|
port = port.split(".")[0].split('/')[-1][-2:]
|
||||||
out = decide_vlan_and_register_switch(nas_machine, nas_type, port, mac)
|
out = decide_vlan_switch(nas_machine, nas_type, port, mac)
|
||||||
sw_name, room, reason, vlan_id = out
|
sw_name, room, reason, vlan_id, decision = out
|
||||||
|
|
||||||
|
if decision:
|
||||||
log_message = '(fil) %s -> %s [%s%s]' % (
|
log_message = '(fil) %s -> %s [%s%s]' % (
|
||||||
sw_name + u":" + port + u"/" + str(room),
|
sw_name + u":" + port + u"/" + str(room),
|
||||||
mac,
|
mac,
|
||||||
|
@ -248,6 +246,15 @@ def post_auth(data):
|
||||||
),
|
),
|
||||||
()
|
()
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
log_message = '(fil) %s -> %s [Reject:%s]' % (
|
||||||
|
sw_name + u":" + port + u"/" + str(room),
|
||||||
|
mac,
|
||||||
|
(reason and u': ' + reason).encode('utf-8')
|
||||||
|
)
|
||||||
|
logger.info(log_message)
|
||||||
|
|
||||||
|
return radiusd.RLM_MODULE_REJECT
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return radiusd.RLM_MODULE_OK
|
return radiusd.RLM_MODULE_OK
|
||||||
|
@ -284,19 +291,19 @@ def check_user_machine_and_register(nas_type, username, mac_address):
|
||||||
Renvoie le mot de passe ntlm de l'user si tout est ok
|
Renvoie le mot de passe ntlm de l'user si tout est ok
|
||||||
Utilise pour les authentifications en 802.1X"""
|
Utilise pour les authentifications en 802.1X"""
|
||||||
interface = Interface.objects.filter(mac_address=mac_address).first()
|
interface = Interface.objects.filter(mac_address=mac_address).first()
|
||||||
user = User.objects.filter(pseudo=username).first()
|
user = User.objects.filter(pseudo__iexact=username).first()
|
||||||
if not user:
|
if not user:
|
||||||
return (False, u"User inconnu", '')
|
return (False, u"User inconnu", '')
|
||||||
if not user.has_access():
|
if not user.has_access():
|
||||||
return (False, u"Adhérent non cotisant", '')
|
return (False, u"Adherent non cotisant", '')
|
||||||
if interface:
|
if interface:
|
||||||
if interface.machine.user != user:
|
if interface.machine.user != user:
|
||||||
return (False,
|
return (False,
|
||||||
u"Machine enregistrée sur le compte d'un autre "
|
u"Machine enregistree sur le compte d'un autre "
|
||||||
"user...",
|
"user...",
|
||||||
'')
|
'')
|
||||||
elif not interface.is_active:
|
elif not interface.is_active:
|
||||||
return (False, u"Machine desactivée", '')
|
return (False, u"Machine desactivee", '')
|
||||||
elif not interface.ipv4:
|
elif not interface.ipv4:
|
||||||
interface.assign_ipv4()
|
interface.assign_ipv4()
|
||||||
return (True, u"Ok, Reassignation de l'ipv4", user.pwd_ntlm)
|
return (True, u"Ok, Reassignation de l'ipv4", user.pwd_ntlm)
|
||||||
|
@ -317,36 +324,51 @@ def check_user_machine_and_register(nas_type, username, mac_address):
|
||||||
return (False, u"Machine inconnue", '')
|
return (False, u"Machine inconnue", '')
|
||||||
|
|
||||||
|
|
||||||
def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
def decide_vlan_switch(nas_machine, nas_type, port_number,
|
||||||
mac_address):
|
mac_address):
|
||||||
"""Fonction de placement vlan pour un switch en radius filaire auth par
|
"""Fonction de placement vlan pour un switch en radius filaire auth par
|
||||||
mac.
|
mac.
|
||||||
Plusieurs modes :
|
Plusieurs modes :
|
||||||
- nas inconnu, port inconnu : on place sur le vlan par defaut VLAN_OK
|
- tous les modes:
|
||||||
|
- nas inconnu: VLAN_OK
|
||||||
|
- port inconnu: Politique définie dans RadiusOption
|
||||||
- pas de radius sur le port: VLAN_OK
|
- pas de radius sur le port: VLAN_OK
|
||||||
- bloq : VLAN_NOK
|
|
||||||
- force: placement sur le vlan indiqué dans la bdd
|
- force: placement sur le vlan indiqué dans la bdd
|
||||||
- mode strict:
|
- mode strict:
|
||||||
- pas de chambre associée : VLAN_NOK
|
- pas de chambre associée: Politique définie
|
||||||
- pas d'utilisateur dans la chambre : VLAN_NOK
|
dans RadiusOption
|
||||||
- cotisation non à jour : VLAN_NOK
|
- pas d'utilisateur dans la chambre : Rejet
|
||||||
|
(redirection web si disponible)
|
||||||
|
- utilisateur de la chambre banni ou désactivé : Rejet
|
||||||
|
(redirection web si disponible)
|
||||||
|
- utilisateur de la chambre non cotisant et non whiteslist:
|
||||||
|
Politique définie dans RadiusOption
|
||||||
|
|
||||||
- sinon passe à common (ci-dessous)
|
- sinon passe à common (ci-dessous)
|
||||||
- mode common :
|
- mode common :
|
||||||
- interface connue (macaddress):
|
- interface connue (macaddress):
|
||||||
- utilisateur proprio non cotisant ou banni : VLAN_NOK
|
- utilisateur proprio non cotisant / machine désactivée:
|
||||||
- user à jour : VLAN_OK
|
Politique définie dans RadiusOption
|
||||||
|
- utilisateur proprio banni :
|
||||||
|
Politique définie dans RadiusOption
|
||||||
|
- user à jour : VLAN_OK (réassignation de l'ipv4 au besoin)
|
||||||
- interface inconnue :
|
- interface inconnue :
|
||||||
- register mac désactivé : VLAN_NOK
|
- register mac désactivé : Politique définie
|
||||||
- register mac activé :
|
dans RadiusOption
|
||||||
- dans la chambre associé au port, pas d'user ou non à
|
- register mac activé: redirection vers webauth
|
||||||
jour : VLAN_NOK
|
Returns:
|
||||||
- user à jour, autocapture de la mac et VLAN_OK
|
tuple avec :
|
||||||
|
- Nom du switch (str)
|
||||||
|
- chambre (str)
|
||||||
|
- raison de la décision (str)
|
||||||
|
- vlan_id (int)
|
||||||
|
- decision (bool)
|
||||||
"""
|
"""
|
||||||
# Get port from switch and port number
|
# Get port from switch and port number
|
||||||
extra_log = ""
|
extra_log = ""
|
||||||
# Si le NAS est inconnu, on place sur le vlan defaut
|
# Si le NAS est inconnu, on place sur le vlan defaut
|
||||||
if not nas_machine:
|
if not nas_machine:
|
||||||
return ('?', u'Chambre inconnue', u'Nas inconnu', VLAN_OK)
|
return ('?', u'Chambre inconnue', u'Nas inconnu', RadiusOption.get_cached_value('vlan_decision_ok').vlan_id, True)
|
||||||
|
|
||||||
sw_name = str(getattr(nas_machine, 'short_name', str(nas_machine)))
|
sw_name = str(getattr(nas_machine, 'short_name', str(nas_machine)))
|
||||||
|
|
||||||
|
@ -361,7 +383,13 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
||||||
# Aucune information particulière ne permet de déterminer quelle
|
# Aucune information particulière ne permet de déterminer quelle
|
||||||
# politique à appliquer sur ce port
|
# politique à appliquer sur ce port
|
||||||
if not port:
|
if not port:
|
||||||
return (sw_name, "Chambre inconnue", u'Port inconnu', VLAN_OK)
|
return (
|
||||||
|
sw_name,
|
||||||
|
"Chambre inconnue",
|
||||||
|
u'Port inconnu',
|
||||||
|
getattr(RadiusOption.get_cached_value('unknown_port_vlan'), 'vlan_id', None),
|
||||||
|
RadiusOption.get_cached_value('unknown_port')!= RadiusOption.REJECT
|
||||||
|
)
|
||||||
|
|
||||||
# On récupère le profil du port
|
# On récupère le profil du port
|
||||||
port_profile = port.get_port_profile
|
port_profile = port.get_port_profile
|
||||||
|
@ -372,46 +400,82 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
||||||
DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id)
|
DECISION_VLAN = int(port_profile.vlan_untagged.vlan_id)
|
||||||
extra_log = u"Force sur vlan " + str(DECISION_VLAN)
|
extra_log = u"Force sur vlan " + str(DECISION_VLAN)
|
||||||
else:
|
else:
|
||||||
DECISION_VLAN = VLAN_OK
|
DECISION_VLAN = RadiusOption.get_cached_value('vlan_decision_ok').vlan_id
|
||||||
|
|
||||||
# Si le port est désactivé, on rejette sur le vlan de déconnexion
|
# Si le port est désactivé, on rejette la connexion
|
||||||
if not port.state:
|
if not port.state:
|
||||||
return (sw_name, port.room, u'Port desactivé', VLAN_NOK)
|
return (sw_name, port.room, u'Port desactive', None, False)
|
||||||
|
|
||||||
# Si radius est désactivé, on laisse passer
|
# Si radius est désactivé, on laisse passer
|
||||||
if port_profile.radius_type == 'NO':
|
if port_profile.radius_type == 'NO':
|
||||||
return (sw_name,
|
return (sw_name,
|
||||||
"",
|
"",
|
||||||
u"Pas d'authentification sur ce port" + extra_log,
|
u"Pas d'authentification sur ce port" + extra_log,
|
||||||
DECISION_VLAN)
|
DECISION_VLAN,
|
||||||
|
True)
|
||||||
|
|
||||||
# Si le 802.1X est activé sur ce port, cela veut dire que la personne a été accept précédemment
|
# 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
|
# 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':
|
if (nas_type.port_access_mode, port_profile.radius_type) == ('802.1X', '802.1X'):
|
||||||
room = port.room or "Chambre/local inconnu"
|
room = port.room or "Chambre/local inconnu"
|
||||||
return (sw_name, room, u'Acceptation authentification 802.1X', DECISION_VLAN)
|
return (
|
||||||
|
sw_name,
|
||||||
|
room,
|
||||||
|
u'Acceptation authentification 802.1X',
|
||||||
|
DECISION_VLAN,
|
||||||
|
True
|
||||||
|
)
|
||||||
|
|
||||||
# Sinon, cela veut dire qu'on fait de l'auth radius par mac
|
# 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
|
# 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)
|
# rattachés à ce port sont bien à jour de cotisation. Sinon on rejette
|
||||||
# Il n'est pas possible de se connecter sur une prise strict sans adhérent à jour de cotis
|
# (anti squattage)
|
||||||
# dedans
|
# 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':
|
if port_profile.radius_mode == 'STRICT':
|
||||||
room = port.room
|
room = port.room
|
||||||
if not room:
|
if not room:
|
||||||
return (sw_name, "Inconnue", u'Chambre inconnue', VLAN_NOK)
|
return (
|
||||||
|
sw_name,
|
||||||
|
"Inconnue",
|
||||||
|
u'Chambre inconnue',
|
||||||
|
getattr(RadiusOption.get_cached_value('unknown_room_vlan'), 'vlan_id', None),
|
||||||
|
RadiusOption.get_cached_value('unknown_room')!= RadiusOption.REJECT
|
||||||
|
)
|
||||||
|
|
||||||
room_user = User.objects.filter(
|
room_user = User.objects.filter(
|
||||||
Q(club__room=port.room) | Q(adherent__room=port.room)
|
Q(club__room=port.room) | Q(adherent__room=port.room)
|
||||||
)
|
)
|
||||||
if not room_user:
|
if not room_user:
|
||||||
return (sw_name, room, u'Chambre non cotisante', VLAN_NOK)
|
return (
|
||||||
|
sw_name,
|
||||||
|
room,
|
||||||
|
u'Chambre non cotisante -> Web redirect',
|
||||||
|
None,
|
||||||
|
False
|
||||||
|
)
|
||||||
for user in room_user:
|
for user in room_user:
|
||||||
if not user.has_access():
|
if user.is_ban() or user.state != User.STATE_ACTIVE:
|
||||||
return (sw_name, room, u'Chambre resident desactive', VLAN_NOK)
|
return (
|
||||||
|
sw_name,
|
||||||
|
room,
|
||||||
|
u'Utilisateur banni ou desactive -> Web redirect',
|
||||||
|
None,
|
||||||
|
False
|
||||||
|
)
|
||||||
|
elif not (user.is_connected() or user.is_whitelisted()):
|
||||||
|
return (
|
||||||
|
sw_name,
|
||||||
|
room,
|
||||||
|
u'Utilisateur non cotisant',
|
||||||
|
getattr(RadiusOption.get_cached_value('non_member_vlan'), 'vlan_id', None),
|
||||||
|
RadiusOption.get_cached_value('non_member')!= RadiusOption.REJECT
|
||||||
|
)
|
||||||
# else: user OK, on passe à la verif MAC
|
# else: user OK, on passe à la verif MAC
|
||||||
|
|
||||||
# Si on fait de l'auth par mac, on cherche l'interface via sa mac dans la bdd
|
# 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':
|
if port_profile.radius_mode == 'COMMON' or port_profile.radius_mode == 'STRICT':
|
||||||
# Authentification par mac
|
# Authentification par mac
|
||||||
interface = (Interface.objects
|
interface = (Interface.objects
|
||||||
|
@ -421,88 +485,67 @@ def decide_vlan_and_register_switch(nas_machine, nas_type, port_number,
|
||||||
.first())
|
.first())
|
||||||
if not interface:
|
if not interface:
|
||||||
room = port.room
|
room = port.room
|
||||||
# On essaye de register la mac, si l'autocapture a été activée
|
# On essaye de register la mac, si l'autocapture a été activée,
|
||||||
# Sinon on rejette sur vlan_nok
|
# on rejette pour faire une redirection web si possible.
|
||||||
if not nas_type.autocapture_mac:
|
if nas_type.autocapture_mac:
|
||||||
return (sw_name, "", u'Machine inconnue', VLAN_NOK)
|
return (
|
||||||
# On ne peut autocapturer que si on connait la chambre et donc l'user correspondant
|
sw_name,
|
||||||
elif not room:
|
room,
|
||||||
return (sw_name,
|
u'Machine Inconnue -> Web redirect',
|
||||||
"Inconnue",
|
None,
|
||||||
u'Chambre et machine inconnues',
|
False
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
if not room_user:
|
# Sinon on bascule sur la politique définie dans les options
|
||||||
return (sw_name,
|
# radius.
|
||||||
room,
|
|
||||||
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,
|
|
||||||
u'Machine inconnue, il y a au moins 2 users '
|
|
||||||
'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:
|
else:
|
||||||
interface, reason = (room_user
|
return (
|
||||||
.first()
|
sw_name,
|
||||||
.autoregister_machine(
|
"",
|
||||||
mac_address,
|
u'Machine inconnue',
|
||||||
nas_type
|
getattr(RadiusOption.get_cached_value('unknown_machine_vlan'), 'vlan_id', None),
|
||||||
))
|
RadiusOption.get_cached_value('unknown_machine')!= RadiusOption.REJECT
|
||||||
if interface:
|
)
|
||||||
## Si on choisi de placer les machines sur le vlan correspondant à leur type :
|
|
||||||
if RADIUS_POLICY == 'MACHINE':
|
# L'interface a été trouvée, on vérifie qu'elle est active,
|
||||||
DECISION_VLAN = interface.type.ip_type.vlan.vlan_id
|
# sinon on reject
|
||||||
return (sw_name,
|
|
||||||
room,
|
|
||||||
u'Access Ok, Capture de la mac: ' + extra_log,
|
|
||||||
DECISION_VLAN)
|
|
||||||
else:
|
|
||||||
return (sw_name,
|
|
||||||
room,
|
|
||||||
u'Erreur dans le register mac %s' % (
|
|
||||||
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
|
# Si elle n'a pas d'ipv4, on lui en met une
|
||||||
# Enfin on laisse passer sur le vlan pertinent
|
# Enfin on laisse passer sur le vlan pertinent
|
||||||
else:
|
else:
|
||||||
room = port.room
|
room = port.room
|
||||||
|
if interface.machine.user.is_ban():
|
||||||
|
return (
|
||||||
|
sw_name,
|
||||||
|
room,
|
||||||
|
u'Adherent banni',
|
||||||
|
getattr(RadiusOption.get_cached_value('banned_vlan'), 'vlan_id', None),
|
||||||
|
RadiusOption.get_cached_value('banned')!= RadiusOption.REJECT
|
||||||
|
)
|
||||||
if not interface.is_active:
|
if not interface.is_active:
|
||||||
return (sw_name,
|
return (
|
||||||
|
sw_name,
|
||||||
room,
|
room,
|
||||||
u'Machine non active / adherent non cotisant',
|
u'Machine non active / adherent non cotisant',
|
||||||
VLAN_NOK)
|
getattr(RadiusOption.get_cached_value('non_member_vlan'), 'vlan_id', None),
|
||||||
## Si on choisi de placer les machines sur le vlan correspondant à leur type :
|
RadiusOption.get_cached_value('non_member')!= RadiusOption.REJECT
|
||||||
if RADIUS_POLICY == 'MACHINE':
|
)
|
||||||
|
# Si on choisi de placer les machines sur le vlan
|
||||||
|
# correspondant à leur type :
|
||||||
|
if RadiusOption.get_cached_value('radius_general_policy') == 'MACHINE':
|
||||||
DECISION_VLAN = interface.type.ip_type.vlan.vlan_id
|
DECISION_VLAN = interface.type.ip_type.vlan.vlan_id
|
||||||
if not interface.ipv4:
|
if not interface.ipv4:
|
||||||
interface.assign_ipv4()
|
interface.assign_ipv4()
|
||||||
return (sw_name,
|
return (
|
||||||
|
sw_name,
|
||||||
room,
|
room,
|
||||||
u"Ok, Reassignation de l'ipv4" + extra_log,
|
u"Ok, Reassignation de l'ipv4" + extra_log,
|
||||||
DECISION_VLAN)
|
DECISION_VLAN,
|
||||||
|
True
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return (sw_name,
|
return (
|
||||||
|
sw_name,
|
||||||
room,
|
room,
|
||||||
u'Machine OK' + extra_log,
|
u'Machine OK' + extra_log,
|
||||||
DECISION_VLAN)
|
DECISION_VLAN,
|
||||||
|
True
|
||||||
|
)
|
||||||
|
|
|
@ -316,6 +316,25 @@ update_django() {
|
||||||
echo "Collecting web frontend statics ..."
|
echo "Collecting web frontend statics ..."
|
||||||
python3 manage.py collectstatic --noinput
|
python3 manage.py collectstatic --noinput
|
||||||
echo "Collecting web frontend statics: Done"
|
echo "Collecting web frontend statics: Done"
|
||||||
|
|
||||||
|
echo "Generating locales ..."
|
||||||
|
python3 manage.py compilemessages
|
||||||
|
echo "Generating locales: Done"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
copy_templates_files() {
|
||||||
|
### Usage: copy_templates_files
|
||||||
|
#
|
||||||
|
# This will copy LaTeX templates in the media root.
|
||||||
|
|
||||||
|
echo "Copying LaTeX templates ..."
|
||||||
|
mkdir -p media/templates/
|
||||||
|
cp cotisations/templates/cotisations/factures.tex media/templates/default_invoice.tex
|
||||||
|
cp cotisations/templates/cotisations/voucher.tex media/templates/default_voucher.tex
|
||||||
|
chown -R www-data:www-data media/templates/
|
||||||
|
echo "Copying LaTeX templates: Done"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -744,9 +763,10 @@ main_function() {
|
||||||
echo " * {help} ---------- Display this quick usage documentation"
|
echo " * {help} ---------- Display this quick usage documentation"
|
||||||
echo " * {setup} --------- Launch the full interactive guide to setup entirely"
|
echo " * {setup} --------- Launch the full interactive guide to setup entirely"
|
||||||
echo " re2o from scratch"
|
echo " re2o from scratch"
|
||||||
echo " * {update} -------- Collect frontend statics, install the missing APT"
|
echo " * {update} -------- Collect frontend statics, install the missing APT and copy LaTeX templates files"
|
||||||
echo " and pip packages and apply the migrations to the DB"
|
echo " and pip packages and apply the migrations to the DB"
|
||||||
echo " * {update-django} - Apply Django migration and collect frontend statics"
|
echo " * {update-django} - Apply Django migration and collect frontend statics"
|
||||||
|
echo " * {copy-template-files} - Copy LaTeX templates files to media/templates"
|
||||||
echo " * {update-packages} Install the missing APT and pip packages"
|
echo " * {update-packages} Install the missing APT and pip packages"
|
||||||
echo " * {update-settings} Interactively rewrite the settings file"
|
echo " * {update-settings} Interactively rewrite the settings file"
|
||||||
echo " * {reset-db} ------ Erase the previous local database, setup a new empty"
|
echo " * {reset-db} ------ Erase the previous local database, setup a new empty"
|
||||||
|
@ -778,9 +798,14 @@ main_function() {
|
||||||
|
|
||||||
update )
|
update )
|
||||||
install_requirements
|
install_requirements
|
||||||
|
copy_templates_files
|
||||||
update_django
|
update_django
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
copy-templates-files )
|
||||||
|
copy_templates_files
|
||||||
|
;;
|
||||||
|
|
||||||
update-django )
|
update-django )
|
||||||
update_django
|
update_django
|
||||||
;;
|
;;
|
||||||
|
|
Binary file not shown.
|
@ -21,7 +21,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 2.5\n"
|
"Project-Id-Version: 2.5\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-08-15 20:12+0200\n"
|
"POT-Creation-Date: 2019-01-08 23:16+0100\n"
|
||||||
"PO-Revision-Date: 2018-06-23 16:01+0200\n"
|
"PO-Revision-Date: 2018-06-23 16:01+0200\n"
|
||||||
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
|
@ -57,7 +57,7 @@ msgstr "Commentaire"
|
||||||
|
|
||||||
#: templates/logs/aff_stats_logs.html:58 templates/logs/aff_summary.html:62
|
#: 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:85 templates/logs/aff_summary.html:104
|
||||||
#: templates/logs/aff_summary.html:123 templates/logs/aff_summary.html:142
|
#: templates/logs/aff_summary.html:128 templates/logs/aff_summary.html:147
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Annuler"
|
msgstr "Annuler"
|
||||||
|
|
||||||
|
@ -113,15 +113,19 @@ msgstr "%(username)s a mis à jour"
|
||||||
|
|
||||||
#: templates/logs/aff_summary.html:113
|
#: templates/logs/aff_summary.html:113
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(username)s has sold %(number)sx %(name)s to"
|
msgid "%(username)s has sold %(number)sx %(name)s"
|
||||||
msgstr "%(username)s a vendu %(number)sx %(name)s à"
|
msgstr "%(username)s a vendu %(number)sx %(name)s"
|
||||||
|
|
||||||
#: templates/logs/aff_summary.html:116
|
#: templates/logs/aff_summary.html:116
|
||||||
|
msgid " to"
|
||||||
|
msgstr " à"
|
||||||
|
|
||||||
|
#: templates/logs/aff_summary.html:119
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "+%(duration)s months"
|
msgid "+%(duration)s months"
|
||||||
msgstr "+%(duration)s mois"
|
msgstr "+%(duration)s mois"
|
||||||
|
|
||||||
#: templates/logs/aff_summary.html:132
|
#: templates/logs/aff_summary.html:137
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(username)s has edited an interface of"
|
msgid "%(username)s has edited an interface of"
|
||||||
msgstr "%(username)s a modifié une interface de"
|
msgstr "%(username)s a modifié une interface de"
|
||||||
|
@ -149,7 +153,7 @@ msgstr "Confirmer"
|
||||||
msgid "Statistics"
|
msgid "Statistics"
|
||||||
msgstr "Statistiques"
|
msgstr "Statistiques"
|
||||||
|
|
||||||
#: templates/logs/index.html:32 templates/logs/stats_logs.html:32 views.py:403
|
#: templates/logs/index.html:32 templates/logs/stats_logs.html:32 views.py:414
|
||||||
msgid "Actions performed"
|
msgid "Actions performed"
|
||||||
msgstr "Actions effectuées"
|
msgstr "Actions effectuées"
|
||||||
|
|
||||||
|
@ -173,7 +177,7 @@ msgstr "Base de données"
|
||||||
msgid "Wiring actions"
|
msgid "Wiring actions"
|
||||||
msgstr "Actions de câblage"
|
msgstr "Actions de câblage"
|
||||||
|
|
||||||
#: templates/logs/sidebar.html:53 views.py:325
|
#: templates/logs/sidebar.html:53 views.py:336
|
||||||
msgid "Users"
|
msgid "Users"
|
||||||
msgstr "Utilisateurs"
|
msgstr "Utilisateurs"
|
||||||
|
|
||||||
|
@ -189,150 +193,154 @@ msgstr "Statistiques sur la base de données"
|
||||||
msgid "Statistics about users"
|
msgid "Statistics about users"
|
||||||
msgstr "Statistiques sur les utilisateurs"
|
msgstr "Statistiques sur les utilisateurs"
|
||||||
|
|
||||||
#: views.py:191
|
#: views.py:194
|
||||||
msgid "Nonexistent revision."
|
msgid "Nonexistent revision."
|
||||||
msgstr "Révision inexistante."
|
msgstr "Révision inexistante."
|
||||||
|
|
||||||
#: views.py:194
|
#: views.py:197
|
||||||
msgid "The action was deleted."
|
msgid "The action was deleted."
|
||||||
msgstr "L'action a été supprimée."
|
msgstr "L'action a été supprimée."
|
||||||
|
|
||||||
#: views.py:227
|
#: views.py:230
|
||||||
msgid "Category"
|
msgid "Category"
|
||||||
msgstr "Catégorie"
|
msgstr "Catégorie"
|
||||||
|
|
||||||
#: views.py:228
|
#: views.py:231
|
||||||
msgid "Number of users (members and clubs)"
|
msgid "Number of users (members and clubs)"
|
||||||
msgstr "Nombre d'utilisateurs (adhérents et clubs)"
|
msgstr "Nombre d'utilisateurs (adhérents et clubs)"
|
||||||
|
|
||||||
#: views.py:229
|
#: views.py:232
|
||||||
msgid "Number of members"
|
msgid "Number of members"
|
||||||
msgstr "Nombre d'adhérents"
|
msgstr "Nombre d'adhérents"
|
||||||
|
|
||||||
#: views.py:230
|
#: views.py:233
|
||||||
msgid "Number of clubs"
|
msgid "Number of clubs"
|
||||||
msgstr "Nombre de clubs"
|
msgstr "Nombre de clubs"
|
||||||
|
|
||||||
#: views.py:234
|
#: views.py:237
|
||||||
msgid "Activated users"
|
msgid "Activated users"
|
||||||
msgstr "Utilisateurs activés"
|
msgstr "Utilisateurs activés"
|
||||||
|
|
||||||
#: views.py:242
|
#: views.py:245
|
||||||
msgid "Disabled users"
|
msgid "Disabled users"
|
||||||
msgstr "Utilisateurs désactivés"
|
msgstr "Utilisateurs désactivés"
|
||||||
|
|
||||||
#: views.py:250
|
#: views.py:253
|
||||||
msgid "Archived users"
|
msgid "Archived users"
|
||||||
msgstr "Utilisateurs archivés"
|
msgstr "Utilisateurs archivés"
|
||||||
|
|
||||||
#: views.py:258
|
#: views.py:261
|
||||||
|
msgid "Not yet active users"
|
||||||
|
msgstr "Utilisateurs pas encore actifs"
|
||||||
|
|
||||||
|
#: views.py:269
|
||||||
msgid "Contributing members"
|
msgid "Contributing members"
|
||||||
msgstr "Adhérents cotisants"
|
msgstr "Adhérents cotisants"
|
||||||
|
|
||||||
#: views.py:264
|
#: views.py:275
|
||||||
msgid "Users benefiting from a connection"
|
msgid "Users benefiting from a connection"
|
||||||
msgstr "Utilisateurs bénéficiant d'une connexion"
|
msgstr "Utilisateurs bénéficiant d'une connexion"
|
||||||
|
|
||||||
#: views.py:270
|
#: views.py:281
|
||||||
msgid "Banned users"
|
msgid "Banned users"
|
||||||
msgstr "Utilisateurs bannis"
|
msgstr "Utilisateurs bannis"
|
||||||
|
|
||||||
#: views.py:276
|
#: views.py:287
|
||||||
msgid "Users benefiting from a free connection"
|
msgid "Users benefiting from a free connection"
|
||||||
msgstr "Utilisateurs bénéficiant d'une connexion gratuite"
|
msgstr "Utilisateurs bénéficiant d'une connexion gratuite"
|
||||||
|
|
||||||
#: views.py:282
|
#: views.py:293
|
||||||
msgid "Active interfaces (with access to the network)"
|
msgid "Active interfaces (with access to the network)"
|
||||||
msgstr "Interfaces actives (ayant accès au réseau)"
|
msgstr "Interfaces actives (ayant accès au réseau)"
|
||||||
|
|
||||||
#: views.py:292
|
#: views.py:303
|
||||||
msgid "Active interfaces assigned IPv4"
|
msgid "Active interfaces assigned IPv4"
|
||||||
msgstr "Interfaces actives assignées IPv4"
|
msgstr "Interfaces actives assignées IPv4"
|
||||||
|
|
||||||
#: views.py:305
|
#: views.py:316
|
||||||
msgid "IP range"
|
msgid "IP range"
|
||||||
msgstr "Plage d'IP"
|
msgstr "Plage d'IP"
|
||||||
|
|
||||||
#: views.py:306
|
#: views.py:317
|
||||||
msgid "VLAN"
|
msgid "VLAN"
|
||||||
msgstr "VLAN"
|
msgstr "VLAN"
|
||||||
|
|
||||||
#: views.py:307
|
#: views.py:318
|
||||||
msgid "Total number of IP addresses"
|
msgid "Total number of IP addresses"
|
||||||
msgstr "Nombre total d'adresses IP"
|
msgstr "Nombre total d'adresses IP"
|
||||||
|
|
||||||
#: views.py:308
|
#: views.py:319
|
||||||
msgid "Number of assigned IP addresses"
|
msgid "Number of assigned IP addresses"
|
||||||
msgstr "Nombre d'adresses IP non assignées"
|
msgstr "Nombre d'adresses IP non assignées"
|
||||||
|
|
||||||
#: views.py:309
|
#: views.py:320
|
||||||
msgid "Number of IP address assigned to an activated machine"
|
msgid "Number of IP address assigned to an activated machine"
|
||||||
msgstr "Nombre d'adresses IP assignées à une machine activée"
|
msgstr "Nombre d'adresses IP assignées à une machine activée"
|
||||||
|
|
||||||
#: views.py:310
|
#: views.py:321
|
||||||
msgid "Number of nonassigned IP addresses"
|
msgid "Number of nonassigned IP addresses"
|
||||||
msgstr "Nombre d'adresses IP non assignées"
|
msgstr "Nombre d'adresses IP non assignées"
|
||||||
|
|
||||||
#: views.py:337
|
#: views.py:348
|
||||||
msgid "Subscriptions"
|
msgid "Subscriptions"
|
||||||
msgstr "Cotisations"
|
msgstr "Cotisations"
|
||||||
|
|
||||||
#: views.py:359 views.py:420
|
#: views.py:370 views.py:431
|
||||||
msgid "Machines"
|
msgid "Machines"
|
||||||
msgstr "Machines"
|
msgstr "Machines"
|
||||||
|
|
||||||
#: views.py:386
|
#: views.py:397
|
||||||
msgid "Topology"
|
msgid "Topology"
|
||||||
msgstr "Topologie"
|
msgstr "Topologie"
|
||||||
|
|
||||||
#: views.py:405
|
#: views.py:416
|
||||||
msgid "Number of actions"
|
msgid "Number of actions"
|
||||||
msgstr "Nombre d'actions"
|
msgstr "Nombre d'actions"
|
||||||
|
|
||||||
#: views.py:419 views.py:437 views.py:442 views.py:447 views.py:462
|
#: views.py:430 views.py:448 views.py:453 views.py:458 views.py:473
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr "Utilisateur"
|
msgstr "Utilisateur"
|
||||||
|
|
||||||
#: views.py:423
|
#: views.py:434
|
||||||
msgid "Invoice"
|
msgid "Invoice"
|
||||||
msgstr "Facture"
|
msgstr "Facture"
|
||||||
|
|
||||||
#: views.py:426
|
#: views.py:437
|
||||||
msgid "Ban"
|
msgid "Ban"
|
||||||
msgstr "Bannissement"
|
msgstr "Bannissement"
|
||||||
|
|
||||||
#: views.py:429
|
#: views.py:440
|
||||||
msgid "Whitelist"
|
msgid "Whitelist"
|
||||||
msgstr "Accès gracieux"
|
msgstr "Accès gracieux"
|
||||||
|
|
||||||
#: views.py:432
|
#: views.py:443
|
||||||
msgid "Rights"
|
msgid "Rights"
|
||||||
msgstr "Droits"
|
msgstr "Droits"
|
||||||
|
|
||||||
#: views.py:436
|
#: views.py:447
|
||||||
msgid "School"
|
msgid "School"
|
||||||
msgstr "Établissement"
|
msgstr "Établissement"
|
||||||
|
|
||||||
#: views.py:441
|
#: views.py:452
|
||||||
msgid "Payment method"
|
msgid "Payment method"
|
||||||
msgstr "Moyen de paiement"
|
msgstr "Moyen de paiement"
|
||||||
|
|
||||||
#: views.py:446
|
#: views.py:457
|
||||||
msgid "Bank"
|
msgid "Bank"
|
||||||
msgstr "Banque"
|
msgstr "Banque"
|
||||||
|
|
||||||
#: views.py:463
|
#: views.py:474
|
||||||
msgid "Action"
|
msgid "Action"
|
||||||
msgstr "Action"
|
msgstr "Action"
|
||||||
|
|
||||||
#: views.py:494
|
#: views.py:505
|
||||||
msgid "No model found."
|
msgid "No model found."
|
||||||
msgstr "Aucun modèle trouvé."
|
msgstr "Aucun modèle trouvé."
|
||||||
|
|
||||||
#: views.py:500
|
#: views.py:511
|
||||||
msgid "Nonexistent entry."
|
msgid "Nonexistent entry."
|
||||||
msgstr "Entrée inexistante."
|
msgstr "Entrée inexistante."
|
||||||
|
|
||||||
#: views.py:507
|
#: views.py:518
|
||||||
msgid "You don't have the right to access this menu."
|
msgid "You don't have the right to access this menu."
|
||||||
msgstr "Vous n'avez pas le droit d'accéder à ce menu."
|
msgstr "Vous n'avez pas le droit d'accéder à ce menu."
|
||||||
|
|
|
@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% if revisions_list.paginator %}
|
{% if revisions_list.paginator %}
|
||||||
{% include "pagination.html" with list=revisions_list %}
|
{% include 'pagination.html' with list=revisions_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% load logs_extra %}
|
{% load logs_extra %}
|
||||||
|
@ -36,9 +36,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th>{% trans "Edited object" %}</th>
|
<th>{% trans "Edited object" %}</th>
|
||||||
<th>{% trans "Object type" %}</th>
|
<th>{% trans "Object type" %}</th>
|
||||||
{% trans "Edited by" as tr_edited_by %}
|
{% trans "Edited by" as tr_edited_by %}
|
||||||
<th>{% include "buttons/sort.html" with prefix='logs' col='author' text=tr_edited_by %}</th>
|
<th>{% include 'buttons/sort.html' with prefix='logs' col='author' text=tr_edited_by %}</th>
|
||||||
{% trans "Date of editing" as tr_date_of_editing %}
|
{% 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>{% include 'buttons/sort.html' with prefix='logs' col='date' text=tr_date_of_editing %}</th>
|
||||||
<th>{% trans "Comment" %}</th>
|
<th>{% trans "Comment" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -65,6 +65,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if revisions_list.paginator %}
|
{% if revisions_list.paginator %}
|
||||||
{% include "pagination.html" with list=revisions_list %}
|
{% include 'pagination.html' with list=revisions_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
{% if versions_list.paginator %}
|
{% if versions_list.paginator %}
|
||||||
{% include "pagination.html" with list=versions_list %}
|
{% include 'pagination.html' with list=versions_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% load logs_extra %}
|
{% load logs_extra %}
|
||||||
|
@ -35,7 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% trans "Date" as tr_date %}
|
{% trans "Date" as tr_date %}
|
||||||
<th>{% include "buttons/sort.html" with prefix='sum' col='date' text=tr_date %}</th>
|
<th>{% include 'buttons/sort.html' with prefix='sum' col='date' text=tr_date %}</th>
|
||||||
<th>{% trans "Editing" %}</th>
|
<th>{% trans "Editing" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -154,6 +154,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if versions_list.paginator %}
|
{% if versions_list.paginator %}
|
||||||
{% include "pagination.html" with list=versions_list %}
|
{% include 'pagination.html' with list=versions_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "logs/sidebar.html" %}
|
{% extends 'logs/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "logs/sidebar.html" %}
|
{% extends 'logs/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Actions performed" %}</h2>
|
<h2>{% trans "Actions performed" %}</h2>
|
||||||
{% include "logs/aff_summary.html" with versions_list=versions_list %}
|
{% include 'logs/aff_summary.html' with versions_list=versions_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends 'base.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -28,27 +28,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
{% can_view_app logs %}
|
{% can_view_app logs %}
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "logs:index" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'logs:index' %}">
|
||||||
<i class="fa fa-clipboard-list"></i>
|
<i class="fa fa-clipboard"></i>
|
||||||
{% trans "Summary" %}
|
{% trans "Summary" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-logs" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-logs' %}">
|
||||||
<i class="fa fa-calendar-alt"></i>
|
<i class="fa fa-calendar"></i>
|
||||||
{% trans "Events" %}
|
{% trans "Events" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-general" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-general' %}">
|
||||||
<i class="fa fa-chart-area"></i>
|
<i class="fa fa-area-chart"></i>
|
||||||
{% trans "General" %}
|
{% trans "General" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-models" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-models' %}">
|
||||||
<i class="fa fa-database"></i>
|
<i class="fa fa-database"></i>
|
||||||
{% trans "Database" %}
|
{% trans "Database" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-actions" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-actions' %}">
|
||||||
<i class="fa fa-plug"></i>
|
<i class="fa fa-plug"></i>
|
||||||
{% trans "Wiring actions" %}
|
{% trans "Wiring actions" %}
|
||||||
</a>
|
</a>
|
||||||
<a class="list-group-item list-group-item-info" href="{% url "logs:stats-users" %}">
|
<a class="list-group-item list-group-item-info" href="{% url 'logs:stats-users' %}">
|
||||||
<i class="fa fa-users"></i>
|
<i class="fa fa-users"></i>
|
||||||
{% trans "Users" %}
|
{% trans "Users" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "logs/sidebar.html" %}
|
{% extends 'logs/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "General statistics" %}</h2>
|
<h2>{% trans "General statistics" %}</h2>
|
||||||
{% include "logs/aff_stats_general.html" with stats_list=stats_list %}
|
{% include 'logs/aff_stats_general.html' with stats_list=stats_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "logs/sidebar.html" %}
|
{% extends 'logs/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Actions performed" %}</h2>
|
<h2>{% trans "Actions performed" %}</h2>
|
||||||
{% include "logs/aff_stats_logs.html" with revisions_list=revisions_list %}
|
{% include 'logs/aff_stats_logs.html' with revisions_list=revisions_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "logs/sidebar.html" %}
|
{% extends 'logs/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Database statistics" %}</h2>
|
<h2>{% trans "Database statistics" %}</h2>
|
||||||
{% include "logs/aff_stats_models.html" with stats_list=stats_list %}
|
{% include 'logs/aff_stats_models.html' with stats_list=stats_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "logs/sidebar.html" %}
|
{% extends 'logs/sidebar.html' %}
|
||||||
{% comment %}
|
{% comment %}
|
||||||
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
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
|
se veut agnostique au réseau considéré, de manière à être installable en
|
||||||
|
@ -30,7 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{% trans "Statistics about users" %}</h2>
|
<h2>{% trans "Statistics about users" %}</h2>
|
||||||
{% include "logs/aff_stats_users.html" with stats_list=stats_list %}
|
{% include 'logs/aff_stats_users.html' with stats_list=stats_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -102,15 +102,18 @@ from re2o.utils import (
|
||||||
all_baned,
|
all_baned,
|
||||||
all_has_access,
|
all_has_access,
|
||||||
all_adherent,
|
all_adherent,
|
||||||
|
all_active_assigned_interfaces_count,
|
||||||
|
all_active_interfaces_count,
|
||||||
|
)
|
||||||
|
from re2o.base import (
|
||||||
re2o_paginator,
|
re2o_paginator,
|
||||||
|
SortTable
|
||||||
)
|
)
|
||||||
from re2o.acl import (
|
from re2o.acl import (
|
||||||
can_view_all,
|
can_view_all,
|
||||||
can_view_app,
|
can_view_app,
|
||||||
can_edit_history,
|
can_edit_history,
|
||||||
)
|
)
|
||||||
from re2o.utils import all_active_assigned_interfaces_count
|
|
||||||
from re2o.utils import all_active_interfaces_count, SortTable
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -254,6 +257,14 @@ def stats_general(request):
|
||||||
.count()),
|
.count()),
|
||||||
Club.objects.filter(state=Club.STATE_ARCHIVE).count()
|
Club.objects.filter(state=Club.STATE_ARCHIVE).count()
|
||||||
],
|
],
|
||||||
|
'not_active_users': [
|
||||||
|
_("Not yet active users"),
|
||||||
|
User.objects.filter(state=User.STATE_NOT_YET_ACTIVE).count(),
|
||||||
|
(Adherent.objects
|
||||||
|
.filter(state=Adherent.STATE_NOT_YET_ACTIVE)
|
||||||
|
.count()),
|
||||||
|
Club.objects.filter(state=Club.STATE_NOT_YET_ACTIVE).count()
|
||||||
|
],
|
||||||
'adherent_users': [
|
'adherent_users': [
|
||||||
_("Contributing members"),
|
_("Contributing members"),
|
||||||
_all_adherent.count(),
|
_all_adherent.count(),
|
||||||
|
|
|
@ -41,4 +41,3 @@ def can_view(user):
|
||||||
can = user.has_module_perms('machines')
|
can = user.has_module_perms('machines')
|
||||||
return can, None if can else _("You don't have the right to view this"
|
return can, None if can else _("You don't have the right to view this"
|
||||||
" application.")
|
" application.")
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ from __future__ import unicode_literals
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from reversion.admin import VersionAdmin
|
from reversion.admin import VersionAdmin
|
||||||
|
|
||||||
from .models import IpType, Machine, MachineType, Domain, IpList, Interface
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Extension,
|
Extension,
|
||||||
SOA,
|
SOA,
|
||||||
|
@ -47,6 +46,7 @@ from .models import (
|
||||||
Ipv6List,
|
Ipv6List,
|
||||||
OuverturePortList,
|
OuverturePortList,
|
||||||
)
|
)
|
||||||
|
from .models import IpType, Machine, MachineType, Domain, IpList, Interface
|
||||||
|
|
||||||
|
|
||||||
class MachineAdmin(VersionAdmin):
|
class MachineAdmin(VersionAdmin):
|
||||||
|
@ -98,6 +98,7 @@ class TxtAdmin(VersionAdmin):
|
||||||
""" Admin view of a TXT object """
|
""" Admin view of a TXT object """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class DNameAdmin(VersionAdmin):
|
class DNameAdmin(VersionAdmin):
|
||||||
""" Admin view of a DName object """
|
""" Admin view of a DName object """
|
||||||
pass
|
pass
|
||||||
|
@ -147,12 +148,12 @@ class ServiceAdmin(VersionAdmin):
|
||||||
""" Admin view of a ServiceAdmin object """
|
""" Admin view of a ServiceAdmin object """
|
||||||
list_display = ('service_type', 'min_time_regen', 'regular_time_regen')
|
list_display = ('service_type', 'min_time_regen', 'regular_time_regen')
|
||||||
|
|
||||||
|
|
||||||
class RoleAdmin(VersionAdmin):
|
class RoleAdmin(VersionAdmin):
|
||||||
""" Admin view of a RoleAdmin object """
|
""" Admin view of a RoleAdmin object """
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Machine, MachineAdmin)
|
admin.site.register(Machine, MachineAdmin)
|
||||||
admin.site.register(MachineType, MachineTypeAdmin)
|
admin.site.register(MachineType, MachineTypeAdmin)
|
||||||
admin.site.register(IpType, IpTypeAdmin)
|
admin.site.register(IpType, IpTypeAdmin)
|
||||||
|
|
|
@ -35,13 +35,12 @@ Formulaires d'ajout, edition et suppressions de :
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.forms import ModelForm, Form
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms import ModelForm, Form
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from re2o.field_permissions import FieldPermissionFormMixin
|
from re2o.field_permissions import FieldPermissionFormMixin
|
||||||
from re2o.mixins import FormRevMixin
|
from re2o.mixins import FormRevMixin
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
Domain,
|
Domain,
|
||||||
Machine,
|
Machine,
|
||||||
|
@ -68,6 +67,7 @@ from .models import (
|
||||||
|
|
||||||
class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||||
"""Formulaire d'édition d'une machine"""
|
"""Formulaire d'édition d'une machine"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Machine
|
model = Machine
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -80,12 +80,14 @@ class EditMachineForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||||
|
|
||||||
class NewMachineForm(EditMachineForm):
|
class NewMachineForm(EditMachineForm):
|
||||||
"""Creation d'une machine, ne renseigne que le nom"""
|
"""Creation d'une machine, ne renseigne que le nom"""
|
||||||
|
|
||||||
class Meta(EditMachineForm.Meta):
|
class Meta(EditMachineForm.Meta):
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||||
"""Edition d'une interface. Edition complète"""
|
"""Edition d'une interface. Edition complète"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
|
fields = ['machine', 'type', 'ipv4', 'mac_address', 'details']
|
||||||
|
@ -128,12 +130,14 @@ class EditInterfaceForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||||
class AddInterfaceForm(EditInterfaceForm):
|
class AddInterfaceForm(EditInterfaceForm):
|
||||||
"""Ajout d'une interface à une machine. En fonction des droits,
|
"""Ajout d'une interface à une machine. En fonction des droits,
|
||||||
affiche ou non l'ensemble des ip disponibles"""
|
affiche ou non l'ensemble des ip disponibles"""
|
||||||
|
|
||||||
class Meta(EditInterfaceForm.Meta):
|
class Meta(EditInterfaceForm.Meta):
|
||||||
fields = ['type', 'ipv4', 'mac_address', 'details']
|
fields = ['type', 'ipv4', 'mac_address', 'details']
|
||||||
|
|
||||||
|
|
||||||
class AliasForm(FormRevMixin, ModelForm):
|
class AliasForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout d'un alias (et edition), CNAME, contenant nom et extension"""
|
"""Ajout d'un alias (et edition), CNAME, contenant nom et extension"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Domain
|
model = Domain
|
||||||
fields = ['name', 'extension']
|
fields = ['name', 'extension']
|
||||||
|
@ -151,6 +155,7 @@ class AliasForm(FormRevMixin, ModelForm):
|
||||||
|
|
||||||
class DomainForm(FormRevMixin, ModelForm):
|
class DomainForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout et edition d'un enregistrement de nom, relié à interface"""
|
"""Ajout et edition d'un enregistrement de nom, relié à interface"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Domain
|
model = Domain
|
||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
@ -183,6 +188,7 @@ class DelAliasForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class MachineTypeForm(FormRevMixin, ModelForm):
|
class MachineTypeForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout et edition d'un machinetype, relié à un iptype"""
|
"""Ajout et edition d'un machinetype, relié à un iptype"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MachineType
|
model = MachineType
|
||||||
fields = ['type', 'ip_type']
|
fields = ['type', 'ip_type']
|
||||||
|
@ -214,6 +220,7 @@ class DelMachineTypeForm(FormRevMixin, Form):
|
||||||
class IpTypeForm(FormRevMixin, ModelForm):
|
class IpTypeForm(FormRevMixin, ModelForm):
|
||||||
"""Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de
|
"""Formulaire d'ajout d'un iptype. Pas d'edition de l'ip de start et de
|
||||||
stop après creation"""
|
stop après creation"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = IpType
|
model = IpType
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -227,6 +234,7 @@ class IpTypeForm(FormRevMixin, ModelForm):
|
||||||
class EditIpTypeForm(IpTypeForm):
|
class EditIpTypeForm(IpTypeForm):
|
||||||
"""Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait
|
"""Edition d'un iptype. Pas d'edition du rangev4 possible, car il faudrait
|
||||||
synchroniser les objets iplist"""
|
synchroniser les objets iplist"""
|
||||||
|
|
||||||
class Meta(IpTypeForm.Meta):
|
class Meta(IpTypeForm.Meta):
|
||||||
fields = ['extension', 'type', 'need_infra', 'domaine_ip_network', 'domaine_ip_netmask',
|
fields = ['extension', 'type', 'need_infra', 'domaine_ip_network', 'domaine_ip_netmask',
|
||||||
'prefix_v6', 'prefix_v6_length',
|
'prefix_v6', 'prefix_v6_length',
|
||||||
|
@ -253,6 +261,7 @@ class DelIpTypeForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class ExtensionForm(FormRevMixin, ModelForm):
|
class ExtensionForm(FormRevMixin, ModelForm):
|
||||||
"""Formulaire d'ajout et edition d'une extension"""
|
"""Formulaire d'ajout et edition d'une extension"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Extension
|
model = Extension
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -264,6 +273,7 @@ class ExtensionForm(FormRevMixin, ModelForm):
|
||||||
self.fields['origin'].label = _("A record origin")
|
self.fields['origin'].label = _("A record origin")
|
||||||
self.fields['origin_v6'].label = _("AAAA record origin")
|
self.fields['origin_v6'].label = _("AAAA record origin")
|
||||||
self.fields['soa'].label = _("SOA record to use")
|
self.fields['soa'].label = _("SOA record to use")
|
||||||
|
self.fields['dnssec'].label = _("Sign with DNSSEC")
|
||||||
|
|
||||||
|
|
||||||
class DelExtensionForm(FormRevMixin, Form):
|
class DelExtensionForm(FormRevMixin, Form):
|
||||||
|
@ -285,6 +295,7 @@ class DelExtensionForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class Ipv6ListForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
class Ipv6ListForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||||
"""Gestion des ipv6 d'une machine"""
|
"""Gestion des ipv6 d'une machine"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ipv6List
|
model = Ipv6List
|
||||||
fields = ['ipv6', 'slaac_ip']
|
fields = ['ipv6', 'slaac_ip']
|
||||||
|
@ -296,6 +307,7 @@ class Ipv6ListForm(FormRevMixin, FieldPermissionFormMixin, ModelForm):
|
||||||
|
|
||||||
class SOAForm(FormRevMixin, ModelForm):
|
class SOAForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout et edition d'un SOA"""
|
"""Ajout et edition d'un SOA"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SOA
|
model = SOA
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -324,6 +336,7 @@ class DelSOAForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class MxForm(FormRevMixin, ModelForm):
|
class MxForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout et edition d'un MX"""
|
"""Ajout et edition d'un MX"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Mx
|
model = Mx
|
||||||
fields = ['zone', 'priority', 'name']
|
fields = ['zone', 'priority', 'name']
|
||||||
|
@ -357,6 +370,7 @@ class NsForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout d'un NS pour une zone
|
"""Ajout d'un NS pour une zone
|
||||||
On exclue les CNAME dans les objets domain (interdit par la rfc)
|
On exclue les CNAME dans les objets domain (interdit par la rfc)
|
||||||
donc on prend uniquemet """
|
donc on prend uniquemet """
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ns
|
model = Ns
|
||||||
fields = ['zone', 'ns']
|
fields = ['zone', 'ns']
|
||||||
|
@ -388,6 +402,7 @@ class DelNsForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class TxtForm(FormRevMixin, ModelForm):
|
class TxtForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout d'un txt pour une zone"""
|
"""Ajout d'un txt pour une zone"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Txt
|
model = Txt
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -416,6 +431,7 @@ class DelTxtForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class DNameForm(FormRevMixin, ModelForm):
|
class DNameForm(FormRevMixin, ModelForm):
|
||||||
"""Add a DNAME entry for a zone"""
|
"""Add a DNAME entry for a zone"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DName
|
model = DName
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -444,6 +460,7 @@ class DelDNameForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class SrvForm(FormRevMixin, ModelForm):
|
class SrvForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout d'un srv pour une zone"""
|
"""Ajout d'un srv pour une zone"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Srv
|
model = Srv
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -473,6 +490,7 @@ class DelSrvForm(FormRevMixin, Form):
|
||||||
class NasForm(FormRevMixin, ModelForm):
|
class NasForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout d'un type de nas (machine d'authentification,
|
"""Ajout d'un type de nas (machine d'authentification,
|
||||||
swicths, bornes...)"""
|
swicths, bornes...)"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Nas
|
model = Nas
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -501,6 +519,7 @@ class DelNasForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class RoleForm(FormRevMixin, ModelForm):
|
class RoleForm(FormRevMixin, ModelForm):
|
||||||
"""Add and edit role."""
|
"""Add and edit role."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Role
|
model = Role
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -533,6 +552,7 @@ class DelRoleForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class ServiceForm(FormRevMixin, ModelForm):
|
class ServiceForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
|
"""Ajout et edition d'une classe de service : dns, dhcp, etc"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Service
|
model = Service
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -574,15 +594,27 @@ class DelServiceForm(FormRevMixin, Form):
|
||||||
|
|
||||||
class VlanForm(FormRevMixin, ModelForm):
|
class VlanForm(FormRevMixin, ModelForm):
|
||||||
"""Ajout d'un vlan : id, nom"""
|
"""Ajout d'un vlan : id, nom"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Vlan
|
model = Vlan
|
||||||
fields = '__all__'
|
fields = ['vlan_id', 'name', 'comment']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
super(VlanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
super(VlanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class EditOptionVlanForm(FormRevMixin, ModelForm):
|
||||||
|
"""Ajout d'un vlan : id, nom"""
|
||||||
|
class Meta:
|
||||||
|
model = Vlan
|
||||||
|
fields = ['dhcp_snooping', 'dhcpv6_snooping', 'arp_protect', 'igmp', 'mld']
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
super(EditOptionVlanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DelVlanForm(FormRevMixin, Form):
|
class DelVlanForm(FormRevMixin, Form):
|
||||||
"""Suppression d'un ou plusieurs vlans"""
|
"""Suppression d'un ou plusieurs vlans"""
|
||||||
vlan = forms.ModelMultipleChoiceField(
|
vlan = forms.ModelMultipleChoiceField(
|
||||||
|
@ -603,6 +635,7 @@ class DelVlanForm(FormRevMixin, Form):
|
||||||
class EditOuverturePortConfigForm(FormRevMixin, ModelForm):
|
class EditOuverturePortConfigForm(FormRevMixin, ModelForm):
|
||||||
"""Edition de la liste des profils d'ouverture de ports
|
"""Edition de la liste des profils d'ouverture de ports
|
||||||
pour l'interface"""
|
pour l'interface"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ['port_lists']
|
fields = ['port_lists']
|
||||||
|
@ -619,6 +652,7 @@ class EditOuverturePortConfigForm(FormRevMixin, ModelForm):
|
||||||
class EditOuverturePortListForm(FormRevMixin, ModelForm):
|
class EditOuverturePortListForm(FormRevMixin, ModelForm):
|
||||||
"""Edition de la liste des ports et profils d'ouverture
|
"""Edition de la liste des ports et profils d'ouverture
|
||||||
des ports"""
|
des ports"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = OuverturePortList
|
model = OuverturePortList
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
@ -634,6 +668,7 @@ class EditOuverturePortListForm(FormRevMixin, ModelForm):
|
||||||
|
|
||||||
class SshFpForm(FormRevMixin, ModelForm):
|
class SshFpForm(FormRevMixin, ModelForm):
|
||||||
"""Edits a SSHFP record."""
|
"""Edits a SSHFP record."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SshFp
|
model = SshFp
|
||||||
exclude = ('machine',)
|
exclude = ('machine',)
|
||||||
|
@ -645,4 +680,3 @@ class SshFpForm(FormRevMixin, ModelForm):
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load diff
40
machines/migrations/0095_auto_20180919_2225.py
Normal file
40
machines/migrations/0095_auto_20180919_2225.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-09-19 20:25
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0094_auto_20180815_1918'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='arp_protect',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='dhcp_snooping',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='dhcpv6_snooping',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='igmp',
|
||||||
|
field=models.BooleanField(default=False, help_text='Gestion multicast v4'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='mld',
|
||||||
|
field=models.BooleanField(default=False, help_text='Gestion multicast v6'),
|
||||||
|
),
|
||||||
|
]
|
22
machines/migrations/0096_auto_20181013_1417.py
Normal file
22
machines/migrations/0096_auto_20181013_1417.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-10-13 12:17
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0095_auto_20180919_2225'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='machine',
|
||||||
|
name='user',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
20
machines/migrations/0097_extension_dnssec.py
Normal file
20
machines/migrations/0097_extension_dnssec.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-12-24 14:00
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0096_auto_20181013_1417'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='extension',
|
||||||
|
name='dnssec',
|
||||||
|
field=models.BooleanField(default=False, help_text='Should the zone be signed with DNSSEC'),
|
||||||
|
),
|
||||||
|
]
|
20
machines/migrations/0098_auto_20190102_1745.py
Normal file
20
machines/migrations/0098_auto_20190102_1745.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2019-01-02 23:45
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0097_extension_dnssec'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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'), ('dns-recursive-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),
|
||||||
|
),
|
||||||
|
]
|
26
machines/migrations/0099_role_recursive_dns.py
Normal file
26
machines/migrations/0099_role_recursive_dns.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2019-01-02 23:45
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate(apps, schema_editor):
|
||||||
|
Role = apps.get_model('machines', 'Role')
|
||||||
|
|
||||||
|
for role in Role.objects.filter(specific_role='dns-recursif-server'):
|
||||||
|
role.specific_role = 'dns-recursive-server'
|
||||||
|
role.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0098_auto_20190102_1745'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migrate),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
20
machines/migrations/0100_auto_20190102_1753.py
Normal file
20
machines/migrations/0100_auto_20190102_1753.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2019-01-02 23:53
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0099_role_recursive_dns'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
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-recursive-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),
|
||||||
|
),
|
||||||
|
]
|
34
machines/migrations/0101_auto_20190108_1623.py
Normal file
34
machines/migrations/0101_auto_20190108_1623.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2019-01-08 22:23
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('machines', '0100_auto_20190102_1753'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='ouvertureport',
|
||||||
|
options={'verbose_name': 'ports opening', 'verbose_name_plural': 'ports openings'},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='nas',
|
||||||
|
name='port_access_mode',
|
||||||
|
field=models.CharField(choices=[('802.1X', '802.1X'), ('Mac-address', 'MAC-address')], default='802.1X', max_length=32),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='igmp',
|
||||||
|
field=models.BooleanField(default=False, help_text='v4 multicast management'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vlan',
|
||||||
|
name='mld',
|
||||||
|
field=models.BooleanField(default=False, help_text='v6 multicast management'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -27,37 +27,35 @@ The models definitions for the Machines app
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from datetime import timedelta
|
import base64
|
||||||
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
from ipaddress import IPv6Address
|
from ipaddress import IPv6Address
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
|
|
||||||
import hashlib
|
|
||||||
import base64
|
|
||||||
|
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_save, post_delete
|
from django.db.models.signals import post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils.functional import cached_property
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from macaddress.fields import MACAddressField, default_dialect
|
||||||
|
from netaddr import mac_bare, EUI, IPSet, IPRange, IPNetwork, IPAddress
|
||||||
|
|
||||||
from macaddress.fields import MACAddressField
|
import preferences.models
|
||||||
|
import users.models
|
||||||
from re2o.field_permissions import FieldPermissionModelMixin
|
from re2o.field_permissions import FieldPermissionModelMixin
|
||||||
from re2o.mixins import AclMixin, RevMixin
|
from re2o.mixins import AclMixin, RevMixin
|
||||||
|
|
||||||
import users.models
|
|
||||||
import preferences.models
|
|
||||||
|
|
||||||
|
|
||||||
class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
|
class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
|
||||||
""" Class définissant une machine, object parent user, objets fils
|
""" Class définissant une machine, object parent user, objets fils
|
||||||
interfaces"""
|
interfaces"""
|
||||||
user = models.ForeignKey('users.User', on_delete=models.PROTECT)
|
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
help_text=_("Optional"),
|
help_text=_("Optional"),
|
||||||
|
@ -199,7 +197,17 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
|
||||||
def short_name(self):
|
def short_name(self):
|
||||||
"""Par defaut, renvoie le nom de la première interface
|
"""Par defaut, renvoie le nom de la première interface
|
||||||
de cette machine"""
|
de cette machine"""
|
||||||
return str(self.interface_set.first().domain.name)
|
interfaces_set = self.interface_set.first()
|
||||||
|
if interfaces_set:
|
||||||
|
return str(interfaces_set.domain.name)
|
||||||
|
else:
|
||||||
|
return _("No name")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def complete_name(self):
|
||||||
|
"""Par defaut, renvoie le nom de la première interface
|
||||||
|
de cette machine"""
|
||||||
|
return str(self.interface_set.first())
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def all_short_names(self):
|
def all_short_names(self):
|
||||||
|
@ -209,6 +217,11 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
|
||||||
interface_parent__machine=self
|
interface_parent__machine=self
|
||||||
).values_list('name', flat=True).distinct()
|
).values_list('name', flat=True).distinct()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def get_name(self):
|
||||||
|
"""Return a name : user provided name or first interface name"""
|
||||||
|
return self.name or self.short_name
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def all_complete_names(self):
|
def all_complete_names(self):
|
||||||
"""Renvoie tous les tls complets de la machine"""
|
"""Renvoie tous les tls complets de la machine"""
|
||||||
|
@ -327,7 +340,7 @@ class IpType(RevMixin, AclMixin, models.Model):
|
||||||
("use_all_iptype", _("Can use all IP types")),
|
("use_all_iptype", _("Can use all IP types")),
|
||||||
)
|
)
|
||||||
verbose_name = _("IP type")
|
verbose_name = _("IP type")
|
||||||
verbose_name_plural = ("IP types")
|
verbose_name_plural = _("IP types")
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def ip_range(self):
|
def ip_range(self):
|
||||||
|
@ -515,6 +528,18 @@ class Vlan(RevMixin, AclMixin, models.Model):
|
||||||
vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)])
|
vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)])
|
||||||
name = models.CharField(max_length=256)
|
name = models.CharField(max_length=256)
|
||||||
comment = models.CharField(max_length=256, blank=True)
|
comment = models.CharField(max_length=256, blank=True)
|
||||||
|
#Réglages supplémentaires
|
||||||
|
arp_protect = models.BooleanField(default=False)
|
||||||
|
dhcp_snooping = models.BooleanField(default=False)
|
||||||
|
dhcpv6_snooping = models.BooleanField(default=False)
|
||||||
|
igmp = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_("v4 multicast management")
|
||||||
|
)
|
||||||
|
mld = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_("v6 multicast management")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -534,7 +559,7 @@ class Nas(RevMixin, AclMixin, models.Model):
|
||||||
default_mode = '802.1X'
|
default_mode = '802.1X'
|
||||||
AUTH = (
|
AUTH = (
|
||||||
('802.1X', '802.1X'),
|
('802.1X', '802.1X'),
|
||||||
('Mac-address', 'Mac-address'),
|
('Mac-address', _("MAC-address")),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.CharField(max_length=255, unique=True)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
|
@ -641,7 +666,7 @@ class SOA(RevMixin, AclMixin, models.Model):
|
||||||
utilisée dans les migrations de la BDD. """
|
utilisée dans les migrations de la BDD. """
|
||||||
return cls.objects.get_or_create(
|
return cls.objects.get_or_create(
|
||||||
name=_("SOA to edit"),
|
name=_("SOA to edit"),
|
||||||
mail="postmaser@example.com"
|
mail="postmaster@example.com"
|
||||||
)[0].pk
|
)[0].pk
|
||||||
|
|
||||||
|
|
||||||
|
@ -671,6 +696,10 @@ class Extension(RevMixin, AclMixin, models.Model):
|
||||||
'SOA',
|
'SOA',
|
||||||
on_delete=models.CASCADE
|
on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
dnssec = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_("Should the zone be signed with DNSSEC")
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -716,6 +745,9 @@ class Extension(RevMixin, AclMixin, models.Model):
|
||||||
.filter(cname__interface_parent__in=all_active_assigned_interfaces())
|
.filter(cname__interface_parent__in=all_active_assigned_interfaces())
|
||||||
.prefetch_related('cname'))
|
.prefetch_related('cname'))
|
||||||
|
|
||||||
|
def get_associated_dname_records(self):
|
||||||
|
return (DName.objects.filter(alias=self))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def can_use_all(user_request, *_args, **_kwargs):
|
def can_use_all(user_request, *_args, **_kwargs):
|
||||||
"""Superdroit qui permet d'utiliser toutes les extensions sans
|
"""Superdroit qui permet d'utiliser toutes les extensions sans
|
||||||
|
@ -902,7 +934,7 @@ class SshFp(RevMixin, AclMixin, models.Model):
|
||||||
|
|
||||||
machine = models.ForeignKey('Machine', on_delete=models.CASCADE)
|
machine = models.ForeignKey('Machine', on_delete=models.CASCADE)
|
||||||
pub_key_entry = models.TextField(
|
pub_key_entry = models.TextField(
|
||||||
help_text="SSH public key",
|
help_text=_("SSH public key"),
|
||||||
max_length=2048
|
max_length=2048
|
||||||
)
|
)
|
||||||
algo = models.CharField(
|
algo = models.CharField(
|
||||||
|
@ -910,7 +942,7 @@ class SshFp(RevMixin, AclMixin, models.Model):
|
||||||
max_length=32
|
max_length=32
|
||||||
)
|
)
|
||||||
comment = models.CharField(
|
comment = models.CharField(
|
||||||
help_text="Comment",
|
help_text=_("Comment"),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
null=True,
|
null=True,
|
||||||
blank=True
|
blank=True
|
||||||
|
@ -955,7 +987,6 @@ class SshFp(RevMixin, AclMixin, models.Model):
|
||||||
return str(self.algo) + ' ' + str(self.comment)
|
return str(self.algo) + ' ' + str(self.comment)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
""" Une interface. Objet clef de l'application machine :
|
""" Une interface. Objet clef de l'application machine :
|
||||||
- une address mac unique. Possibilité de la rendre unique avec le
|
- une address mac unique. Possibilité de la rendre unique avec le
|
||||||
|
@ -1065,7 +1096,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
.get_cached_value('ipv6_mode') == 'DHCPV6'):
|
.get_cached_value('ipv6_mode') == 'DHCPV6'):
|
||||||
return self.ipv6list.filter(slaac_ip=False)
|
return self.ipv6list.filter(slaac_ip=False)
|
||||||
else:
|
else:
|
||||||
return None
|
return []
|
||||||
|
|
||||||
def mac_bare(self):
|
def mac_bare(self):
|
||||||
""" Formatage de la mac type mac_bare"""
|
""" Formatage de la mac type mac_bare"""
|
||||||
|
@ -1075,28 +1106,10 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
""" Tente un formatage mac_bare, si échoue, lève une erreur de
|
""" Tente un formatage mac_bare, si échoue, lève une erreur de
|
||||||
validation"""
|
validation"""
|
||||||
try:
|
try:
|
||||||
self.mac_address = str(EUI(self.mac_address))
|
self.mac_address = str(EUI(self.mac_address, dialect=default_dialect()))
|
||||||
except:
|
except:
|
||||||
raise ValidationError(_("The given MAC address is invalid."))
|
raise ValidationError(_("The given MAC address is invalid."))
|
||||||
|
|
||||||
def clean(self, *args, **kwargs):
|
|
||||||
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
|
||||||
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
|
||||||
# If type was an invalid value, django won't create an attribute type
|
|
||||||
# but try clean() as we may be able to create it from another value
|
|
||||||
# so even if the error as yet been detected at this point, django
|
|
||||||
# continues because the error might not prevent us from creating the
|
|
||||||
# instance.
|
|
||||||
# But in our case, it's impossible to create a type value so we raise
|
|
||||||
# the error.
|
|
||||||
if not hasattr(self, 'type'):
|
|
||||||
raise ValidationError(_("The selected IP type is invalid."))
|
|
||||||
self.filter_macaddress()
|
|
||||||
self.mac_address = str(EUI(self.mac_address)) or None
|
|
||||||
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
|
||||||
self.assign_ipv4()
|
|
||||||
super(Interface, self).clean(*args, **kwargs)
|
|
||||||
|
|
||||||
def assign_ipv4(self):
|
def assign_ipv4(self):
|
||||||
""" Assigne une ip à l'interface """
|
""" Assigne une ip à l'interface """
|
||||||
free_ips = self.type.ip_type.free_ip()
|
free_ips = self.type.ip_type.free_ip()
|
||||||
|
@ -1116,6 +1129,42 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
self.clean()
|
self.clean()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def has_private_ip(self):
|
||||||
|
""" True si l'ip associée est privée"""
|
||||||
|
if self.ipv4:
|
||||||
|
return IPAddress(str(self.ipv4)).is_private()
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def may_have_port_open(self):
|
||||||
|
""" True si l'interface a une ip et une ip publique.
|
||||||
|
Permet de ne pas exporter des ouvertures sur des ip privées
|
||||||
|
(useless)"""
|
||||||
|
return self.ipv4 and not self.has_private_ip()
|
||||||
|
|
||||||
|
def clean(self, *args, **kwargs):
|
||||||
|
""" Formate l'addresse mac en mac_bare (fonction filter_mac)
|
||||||
|
et assigne une ipv4 dans le bon range si inexistante ou incohérente"""
|
||||||
|
# If type was an invalid value, django won't create an attribute type
|
||||||
|
# but try clean() as we may be able to create it from another value
|
||||||
|
# so even if the error as yet been detected at this point, django
|
||||||
|
# continues because the error might not prevent us from creating the
|
||||||
|
# instance.
|
||||||
|
# But in our case, it's impossible to create a type value so we raise
|
||||||
|
# the error.
|
||||||
|
if not hasattr(self, 'type'):
|
||||||
|
raise ValidationError(_("The selected IP type is invalid."))
|
||||||
|
self.filter_macaddress()
|
||||||
|
if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type:
|
||||||
|
self.assign_ipv4()
|
||||||
|
super(Interface, self).clean(*args, **kwargs)
|
||||||
|
|
||||||
|
def validate_unique(self, *args, **kwargs):
|
||||||
|
super(Interface, self).validate_unique(*args, **kwargs)
|
||||||
|
interfaces_similar = Interface.objects.filter(mac_address=self.mac_address, type__ip_type=self.type.ip_type)
|
||||||
|
if interfaces_similar and interfaces_similar.first() != self:
|
||||||
|
raise ValidationError(_("Mac address already registered in this Machine Type/Subnet"))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.filter_macaddress()
|
self.filter_macaddress()
|
||||||
# On verifie la cohérence en forçant l'extension par la méthode
|
# On verifie la cohérence en forçant l'extension par la méthode
|
||||||
|
@ -1123,6 +1172,7 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
if self.type.ip_type != self.ipv4.ip_type:
|
if self.type.ip_type != self.ipv4.ip_type:
|
||||||
raise ValidationError(_("The IPv4 address and the machine type"
|
raise ValidationError(_("The IPv4 address and the machine type"
|
||||||
" don't match."))
|
" don't match."))
|
||||||
|
self.validate_unique()
|
||||||
super(Interface, self).save(*args, **kwargs)
|
super(Interface, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1220,19 +1270,6 @@ class Interface(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
domain = None
|
domain = None
|
||||||
return str(domain)
|
return str(domain)
|
||||||
|
|
||||||
def has_private_ip(self):
|
|
||||||
""" True si l'ip associée est privée"""
|
|
||||||
if self.ipv4:
|
|
||||||
return IPAddress(str(self.ipv4)).is_private()
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def may_have_port_open(self):
|
|
||||||
""" True si l'interface a une ip et une ip publique.
|
|
||||||
Permet de ne pas exporter des ouvertures sur des ip privées
|
|
||||||
(useless)"""
|
|
||||||
return self.ipv4 and not self.has_private_ip()
|
|
||||||
|
|
||||||
|
|
||||||
class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
""" A list of IPv6 """
|
""" A list of IPv6 """
|
||||||
|
@ -1350,7 +1387,10 @@ class Ipv6List(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
.filter(interface=self.interface, slaac_ip=True)
|
.filter(interface=self.interface, slaac_ip=True)
|
||||||
.exclude(id=self.id)):
|
.exclude(id=self.id)):
|
||||||
raise ValidationError(_("A SLAAC IP address is already registered."))
|
raise ValidationError(_("A SLAAC IP address is already registered."))
|
||||||
|
try:
|
||||||
prefix_v6 = self.interface.type.ip_type.prefix_v6.encode().decode('utf-8')
|
prefix_v6 = self.interface.type.ip_type.prefix_v6.encode().decode('utf-8')
|
||||||
|
except AttributeError: # Prevents from crashing when there is no defined prefix_v6
|
||||||
|
prefix_v6 = None
|
||||||
if prefix_v6:
|
if prefix_v6:
|
||||||
if (IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[:20] !=
|
if (IPv6Address(self.ipv6.encode().decode('utf-8')).exploded[:20] !=
|
||||||
IPv6Address(prefix_v6).exploded[:20]):
|
IPv6Address(prefix_v6).exploded[:20]):
|
||||||
|
@ -1579,7 +1619,7 @@ class Role(RevMixin, AclMixin, models.Model):
|
||||||
ROLE = (
|
ROLE = (
|
||||||
('dhcp-server', _("DHCP server")),
|
('dhcp-server', _("DHCP server")),
|
||||||
('switch-conf-server', _("Switches configuration server")),
|
('switch-conf-server', _("Switches configuration server")),
|
||||||
('dns-recursif-server', _("Recursive DNS server")),
|
('dns-recursive-server', _("Recursive DNS server")),
|
||||||
('ntp-server', _("NTP server")),
|
('ntp-server', _("NTP server")),
|
||||||
('radius-server', _("RADIUS server")),
|
('radius-server', _("RADIUS server")),
|
||||||
('log-server', _("Log server")),
|
('log-server', _("Log server")),
|
||||||
|
@ -1608,18 +1648,6 @@ class Role(RevMixin, AclMixin, models.Model):
|
||||||
verbose_name = _("server role")
|
verbose_name = _("server role")
|
||||||
verbose_name_plural = _("server roles")
|
verbose_name_plural = _("server roles")
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_instance(cls, roleid, *_args, **_kwargs):
|
|
||||||
"""Get the Role instance with roleid.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
roleid: The id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The role.
|
|
||||||
"""
|
|
||||||
return cls.objects.get(pk=roleid)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def interface_for_roletype(cls, roletype):
|
def interface_for_roletype(cls, roletype):
|
||||||
"""Return interfaces for a roletype"""
|
"""Return interfaces for a roletype"""
|
||||||
|
@ -1634,6 +1662,11 @@ class Role(RevMixin, AclMixin, models.Model):
|
||||||
machine__interface__role=cls.objects.filter(specific_role=roletype)
|
machine__interface__role=cls.objects.filter(specific_role=roletype)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def interface_for_roletype(cls, roletype):
|
||||||
|
"""Return interfaces for a roletype"""
|
||||||
|
return Interface.objects.filter(role=cls.objects.filter(specific_role=roletype))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
super(Role, self).save(*args, **kwargs)
|
super(Role, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -1847,7 +1880,7 @@ class OuverturePort(RevMixin, AclMixin, models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("ports opening")
|
verbose_name = _("ports opening")
|
||||||
verbose_name = _("ports openings")
|
verbose_name_plural = _("ports openings")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.begin == self.end:
|
if self.begin == self.end:
|
||||||
|
@ -2013,4 +2046,3 @@ def srv_post_save(**_kwargs):
|
||||||
def srv_post_delete(**_kwargs):
|
def srv_post_delete(**_kwargs):
|
||||||
"""Regeneration dns après modification d'un SRV"""
|
"""Regeneration dns après modification d'un SRV"""
|
||||||
regen('dns')
|
regen('dns')
|
||||||
|
|
||||||
|
|
|
@ -26,8 +26,8 @@
|
||||||
Serializers for the Machines app
|
Serializers for the Machines app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from machines.models import (
|
from machines.models import (
|
||||||
Interface,
|
Interface,
|
||||||
IpType,
|
IpType,
|
||||||
|
|
|
@ -45,4 +45,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -45,4 +45,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% if ipv6_enabled %}
|
{% if ipv6_enabled %}
|
||||||
<th>{% trans "AAAA record origin" %}</th>
|
<th>{% trans "AAAA record origin" %}</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<th>{% trans "DNSSEC" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -50,6 +51,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% if ipv6_enabled %}
|
{% if ipv6_enabled %}
|
||||||
<td>{{ extension.origin_v6 }}</td>
|
<td>{{ extension.origin_v6 }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<td>{{ extension.dnssec|tick }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% can_edit extension %}
|
{% can_edit extension %}
|
||||||
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
|
{% include 'buttons/edit.html' with href='machines:edit-extension' id=extension.id %}
|
||||||
|
@ -60,4 +62,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>{{ type.type }}</td>
|
<td>{{ type.type }}</td>
|
||||||
<td>{{ type.extension }}</td>
|
<td>{{ type.extension }}</td>
|
||||||
<td>{{ type.need_infra|tick }}</td>
|
<td>{{ type.need_infra|tick }}</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.domaine_ip_start }}-{{ type.domaine_ip_stop }}{% if type.ip_network %}<b><u> on </u></b>
|
||||||
|
{{ type.ip_network }}{% endif %}</td>
|
||||||
<td>{{ type.prefix_v6 }}/{{ type.prefix_v6_length }}</td>
|
<td>{{ type.prefix_v6 }}/{{ type.prefix_v6_length }}</td>
|
||||||
<td>{{ type.reverse_v4|tick }}/{{ type.reverse_v6|tick }}</td>
|
<td>{{ type.reverse_v4|tick }}/{{ type.reverse_v6|tick }}</td>
|
||||||
<td>{{ type.vlan }}</td>
|
<td>{{ type.vlan }}</td>
|
||||||
|
@ -63,4 +64,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -50,4 +50,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
{% if machines_list.paginator %}
|
{% if machines_list.paginator %}
|
||||||
{% include "pagination.html" with list=machines_list %}
|
{% include 'pagination.html' with list=machines_list go_to_id="machines" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<table class="table" id="machines_table">
|
<table class="table" id="machines_table">
|
||||||
|
@ -41,7 +41,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
{% trans "DNS name" as tr_dns_name %}
|
{% trans "DNS name" as tr_dns_name %}
|
||||||
<th>{% include "buttons/sort.html" with prefix='machine' col='name' text=tr_dns_name %}</th>
|
<th>{% include 'buttons/sort.html' with prefix='machine' col='name' text=tr_dns_name %}</th>
|
||||||
<th>{% trans "Type" %}</th>
|
<th>{% trans "Type" %}</th>
|
||||||
<th>{% trans "MAC address" %}</th>
|
<th>{% trans "MAC address" %}</th>
|
||||||
<th>{% trans "IP address" %}</th>
|
<th>{% trans "IP address" %}</th>
|
||||||
|
@ -52,7 +52,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td colspan="4">
|
<td colspan="4">
|
||||||
{% trans "No name" as tr_no_name %}
|
{% trans "No name" as tr_no_name %}
|
||||||
{% trans "View the profile" as tr_view_the_profile %}
|
{% trans "View the profile" as tr_view_the_profile %}
|
||||||
<b>{{ machine.name|default:tr_no_name }}</b> <i class="fa-angle-right"></i>
|
<b>{{ machine.get_name|default:tr_no_name }}</b> <i class="fa fa-angle-right"></i>
|
||||||
<a href="{% url 'users:profil' userid=machine.user.id %}" title=tr_view_the_profile>
|
<a href="{% url 'users:profil' userid=machine.user.id %}" title=tr_view_the_profile>
|
||||||
<i class="fa fa-user"></i> {{ machine.user }}
|
<i class="fa fa-user"></i> {{ machine.user }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -73,7 +73,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td>
|
<td>
|
||||||
{% if interface.domain.related_domain.all %}
|
{% if interface.domain.related_domain.all %}
|
||||||
{{ interface.domain }}
|
{{ 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 }}">
|
<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" %}
|
{% trans "Display the aliases" %}
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -91,7 +93,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<br>
|
<br>
|
||||||
{% if ipv6_enabled and interface.ipv6 != 'None' %}
|
{% if ipv6_enabled and interface.ipv6 != 'None' %}
|
||||||
<b>IPv6</b>
|
<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 }}">
|
<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" %}
|
{% trans "Display the IPv6 address" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -99,7 +103,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div style="width: 128px;">
|
<div style="width: 128px;">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<button class="btn btn-primary btn-sm dropdown-toggle" type="button" id="editioninterface" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
<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>
|
<i class="fa fa-edit"></i> <span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="editioninterface">
|
<ul class="dropdown-menu" aria-labelledby="editioninterface">
|
||||||
|
@ -156,7 +162,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan=5 style="border-top: none; padding: 1px;">
|
<td colspan=5 style="border-top: none; padding: 1px;">
|
||||||
<div class="collapse in" id="collapseIpv6_{{ interface.id }}">
|
<div class="collapse in" id="collapseIpv6_{{ interface.id }}">
|
||||||
<ul class="list-group" style="margin-bottom: 0px;">
|
<ul class="list-group" style="margin-bottom: 0;">
|
||||||
{% 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;">
|
<li class="list-group-item col-xs-6 col-sm-6 col-md-6" style="border: none;">
|
||||||
{{ ipv6 }}
|
{{ ipv6 }}
|
||||||
|
@ -171,7 +177,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan=5 style="border-top: none; padding: 1px;">
|
<td colspan=5 style="border-top: none; padding: 1px;">
|
||||||
<div class="collapse in" id="collapseDomain_{{ interface.id }}">
|
<div class="collapse in" id="collapseDomain_{{ interface.id }}">
|
||||||
<ul class="list-group" style="margin-bottom: 0px;">
|
<ul class="list-group" style="margin-bottom: 0;">
|
||||||
{% for al in interface.domain.related_domain.all %}
|
{% 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;">
|
<li class="list-group-item col-xs-6 col-sm-4 col-md-3" style="border: none;">
|
||||||
<a href="http://{{ al }}">
|
<a href="http://{{ al }}">
|
||||||
|
@ -191,7 +197,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</thead>
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -210,7 +215,6 @@ $("#machines_table").ready( function() {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% if machines_list.paginator %}
|
{% if machines_list.paginator %}
|
||||||
{% include "pagination.html" with list=machines_list %}
|
{% include 'pagination.html' with list=machines_list go_to_id="machines" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -47,4 +47,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -49,4 +49,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -54,4 +54,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -47,4 +47,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -51,4 +51,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -47,4 +47,3 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue