mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-25 22:22:26 +00:00
Merge branch 'fix_121_prettier_ACL_decorators' into 'master'
Fix #121 prettier acl decorators Closes #121 See merge request federez/re2o!155
This commit is contained in:
commit
05bc645a65
5 changed files with 178 additions and 200 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -200,9 +200,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
|
||||||
|
@ -241,8 +239,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')
|
||||||
|
@ -957,7 +954,7 @@ edge[arrowhead=odot,arrowtail=dot]''']
|
||||||
<FONT COLOR="#7B7B7B" >{}</FONT>
|
<FONT COLOR="#7B7B7B" >{}</FONT>
|
||||||
</TD>
|
</TD>
|
||||||
<TD ALIGN="LEFT">
|
<TD ALIGN="LEFT">
|
||||||
<FONT COLOR="#7B7B7B" >{}</FONT>
|
<FONT COLOR="#7B7B7B" >{}</FONT>
|
||||||
</TD></TR>
|
</TD></TR>
|
||||||
<TR><TD ALIGN="LEFT" BORDER="0">
|
<TR><TD ALIGN="LEFT" BORDER="0">
|
||||||
<FONT COLOR="#7B7B7B" >{}</FONT>
|
<FONT COLOR="#7B7B7B" >{}</FONT>
|
||||||
|
|
Loading…
Reference in a new issue