From 9f2ad26f86f1b47d75d6bb8ca45b1c758e32c6cf Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Wed, 29 Oct 2014 11:38:53 +0100 Subject: [PATCH] Premier commit --- COPYING | 1 + MANIFEST.in | 3 + README | 18 ++ old-doc/cdg59.schema | 193 ++++++++++++ setup.py | 66 ++++ src/authentic2_pratic/__init__.py | 15 + src/authentic2_pratic/admin.py | 0 src/authentic2_pratic/app_settings.py | 22 ++ src/authentic2_pratic/auth_frontends.py | 21 ++ src/authentic2_pratic/backends.py | 8 + src/authentic2_pratic/dashboard.py | 11 + src/authentic2_pratic/forms.py | 35 +++ src/authentic2_pratic/models.py | 296 ++++++++++++++++++ .../pratic_attribute_source.py | 47 +++ src/authentic2_pratic/tables.py | 68 ++++ .../templates/authentic2_pratic/accesses.html | 19 ++ .../templates/authentic2_pratic/base.html | 35 +++ .../authentic2_pratic/collectivities.html | 19 ++ .../authentic2_pratic/collectivity_edit.html | 54 ++++ .../collectivity_sidebar.html | 8 + .../templates/authentic2_pratic/delete.html | 33 ++ .../templates/authentic2_pratic/form.html | 44 +++ .../templates/authentic2_pratic/homepage.html | 24 ++ .../authentic2_pratic/service_instances.html | 19 ++ .../templates/authentic2_pratic/services.html | 19 ++ .../templates/authentic2_pratic/sidebar.html | 12 + .../templates/authentic2_pratic/table.html | 58 ++++ .../templates/authentic2_pratic/users.html | 19 ++ src/authentic2_pratic/urls.py | 61 ++++ src/authentic2_pratic/views.py | 239 ++++++++++++++ 30 files changed, 1467 insertions(+) create mode 100644 COPYING create mode 100644 MANIFEST.in create mode 100644 README create mode 100644 old-doc/cdg59.schema create mode 100755 setup.py create mode 100644 src/authentic2_pratic/__init__.py create mode 100644 src/authentic2_pratic/admin.py create mode 100644 src/authentic2_pratic/app_settings.py create mode 100644 src/authentic2_pratic/auth_frontends.py create mode 100644 src/authentic2_pratic/backends.py create mode 100644 src/authentic2_pratic/dashboard.py create mode 100644 src/authentic2_pratic/forms.py create mode 100644 src/authentic2_pratic/models.py create mode 100644 src/authentic2_pratic/pratic_attribute_source.py create mode 100644 src/authentic2_pratic/tables.py create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/accesses.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/base.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/collectivities.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/collectivity_edit.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/collectivity_sidebar.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/delete.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/form.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/homepage.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/service_instances.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/services.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/sidebar.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/table.html create mode 100644 src/authentic2_pratic/templates/authentic2_pratic/users.html create mode 100644 src/authentic2_pratic/urls.py create mode 100644 src/authentic2_pratic/views.py diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..605082e --- /dev/null +++ b/COPYING @@ -0,0 +1 @@ +authentic2-pratic is distributed under the license AGPLv3 or later. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..46d5c4e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include COPYING +recursive-include src/authentic2_pratic/templates *.html +recursive-include src/authentic2_pratic/static *.js *.css *.png diff --git a/README b/README new file mode 100644 index 0000000..2582de9 --- /dev/null +++ b/README @@ -0,0 +1,18 @@ +Authentic2 Pr@tic +================= + +Remplacement pour les pages de gestion d'Authentic2: +- gestion des collectivités +- gestion des utilisateurs +- gestion des services +- gestion des droits d'accès + + +URLs +==== + +/manage/ - liste des collectivités +/manage// - menu services, utilisateurs et autorisations +/manage//services/ - services +/manage//utilisateurs/ - utilisateurs +/manage//autorisations/ - autorisations diff --git a/old-doc/cdg59.schema b/old-doc/cdg59.schema new file mode 100644 index 0000000..acb92e9 --- /dev/null +++ b/old-doc/cdg59.schema @@ -0,0 +1,193 @@ +# Schéma LDAP +# + +objectIdentifier Cdg59Root 1.1 +objectIdentifier Cdg59LDAP Cdg59Root:2 +objectIdentifier Cdg59LDAPAttribute Cdg59LDAP:1 +objectIdentifier Cdg59LDAPObjectClass Cdg59LDAP:2 + + +attributetype ( Cdg59LDAPAttribute:1 NAME 'cdg59siretCode' + DESC 'Collectivity SIRET code' + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SINGLE-VALUE) + +attributetype ( Cdg59LDAPAttribute:2 NAME 'cdg59direction' + DESC 'Collectivity Direction' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:3 NAME 'cdg59isAdmin' + DESC 'Admin or not' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7) + +attributetype ( Cdg59LDAPAttribute:4 NAME 'cdg59isDisabled' + DESC 'Acccount disabled or not' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7) + +attributetype ( Cdg59LDAPAttribute:5 NAME 'cdg59sid' + DESC 'Service Id' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:6 NAME 'cdg59siid' + DESC 'Service Instance Id' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:7 NAME 'cdg59serviceType' + DESC 'Service Type' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:8 NAME 'cdg59URL' + DESC 'Generic URL' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) + +attributetype ( Cdg59LDAPAttribute:9 NAME 'cdg59metadataURL' + DESC 'Service Metadata URL' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26) + +attributetype ( Cdg59LDAPAttribute:10 NAME 'cdg59serviceAccesses' + DESC 'Services an agent can access' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:11 NAME 'cdg59collectivitySirhCode' + DESC 'Collectivity SIRH code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + SINGLE-VALUE) + +attributetype ( Cdg59LDAPAttribute:12 NAME 'cdg59collectivitySirhLabel' + DESC 'Collectivity SIRH Label' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:13 NAME 'cdg59regionCode' + DESC 'Collectivity "Region" Code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}) + +attributetype ( Cdg59LDAPAttribute:14 NAME 'cdg59departementCode' + DESC 'Collectivity "Departement" Code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}) + +attributetype ( Cdg59LDAPAttribute:15 NAME 'cdg59arrondissementCode' + DESC 'Collectivity "Arrondissement" Code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}) + +attributetype ( Cdg59LDAPAttribute:16 NAME 'cdg59cantonCode' + DESC 'Collectivity "Canton" Code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}) + +attributetype ( Cdg59LDAPAttribute:17 NAME 'cdg59inseeCode' + DESC 'Collectivity INSEE Code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}) + +attributetype ( Cdg59LDAPAttribute:18 NAME 'cdg59streetNumber' + DESC 'Collectivity street number' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256}) + +attributetype ( Cdg59LDAPAttribute:19 NAME 'cdg59distOffice' + DESC 'Collectivity distribution office' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:20 NAME 'cdg59addressCompl' + DESC 'Collectivity complementary address infomation' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:21 NAME 'cdg59addressMention' + DESC 'Collectivity particular mention on address' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:22 NAME 'cdg59agentSirhCode' + DESC 'Collectivity SIRH code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + SINGLE-VALUE) + +attributetype ( Cdg59LDAPAttribute:23 NAME 'cdg59isGlobal' + DESC 'Global service, or not' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7) + +attributetype ( Cdg59LDAPAttribute:24 NAME 'cdg59ssoRelayState' + DESC 'URL for redirection after a single sign on' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) + +attributetype ( Cdg59LDAPAttribute:25 NAME 'cdg59lastConnectionTime' + DESC 'Last connection time' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27) + +attributetype ( Cdg59LDAPAttribute:26 NAME 'cdg59lastConnectionDuration' + DESC 'Last connection duration' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27) + +attributetype ( Cdg59LDAPAttribute:28 NAME 'cdg59collectivityId' + DESC 'Id Code' + EQUALITY caseIgnoreIA5Match + SUBSTR caseIgnoreIA5SubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256} + SINGLE-VALUE) + +# CDG 59 Collectivity +objectclass ( Cdg59LDAPObjectClass:1 + NAME 'cdg59collectivity' + DESC 'CDG 59 Collectivity Objectclass' + STRUCTURAL + SUP organizationalUnit + MAY ( cdg59siretCode $ cdg59collectivitySirhCode $ cdg59collectivitySirhLabel $ cn $ mail $ cdg59URL $ cdg59regionCode $ cdg59departementCode $ cdg59arrondissementCode $ cdg59cantonCode $ cdg59inseeCode $ cdg59streetNumber $ cdg59distOffice $ cdg59addressCompl $ cdg59addressMention $ cdg59collectivityId )) + +# CDG 59 Agent +objectclass (Cdg59LDAPObjectClass:2 + NAME 'cdg59agent' + DESC 'CDG 59 Agent Objectclass' + STRUCTURAL + SUP inetOrgPerson + MUST ( uid ) + MAY ( cdg59isAdmin $ cdg59direction $ cdg59isDisabled $ cdg59serviceAccesses $ cdg59agentSirhCode $ cdg59lastConnectionTime $ cdg59lastConnectionDuration )) + +# CDG 59 Service +objectclass ( Cdg59LDAPObjectClass:3 + NAME 'cdg59service' + DESC 'CDG 59 Service Objectclass' + STRUCTURAL + MUST ( cdg59sid ) + MAY ( cn $ description $ cdg59URL $ cdg59isGlobal $ cdg59metadataURL $ cdg59ssoRelayState )) + +# CDG 59 Service Instance +objectclass ( Cdg59LDAPObjectClass:4 + NAME 'cdg59serviceInstance' + DESC 'CDG 59 Service Instance Objectclass' + STRUCTURAL + MUST ( cdg59siid ) + MAY ( cdg59serviceType $ cdg59URL $ cdg59metadataURL $ cdg59ssoRelayState )) + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..2eaeb1f --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +from setuptools import setup, find_packages +import os + +def get_version(): + import glob + import re + import os + + version = None + for d in glob.glob('src/*'): + if not os.path.isdir(d): + continue + module_file = os.path.join(d, '__init__.py') + if not os.path.exists(module_file): + continue + for v in re.findall("""__version__ *= *['"](.*)['"]""", + open(module_file).read()): + assert version is None + version = v + if version: + break + assert version is not None + if os.path.exists('.git'): + import subprocess + p = subprocess.Popen(['git','describe','--dirty','--match=v*'], + stdout=subprocess.PIPE) + result = p.communicate()[0] + assert p.returncode == 0, 'git returned non-zero' + new_version = result.split()[0][1:] + assert new_version.split('-')[0] == version, '__version__ must match the last git annotated tag' + version = new_version.replace('-', '.') + return version + +README = file(os.path.join( + os.path.dirname(__file__), + 'README')).read() + +setup(name='authentic2-pratic', + version=get_version(), + license='AGPLv3', + description='Authentic2 Pr@tic', + long_description=README, + author="Entr'ouvert", + author_email="info@entrouvert.com", + packages=find_packages('src'), + package_dir={ + '': 'src', + }, + package_data={ + 'authentic2_pratic': [ + 'templates/authentic2_pratic/*.html', + 'static/authentic2_pratic/js/*.js', + 'static/authentic2_pratic/css/*.css', + 'static/authentic2_pratic/img/*.png', + ], + }, + install_requires=[ + 'authentic2', + ], + entry_points={ + 'authentic2.plugin': [ + 'authentic2-pratic = authentic2_pratic:Plugin', + ], + }, +) diff --git a/src/authentic2_pratic/__init__.py b/src/authentic2_pratic/__init__.py new file mode 100644 index 0000000..705fcdd --- /dev/null +++ b/src/authentic2_pratic/__init__.py @@ -0,0 +1,15 @@ +__version__ = '1.0.0b1' + +class Plugin(object): + def get_before_urls(self): + from . import urls + return urls.urlpatterns + + def get_apps(self): + return [__name__] + + def get_authentication_backends(self): + return ['authentic2_pratic.backends.PraticBackend'] + + def get_auth_frontends(self): + return ['authentic2_pratic.auth_frontends.PraticFrontend'] diff --git a/src/authentic2_pratic/admin.py b/src/authentic2_pratic/admin.py new file mode 100644 index 0000000..e69de29 diff --git a/src/authentic2_pratic/app_settings.py b/src/authentic2_pratic/app_settings.py new file mode 100644 index 0000000..45fee0b --- /dev/null +++ b/src/authentic2_pratic/app_settings.py @@ -0,0 +1,22 @@ +class AppSettings(object): + __DEFAULTS = { + } + + def __init__(self, prefix): + self.prefix = prefix + + def _setting(self, name, dflt): + from django.conf import settings + return getattr(settings, self.prefix+name, dflt) + + def __getattr__(self, name): + if name not in self.__DEFAULTS: + raise AttributeError(name) + return self._setting(name, self.__DEFAULTS[name]) + +# Ugly? Guido recommends this himself ... +# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html +import sys +app_settings = AppSettings('A2_PRATIC_') +app_settings.__name__ = __name__ +sys.modules[__name__] = app_settings diff --git a/src/authentic2_pratic/auth_frontends.py b/src/authentic2_pratic/auth_frontends.py new file mode 100644 index 0000000..6dc0b17 --- /dev/null +++ b/src/authentic2_pratic/auth_frontends.py @@ -0,0 +1,21 @@ +from django.utils.translation import gettext_noop +from django import forms + +class PraticFrontend(object): + def enabled(self): + True + + def name(self): + return gettext_noop('Pr@tic') + + def id(self): + return 'pratic' + + def form(self): + return forms.Form + + def post(self, request, form, nonce, next): + raise NotImplemented + + def template(self): + return 'authentic2_pratic/login.html' diff --git a/src/authentic2_pratic/backends.py b/src/authentic2_pratic/backends.py new file mode 100644 index 0000000..246ae03 --- /dev/null +++ b/src/authentic2_pratic/backends.py @@ -0,0 +1,8 @@ +import logging + +class PraticBackend(object): + def __init__(self): + self.logger = logging.getLogger(__name__) + + def authenticate(self, **kwargs): + raise NotImplemented diff --git a/src/authentic2_pratic/dashboard.py b/src/authentic2_pratic/dashboard.py new file mode 100644 index 0000000..cb19654 --- /dev/null +++ b/src/authentic2_pratic/dashboard.py @@ -0,0 +1,11 @@ +from django.utils.translation import ugettext_lazy as _ + +from admin_tools.dashboard import modules + + +def get_admin_modules(): + '''Show models in authentic2 admin''' + model_list = modules.ModelList(_('Pr@tic'), + models=('authentic2_pratic.models.*',)) + return (model_list,) + diff --git a/src/authentic2_pratic/forms.py b/src/authentic2_pratic/forms.py new file mode 100644 index 0000000..2e008bd --- /dev/null +++ b/src/authentic2_pratic/forms.py @@ -0,0 +1,35 @@ +from django import forms + +from . import models + +class BaseForm(forms.ModelForm): + required_css_class = 'required' + error_css_class = 'error' + +class ServiceForm(BaseForm): + class Meta: + model = models.Service + +class CollectivityForm(BaseForm): + class Meta: + model = models.Collectivity + +class ServiceInstanceForm(BaseForm): + class Meta: + model = models.ServiceInstance + exclude = ('collectivity',) + +class AccessForm(BaseForm): + def __init__(self, *args, **kwargs): + collectivity = kwargs.pop('collectivity') + super(AccessForm, self).__init__(*args, **kwargs) + self.fields['user'].queryset = self.fields['user'].queryset.filter(collectivity=collectivity) + self.fields['service_instance'].queryset = self.fields['service_instance'].queryset.filter(collectivity=collectivity) + + class Meta: + model = models.Access + +class UserForm(BaseForm): + class Meta: + model = models.User + fields = ('uid', 'first_name', 'last_name', 'email') diff --git a/src/authentic2_pratic/models.py b/src/authentic2_pratic/models.py new file mode 100644 index 0000000..ebff401 --- /dev/null +++ b/src/authentic2_pratic/models.py @@ -0,0 +1,296 @@ +from django.db.models import (Model, TextField, CharField, EmailField, + URLField, BooleanField, IntegerField, ForeignKey, SlugField) +from django.contrib.auth.models import User as AuthUser +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError + +class User(AuthUser): + # givenName -> first_name + # sn -> last_name + # userPassword -> password + # mail -> email + # cdg59isDisabled -> ! active + # cdg59lastConnectionTime -> last_login + # cn -> get_full_name() + # ou -> collectivity + # username = uid + '@' + collectivity.slug + uid = CharField( + verbose_name=_('identifier'), + max_length=128) + collectivity = ForeignKey( + 'Collectivity', + verbose_name=_('collectivity')) + # cdg59isAdmin + is_admin = BooleanField( + verbose_name=_('is admin'), + default=False, + blank=True) + # cdg59agentSirhCode + sirh_code = CharField( + verbose_name=_('SIRH Code'), + max_length=8, + blank=True) + # cdg59direction + direction = CharField( + verbose_name=_('direction'), + max_length=32, + blank=True) + # cdg59lastConnectionDuration + last_login_duration = IntegerField( + verbose_name=_('last connection duration'), + default=0, + blank=True) + # cdg59serviceAccesses -> convert to ACLs + # employeeType + employee_type = CharField( + verbose_name=_('employee type'), + max_length=64, + blank=True) + # postalAddress + postal_address = TextField( + verbose_name=_('postal address'), + blank=True) + # facsimileTelephoneNumber + fax = CharField( + verbose_name=_('fax'), + max_length=32) + # mobile + mobile = CharField( + verbose_name=_('mobile'), + max_length=16, + blank=True) + # telephoneNumber + phone = CharField( + verbose_name=_('phone'), + max_length=32) + + class Meta: + verbose_name = _('agent') + verbose_name_plural = _('agents') + # enforce unicity of login by collectivity + unique_together = (('uid', 'collectivity'),) + + def clean(self): + # prevent collisions between users from multiple collectivities + if self.uid and not self.username and self.collectivity: + self.username = u'%s@%s' % (self.uid, self.collectivity.slug) + super(User, self).clean() + +# Fields to support +class Collectivity(Model): + # Identifiers + # cn = ou + name = CharField( + verbose_name=_('name'), + max_length=64, + unique=True) + slug = SlugField( + verbose_name=_('identifier'), + max_length=64, + unique=True) + # cdg59collectivityId + collectivity_id = CharField( + verbose_name=_('collectivity id'), + max_length=8, + blank=True) + # cdg59collectivitySirhCode + sirh_code = CharField( + verbose_name=_('SIRH Code'), + max_length=8, + blank=True) + # cdg59collectivitySirhLabel + sirh_label = CharField( + verbose_name=_('SIRH Code'), + max_length=64, + blank=True) + # cdg59inseeCode + insee_code = CharField( + verbose_name=_('INSEE Code'), + max_length=8, + blank=True) + # cdg59siretCode + siret_code = CharField( + verbose_name=_('SIRET Code'), + max_length=8, + blank=True) + + + # Postal addresse + # postalAddress + postal_address = TextField( + verbose_name=_('postal address'), + blank=True) + # cdg59streetNumber + street_number = CharField( + verbose_name=_('street number'), + max_length=8, + blank=True) + # street + street = CharField( + verbose_name=_('street'), + max_length=128, + blank=True) + # postalCode + postal_code = CharField( + verbose_name=_('postal code'), + max_length=16, + blank=True) + # cdg59addressCompl + address_complementary = CharField( + verbose_name=_('complementary address'), + max_length=64, + blank=True) + # cdg59addressMention + address_mention = CharField( + verbose_name=_('address mention'), + max_length=64, + blank=True) + # cdg59arrondissementCode + arrondissement_code = CharField( + verbose_name=_('arrondissement code'), + max_length=64, + blank=True) + # cdg59cantonCode + canton_code = CharField( + verbose_name=_('canton code'), + max_length=4, + blank=True) + # cdg59departementCode + departement_code = CharField( + verbose_name=_('departement code'), + max_length=2, + blank=True) + # cdg59distOffice + dist_office = CharField( + verbose_name=_('distribution office'), + max_length=64, + blank=True) + # cdg59regionCode + region_code = CharField( + verbose_name=_('distribution office'), + max_length=4, + blank=True) + + + # Contact + # telephoneNumber + phone = CharField( + verbose_name=_('phone'), + max_length=32, + blank=True) + # facsimileTelephoneNumber + fax = CharField( + verbose_name=_('fax'), + max_length=32, + blank=True) + # mail + email = EmailField( + verbose_name=_('email'), + max_length=64, + blank=True) + # cdg59URL + url = URLField( + verbose_name=_('URL'), + max_length=128, + blank=True) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = _('collectivity') + verbose_name_plural = _('collectivities') + ordering = ('name',) + + +class Service(Model): + # Services without a collectivity are global + # cn + name = CharField( + verbose_name=_('name'), + max_length=32, + unique=True) + # cdg59sid + slug = SlugField( + verbose_name=('identifier'), + unique=True) + is_global = BooleanField( + verbose_name=_('is global'), + default=False, + blank=True) + service_url = URLField( + verbose_name=_('URL')) + metadata_url = URLField( + verbose_name=_('SAML Metadata URL'), + blank=True) + oauth2_url = URLField( + verbose_name=_('OAuth2 URL'), + blank=True) + oauth2_key = CharField( + verbose_name=_('OAuth2 Key'), + max_length=64, + blank=True) + + def __unicode__(self): + return self.name + + class Meta: + verbose_name = _('service') + verbose_name_plural = _('services') + ordering = ('name',) + +class ServiceInstance(Model): + # cdg59sid + slug = SlugField( + verbose_name=('identifier')) + service = ForeignKey( + 'Service', + verbose_name=_('service')) + collectivity = ForeignKey( + 'Collectivity', + verbose_name=_('collectivity')) + service_url = URLField( + verbose_name=_('URL'), + blank=True) + metadata_url = URLField( + verbose_name=_('SAML Metadata URL'), + blank=True) + oauth2_url = URLField( + verbose_name=_('OAuth2 URL'), + blank=True) + oauth2_key = CharField( + verbose_name=_('OAuth2 Key'), + max_length=64, + blank=True) + + def __unicode__(self): + return unicode(self.service) + + def clean(self): + if self.collectivity and self.service and self.service.is_global: + qs = ServiceInstance.objects.exclude(id=self.id) + qs = qs.filter(collectivity=self.collectivity, + service=self.service) + if qs.exists(): + raise ValidationError(_('There can be only one instance of a global service by collectivity')) + if not self.service.is_global and not self.service_url: + raise ValidationError(_('Service URL field is required')) + + class Meta: + verbose_name = _('service instance') + verbose_name = _('service instances') + unique_together = (('slug', 'service', 'collectivity'),) + ordering = ('service__name', 'slug') + +class Access(Model): + user = ForeignKey('User', + verbose_name=_('user')) + service_instance = ForeignKey('ServiceInstance', + verbose_name=_('service instance')) + + class Meta: + verbose_name = _('access') + verbose_name = _('accesses') + unique_together = (('user', 'service_instance'),) + ordering = ('user__last_name', 'user__first_name', 'service_instance__service__name') + diff --git a/src/authentic2_pratic/pratic_attribute_source.py b/src/authentic2_pratic/pratic_attribute_source.py new file mode 100644 index 0000000..03472c0 --- /dev/null +++ b/src/authentic2_pratic/pratic_attribute_source.py @@ -0,0 +1,47 @@ +from django.utils.translation import ugettext_lazy as _ + +from ...models import Attribute, AttributeValue + +from ...decorators import to_list +from ...compat import get_user_model + +@to_list +def get_instances(ctx): + ''' + Retrieve instances from settings + ''' + return [None] + +@to_list +def get_attribute_names(instance, ctx): + User = get_user_model() + for field in User._meta.fields: + name = 'django_user_' + str(field.name) + description = field.verbose_name + u' (%s)' % name + yield name, description + for attribute in Attribute.objects.all(): + name = 'django_user_' + str(attribute.name) + description = attribute.label + u' (%s)' % name + yield name, description + yield 'django_user_groups', User._meta.get_field_by_name('groups')[0].verbose_name + u' (django_user_groups)' + yield 'django_user_group_names', User._meta.get_field_by_name('groups')[0].verbose_name + u' (django_user_group_names)' + yield 'django_user_domain', _('User domain') + u' (django_user_domain)' + yield 'django_user_identifier', _('User identifier') + u' (django_user_identifier)' + +def get_dependencies(instance, ctx): + return ('user',) + +def get_attributes(instance, ctx): + user = ctx.get('user') + User = get_user_model() + if not user or not isinstance(user, User): + return ctx + for field in User._meta.fields: + ctx['django_user_' + str(field.name)] = getattr(user, field.name) + for av in AttributeValue.objects.with_owner(user): + ctx['django_user_' + str(av.attribute.name)] = av.to_python() + ctx['django_user_groups'] = [group for group in user.groups.all()] + ctx['django_user_group_names'] = [unicode(group) for group in user.groups.all()] + ctx['django_user_domain'] = user.username.rsplit('@', 1)[1] if '@' in user.username else '' + ctx['django_user_identifier'] = user.username.rsplit('@', 1)[0] if '@' in user.username else '' + return ctx diff --git a/src/authentic2_pratic/tables.py b/src/authentic2_pratic/tables.py new file mode 100644 index 0000000..0b8dfdb --- /dev/null +++ b/src/authentic2_pratic/tables.py @@ -0,0 +1,68 @@ +from django.utils.translation import ugettext_lazy as _ + +import django_tables2 as tables + +from . import models + +class ServiceTable(tables.Table): + name = tables.TemplateColumn( + '{{ record.name }}', + verbose_name=_('name')) + delete = tables.TemplateColumn( + '{% load i18n %}{% trans "Delete" %}', + verbose_name=_('delete')) + + class Meta: + model = models.Service + attrs = {'class': 'main', 'id': 'services-table'} + fields = ('name', 'slug', 'is_global', 'service_url', 'delete') + +class CollectivityTable(tables.Table): + name = tables.TemplateColumn( + '{{ record.name }}', + verbose_name=_('name')) + delete = tables.TemplateColumn( + '{% load i18n %}{% trans "Delete" %}', + verbose_name=_('delete')) + + class Meta: + model = models.Collectivity + attrs = {'class': 'main', 'id': 'collectivities-table'} + fields = ('name', 'insee_code', 'postal_code', 'delete') + +class UserTable(tables.Table): + uid = tables.TemplateColumn( + '{{ record.uid }}', + verbose_name=_('identifier')) + delete = tables.TemplateColumn( + '{% load i18n %}{% trans "Delete" %}', + verbose_name=_('delete')) + + class Meta: + model = models.User + attrs = {'class': 'main', 'id': 'users-table'} + fields = ('first_name', 'last_name', 'uid', 'is_admin', 'is_active', 'delete') + +class ServiceInstanceTable(tables.Table): + slug = tables.TemplateColumn( + '{{ record.slug}}', + verbose_name=_('identifier')) + delete = tables.TemplateColumn( + '{% load i18n %}{% trans "Delete" %}', + verbose_name=_('delete')) + + class Meta: + model = models.User + attrs = {'class': 'main', 'id': 'users-table'} + fields = ('slug', 'service_url') + +class AccessTable(tables.Table): + last_name = tables.TemplateColumn('{{ record.user.last_name}}', + verbose_name=_('Last name')) + first_name = tables.TemplateColumn('{{ record.user.first_name }}', + verbose_name=_('First name')) + + class Meta: + model = models.Access + attrs = {'class': 'main', 'id': 'accesses-table'} + fields = ('last_name', 'first_name', 'service_instance') diff --git a/src/authentic2_pratic/templates/authentic2_pratic/accesses.html b/src/authentic2_pratic/templates/authentic2_pratic/accesses.html new file mode 100644 index 0000000..4c153ef --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/accesses.html @@ -0,0 +1,19 @@ +{% extends "authentic2_pratic/collectivity_sidebar.html" %} +{% load i18n staticfiles django_tables2 %} + +{% block page-title %}{{ block.super }} - {% trans "Accesses management" %}{% endblock %} + +{% block page_title %}{{ block.super }} {% trans "Accesses management" %}{% endblock %} + +{% block appbar %} + {{ block.super }} + {% trans "Add access" %} +{% endblock %} + +{% block sidebar %} +

