mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-26 22:52:26 +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.core.wsgi import get_wsgi_application
|
||||||
from django.db.models import Q
|
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/"
|
proj_path = "/var/www/re2o/"
|
||||||
# This is so Django knows where to find stuff.
|
# This is so Django knows where to find stuff.
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "re2o.settings")
|
||||||
|
@ -59,6 +54,12 @@ os.chdir(proj_path)
|
||||||
# This is so models get loaded.
|
# This is so models get loaded.
|
||||||
application = get_wsgi_application()
|
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()
|
options, created = OptionalTopologie.objects.get_or_create()
|
||||||
VLAN_NOK = options.vlan_decision_nok.vlan_id
|
VLAN_NOK = options.vlan_decision_nok.vlan_id
|
||||||
VLAN_OK = options.vlan_decision_ok.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>
|
<td><p class="text-success">{{utilisateur.last}}</p></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
|
{% if droit != 'Superuser' %}
|
||||||
<a href="{% url 'users:del-group' utilisateur.id droit.id %}">
|
<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">
|
<button type="button" class="btn btn-danger" aria-label="Left Align">
|
||||||
<span class="fa fa-user-times" aria-hidden="true"></span>
|
<span class="fa fa-user-times" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -79,4 +83,4 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -41,7 +41,7 @@ from django.urls import reverse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
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 Revision
|
||||||
from reversion.models import Version, ContentType
|
from reversion.models import Version, ContentType
|
||||||
|
@ -195,9 +195,7 @@ def revert_action(request, revision_id):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_all(IpList)
|
@can_view_all(IpList, Interface, User)
|
||||||
@can_view_all(Interface)
|
|
||||||
@can_view_all(User)
|
|
||||||
def stats_general(request):
|
def stats_general(request):
|
||||||
"""Statistiques générales affinées sur les ip, activées, utilisées par
|
"""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,
|
range, et les statistiques générales sur les users : users actifs,
|
||||||
|
@ -313,10 +311,7 @@ def stats_general(request):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_app('users')
|
@can_view_app('users', 'cotisations', 'machines', 'topologie')
|
||||||
@can_view_app('cotisations')
|
|
||||||
@can_view_app('machines')
|
|
||||||
@can_view_app('topologie')
|
|
||||||
def stats_models(request):
|
def stats_models(request):
|
||||||
"""Statistiques générales, affiche les comptages par models:
|
"""Statistiques générales, affiche les comptages par models:
|
||||||
nombre d'users, d'écoles, de droits, de bannissements,
|
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'):
|
for droit in ListRight.objects.all().select_related('group_ptr'):
|
||||||
stats_list[droit] = droit.user_set.all().annotate(
|
stats_list[droit] = droit.user_set.all().annotate(
|
||||||
num=Count('revision'),
|
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(
|
return render(
|
||||||
request,
|
request,
|
||||||
'logs/stats_droits.html',
|
'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"
|
PRETTY_NAME = "Enregistrements NS"
|
||||||
|
|
||||||
zone = models.ForeignKey('Extension', on_delete=models.PROTECT)
|
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:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
|
|
@ -28,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
{% include "pagination.html" with list=machines_list %}
|
{% include "pagination.html" with list=machines_list %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<table class="table" id="machines_table">
|
<table class="table" id="machines_table">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col>
|
<col>
|
||||||
|
|
|
@ -1272,12 +1272,7 @@ def index_nas(request):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_all(SOA)
|
@can_view_all(SOA, Mx, Ns, Txt, Srv, Extension)
|
||||||
@can_view_all(Mx)
|
|
||||||
@can_view_all(Ns)
|
|
||||||
@can_view_all(Txt)
|
|
||||||
@can_view_all(Srv)
|
|
||||||
@can_view_all(Extension)
|
|
||||||
def index_extension(request):
|
def index_extension(request):
|
||||||
""" View displaying the list of existing extensions, the list of
|
""" View displaying the list of existing extensions, the list of
|
||||||
existing SOA records, the list of existing MX records , 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
|
# 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.
|
||||||
|
|
|
@ -58,13 +58,8 @@ from . import forms
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_all(OptionalUser)
|
@can_view_all(OptionalUser, OptionalMachine, OptionalTopologie, GeneralOption,
|
||||||
@can_view_all(OptionalMachine)
|
AssoOption, MailMessageOption, HomeOption)
|
||||||
@can_view_all(OptionalTopologie)
|
|
||||||
@can_view_all(GeneralOption)
|
|
||||||
@can_view_all(AssoOption)
|
|
||||||
@can_view_all(MailMessageOption)
|
|
||||||
@can_view_all(HomeOption)
|
|
||||||
def display_options(request):
|
def display_options(request):
|
||||||
"""Vue pour affichage des options (en vrac) classé selon les models
|
"""Vue pour affichage des options (en vrac) classé selon les models
|
||||||
correspondants dans un tableau"""
|
correspondants dans un tableau"""
|
||||||
|
@ -149,7 +144,11 @@ def add_service(request):
|
||||||
@can_edit(Service)
|
@can_edit(Service)
|
||||||
def edit_service(request, service_instance, **_kwargs):
|
def edit_service(request, service_instance, **_kwargs):
|
||||||
"""Edition des services affichés sur la page d'accueil"""
|
"""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():
|
if service.is_valid():
|
||||||
with transaction.atomic(), reversion.create_revision():
|
with transaction.atomic(), reversion.create_revision():
|
||||||
service.save()
|
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
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from django.db.models import Model
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
def can_create(model):
|
def acl_base_decorator(method_name, *targets, on_instance=True):
|
||||||
"""Decorator to check if an user can create a model.
|
"""Base decorator for acl. It checks if the `request.user` has the
|
||||||
It assumes that a valid user exists in the request and that the model has a
|
permission by calling model.method_name. If the flag on_instance is True,
|
||||||
method can_create(user) which returns true if the user can create this kind
|
tries to get an instance of the model by calling
|
||||||
of models.
|
`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):
|
def decorator(view):
|
||||||
"""The decorator to use on a specific view
|
"""The decorator to use on a specific view
|
||||||
"""
|
"""
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
"""The wrapper used for a specific request
|
"""The wrapper used for a specific request"""
|
||||||
"""
|
instances = []
|
||||||
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
|
|
||||||
|
|
||||||
|
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):
|
error_messages = [
|
||||||
"""Decorator to check if an user can edit a model.
|
x[1] for x in chain.from_iterable(
|
||||||
It tries to get an instance of the model, using
|
process_target(x[0], x[1]) for x in group_targets()
|
||||||
`model.get_instance(*args, **kwargs)` and assumes that the model has a
|
) if not x[0]
|
||||||
method `can_edit(user)` which returns `true` if the user can edit this
|
]
|
||||||
kind of models.
|
if error_messages:
|
||||||
"""
|
for msg in error_messages:
|
||||||
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:
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
request, msg or "Vous ne pouvez pas accéder à ce menu")
|
||||||
return redirect(reverse(
|
return redirect(reverse(
|
||||||
'users:profil',
|
'users:profil',
|
||||||
kwargs={'userid': str(request.user.id)}
|
kwargs={'userid': str(request.user.id)}
|
||||||
))
|
))
|
||||||
return view(request, instance, *args, **kwargs)
|
return view(request, *chain(instances, args), **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
return decorator
|
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.
|
"""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):
|
return acl_base_decorator('can_change', *targets)
|
||||||
"""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
|
|
||||||
|
|
||||||
|
|
||||||
def can_delete(model):
|
def can_delete(*targets):
|
||||||
"""Decorator to check if an user can delete a model.
|
"""Decorator to check if an user can delete a model.
|
||||||
It tries to get an instance of the model, using
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
||||||
`model.get_instance(*args, **kwargs)` and assumes that the model has a
|
method 'can_edit'. See `acl_base_decorator` documentation for further
|
||||||
method `can_delete(user)` which returns `true` if the user can delete this
|
details.
|
||||||
kind of models.
|
|
||||||
"""
|
"""
|
||||||
def decorator(view):
|
return acl_base_decorator('can_delete', *targets)
|
||||||
"""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
|
|
||||||
|
|
||||||
|
|
||||||
def can_delete_set(model):
|
def can_delete_set(model):
|
||||||
|
@ -187,84 +229,34 @@ def can_delete_set(model):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def can_view(model):
|
def can_view(*targets):
|
||||||
"""Decorator to check if an user can view a model.
|
"""Decorator to check if an user can view a model.
|
||||||
It tries to get an instance of the model, using
|
It runs `acl_base_decorator` with the flag `on_instance=True` and the
|
||||||
`model.get_instance(*args, **kwargs)` and assumes that the model has a
|
method 'can_view'. See `acl_base_decorator` documentation for further
|
||||||
method `can_view(user)` which returns `true` if the user can view this
|
details.
|
||||||
kind of models.
|
|
||||||
"""
|
"""
|
||||||
def decorator(view):
|
return acl_base_decorator('can_view', *targets)
|
||||||
"""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
|
|
||||||
|
|
||||||
|
|
||||||
def can_view_all(model):
|
def can_view_all(*targets):
|
||||||
"""Decorator to check if an user can view a class of model.
|
"""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):
|
return acl_base_decorator('can_view_all', *targets, on_instance=False)
|
||||||
"""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
|
|
||||||
|
|
||||||
|
|
||||||
def can_view_app(app_name):
|
def can_view_app(*apps_name):
|
||||||
"""Decorator to check if an user can view an application.
|
"""Decorator to check if an user can view the applications.
|
||||||
"""
|
"""
|
||||||
assert app_name in sys.modules.keys()
|
for app_name in apps_name:
|
||||||
|
assert app_name in sys.modules.keys()
|
||||||
def decorator(view):
|
return acl_base_decorator(
|
||||||
"""The decorator to use on a specific view
|
'can_view',
|
||||||
"""
|
*chain(sys.modules[app_name] for app_name in apps_name),
|
||||||
def wrapper(request, *args, **kwargs):
|
on_instance=False
|
||||||
"""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
|
|
||||||
|
|
||||||
|
|
||||||
def can_edit_history(view):
|
def can_edit_history(view):
|
||||||
|
|
Binary file not shown.
|
@ -30,6 +30,14 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\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
|
#: templates/re2o/about.html:29 templates/re2o/about.html:35
|
||||||
msgid "About Re2o"
|
msgid "About Re2o"
|
||||||
msgstr "A propos de Re2o"
|
msgstr "A propos de Re2o"
|
||||||
|
@ -147,6 +155,10 @@ msgstr ""
|
||||||
msgid "Dependencies"
|
msgid "Dependencies"
|
||||||
msgstr "Dépendances"
|
msgstr "Dépendances"
|
||||||
|
|
||||||
|
#: templates/re2o/buttons/setlang.html:34
|
||||||
|
msgid "Translation in development"
|
||||||
|
msgstr "Traduction en développement"
|
||||||
|
|
||||||
#: views.py:172
|
#: views.py:172
|
||||||
msgid "No Git repository configured"
|
msgid "No Git repository configured"
|
||||||
msgstr "Aucun repository git configuré"
|
msgstr "Aucun repository git configuré"
|
||||||
|
|
|
@ -37,6 +37,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from .settings_local import *
|
from .settings_local import *
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
# The root directory for the project
|
# The root directory for the project
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
|
@ -135,6 +136,10 @@ LOCALE_PATHS = [
|
||||||
# For translations outside of apps
|
# For translations outside of apps
|
||||||
os.path.join(BASE_DIR, 'templates', 'locale').replace('\\', '/')
|
os.path.join(BASE_DIR, 'templates', 'locale').replace('\\', '/')
|
||||||
]
|
]
|
||||||
|
LANGUAGES = [
|
||||||
|
('en', _('English')),
|
||||||
|
('fr', _('French'))
|
||||||
|
]
|
||||||
|
|
||||||
# Should use time zone ?
|
# Should use time zone ?
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
|
@ -55,6 +55,7 @@ urlpatterns = [
|
||||||
url(r'^about/$', about_page, name='about'),
|
url(r'^about/$', about_page, name='about'),
|
||||||
url('^logout/', auth_views.logout, {'next_page': '/'}),
|
url('^logout/', auth_views.logout, {'next_page': '/'}),
|
||||||
url('^', include('django.contrib.auth.urls')),
|
url('^', include('django.contrib.auth.urls')),
|
||||||
|
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
url(r'^admin/', include(admin.site.urls)),
|
||||||
url(r'^users/', include('users.urls', namespace='users')),
|
url(r'^users/', include('users.urls', namespace='users')),
|
||||||
url(r'^search/', include('search.urls', namespace='search')),
|
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 machines.models import Interface, Machine
|
||||||
from users.models import Adherent, User, Ban, Whitelist
|
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):
|
def all_adherent(search_time=None):
|
||||||
""" Fonction renvoyant tous les users adherents. Optimisee pour n'est
|
""" Fonction renvoyant tous les users adherents. Optimisee pour n'est
|
||||||
|
@ -318,3 +361,19 @@ def remove_user_room(room):
|
||||||
return
|
return
|
||||||
user.room = None
|
user.room = None
|
||||||
user.save()
|
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 import forms
|
||||||
from django.forms import Form
|
from django.forms import Form
|
||||||
|
from re2o.utils import get_input_formats_help_text
|
||||||
|
|
||||||
CHOICES_USER = (
|
CHOICES_USER = (
|
||||||
('0', 'Actifs'),
|
('0', 'Actifs'),
|
||||||
|
@ -91,12 +92,17 @@ class SearchFormPlus(Form):
|
||||||
s = forms.DateField(
|
s = forms.DateField(
|
||||||
required=False,
|
required=False,
|
||||||
label="Date de début",
|
label="Date de début",
|
||||||
help_text='DD/MM/YYYY',
|
|
||||||
input_formats=['%d/%m/%Y']
|
|
||||||
)
|
)
|
||||||
e = forms.DateField(
|
e = forms.DateField(
|
||||||
required=False,
|
required=False,
|
||||||
help_text='DD/MM/YYYY',
|
|
||||||
input_formats=['%d/%m/%Y'],
|
|
||||||
label="Date de fin"
|
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;
|
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) */
|
/* Set height of the grid so .sidenav can be 100% (adjust as needed) */
|
||||||
.row.content {
|
.row.content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -118,7 +118,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</li>
|
</li>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
<li>
|
<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>
|
</li>
|
||||||
{% if not request.user.is_authenticated %}
|
{% if not request.user.is_authenticated %}
|
||||||
{% if var_sa %}
|
{% if var_sa %}
|
||||||
|
@ -145,48 +145,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% 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 %}
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'preferences:display-options' %}">
|
{% include 'buttons/setlang.html' %}
|
||||||
<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>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
{% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</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 %}
|
{% load url_insert_param %}
|
||||||
|
|
||||||
|
{% if list.paginator.num_pages > 1 %}
|
||||||
<ul class="pagination nav navbar-nav">
|
<ul class="pagination nav navbar-nav">
|
||||||
{% if list.has_previous %}
|
{% if list.has_previous %}
|
||||||
<li><a href="{% url_insert_param request.get_full_path page=1 %}"> << </a></li>
|
<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.next_page_number %}"> > </a></li>
|
||||||
<li><a href="{% url_insert_param request.get_full_path page=list.paginator.page_range|length %}"> >> </a></li>
|
<li><a href="{% url_insert_param request.get_full_path page=list.paginator.page_range|length %}"> >> </a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -24,69 +24,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
|
||||||
{% load acl %}
|
{% 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">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -105,37 +42,47 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ port.port }}</td>
|
<td>{{ port.port }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if port.room %}
|
{% if port.room %}{{ port.room }}{% endif %}
|
||||||
{{ port.room }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if port.machine_interface %}
|
{% if port.machine_interface %}
|
||||||
<a href="{% url 'users:profil' userid=port.machine_interface.machine.user.id %}">{{ port.machine_interface }}</a>
|
{% can_view port.machine_interface.machine.user %}
|
||||||
{% endif %}
|
<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>
|
||||||
<td>
|
<td>
|
||||||
{% if port.related %}
|
{% if port.related %}
|
||||||
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">{{ port.related }}</a>
|
{% can_view port.related.switch %}
|
||||||
{% endif %}
|
<a href="{% url 'topologie:index-port' switchid=port.related.switch.id %}">
|
||||||
|
{{ port.related }}
|
||||||
|
</a>
|
||||||
|
{% acl_else %}
|
||||||
|
{{ port.related }}
|
||||||
|
{% acl_end %}
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ port.radius }}</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>{{ port.details }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'port' port.pk %}">
|
<a class="btn btn-info btn-sm" role="button" title="Historique" href="{% url 'topologie:history' 'port' port.pk %}">
|
||||||
<i class="fa fa-history"></i>
|
<i class="fa fa-history"></i>
|
||||||
</a>
|
</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 %}">
|
<a class="btn btn-primary btn-sm" role="button" title="Éditer" href="{% url 'topologie:edit-port' port.id %}">
|
||||||
<i class="fa fa-edit"></i>
|
<i class="fa fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
{% can_delete port %}
|
{% can_delete port %}
|
||||||
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-port' port.pk %}">
|
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'topologie:del-port' port.pk %}">
|
||||||
<i class="fa fa-trash"></i>
|
<i class="fa fa-trash"></i>
|
||||||
</a>
|
</a>
|
||||||
{% acl_end %}
|
{% acl_end %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% 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>
|
<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 %}
|
{% acl_end %}
|
||||||
<hr>
|
<hr>
|
||||||
|
{% include "topologie/aff_repr_switch.html" with port_list=port_list %}
|
||||||
{% include "topologie/aff_port.html" with port_list=port_list %}
|
{% include "topologie/aff_port.html" with port_list=port_list %}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -208,9 +208,7 @@ def index_ap(request):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_all(Stack)
|
@can_view_all(Stack, Building, SwitchBay)
|
||||||
@can_view_all(Building)
|
|
||||||
@can_view_all(SwitchBay)
|
|
||||||
def index_physical_grouping(request):
|
def index_physical_grouping(request):
|
||||||
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
|
"""Affichage de la liste des stacks (affiche l'ensemble des switches)"""
|
||||||
stack_list = (Stack.objects
|
stack_list = (Stack.objects
|
||||||
|
@ -249,8 +247,7 @@ def index_physical_grouping(request):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@can_view_all(ModelSwitch)
|
@can_view_all(ModelSwitch, ConstructorSwitch)
|
||||||
@can_view_all(ConstructorSwitch)
|
|
||||||
def index_model_switch(request):
|
def index_model_switch(request):
|
||||||
""" Affichage de l'ensemble des modèles de switches"""
|
""" Affichage de l'ensemble des modèles de switches"""
|
||||||
model_switch_list = ModelSwitch.objects.select_related('constructor')
|
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 django.contrib.auth.models import Group, Permission
|
||||||
|
|
||||||
from preferences.models import OptionalUser
|
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.mixins import FormRevMixin
|
||||||
from re2o.field_permissions import FieldPermissionFormMixin
|
from re2o.field_permissions import FieldPermissionFormMixin
|
||||||
|
|
||||||
|
@ -422,12 +422,19 @@ class ServiceUserForm(FormRevMixin, ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ServiceUser
|
model = ServiceUser
|
||||||
fields = ('pseudo', 'access_group')
|
fields = ('pseudo', 'access_group','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(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs)
|
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):
|
class EditServiceUserForm(ServiceUserForm):
|
||||||
"""Formulaire d'edition de base d'un service user. Ne permet
|
"""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)
|
super(StateForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class GroupForm(FormRevMixin, ModelForm):
|
class GroupForm(FieldPermissionFormMixin, FormRevMixin, ModelForm):
|
||||||
""" Gestion des groupes d'un user"""
|
""" Gestion des groupes d'un user"""
|
||||||
groups = forms.ModelMultipleChoiceField(
|
groups = forms.ModelMultipleChoiceField(
|
||||||
Group.objects.all(),
|
Group.objects.all(),
|
||||||
|
@ -457,11 +464,13 @@ class GroupForm(FormRevMixin, ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ['groups']
|
fields = ['is_superuser', 'groups']
|
||||||
|
|
||||||
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(GroupForm, self).__init__(*args, prefix=prefix, **kwargs)
|
super(GroupForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
|
if 'is_superuser' in self.fields:
|
||||||
|
self.fields['is_superuser'].label = "Superuser"
|
||||||
|
|
||||||
|
|
||||||
class SchoolForm(FormRevMixin, ModelForm):
|
class SchoolForm(FormRevMixin, ModelForm):
|
||||||
|
@ -558,6 +567,9 @@ class BanForm(FormRevMixin, ModelForm):
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
super(BanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
super(BanForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
self.fields['date_end'].label = 'Date de fin'
|
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:
|
class Meta:
|
||||||
model = Ban
|
model = Ban
|
||||||
|
@ -570,6 +582,9 @@ class WhitelistForm(FormRevMixin, ModelForm):
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs)
|
super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
self.fields['date_end'].label = 'Date de fin'
|
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:
|
class Meta:
|
||||||
model = Whitelist
|
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"
|
"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):
|
def can_view(self, user_request, *_args, **_kwargs):
|
||||||
"""Check if an user can view an user object.
|
"""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)
|
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||||
raison = models.CharField(max_length=255)
|
raison = models.CharField(max_length=255)
|
||||||
date_start = models.DateTimeField(auto_now_add=True)
|
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)
|
state = models.IntegerField(choices=STATES, default=STATE_HARD)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1314,7 +1326,7 @@ class Whitelist(RevMixin, AclMixin, models.Model):
|
||||||
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
user = models.ForeignKey('User', on_delete=models.PROTECT)
|
||||||
raison = models.CharField(max_length=255)
|
raison = models.CharField(max_length=255)
|
||||||
date_start = models.DateTimeField(auto_now_add=True)
|
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:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
|
|
@ -33,6 +33,44 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</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 %}
|
{% for listright in listright_list %}
|
||||||
<tr class="active">
|
<tr class="active">
|
||||||
<td>
|
<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}}">
|
<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 }})
|
Ensemble des permissions ({{ listright.permissions.all|length }})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ listright.details }}</td>
|
<td>{{ listright.details }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
{% include 'buttons/edit.html' with href='users:edit-listright' id=listright.id %}
|
{% 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 %}
|
{% 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">
|
<h3 class="panel-title pull-left">
|
||||||
<i class="fa fa-desktop"></i>
|
<i class="fa fa-desktop"></i>
|
||||||
Machines
|
Machines
|
||||||
<span class="badge">{{machines_list.count}}</span>
|
<span class="badge">{{nb_machines}}</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div id="collapse3" class="panel-collapse collapse">
|
<div id="collapse3" class="panel-collapse collapse">
|
||||||
|
|
|
@ -43,6 +43,9 @@ urlpatterns = [
|
||||||
url(r'^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$',
|
url(r'^del_group/(?P<userid>[0-9]+)/(?P<listrightid>[0-9]+)$',
|
||||||
views.del_group,
|
views.del_group,
|
||||||
name='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'^new_serviceuser/$', views.new_serviceuser, name='new-serviceuser'),
|
||||||
url(r'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
|
url(r'^edit_serviceuser/(?P<serviceuserid>[0-9]+)$',
|
||||||
views.edit_serviceuser,
|
views.edit_serviceuser,
|
||||||
|
|
|
@ -246,7 +246,7 @@ def state(request, user, userid):
|
||||||
@can_edit(User, 'groups')
|
@can_edit(User, 'groups')
|
||||||
def groups(request, user, userid):
|
def groups(request, user, userid):
|
||||||
""" View to edit the groups of a user """
|
""" 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.is_valid():
|
||||||
if group_form.changed_data:
|
if group_form.changed_data:
|
||||||
group_form.save()
|
group_form.save()
|
||||||
|
@ -294,18 +294,26 @@ def del_group(request, user, listrightid, **_kwargs):
|
||||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
|
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
|
@login_required
|
||||||
@can_create(ServiceUser)
|
@can_create(ServiceUser)
|
||||||
def new_serviceuser(request):
|
def new_serviceuser(request):
|
||||||
""" Vue de création d'un nouvel utilisateur service"""
|
""" Vue de création d'un nouvel utilisateur service"""
|
||||||
user = ServiceUserForm(request.POST or None)
|
user = ServiceUserForm(request.POST or None)
|
||||||
if user.is_valid():
|
if user.is_valid():
|
||||||
user_object = user.save(commit=False)
|
user.save()
|
||||||
user_object.set_password(user.cleaned_data['password'])
|
|
||||||
user_object.save()
|
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
"L'utilisateur %s a été crée" % user_object.pseudo
|
"L'utilisateur a été crée"
|
||||||
)
|
)
|
||||||
return redirect(reverse('users:index-serviceusers'))
|
return redirect(reverse('users:index-serviceusers'))
|
||||||
return form(
|
return form(
|
||||||
|
@ -324,11 +332,8 @@ def edit_serviceuser(request, serviceuser, **_kwargs):
|
||||||
instance=serviceuser
|
instance=serviceuser
|
||||||
)
|
)
|
||||||
if serviceuser.is_valid():
|
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:
|
if serviceuser.changed_data:
|
||||||
user_object.save()
|
serviceuser.save()
|
||||||
messages.success(request, "L'user a bien été modifié")
|
messages.success(request, "L'user a bien été modifié")
|
||||||
return redirect(reverse('users:index-serviceusers'))
|
return redirect(reverse('users:index-serviceusers'))
|
||||||
return form(
|
return form(
|
||||||
|
@ -344,7 +349,7 @@ def del_serviceuser(request, serviceuser, **_kwargs):
|
||||||
"""Suppression d'un ou plusieurs serviceusers"""
|
"""Suppression d'un ou plusieurs serviceusers"""
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
serviceuser.delete()
|
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 redirect(reverse('users:index-serviceusers'))
|
||||||
return form(
|
return form(
|
||||||
{'objet': serviceuser, 'objet_name': 'serviceuser'},
|
{'objet': serviceuser, 'objet_name': 'serviceuser'},
|
||||||
|
@ -768,10 +773,14 @@ def index_listright(request):
|
||||||
""" Affiche l'ensemble des droits"""
|
""" Affiche l'ensemble des droits"""
|
||||||
listright_list = ListRight.objects.order_by('unix_name')\
|
listright_list = ListRight.objects.order_by('unix_name')\
|
||||||
.prefetch_related('permissions').prefetch_related('user_set')
|
.prefetch_related('permissions').prefetch_related('user_set')
|
||||||
|
superuser_right = User.objects.filter(is_superuser=True)
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'users/index_listright.html',
|
'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 = GeneralOption.get_cached_value(
|
||||||
'pagination_large_number'
|
'pagination_large_number'
|
||||||
)
|
)
|
||||||
|
nb_machines = machines.count()
|
||||||
machines = re2o_paginator(request, machines, pagination_large_number)
|
machines = re2o_paginator(request, machines, pagination_large_number)
|
||||||
factures = Facture.objects.filter(user=users)
|
factures = Facture.objects.filter(user=users)
|
||||||
factures = SortTable.sort(
|
factures = SortTable.sort(
|
||||||
|
@ -844,6 +854,7 @@ def profil(request, users, **_kwargs):
|
||||||
{
|
{
|
||||||
'users': users,
|
'users': users,
|
||||||
'machines_list': machines,
|
'machines_list': machines,
|
||||||
|
'nb_machines' : nb_machines,
|
||||||
'facture_list': factures,
|
'facture_list': factures,
|
||||||
'ban_list': bans,
|
'ban_list': bans,
|
||||||
'white_list': whitelists,
|
'white_list': whitelists,
|
||||||
|
|
Loading…
Reference in a new issue