mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-23 07:23:46 +00:00
Merge branch 'master' into graph_topo
This commit is contained in:
commit
e690292e77
31 changed files with 647 additions and 353 deletions
|
@ -43,11 +43,6 @@ import radiusd # Module magique freeradius (radiusd.py is dummy)
|
|||
from django.core.wsgi import get_wsgi_application
|
||||
from django.db.models import Q
|
||||
|
||||
from machines.models import Interface, IpList, Nas, Domain
|
||||
from topologie.models import Port, Switch
|
||||
from users.models import User
|
||||
from preferences.models import OptionalTopologie
|
||||
|
||||
proj_path = "/var/www/re2o/"
|
||||
# This is so Django knows where to find stuff.
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
||||
|
@ -59,6 +54,12 @@ os.chdir(proj_path)
|
|||
# This is so models get loaded.
|
||||
application = get_wsgi_application()
|
||||
|
||||
from machines.models import Interface, IpList, Nas, Domain
|
||||
from topologie.models import Port, Switch
|
||||
from users.models import User
|
||||
from preferences.models import OptionalTopologie
|
||||
|
||||
|
||||
options, created = OptionalTopologie.objects.get_or_create()
|
||||
VLAN_NOK = options.vlan_decision_nok.vlan_id
|
||||
VLAN_OK = options.vlan_decision_ok.vlan_id
|
||||
|
|
|
@ -66,7 +66,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<td><p class="text-success">{{utilisateur.last}}</p></td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if droit != 'Superuser' %}
|
||||
<a href="{% url 'users:del-group' utilisateur.id droit.id %}">
|
||||
{% else %}
|
||||
<a href="{% url 'users:del-superuser' utilisateur.id %}">
|
||||
{% endif %}
|
||||
<button type="button" class="btn btn-danger" aria-label="Left Align">
|
||||
<span class="fa fa-user-times" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
@ -79,4 +83,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
|
|
@ -41,7 +41,7 @@ from django.urls import reverse
|
|||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Count, Max
|
||||
from django.db.models import Count, Max, F
|
||||
|
||||
from reversion.models import Revision
|
||||
from reversion.models import Version, ContentType
|
||||
|
@ -195,9 +195,7 @@ def revert_action(request, revision_id):
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view_all(IpList)
|
||||
@can_view_all(Interface)
|
||||
@can_view_all(User)
|
||||
@can_view_all(IpList, Interface, User)
|
||||
def stats_general(request):
|
||||
"""Statistiques générales affinées sur les ip, activées, utilisées par
|
||||
range, et les statistiques générales sur les users : users actifs,
|
||||
|
@ -313,10 +311,7 @@ def stats_general(request):
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view_app('users')
|
||||
@can_view_app('cotisations')
|
||||
@can_view_app('machines')
|
||||
@can_view_app('topologie')
|
||||
@can_view_app('users', 'cotisations', 'machines', 'topologie')
|
||||
def stats_models(request):
|
||||
"""Statistiques générales, affiche les comptages par models:
|
||||
nombre d'users, d'écoles, de droits, de bannissements,
|
||||
|
@ -469,9 +464,14 @@ def stats_droits(request):
|
|||
for droit in ListRight.objects.all().select_related('group_ptr'):
|
||||
stats_list[droit] = droit.user_set.all().annotate(
|
||||
num=Count('revision'),
|
||||
last=Max('revision__date_created')
|
||||
last=Max('revision__date_created'),
|
||||
)
|
||||
|
||||
stats_list['Superuser'] = User.objects.filter(is_superuser=True).annotate(
|
||||
num=Count('revision'),
|
||||
last=Max('revision__date_created'),
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
'logs/stats_droits.html',
|
||||
|
|
21
machines/migrations/0080_auto_20180502_2334.py
Normal file
21
machines/migrations/0080_auto_20180502_2334.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.6 on 2018-05-03 04:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('machines', '0079_auto_20180416_0107'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ns',
|
||||
name='ns',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='machines.Domain'),
|
||||
),
|
||||
]
|
|
@ -613,7 +613,7 @@ class Ns(RevMixin, AclMixin, models.Model):
|
|||
PRETTY_NAME = "Enregistrements NS"
|
||||
|
||||
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
||||
ns = models.OneToOneField('Domain', on_delete=models.PROTECT)
|
||||
ns = models.ForeignKey('Domain', on_delete=models.PROTECT)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
|
|
|
@ -28,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
{% include "pagination.html" with list=machines_list %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
<table class="table" id="machines_table">
|
||||
<colgroup>
|
||||
<col>
|
||||
|
|
|
@ -1272,12 +1272,7 @@ def index_nas(request):
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view_all(SOA)
|
||||
@can_view_all(Mx)
|
||||
@can_view_all(Ns)
|
||||
@can_view_all(Txt)
|
||||
@can_view_all(Srv)
|
||||
@can_view_all(Extension)
|
||||
@can_view_all(SOA, Mx, Ns, Txt, Srv, Extension)
|
||||
def index_extension(request):
|
||||
""" View displaying the list of existing extensions, the list of
|
||||
existing SOA records, the list of existing MX records , the list of
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# coding:utf-8
|
||||
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
|
||||
# se veut agnostique au réseau considéré, de manière à être installable en
|
||||
# quelques clics.
|
||||
|
|
|
@ -58,13 +58,8 @@ from . import forms
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view_all(OptionalUser)
|
||||
@can_view_all(OptionalMachine)
|
||||
@can_view_all(OptionalTopologie)
|
||||
@can_view_all(GeneralOption)
|
||||
@can_view_all(AssoOption)
|
||||
@can_view_all(MailMessageOption)
|
||||
@can_view_all(HomeOption)
|
||||
@can_view_all(OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption,
|
||||
AssoOption, MailMessageOption, HomeOption)
|
||||
def display_options(request):
|
||||
"""Vue pour affichage des options (en vrac) classé selon les models
|
||||
correspondants dans un tableau"""
|
||||
|
@ -149,7 +144,11 @@ def add_service(request):
|
|||
@can_edit(Service)
|
||||
def edit_service(request, service_instance, **_kwargs):
|
||||
"""Edition des services affichés sur la page d'accueil"""
|
||||
service = ServiceForm(request.POST or None, request.FILES or None,instance=service_instance)
|
||||
service = ServiceForm(
|
||||
request.POST or None,
|
||||
request.FILES or None,
|
||||
instance=service_instance
|
||||
)
|
||||
if service.is_valid():
|
||||
with transaction.atomic(), reversion.create_revision():
|
||||
service.save()
|
||||
|
|
338
re2o/acl.py
338
re2o/acl.py
|
@ -28,135 +28,177 @@ Here are defined some decorators that can be used in views to handle ACL.
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
from itertools import chain
|
||||
|
||||
from django.db.models import Model
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def can_create(model):
|
||||
"""Decorator to check if an user can create a model.
|
||||
It assumes that a valid user exists in the request and that the model has a
|
||||
method can_create(user) which returns true if the user can create this kind
|
||||
of models.
|
||||
def acl_base_decorator(method_name, *targets, on_instance=True):
|
||||
"""Base decorator for acl. It checks if the `request.user` has the
|
||||
permission by calling model.method_name. If the flag on_instance is True,
|
||||
tries to get an instance of the model by calling
|
||||
`model.get_instance(*args, **kwargs)` and runs `instance.mehod_name`
|
||||
rather than model.method_name.
|
||||
|
||||
It is not intended to be used as is. It is a base for others ACL
|
||||
decorators.
|
||||
|
||||
Args:
|
||||
method_name: The name of the method which is to to be used for ACL.
|
||||
(ex: 'can_edit') WARNING: if no method called 'method_name' exists,
|
||||
then no error will be triggered, the decorator will act as if
|
||||
permission was granted. This is to allow you to run ACL tests on
|
||||
fields only. If the method exists, it has to return a 2-tuple
|
||||
`(can, reason)` with `can` being a boolean stating whether the
|
||||
access is granted and `reason` a message to be displayed if `can`
|
||||
equals `False` (can be `None`)
|
||||
*targets: The targets. Targets are specified like a sequence of models
|
||||
and fields names. As an example
|
||||
```
|
||||
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
|
||||
ModelB, ModelC, 'field3', on_instance=False)
|
||||
```
|
||||
will make the following calls (where `user` is the current user,
|
||||
`*args` and `**kwargs` are the arguments initially passed to the
|
||||
view):
|
||||
- `ModelA.can_edit(user, *args, **kwargs)`
|
||||
- `ModelA.can_change_field1(user, *args, **kwargs)`
|
||||
- `ModelA.can_change_field2(user, *args, **kwargs)`
|
||||
- `ModelB.can_edit(user, *args, **kwargs)`
|
||||
- `ModelC.can_edit(user, *args, **kwargs)`
|
||||
- `ModelC.can_change_field3(user, *args, **kwargs)`
|
||||
|
||||
Note that
|
||||
```
|
||||
acl_base_decorator('can_edit', 'field1', ModelA, 'field2', \
|
||||
on_instance=False)
|
||||
```
|
||||
would have the same effect that
|
||||
```
|
||||
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', \
|
||||
on_instance=False)
|
||||
```
|
||||
But don't do that, it's silly.
|
||||
on_instance: When `on_instance` equals `False`, the decorator runs the
|
||||
ACL method on the model class rather than on an instance. If an
|
||||
instance need to fetched, it is done calling the assumed existing
|
||||
method `get_instance` of the model, with the arguments originally
|
||||
passed to the view.
|
||||
|
||||
Returns:
|
||||
The user is either redirected to their own page with an explanation
|
||||
message if at least one access is not granted, or to the view. In order
|
||||
to avoid duplicate DB calls, when the `on_instance` flag equals `True`,
|
||||
the instances are passed to the view. Example, with this decorator:
|
||||
```
|
||||
acl_base_decorator('can_edit', ModelA, 'field1', 'field2', ModelB,\
|
||||
ModelC)
|
||||
```
|
||||
The view will be called like this:
|
||||
```
|
||||
view(request, instance_of_A, instance_of_b, *args, **kwargs)
|
||||
```
|
||||
where `*args` and `**kwargs` are the original view arguments.
|
||||
"""
|
||||
|
||||
def group_targets():
|
||||
"""This generator parses the targets of the decorator, yielding
|
||||
2-tuples of (model, [fields]).
|
||||
"""
|
||||
current_target = None
|
||||
current_fields = []
|
||||
for target in targets:
|
||||
if not isinstance(target, str):
|
||||
if current_target:
|
||||
yield (current_target, current_fields)
|
||||
current_target = target
|
||||
current_fields = []
|
||||
else:
|
||||
current_fields.append(target)
|
||||
yield (current_target, current_fields)
|
||||
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
can, msg = model.can_create(request.user, *args, **kwargs)
|
||||
if not can:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse('index'))
|
||||
return view(request, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
"""The wrapper used for a specific request"""
|
||||
instances = []
|
||||
|
||||
def process_target(target, fields):
|
||||
"""This function calls the methods on the target and checks for
|
||||
the can_change_`field` method with the given fields. It also
|
||||
stores the instances of models in order to avoid duplicate DB
|
||||
calls for the view.
|
||||
"""
|
||||
if on_instance:
|
||||
try:
|
||||
target = target.get_instance(*args, **kwargs)
|
||||
instances.append(target)
|
||||
except target.DoesNotExist:
|
||||
yield False, u"Entrée inexistante"
|
||||
return
|
||||
if hasattr(target, method_name):
|
||||
can_fct = getattr(target, method_name)
|
||||
yield can_fct(request.user, *args, **kwargs)
|
||||
for field in fields:
|
||||
can_change_fct = getattr(target, 'can_change_' + field)
|
||||
yield can_change_fct(request.user, *args, **kwargs)
|
||||
|
||||
def can_edit(model, *field_list):
|
||||
"""Decorator to check if an user can edit a model.
|
||||
It tries to get an instance of the model, using
|
||||
`model.get_instance(*args, **kwargs)` and assumes that the model has a
|
||||
method `can_edit(user)` which returns `true` if the user can edit this
|
||||
kind of models.
|
||||
"""
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
try:
|
||||
instance = model.get_instance(*args, **kwargs)
|
||||
except model.DoesNotExist:
|
||||
messages.error(request, u"Entrée inexistante")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
can, msg = instance.can_edit(request.user)
|
||||
if not can:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
for field in field_list:
|
||||
can_change_fct = getattr(instance, 'can_change_' + field)
|
||||
can, msg = can_change_fct(request.user, *args, **kwargs)
|
||||
if not can:
|
||||
error_messages = [
|
||||
x[1] for x in chain.from_iterable(
|
||||
process_target(x[0], x[1]) for x in group_targets()
|
||||
) if not x[0]
|
||||
]
|
||||
if error_messages:
|
||||
for msg in error_messages:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, instance, *args, **kwargs)
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, *chain(instances, args), **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def can_change(model, *field_list):
|
||||
def can_create(*models):
|
||||
"""Decorator to check if an user can create the given models. It runs
|
||||
`acl_base_decorator` with the flag `on_instance=False` and the method
|
||||
'can_create'. See `acl_base_decorator` documentation for further details.
|
||||
"""
|
||||
return acl_base_decorator('can_create', *models, on_instance=False)
|
||||
|
||||
|
||||
def can_edit(*targets):
|
||||
"""Decorator to check if an user can edit the models.
|
||||
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
||||
method 'can_edit'. See `acl_base_decorator` documentation for further
|
||||
details.
|
||||
"""
|
||||
return acl_base_decorator('can_edit', *targets)
|
||||
|
||||
|
||||
def can_change(*targets):
|
||||
"""Decorator to check if an user can edit a field of a model class.
|
||||
Difference with can_edit : take a class and not an instance
|
||||
Difference with can_edit : takes a class and not an instance
|
||||
It runs `acl_base_decorator` with the flag `on_instance=False` and the
|
||||
method 'can_change'. See `acl_base_decorator` documentation for further
|
||||
details.
|
||||
"""
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
for field in field_list:
|
||||
can_change_fct = getattr(model, 'can_change_' + field)
|
||||
can, msg = can_change_fct(request.user, *args, **kwargs)
|
||||
if not can:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
return acl_base_decorator('can_change', *targets)
|
||||
|
||||
|
||||
def can_delete(model):
|
||||
def can_delete(*targets):
|
||||
"""Decorator to check if an user can delete a model.
|
||||
It tries to get an instance of the model, using
|
||||
`model.get_instance(*args, **kwargs)` and assumes that the model has a
|
||||
method `can_delete(user)` which returns `true` if the user can delete this
|
||||
kind of models.
|
||||
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
||||
method 'can_edit'. See `acl_base_decorator` documentation for further
|
||||
details.
|
||||
"""
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
try:
|
||||
instance = model.get_instance(*args, **kwargs)
|
||||
except model.DoesNotExist:
|
||||
messages.error(request, u"Entrée inexistante")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
can, msg = instance.can_delete(request.user)
|
||||
if not can:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, instance, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
return acl_base_decorator('can_delete', *targets)
|
||||
|
||||
|
||||
def can_delete_set(model):
|
||||
|
@ -187,84 +229,34 @@ def can_delete_set(model):
|
|||
return decorator
|
||||
|
||||
|
||||
def can_view(model):
|
||||
def can_view(*targets):
|
||||
"""Decorator to check if an user can view a model.
|
||||
It tries to get an instance of the model, using
|
||||
`model.get_instance(*args, **kwargs)` and assumes that the model has a
|
||||
method `can_view(user)` which returns `true` if the user can view this
|
||||
kind of models.
|
||||
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
||||
method 'can_view'. See `acl_base_decorator` documentation for further
|
||||
details.
|
||||
"""
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
try:
|
||||
instance = model.get_instance(*args, **kwargs)
|
||||
except model.DoesNotExist:
|
||||
messages.error(request, u"Entrée inexistante")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
can, msg = instance.can_view(request.user)
|
||||
if not can:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, instance, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
return acl_base_decorator('can_view', *targets)
|
||||
|
||||
|
||||
def can_view_all(model):
|
||||
def can_view_all(*targets):
|
||||
"""Decorator to check if an user can view a class of model.
|
||||
It runs `acl_base_decorator` with the flag `on_instance=False` and the
|
||||
method 'can_view_all'. See `acl_base_decorator` documentation for further
|
||||
details.
|
||||
"""
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
can, msg = model.can_view_all(request.user)
|
||||
if not can:
|
||||
messages.error(
|
||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return view(request, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
return acl_base_decorator('can_view_all', *targets, on_instance=False)
|
||||
|
||||
|
||||
def can_view_app(app_name):
|
||||
"""Decorator to check if an user can view an application.
|
||||
def can_view_app(*apps_name):
|
||||
"""Decorator to check if an user can view the applications.
|
||||
"""
|
||||
assert app_name in sys.modules.keys()
|
||||
|
||||
def decorator(view):
|
||||
"""The decorator to use on a specific view
|
||||
"""
|
||||
def wrapper(request, *args, **kwargs):
|
||||
"""The wrapper used for a specific request
|
||||
"""
|
||||
app = sys.modules[app_name]
|
||||
can, msg = app.can_view(request.user)
|
||||
if can:
|
||||
return view(request, *args, **kwargs)
|
||||
messages.error(request, msg)
|
||||
return redirect(reverse(
|
||||
'users:profil',
|
||||
kwargs={'userid': str(request.user.id)}
|
||||
))
|
||||
return wrapper
|
||||
return decorator
|
||||
for app_name in apps_name:
|
||||
assert app_name in sys.modules.keys()
|
||||
return acl_base_decorator(
|
||||
'can_view',
|
||||
*chain(sys.modules[app_name] for app_name in apps_name),
|
||||
on_instance=False
|
||||
)
|
||||
|
||||
|
||||
def can_edit_history(view):
|
||||
|
|
Binary file not shown.
|
@ -30,6 +30,14 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: settings.py:140
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: settings.py:141
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: templates/re2o/about.html:29 templates/re2o/about.html:35
|
||||
msgid "About Re2o"
|
||||
msgstr "A propos de Re2o"
|
||||
|
@ -147,6 +155,10 @@ msgstr ""
|
|||
msgid "Dependencies"
|
||||
msgstr "Dépendances"
|
||||
|
||||
#: templates/re2o/buttons/setlang.html:34
|
||||
msgid "Translation in development"
|
||||
msgstr "Traduction en développement"
|
||||
|
||||
#: views.py:172
|
||||
msgid "No Git repository configured"
|
||||
msgstr "Aucun repository git configuré"
|
||||
|
|
|
@ -37,6 +37,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import os
|
||||
from .settings_local import *
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
# The root directory for the project
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
|
@ -135,6 +136,10 @@ LOCALE_PATHS = [
|
|||
# For translations outside of apps
|
||||
os.path.join(BASE_DIR, 'templates', 'locale').replace('\\', '/')
|
||||
]
|
||||
LANGUAGES = [
|
||||
('en', _('English')),
|
||||
('fr', _('French'))
|
||||
]
|
||||
|
||||
# Should use time zone ?
|
||||
USE_TZ = True
|
||||
|
|
|
@ -55,6 +55,7 @@ urlpatterns = [
|
|||
url(r'^about/$', about_page, name='about'),
|
||||
url('^logout/', auth_views.logout, {'next_page': '/'}),
|
||||
url('^', include('django.contrib.auth.urls')),
|
||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^users/', include('users.urls', namespace='users')),
|
||||
url(r'^search/', include('search.urls', namespace='search')),
|
||||
|
|
|
@ -44,6 +44,49 @@ from cotisations.models import Cotisation, Facture, Vente
|
|||
from machines.models import Interface, Machine
|
||||
from users.models import Adherent, User, Ban, Whitelist
|
||||
|
||||
# Mapping of srtftime format for better understanding
|
||||
# https://docs.python.org/3.6/library/datetime.html#strftime-strptime-behavior
|
||||
datetime_mapping={
|
||||
'%a': '%a',
|
||||
'%A': '%A',
|
||||
'%w': '%w',
|
||||
'%d': 'dd',
|
||||
'%b': '%b',
|
||||
'%B': '%B',
|
||||
'%m': 'mm',
|
||||
'%y': 'yy',
|
||||
'%Y': 'yyyy',
|
||||
'%H': 'HH',
|
||||
'%I': 'HH(12h)',
|
||||
'%p': 'AMPM',
|
||||
'%M': 'MM',
|
||||
'%S': 'SS',
|
||||
'%f': 'µµ',
|
||||
'%z': 'UTC(+/-HHMM)',
|
||||
'%Z': 'UTC(TZ)',
|
||||
'%j': '%j',
|
||||
'%U': 'ww',
|
||||
'%W': 'ww',
|
||||
'%c': '%c',
|
||||
'%x': '%x',
|
||||
'%X': '%X',
|
||||
'%%': '%%',
|
||||
}
|
||||
|
||||
|
||||
def convert_datetime_format(format):
|
||||
i=0
|
||||
new_format = ""
|
||||
while i < len(format):
|
||||
if format[i] == '%':
|
||||
char = format[i:i+2]
|
||||
new_format += datetime_mapping.get(char, char)
|
||||
i += 2
|
||||
else:
|
||||
new_format += format[i]
|
||||
i += 1
|
||||
return new_format
|
||||
|
||||
|
||||
def all_adherent(search_time=None):
|
||||
""" Fonction renvoyant tous les users adherents. Optimisee pour n'est
|
||||
|
@ -318,3 +361,19 @@ def remove_user_room(room):
|
|||
return
|
||||
user.room = None
|
||||
user.save()
|
||||
|
||||
|
||||
def get_input_formats_help_text(input_formats):
|
||||
"""Returns a help text about the possible input formats"""
|
||||
if len(input_formats) > 1:
|
||||
help_text_template="Format: {main} {more}"
|
||||
else:
|
||||
help_text_template="Format: {main}"
|
||||
more_text_template="<i class=\"fa fa-question-circle\" title=\"{}\"></i>"
|
||||
help_text = help_text_template.format(
|
||||
main=convert_datetime_format(input_formats[0]),
|
||||
more=more_text_template.format(
|
||||
'\n'.join(map(convert_datetime_format, input_formats))
|
||||
)
|
||||
)
|
||||
return help_text
|
||||
|
|
|
@ -26,6 +26,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django import forms
|
||||
from django.forms import Form
|
||||
from re2o.utils import get_input_formats_help_text
|
||||
|
||||
CHOICES_USER = (
|
||||
('0', 'Actifs'),
|
||||
|
@ -91,12 +92,17 @@ class SearchFormPlus(Form):
|
|||
s = forms.DateField(
|
||||
required=False,
|
||||
label="Date de début",
|
||||
help_text='DD/MM/YYYY',
|
||||
input_formats=['%d/%m/%Y']
|
||||
)
|
||||
e = forms.DateField(
|
||||
required=False,
|
||||
help_text='DD/MM/YYYY',
|
||||
input_formats=['%d/%m/%Y'],
|
||||
label="Date de fin"
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SearchFormPlus, self).__init__(*args, **kwargs)
|
||||
self.fields['s'].help_text = get_input_formats_help_text(
|
||||
self.fields['s'].input_formats
|
||||
)
|
||||
self.fields['e'].help_text = get_input_formats_help_text(
|
||||
self.fields['e'].input_formats
|
||||
)
|
||||
|
|
|
@ -35,6 +35,26 @@ footer a {
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Add right colors for buttons in dropdown in navbar-inverse (else it is light
|
||||
* gray on white bg and white when hovered */
|
||||
.navbar-inverse .dropdown-menu .btn-link {
|
||||
text-decoration: none;
|
||||
color: #262626;
|
||||
padding: 0;
|
||||
}
|
||||
.navbar-inverse .dropdown-menu .btn-link:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
@media screen and (max-width: 767px) {
|
||||
.navbar-inverse .dropdown-menu .btn-link {
|
||||
color: #9d9d9d;
|
||||
}
|
||||
.navbar-inverse .dropdown-menu .btn-link:hover {
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set height of the grid so .sidenav can be 100% (adjust as needed) */
|
||||
.row.content {
|
||||
height: 100%;
|
||||
|
|
|
@ -118,7 +118,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</li>
|
||||
{% acl_end %}
|
||||
<li>
|
||||
<a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> {% trans "About" %}</a>
|
||||
<a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> {% trans "About" %}</a>
|
||||
</li>
|
||||
{% if not request.user.is_authenticated %}
|
||||
{% if var_sa %}
|
||||
|
@ -145,48 +145,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
</div>
|
||||
</form>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</ul>
|
||||
{% comment %}
|
||||
<div class="navbar-right">
|
||||
<form action="{% url "search:search"%}" class="navbar-form" role="search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search" name="q" id="search-term" {% if search_term %}value="{{ search_term }}"{% endif %}>
|
||||
<div class="input-group-btn">
|
||||
<button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
|
||||
<a href="{% url "search:searchp" %}" class="btn btn-default" role="button"><i class="fa fa-plus"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if not request.user.is_authenticated %}
|
||||
{% if var_sa %}
|
||||
<li>
|
||||
<a href="{% url 'users:new-user' %}">
|
||||
<i class="fa fa-user-plus"></i> Créer un compte
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{% url 'login' %}">
|
||||
<i class="fa fa-sign-in-alt"></i> Login
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% can_view_app preferences %}
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{% url 'preferences:display-options' %}">
|
||||
<i class="fa fa-cogs"></i> Preferences
|
||||
</a>
|
||||
</li>
|
||||
{% acl_end %}
|
||||
<li>
|
||||
<a href="{% url 'about' %}"><i class="fa fa-info-circle"></i> A propos</a>
|
||||
{% include 'buttons/setlang.html' %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
50
templates/buttons/setlang.html
Normal file
50
templates/buttons/setlang.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
{% 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 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.
|
||||
{% endcomment %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
{% get_available_languages as LANGUAGES %}
|
||||
{% get_language_info_list for LANGUAGES as languages %}
|
||||
|
||||
<a class="dropdown" type="button" role="button" id="setlang"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-globe"></i> <span class="caret"></span>
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="setlang">
|
||||
<p style="text-align: center"><small><i class="fa fa-exclamation-triangle"></i> {% trans "Translation in development" %}</small></p>
|
||||
<hr>
|
||||
<form method="post" action="{% url 'set_language' %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.path }}">
|
||||
{% for language in languages %}
|
||||
<button type="submit" name="language" value="{{ language.code }}"
|
||||
class="btn btn-link btn-block
|
||||
{% if language.code == LANGUAGE_CODE %}disabled{% endif %}">
|
||||
{% if language.code == LANGUAGE_CODE %}
|
||||
<i class="fa fa-check"></i>
|
||||
{% endif %}
|
||||
{{ language.name_local | title }} ({{ language.code }})
|
||||
</button>
|
||||
{% endfor %}
|
||||
</form>
|
||||
</div>
|
|
@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load url_insert_param %}
|
||||
|
||||
{% if list.paginator.num_pages > 1 %}
|
||||
<ul class="pagination nav navbar-nav">
|
||||
{% if list.has_previous %}
|
||||
<li><a href="{% url_insert_param request.get_full_path page=1 %}"> << </a></li>
|
||||
|
@ -39,5 +40,5 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<li><a href="{% url_insert_param request.get_full_path page=list.next_page_number %}"> > </a></li>
|
||||
<li><a href="{% url_insert_param request.get_full_path page=list.paginator.page_range|length %}"> >> </a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
|
@ -24,69 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
|
||||
{% load acl %}
|
||||
|
||||
|
||||
<div class="table-responsive" style="font-size: 12px">
|
||||
<table class="table table-bordered text-center text-nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"::2" %}
|
||||
<td class="bg-primary text-white">{{ port.port }}</td>
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"::2" %}
|
||||
{% if port.room %}
|
||||
<td class="p-3 mb-2 bg-success text-dark">
|
||||
{{ port.room }}
|
||||
{% elif port.machine_interface %}
|
||||
<td class="p-3 mb-2 bg-warning text-dark">
|
||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">{{ port.machine_interface }}</a>
|
||||
{% elif port.related%}
|
||||
<td class="p-3 mb-2 bg-danger text-dark">
|
||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">{{ port.related }}</a>
|
||||
{% else %}
|
||||
<td class="p-3 mb-2 bg-info text-dark">
|
||||
Vide
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"1::2" %}
|
||||
<td class="bg-primary text-white">{{ port.port }}</td>
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"1::2" %}
|
||||
{% if port.room %}
|
||||
<td class="p-3 mb-2 bg-success text-dark">
|
||||
{{ port.room }}
|
||||
{% elif port.machine_interface %}
|
||||
<td class="p-3 mb-2 bg-warning text-dark">
|
||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">{{ port.machine_interface }}</a>
|
||||
{% elif port.related%}
|
||||
<td class="p-3 mb-2 bg-danger text-dark">
|
||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">{{ port.related }}</a>
|
||||
{% else %}
|
||||
<td class="p-3 mb-2 bg-info text-dark">
|
||||
Vide
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
|
@ -105,37 +42,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<tr>
|
||||
<td>{{ port.port }}</td>
|
||||
<td>
|
||||
{% if port.room %}
|
||||
{{ port.room }}
|
||||
{% endif %}
|
||||
{% if port.room %}{{ port.room }}{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if port.machine_interface %}
|
||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">{{ port.machine_interface }}</a>
|
||||
{% endif %}
|
||||
{% if port.machine_interface %}
|
||||
{% can_view port.machine_interface.machine.user %}
|
||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">
|
||||
{{ port.machine_interface }}
|
||||
</a>
|
||||
{% acl_else %}
|
||||
{{ port.machine_interface }}
|
||||
{% acl_end %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if port.related %}
|
||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">{{ port.related }}</a>
|
||||
{% endif %}
|
||||
{% if port.related %}
|
||||
{% can_view port.related.switch %}
|
||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">
|
||||
{{ port.related }}
|
||||
</a>
|
||||
{% acl_else %}
|
||||
{{ port.related }}
|
||||
{% acl_end %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ port.radius }}</td>
|
||||
<td>{% if not port.vlan_force %} Aucun{%else %}{{ port.vlan_force }}{% endif %}</td>
|
||||
<td>{% if not port.vlan_force %}Aucun{% else %}{{ port.vlan_force }}{% endif %}</td>
|
||||
<td>{{ port.details }}</td>
|
||||
<td class="text-right">
|
||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'port' port.pk %}">
|
||||
<i class="fa fa-history"></i>
|
||||
</a>
|
||||
{% can_edit port %}
|
||||
{% can_edit port %}
|
||||
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-port' port.id %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% can_delete port %}
|
||||
{% acl_end %}
|
||||
{% can_delete port %}
|
||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-port' port.pk %}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
{% acl_end %}
|
||||
{% acl_end %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
116
topologie/templates/topologie/aff_repr_switch.html
Normal file
116
topologie/templates/topologie/aff_repr_switch.html
Normal file
|
@ -0,0 +1,116 @@
|
|||
{% 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 %}
|
||||
|
||||
<div class="table-responsive" style="font-size: 12px">
|
||||
<table class="table table-bordered text-center text-nowrap">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"::2" %}
|
||||
<td class="bg-primary text-white">{{ port.port }}</td>
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"::2" %}
|
||||
{% if port.room %}
|
||||
<td class="p-3 mb-2 bg-success text-dark">
|
||||
{{ port.room }}
|
||||
</td>
|
||||
{% elif port.machine_interface %}
|
||||
<td class="p-3 mb-2 bg-warning text-dark">
|
||||
{% can_view port.machine_interface.machine.user %}
|
||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">
|
||||
{{ port.machine_interface }}
|
||||
</a>
|
||||
{% acl_else %}
|
||||
{{ port.machine_interface }}
|
||||
{% acl_end %}
|
||||
</td>
|
||||
{% elif port.related%}
|
||||
<td class="p-3 mb-2 bg-danger text-dark">
|
||||
{% can_view port.related.switch %}
|
||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">
|
||||
{{ port.related }}
|
||||
</a>
|
||||
{% acl_else %}
|
||||
{{ port.related }}
|
||||
{% acl_end %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="p-3 mb-2 bg-info text-dark">
|
||||
Vide
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"1::2" %}
|
||||
<td class="bg-primary text-white">{{ port.port }}</td>
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
{% for port in port_list|slice:"1::2" %}
|
||||
{% if port.room %}
|
||||
<td class="p-3 mb-2 bg-success text-dark">
|
||||
{{ port.room }}
|
||||
</td>
|
||||
{% elif port.machine_interface %}
|
||||
<td class="p-3 mb-2 bg-warning text-dark">
|
||||
{% can_view port.machine_interface.machine.user %}
|
||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">
|
||||
{{ port.machine_interface }}
|
||||
</a>
|
||||
{% acl_else %}
|
||||
{{ port.machine_interface }}
|
||||
{% acl_end %}
|
||||
</td>
|
||||
{% elif port.related%}
|
||||
<td class="p-3 mb-2 bg-danger text-dark">
|
||||
{% can_view port.related.switch %}
|
||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">
|
||||
{{ port.related }}
|
||||
</a>
|
||||
{% acl_else %}
|
||||
{{ port.related }}
|
||||
{% acl_end %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="p-3 mb-2 bg-info text-dark">
|
||||
Vide
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
|
@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:create-ports' id_switch %}"><i class="fa fa-plus"></i> Ajouter des ports</a>
|
||||
{% acl_end %}
|
||||
<hr>
|
||||
{% include "topologie/aff_repr_switch.html" with port_list=port_list %}
|
||||
{% include "topologie/aff_port.html" with port_list=port_list %}
|
||||
<br />
|
||||
<br />
|
||||
|
|
|
@ -208,9 +208,7 @@ def index_ap(request):
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view_all(Stack)
|
||||
@can_view_all(Building)
|
||||
@can_view_all(SwitchBay)
|
||||
@can_view_all(Stack, Building, SwitchBay)
|
||||
def index_physical_grouping(request):
|
||||
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
|
||||
stack_list = (Stack.objects
|
||||
|
@ -249,8 +247,7 @@ def index_physical_grouping(request):
|
|||
|
||||
|
||||
@login_required
|
||||
@can_view_all(ModelSwitch)
|
||||
@can_view_all(ConstructorSwitch)
|
||||
@can_view_all(ModelSwitch, ConstructorSwitch)
|
||||
def index_model_switch(request):
|
||||
""" Affichage de l'ensemble des modèles de switches"""
|
||||
model_switch_list = ModelSwitch.objects.select_related('constructor')
|
||||
|
|
|
@ -41,7 +41,7 @@ from django.utils import timezone
|
|||
from django.contrib.auth.models import Group, Permission
|
||||
|
||||
from preferences.models import OptionalUser
|
||||
from re2o.utils import remove_user_room
|
||||
from re2o.utils import remove_user_room, get_input_formats_help_text
|
||||
from re2o.mixins import FormRevMixin
|
||||
from re2o.field_permissions import FieldPermissionFormMixin
|
||||
|
||||
|
@ -422,12 +422,19 @@ class ServiceUserForm(FormRevMixin, ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = ServiceUser
|
||||
fields = ('pseudo', 'access_group')
|
||||
fields = ('pseudo', 'access_group','comment')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
def save(self, commit=True):
|
||||
"""Changement du mot de passe"""
|
||||
user = super(ServiceUserForm, self).save(commit=False)
|
||||
if self.cleaned_data['password']:
|
||||
user.set_password(self.cleaned_data.get("password"))
|
||||
user.save()
|
||||
|
||||
|
||||
class EditServiceUserForm(ServiceUserForm):
|
||||
"""Formulaire d'edition de base d'un service user. Ne permet
|
||||
|
@ -447,7 +454,7 @@ class StateForm(FormRevMixin, ModelForm):
|
|||
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
class GroupForm(FormRevMixin, ModelForm):
|
||||
class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
|
||||
""" Gestion des groupes d'un user"""
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
Group.objects.all(),
|
||||
|
@ -457,11 +464,13 @@ class GroupForm(FormRevMixin, ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['groups']
|
||||
fields = ['is_superuser', 'groups']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(GroupForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
if 'is_superuser' in self.fields:
|
||||
self.fields['is_superuser'].label = "Superuser"
|
||||
|
||||
|
||||
class SchoolForm(FormRevMixin, ModelForm):
|
||||
|
@ -558,6 +567,9 @@ class BanForm(FormRevMixin, ModelForm):
|
|||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(BanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['date_end'].label = 'Date de fin'
|
||||
self.fields['date_end'].help_text = get_input_formats_help_text(
|
||||
self.fields['date_end'].input_formats
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Ban
|
||||
|
@ -570,6 +582,9 @@ class WhitelistForm(FormRevMixin, ModelForm):
|
|||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||
super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||
self.fields['date_end'].label = 'Date de fin'
|
||||
self.fields['date_end'].help_text = get_input_formats_help_text(
|
||||
self.fields['date_end'].input_formats
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Whitelist
|
||||
|
|
25
users/migrations/0072_auto_20180426_2021.py
Normal file
25
users/migrations/0072_auto_20180426_2021.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2018-04-26 18:21
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0071_auto_20180415_1252'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ban',
|
||||
name='date_end',
|
||||
field=models.DateTimeField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='whitelist',
|
||||
name='date_end',
|
||||
field=models.DateTimeField(),
|
||||
),
|
||||
]
|
|
@ -812,6 +812,18 @@ class User(RevMixin, FieldPermissionModelMixin, AbstractBaseUser,
|
|||
"Droit requis pour éditer les groupes de l'user"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def can_change_is_superuser(user_request, *_args, **_kwargs):
|
||||
""" Check if an user can change a is_superuser flag
|
||||
|
||||
:param user_request: The user who request
|
||||
:returns: a message and a boolean which is True if permission is granted.
|
||||
"""
|
||||
return (
|
||||
user_request.is_superuser,
|
||||
"Droit superuser requis pour éditer le flag superuser"
|
||||
)
|
||||
|
||||
def can_view(self, user_request, *_args, **_kwargs):
|
||||
"""Check if an user can view an user object.
|
||||
|
||||
|
@ -1229,7 +1241,7 @@ class Ban(RevMixin, AclMixin, models.Model):
|
|||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
raison = models.CharField(max_length=255)
|
||||
date_start = models.DateTimeField(auto_now_add=True)
|
||||
date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S')
|
||||
date_end = models.DateTimeField()
|
||||
state = models.IntegerField(choices=STATES, default=STATE_HARD)
|
||||
|
||||
class Meta:
|
||||
|
@ -1314,7 +1326,7 @@ class Whitelist(RevMixin, AclMixin, models.Model):
|
|||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||
raison = models.CharField(max_length=255)
|
||||
date_start = models.DateTimeField(auto_now_add=True)
|
||||
date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S')
|
||||
date_end = models.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
|
|
|
@ -33,6 +33,44 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% if superuser_right %}
|
||||
<tr class="active">
|
||||
<td>Superuser</td>
|
||||
<td></td>
|
||||
<td>True</td>
|
||||
<td>
|
||||
<button class="btn btn-default" data-parent="#accordion_superuser" type="button" data-toggle="collapse" data-target="#collapseListRight_user_superuser" aria-expanded="true" aria-controls="collapseListRight_user_superuser">
|
||||
Utilisateurs ({{ superuser_right.count }})
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
Donne tous les droits sur Re2o.
|
||||
</td>
|
||||
<td class="text-right">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan=5>
|
||||
<div class="panel-group" id="accordion_superuser" role="tablist" aria-multiselectable="true" style="margin-bottom: 0px;">
|
||||
<div class="panel" style="border: none;">
|
||||
<div class="panel-collapse collapse in" id="collapseListRight_user_superuser" role="tabpanel">
|
||||
<ul class="list-group" style="margin-bottom: 0px">
|
||||
{% for user in superuser_right %}
|
||||
<li class="list-group-item col-xs-12 col-sm-6 col-md-4" style="border:none;">
|
||||
{{user}}
|
||||
<a role="button" href="{% url 'users:del-superuser' user.pk %}" title="{{ desc|default:"Supprimer" }}">
|
||||
<i class="fa fa-times" style="color:red"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% endif %}
|
||||
{% for listright in listright_list %}
|
||||
<tr class="active">
|
||||
<td>
|
||||
|
@ -48,9 +86,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
<button class="btn btn-default" data-parent="#accordion_{{listright.gid}}" type="button" data-toggle="collapse" data-target="#collapseListRight_perm_{{listright.gid}}" aria-expanded="true" aria-controls="collapseListRight_perm_{{listright.gid}}">
|
||||
Ensemble des permissions ({{ listright.permissions.all|length }})
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ listright.details }}</td>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ listright.details }}</td>
|
||||
<td class="text-right">
|
||||
{% include 'buttons/edit.html' with href='users:edit-listright' id=listright.id %}
|
||||
{% include 'buttons/history.html' with href='users:history' name='listright' id=listright.id %}
|
||||
|
|
|
@ -245,7 +245,7 @@ non adhérent</span>{% endif %} et votre connexion est {% if users.has_access %}
|
|||
<h3 class="panel-title pull-left">
|
||||
<i class="fa fa-desktop"></i>
|
||||
Machines
|
||||
<span class="badge">{{machines_list.count}}</span>
|
||||
<span class="badge">{{nb_machines}}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div id="collapse3" class="panel-collapse collapse">
|
||||
|
|
|
@ -43,6 +43,9 @@ urlpatterns = [
|
|||
url(r'^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$',
|
||||
views.del_group,
|
||||
name='del-group'),
|
||||
url(r'^del_superuser/(?P<userid>[0-9]+)$',
|
||||
views.del_superuser,
|
||||
name='del-superuser'),
|
||||
url(r'^new_serviceuser/$', views.new_serviceuser, name='new-serviceuser'),
|
||||
url(r'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
|
||||
views.edit_serviceuser,
|
||||
|
|
|
@ -246,7 +246,7 @@ def state(request, user, userid):
|
|||
@can_edit(User, 'groups')
|
||||
def groups(request, user, userid):
|
||||
""" View to edit the groups of a user """
|
||||
group_form = GroupForm(request.POST or None, instance=user)
|
||||
group_form = GroupForm(request.POST or None, instance=user, user=request.user)
|
||||
if group_form.is_valid():
|
||||
if group_form.changed_data:
|
||||
group_form.save()
|
||||
|
@ -294,18 +294,26 @@ def del_group(request, user, listrightid, **_kwargs):
|
|||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
|
||||
@login_required
|
||||
@can_edit(User, 'is_superuser')
|
||||
def del_superuser(request, user, **_kwargs):
|
||||
"""Remove the superuser right of an user."""
|
||||
user.is_superuser = False
|
||||
user.save()
|
||||
messages.success(request, "%s n'est plus superuser" % user)
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
|
||||
@login_required
|
||||
@can_create(ServiceUser)
|
||||
def new_serviceuser(request):
|
||||
""" Vue de création d'un nouvel utilisateur service"""
|
||||
user = ServiceUserForm(request.POST or None)
|
||||
if user.is_valid():
|
||||
user_object = user.save(commit=False)
|
||||
user_object.set_password(user.cleaned_data['password'])
|
||||
user_object.save()
|
||||
user.save()
|
||||
messages.success(
|
||||
request,
|
||||
"L'utilisateur %s a été crée" % user_object.pseudo
|
||||
"L'utilisateur a été crée"
|
||||
)
|
||||
return redirect(reverse('users:index-serviceusers'))
|
||||
return form(
|
||||
|
@ -324,11 +332,8 @@ def edit_serviceuser(request, serviceuser, **_kwargs):
|
|||
instance=serviceuser
|
||||
)
|
||||
if serviceuser.is_valid():
|
||||
user_object = serviceuser.save(commit=False)
|
||||
if serviceuser.cleaned_data['password']:
|
||||
user_object.set_password(serviceuser.cleaned_data['password'])
|
||||
if serviceuser.changed_data:
|
||||
user_object.save()
|
||||
serviceuser.save()
|
||||
messages.success(request, "L'user a bien été modifié")
|
||||
return redirect(reverse('users:index-serviceusers'))
|
||||
return form(
|
||||
|
@ -344,7 +349,7 @@ def del_serviceuser(request, serviceuser, **_kwargs):
|
|||
"""Suppression d'un ou plusieurs serviceusers"""
|
||||
if request.method == "POST":
|
||||
serviceuser.delete()
|
||||
messages.success(request, "L'user a été détruite")
|
||||
messages.success(request, "L'user a été détruit")
|
||||
return redirect(reverse('users:index-serviceusers'))
|
||||
return form(
|
||||
{'objet': serviceuser, 'objet_name': 'serviceuser'},
|
||||
|
@ -768,10 +773,14 @@ def index_listright(request):
|
|||
""" Affiche l'ensemble des droits"""
|
||||
listright_list = ListRight.objects.order_by('unix_name')\
|
||||
.prefetch_related('permissions').prefetch_related('user_set')
|
||||
superuser_right = User.objects.filter(is_superuser=True)
|
||||
return render(
|
||||
request,
|
||||
'users/index_listright.html',
|
||||
{'listright_list': listright_list}
|
||||
{
|
||||
'listright_list': listright_list,
|
||||
'superuser_right' : superuser_right,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
@ -814,6 +823,7 @@ def profil(request, users, **_kwargs):
|
|||
pagination_large_number = GeneralOption.get_cached_value(
|
||||
'pagination_large_number'
|
||||
)
|
||||
nb_machines = machines.count()
|
||||
machines = re2o_paginator(request, machines, pagination_large_number)
|
||||
factures = Facture.objects.filter(user=users)
|
||||
factures = SortTable.sort(
|
||||
|
@ -844,6 +854,7 @@ def profil(request, users, **_kwargs):
|
|||
{
|
||||
'users': users,
|
||||
'machines_list': machines,
|
||||
'nb_machines' : nb_machines,
|
||||
'facture_list': factures,
|
||||
'ban_list': bans,
|
||||
'white_list': whitelists,
|
||||
|
|
Loading…
Reference in a new issue