{% blocktrans count count=object_list.count %}{{ count }} accesses{% plural %}{{ count }} access{% endblocktrans %}

+{% endblock %} + +{% block main %} + {% render_table table "authentic2_pratic/table.html" %} +{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/base.html b/src/authentic2_pratic/templates/authentic2_pratic/base.html new file mode 100644 index 0000000..ec32fbd --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/base.html @@ -0,0 +1,35 @@ +{% extends "gadjo/base.html" %} +{% load i18n staticfiles %} + +{% block page-title %}{% trans "Management" %}{% endblock %} + +{% block css %} + +{% endblock %} + +{% block extrascripts %} + {% if debug %} + + {% else %} + + {% endif %} + + + + + +{% endblock %} + +{% block site-url %}/{% endblock %} +{% block site-title %}{% trans "Management" %}{% endblock %} + +{% block logout-url %}{% url "auth_logout" %}{% endblock %} + +{% block homepage-url %}{% url "a2-pratic-homepage" %}{% endblock %} + +{% block appbar %} +

{% block page_title %}{% endblock %}

+{% endblock %} + diff --git a/src/authentic2_pratic/templates/authentic2_pratic/collectivities.html b/src/authentic2_pratic/templates/authentic2_pratic/collectivities.html new file mode 100644 index 0000000..f26373f --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/collectivities.html @@ -0,0 +1,19 @@ +{% extends "authentic2_pratic/sidebar.html" %} +{% load i18n staticfiles django_tables2 %} + +{% block page-title %}{{ block.super }} - {% trans "Collectivities management" %}{% endblock %} + +{% block page_title %}{% trans "Collectivities management" %}{% endblock %} + +{% block appbar %} + {{ block.super }} + {% trans "Add collectivity" %} +{% endblock %} + +{% block sidebar %} +

{% blocktrans count count=object_list.count %}{{ count }} collectivities{% plural %}{{ count }} collectivity{% endblocktrans %}

+{% endblock %} + +{% block main %} + {% render_table table "authentic2_pratic/table.html" %} +{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/collectivity_edit.html b/src/authentic2_pratic/templates/authentic2_pratic/collectivity_edit.html new file mode 100644 index 0000000..1143793 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/collectivity_edit.html @@ -0,0 +1,54 @@ +{% extends "authentic2_pratic/sidebar.html" %} +{% load i18n %} + +{% block messages %} +{% endblock %} + +{% block page_title %} +{% trans "Collectivities management" %} +{% endblock %} + +{% block sidebar %} +

{% trans "Users management" %}

+

{% trans "Service instances management" %}

+

{% trans "Accesses management" %}

+{% endblock %} + +{% block main %} +
+ {% if title %} +

{{ title }}

+ {% endif %} + {% if other_actions %} +
+ {% trans "Actions" %} +
+ {% for action in other_actions %} + + {% endfor %} +
+
+ {% endif %} +
+
+ {% if messages %} +
    + {% for message in messages %} + + {{ message }} + + {% endfor %} +
+ {% endif %} + {% csrf_token %} + {{ form.as_p }} +
+ + {% trans "Cancel" %} +
+
+
+
+{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/collectivity_sidebar.html b/src/authentic2_pratic/templates/authentic2_pratic/collectivity_sidebar.html new file mode 100644 index 0000000..0a1693f --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/collectivity_sidebar.html @@ -0,0 +1,8 @@ +{% extends "authentic2_pratic/sidebar.html" %} +{% load i18n staticfiles django_tables2 %} + +{% block page_title %} + + {{ collectivity }} + +{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/delete.html b/src/authentic2_pratic/templates/authentic2_pratic/delete.html new file mode 100644 index 0000000..fd0c0c4 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/delete.html @@ -0,0 +1,33 @@ +{% extends "authentic2_pratic/sidebar.html" %} +{% load i18n %} + +{% block messages %} +{% endblock %} + +{% block main %} +
+ {% if title %} +

{{ title }}

+ {% endif %} +
+
+ {% if messages %} +
    + {% for message in messages %} + + {{ message }} + + {% endfor %} +
+ {% endif %} +

{% blocktrans %}Do you really want to delete « {{ object }} » ?{% endblocktrans %}

+ {% csrf_token %} + {{ form.as_p }} +
+ + {% trans "Cancel" %} +
+
+
+
+{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/form.html b/src/authentic2_pratic/templates/authentic2_pratic/form.html new file mode 100644 index 0000000..5a9dbba --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/form.html @@ -0,0 +1,44 @@ +{% extends "authentic2_pratic/sidebar.html" %} +{% load i18n %} + +{% block messages %} +{% endblock %} + +{% block main %} +
+ {% if title %} +

{{ title }}

+ {% endif %} + {% if other_actions %} +
+ {% trans "Actions" %} +
+ {% for action in other_actions %} + + {% endfor %} +
+
+ {% endif %} +
+
+ {% if messages %} +
    + {% for message in messages %} + + {{ message }} + + {% endfor %} +
+ {% endif %} + {% csrf_token %} + {{ form.as_p }} +
+ + {% trans "Cancel" %} +
+
+
+
+{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/homepage.html b/src/authentic2_pratic/templates/authentic2_pratic/homepage.html new file mode 100644 index 0000000..a998678 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/homepage.html @@ -0,0 +1,24 @@ +{% extends "authentic2_pratic/base.html" %} +{% load i18n %} + +{% block beforecontent %} +{% endblock %} + +{% block appbar %} +

{% trans "Welcome" %}

+{% endblock %} + +{% block content %} +
+
+ {{ user.get_full_name }} + ({% trans "Password change" %}) +
+ + +
+
+{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/service_instances.html b/src/authentic2_pratic/templates/authentic2_pratic/service_instances.html new file mode 100644 index 0000000..5d597da --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/service_instances.html @@ -0,0 +1,19 @@ +{% extends "authentic2_pratic/collectivity_sidebar.html" %} +{% load i18n staticfiles django_tables2 %} + +{% block page-title %}{{ block.super }} - {% trans "Service instances management" %}{% endblock %} + +{% block page_title %}{{ block.super }}{% trans "Service instances management" %}{% endblock %} + +{% block appbar %} + {{ block.super }} + {% trans "Add service-instance" %} +{% endblock %} + +{% block sidebar %} +

{% blocktrans count count=object_list.count %}{{ count }} services instances{% plural %}{{ count }} service instance{% endblocktrans %}

+{% endblock %} + +{% block main %} + {% render_table table "authentic2_pratic/table.html" %} +{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/services.html b/src/authentic2_pratic/templates/authentic2_pratic/services.html new file mode 100644 index 0000000..1766069 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/services.html @@ -0,0 +1,19 @@ +{% extends "authentic2_pratic/sidebar.html" %} +{% load i18n staticfiles django_tables2 %} + +{% block page-title %}{{ block.super }} - {% trans "Services management" %}{% endblock %} + +{% block page_title %}{% trans "Services management" %}{% endblock %} + +{% block appbar %} + {{ block.super }} + {% trans "Add service" %} +{% endblock %} + +{% block sidebar %} +

{% blocktrans count count=object_list.count %}{{ count }} services{% plural %}{{ count }} service{% endblocktrans %}

+{% endblock %} + +{% block main %} + {% render_table table "authentic2_pratic/table.html" %} +{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/sidebar.html b/src/authentic2_pratic/templates/authentic2_pratic/sidebar.html new file mode 100644 index 0000000..6cc6f31 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/sidebar.html @@ -0,0 +1,12 @@ +{% extends "authentic2_pratic/base.html" %} + +{% block content %} + +
+ {% block main %} + {% endblock %} +
+{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/table.html b/src/authentic2_pratic/templates/authentic2_pratic/table.html new file mode 100644 index 0000000..34f0479 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/table.html @@ -0,0 +1,58 @@ +{% extends "django_tables2/table.html" %} + +{% load django_tables2 %} + +{% block table.thead %} + + + {% for column in table.columns %} + {% if column.orderable %} + {{ column.header }} + {% else %} + {{ column.header }} + {% endif %} + {% endfor %} + {% block table.head.last.column %} + {% endblock %} + + +{% endblock table.thead %} + +{% block table.tbody.row %} + {# avoid cycle for Django 1.2-1.6 compatibility #} + {% for column, cell in row.items %} + {% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %} + {% endfor %} + {% block table.tbody.last.column %} + {% endblock %} + +{% endblock table.tbody.row %} + +{% block pagination %} +

+ {% if table.page.number > 1 %} + {% if table.page.previous_page_number != 1 %} + 1 + ... + {% endif %} + {% endif %} + + {% if table.page.has_previous %} + {{ table.page.previous_page_number }} + {% endif %} + + {{ table.page.number }} + + {% if table.page.has_next %} + {{ table.page.next_page_number }} + {% endif %} + {% if table.page.number != table.page.paginator.num_pages %} + {% if table.page.paginator.num_pages > 1 %} + {% if table.page.next_page_number != table.page.paginator.num_pages %} + ... + {{ table.page.paginator.num_pages }} + {% endif %} + {% endif %} + {% endif %} +

+{% endblock %} diff --git a/src/authentic2_pratic/templates/authentic2_pratic/users.html b/src/authentic2_pratic/templates/authentic2_pratic/users.html new file mode 100644 index 0000000..5de0ac2 --- /dev/null +++ b/src/authentic2_pratic/templates/authentic2_pratic/users.html @@ -0,0 +1,19 @@ +{% extends "authentic2_pratic/collectivity_sidebar.html" %} +{% load i18n staticfiles django_tables2 %} + +{% block page-title %}{{ block.super }} - {% trans "Users management" %}{% endblock %} + +{% block page_title %}{{ block.super }}{% trans "Users management" %}{% endblock %} + +{% block appbar %} + {{ block.super }} + {% trans "Add user" %} +{% endblock %} + +{% block sidebar %} +

{% blocktrans count count=object_list.count %}{{ count }} users{% plural %}{{ count }} user{% endblocktrans %}

+{% endblock %} + +{% block main %} + {% render_table table "authentic2_pratic/table.html" %} +{% endblock %} diff --git a/src/authentic2_pratic/urls.py b/src/authentic2_pratic/urls.py new file mode 100644 index 0000000..b789b46 --- /dev/null +++ b/src/authentic2_pratic/urls.py @@ -0,0 +1,61 @@ +from django.conf.urls import patterns, url, include + +from . import views + +access_urlpatterns = patterns('', + url('^$', + views.access_edit, + name='a2-pratic-access-edit'), + url('^delete/$', + views.access_delete, + name='a2-pratic-access-delete')) + +service_instance_urlpatterns = patterns('', + url('^$', + views.service_instance_edit, + name='a2-pratic-service-instance-edit'), + url('^delete/$', + views.service_instance_delete, + name='a2-pratic-service-instance-delete')) + +user_urlpatterns = patterns('', + url('^$', views.user_edit, name='a2-pratic-user-edit'), + url('^delete/$', views.user_delete, name='a2-pratic-user-delete')) + +collectivity_urlpatterns = patterns('', + url('^$', views.collectivity_edit, name='a2-pratic-collectivity-edit'), + url('^delete/$', views.collectivity_delete, name='a2-pratic-collectivity-delete'), + url('^users/$', views.collectivity_users, name='a2-pratic-users'), + url('^users/add/$', + views.user_add, + name='a2-pratic-user-add'), + url('^users/(?P\d+)/', + include(user_urlpatterns)), + url('^services/$', + views.collectivity_service_instances, + name='a2-pratic-service-instances'), + url('^services/add/$', + views.service_instance_add, + name='a2-pratic-service-instance-add'), + url('^services/(?P\d+)/', + include(service_instance_urlpatterns)), + url('^accesses/$', + views.collectivity_accesses, + name='a2-pratic-accesses'), + url('^accesses/add/$', + views.access_add, + name='a2-pratic-access-add'), + url('^accesses/(?P\d+)/$', + include(access_urlpatterns)), + ) + +urlpatterns = patterns('', + url('^manage/$', views.homepage, name='a2-pratic-homepage'), + url('^manage/services/$', views.services, name='a2-pratic-services'), + url('^manage/services/add/$', views.service_add, name='a2-pratic-service-add'), + url('^manage/services/(?P\d+)/delete/$', views.service_delete, name='a2-pratic-service-delete'), + url('^manage/services/(?P\d+)/$', views.service_edit, name='a2-pratic-service-edit'), + url('^manage/collectivities/$', views.collectivities, name='a2-pratic-collectivities'), + url('^manage/collectivities/add/$', views.collectivity_add, name='a2-pratic-collectivity-add'), + url('^manage/collectivities/(?P\d+)/', include(collectivity_urlpatterns)), +) diff --git a/src/authentic2_pratic/views.py b/src/authentic2_pratic/views.py new file mode 100644 index 0000000..fa2b0df --- /dev/null +++ b/src/authentic2_pratic/views.py @@ -0,0 +1,239 @@ +import logging +from django.contrib.auth.decorators import login_required, user_passes_test +from django.utils.translation import ugettext_lazy as _ +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404 +from django.contrib import messages + +from django.views.generic import (TemplateView, FormView, UpdateView, + CreateView, DeleteView, View, ListView) +from django_tables2 import SingleTableView + +from authentic2.manager.views import (AjaxFormViewMixin, + ActionMixin, OtherActionsMixin, TitleMixin, Action, UserEditView) + +from . import models, tables, forms + +def is_pratic_admin(user): + if user.is_superuser: + return True + return getattr(user, 'is_admin', False) + +pratic_admin = user_passes_test(is_pratic_admin) +class DeleteActionMixin(object): + + def get_other_actions(self): + yield Action('delete', + _('Delete'), + _(u'Do you really want to delete "%s" ?') % self.object) + + def action_delete(self, request, *args, **kwargs): + self.object.delete() + return HttpResponseRedirect('.') + +class HomepageView(TemplateView): + template_name = 'authentic2_pratic/homepage.html' + +# Services + +class ServicesView(SingleTableView): + template_name = 'authentic2_pratic/services.html' + model = models.Service + table_class = tables.ServiceTable + +class ServiceAddView(TitleMixin, ActionMixin, AjaxFormViewMixin, CreateView): + model = models.Service + form_class = forms.ServiceForm + title = _('Add service') + template_name = 'authentic2_pratic/form.html' + action = _('Add') + +class ServiceView(DeleteActionMixin, TitleMixin, OtherActionsMixin, + AjaxFormViewMixin, UpdateView): + model = models.Service + title = _('Edit service') + template_name = 'authentic2_pratic/form.html' + form_class = forms.ServiceForm + +class ServiceDeleteView(TitleMixin, AjaxFormViewMixin, DeleteView): + model = models.Service + template_name = 'authentic2_pratic/delete.html' + title = _('Delete service') + success_url = 'a2-pratic-services' + +# Collectivities +class CollectivitiesView(SingleTableView): + template_name = 'authentic2_pratic/collectivities.html' + model = models.Collectivity + table_class = tables.CollectivityTable + +class CollectivityAddView(TitleMixin, ActionMixin, AjaxFormViewMixin, CreateView): + model = models.Collectivity + title = _('Add collectivity') + template_name = 'authentic2_pratic/form.html' + action = _('Add') + +class CollectivityView(DeleteActionMixin, TitleMixin, OtherActionsMixin, + AjaxFormViewMixin, UpdateView): + model = models.Collectivity + title = _('Edit collectivity') + template_name = 'authentic2_pratic/collectivity_edit.html' + form_class = forms.CollectivityForm + pk_url_kwarg = 'collectivity_pk' + +class CollectivityDeleteView(TitleMixin, AjaxFormViewMixin, DeleteView): + model = models.Service + template_name = 'authentic2_pratic/delete.html' + title = _('Delete collectivity') + success_url = 'a2-pratic-collectivities' + pk_url_kwarg = 'collectivity_pk' + +class CollectivityMixin(object): + def dispatch(self, request, *args, **kwargs): + self.collectivity = get_object_or_404(models.Collectivity, + pk=kwargs['collectivity_pk']) + return super(CollectivityMixin, self).dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + ctx = super(CollectivityMixin, self).get_context_data(**kwargs) + ctx['collectivity'] = self.collectivity + return ctx + +class CollectivityChildMixin(CollectivityMixin): + def get_queryset(self): + qs = super(CollectivityMixin, self).get_queryset() + return qs.filter(collectivity=self.collectivity) + + def get_form_kwargs(self): + kwargs = super(CollectivityMixin, self).get_form_kwargs() + if not kwargs.get('instance'): + kwargs['instance'] = self.model(collectivity=self.collectivity) + return kwargs + +# users +class UsersView(CollectivityChildMixin, SingleTableView): + template_name = 'authentic2_pratic/users.html' + model = models.User + table_class = tables.UserTable + +class UserAddView(CollectivityChildMixin, TitleMixin, ActionMixin, + AjaxFormViewMixin, CreateView): + model = models.User + title = _('Add user') + template_name = 'authentic2_pratic/form.html' + action = _('Add') + form_class = forms.UserForm + +class UserView(CollectivityChildMixin, UserEditView): + model = models.User + title = _('Edit user') + template_name = 'authentic2_pratic/form.html' + form_class = forms.UserForm + +class UserDeleteView(CollectivityChildMixin, TitleMixin, AjaxFormViewMixin, + DeleteView): + model = models.Service + template_name = 'authentic2_pratic/delete.html' + title = _('Delete user') + success_url = 'a2-pratic-users' + +# service instances +class ServiceInstancesView(CollectivityChildMixin, SingleTableView): + template_name = 'authentic2_pratic/service_instances.html' + model = models.ServiceInstance + table_class = tables.ServiceInstanceTable + +class ServiceInstanceAddView(CollectivityChildMixin, TitleMixin, ActionMixin, + AjaxFormViewMixin, CreateView): + model = models.ServiceInstance + title = _('Add service instance') + template_name = 'authentic2_pratic/form.html' + action = _('Add') + form_class = forms.ServiceInstanceForm + +class ServiceInstanceView(CollectivityChildMixin, DeleteActionMixin, TitleMixin, + OtherActionsMixin, AjaxFormViewMixin, UpdateView): + model = models.ServiceInstance + title = _('Edit service instance') + template_name = 'authentic2_pratic/form.html' + form_class = forms.ServiceInstanceForm + +class ServiceInstanceDeleteView(CollectivityChildMixin, TitleMixin, + AjaxFormViewMixin, DeleteView): + model = models.ServiceInstance + template_name = 'authentic2_pratic/delete.html' + title = _('Delete service instance') + success_url = 'a2-pratic-users' + +# accesses +class AccessMixin(object): + def get_queryset(self): + qs = self.model.objects.all() + return qs.filter(user__collectivity=self.collectivity, + service_instance__collectivity=self.collectivity) + + def get_form_kwargs(self): + kwargs = super(CollectivityMixin, self).get_form_kwargs() + kwargs['collectivity'] = self.collectivity + return kwargs + +class AccessesView(AccessMixin, CollectivityMixin, SingleTableView): + template_name = 'authentic2_pratic/accesses.html' + model = models.Access + table_class = tables.AccessTable + +class AccessAddView(AccessMixin, CollectivityMixin, TitleMixin, ActionMixin, + AjaxFormViewMixin, CreateView): + model = models.Access + title = _('Add service instance') + template_name = 'authentic2_pratic/form.html' + action = _('Add') + form_class = forms.AccessForm + +class AccessView(AccessMixin, CollectivityMixin, DeleteActionMixin, TitleMixin, + OtherActionsMixin, AjaxFormViewMixin, UpdateView): + model = models.Access + title = _('Edit service instance') + template_name = 'authentic2_pratic/form.html' + form_class = forms.AccessForm + +class AccessDeleteView(AccessMixin, CollectivityMixin, TitleMixin, + AjaxFormViewMixin, DeleteView): + model = models.Access + template_name = 'authentic2_pratic/delete.html' + title = _('Delete service instance') + success_url = 'a2-pratic-users' + +# general views +homepage = pratic_admin(HomepageView.as_view()) + +# services +services = pratic_admin(ServicesView.as_view()) +service_edit = pratic_admin(ServiceView.as_view()) +service_add = pratic_admin(ServiceAddView.as_view()) +service_delete = pratic_admin(ServiceDeleteView.as_view()) + +# collectivities +collectivities = pratic_admin(CollectivitiesView.as_view()) +collectivity_edit = pratic_admin(CollectivityView.as_view()) +collectivity_add = pratic_admin(CollectivityAddView.as_view()) +collectivity_delete = pratic_admin(CollectivityDeleteView.as_view()) + +# collectivity users +collectivity_users = pratic_admin(UsersView.as_view()) +user_add = pratic_admin(UserAddView.as_view()) +user_edit = pratic_admin(UserView.as_view()) +user_delete = pratic_admin(UserDeleteView.as_view()) + +# service instances +collectivity_service_instances = pratic_admin(ServiceInstancesView.as_view()) +service_instance_add = pratic_admin(ServiceInstanceAddView.as_view()) +service_instance_edit = pratic_admin(ServiceInstanceView.as_view()) +service_instance_delete = pratic_admin(ServiceInstanceDeleteView.as_view()) + +# accesses +collectivity_accesses = pratic_admin(AccessesView.as_view()) +access_add = pratic_admin(AccessAddView.as_view()) +access_edit = pratic_admin(AccessView.as_view()) +access_delete = pratic_admin(AccessDeleteView.as_view()) +