commit f13f8f15f9ae2146abd070fa4cc5426bd0105bdc Author: Christophe Boulanger Date: Fri Jun 2 15:12:39 2017 +0200 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed2b85e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/MANIFEST +*.pyc +/dist +/build +/passerelle_imio_ia_delib.egg-info +django.mo +sed.sh diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8a5b6c8 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +recursive-include passerelle_imio_ia_delib/static *.css *.png +recursive-include passerelle_imio_ia_delib/templates *.html *.txt +recursive-include passerelle_imio_ia_delib/locale *.po *.mo +include MANIFEST.in +include VERSION +include README diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb65d2b --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +Passerelle connector to Liege Lisrue service +============================================ + +Installation +------------ + + - add to Passerelle installed apps settings: + INSTALLED_APPS += ('passerelle_imio_ia_delib',) + + - enable module: + PASSERELLE_APP_PASSERELLE_IMIO_IA_DELIB_ENABLED = True + + +Usage +----- + + - create and configure new connector + - Title/description: whatever you want + - Certificate check: uncheck if the service has no valid certificate + + - test service by clicking on the available links + - the /testConnection/ endpoint try to establish a connection with IA DELIB + - the /test_createItem/ endpoint try to create a new point in IA DELIB + + +Usage in w.c.s. +--------------- diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..342f1ca --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passerelle.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/passerelle_imio_ia_delib/__init__.py b/passerelle_imio_ia_delib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/passerelle_imio_ia_delib/migrations/0001_initial.py b/passerelle_imio_ia_delib/migrations/0001_initial.py new file mode 100644 index 0000000..070b297 --- /dev/null +++ b/passerelle_imio_ia_delib/migrations/0001_initial.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0005_resourcelog'), + ] + + operations = [ + migrations.CreateModel( + name='IImioIaDelib', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('title', models.CharField(max_length=50)), + ('slug', models.SlugField()), + ('description', models.TextField()), + ('log_level', models.CharField(default=b'INFO', max_length=10, verbose_name='Log Level', choices=[(b'NOTSET', b'NOTSET'), (b'DEBUG', b'DEBUG'), (b'INFO', b'INFO'), (b'WARNING', b'WARNING'), (b'ERROR', b'ERROR'), (b'CRITICAL', b'CRITICAL'), (b'FATAL', b'FATAL')])), + ('wsdl_url', models.CharField(help_text='WSDL URL', max_length=128, verbose_name='WSDL URL')), + ('verify_cert', models.BooleanField(default=True, verbose_name='Check HTTPS Certificate validity')), + ('username', models.CharField(max_length=128, verbose_name='Username', blank=True)), + ('password', models.CharField(max_length=128, verbose_name='Password', blank=True)), + ('keystore', models.FileField(help_text='Certificate and private key in PEM format', upload_to=b'iparapheur', null=True, verbose_name='Keystore', blank=True)), + ('users', models.ManyToManyField(to='base.ApiUser', blank=True)), + ], + options={ + 'verbose_name': 'i-ImioIaDelib', + }, + ), + ] diff --git a/passerelle_imio_ia_delib/migrations/__init__.py b/passerelle_imio_ia_delib/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/passerelle_imio_ia_delib/models.py b/passerelle_imio_ia_delib/models.py new file mode 100644 index 0000000..7ffe501 --- /dev/null +++ b/passerelle_imio_ia_delib/models.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# passerelle-imio-ia-delib - passerelle connector to IA DELIB IMIO PRODUCTS. +# Copyright (C) 2016 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# https://demo-pm.imio.be/ws4pm.wsdl +# https://demo-pm.imio.be/ +# http://trac.imio.be/trac/browser/communesplone/imio.pm.wsclient/trunk/src/imio/pm/wsclient/browser/settings.py#L211 +# http://svn.communesplone.org/svn/communesplone/imio.pm.ws/trunk/docs/README.txt +import base64 +import json +import magic +import suds + +from requests.exceptions import ConnectionError +from django.db import models +from django.core.urlresolvers import reverse +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.http import HttpResponse, Http404 + +from passerelle.base.models import BaseResource +from passerelle.utils.api import endpoint +from passerelle.utils.jsonresponse import APIError +from .soap import get_client as soap_get_client + +from suds.xsd.doctor import ImportDoctor, Import +from suds.transport.http import HttpAuthenticated + +def get_client(model): + try: + return soap_get_client(model) + except ConnectionError as e: + raise APIError('i-ImioIaDelib error: %s' % e) + + +def format_type(t): + return {'id': unicode(t), 'text': unicode(t)} + + +def format_file(f): + return {'status': f.status, 'id': f.nom, 'timestamp': f.timestamp} + + +class FileError(Exception): + pass + + +class FileNotFoundError(Exception): + http_status = 404 + + +class PayloadInterceptor(suds.plugin.MessagePlugin): + def __init__(self, *args, **kwargs): + self.last_payload = None + + def received(self, context): + #recieved xml as a string + print "%s bytes received" % len(context.reply) + self.last_payload = context.reply + #clean up reply to prevent parsing + context.reply = "" + return context + + +class IImioIaDelib(BaseResource): + wsdl_url = models.CharField(max_length=128, blank=False, + verbose_name=_('WSDL URL'), + help_text=_('WSDL URL')) + verify_cert = models.BooleanField(default=True, + verbose_name=_('Check HTTPS Certificate validity')) + username = models.CharField(max_length=128, blank=True, + verbose_name=_('Username')) + password = models.CharField(max_length=128, blank=True, + verbose_name=_('Password')) + keystore = models.FileField(upload_to='iparapheur', null=True, blank=True, + verbose_name=_('Keystore'), + help_text=_('Certificate and private key in PEM format')) + + category = _('Business Process Connectors') + + class Meta: + verbose_name = _('i-ImioIaDelib') + + @classmethod + def get_verbose_name(cls): + return cls._meta.verbose_name + + @endpoint() + def test(self): + return 'True' + + @endpoint(serializer_type='json-api', perm='can_access') + def testConnection(self, request): + client = get_client(self) + # payload_interceptor = PayloadInterceptor() + # client.options.plugins = [payload_interceptor] + return dict(client.service.testConnection('')) + + @endpoint(serializer_type='json-api', perm='can_access') + def test_createItem(self, request): + client = get_client(self) + return dict(client.service.createItem('meeting-config-college', 'dirgen', + {'title': 'CREATION DE POINT TS2', + 'description': 'My new item description', + 'decision': 'My decision'})) + + #createItem?meetingConfigId=meeting-config-college&proposingGroupId=dirgen&title=Mon%20nouveau%20point&description=Ma%20nouvelle%20description&decision=Ma%20nouvelle%20decision + @endpoint(serializer_type='json-api', perm='can_access') + def createItem(self, request, meetingConfigId, proposingGroupId, title, description,decision): + creationData ={'title':title, + 'description':description, + 'decision':decision + } + client = get_client(self) + return dict(client.service.createItem(meetingConfigId, + proposingGroupId, + creationData)) diff --git a/passerelle_imio_ia_delib/soap.py b/passerelle_imio_ia_delib/soap.py new file mode 100644 index 0000000..ed23122 --- /dev/null +++ b/passerelle_imio_ia_delib/soap.py @@ -0,0 +1,83 @@ +# Passerelle - uniform access to data and services +# Copyright (C) 2015 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +# borrowed from https://pypi.python.org/pypi/suds_requests +# and https://docs.oracle.com/cd/E50245_01/E50253/html/vmprg-soap-example-authentication-python.html + + +import requests +import StringIO + +from suds.client import Client +from suds.transport.http import HttpAuthenticated +from suds.transport import Reply +from suds.plugin import MessagePlugin, DocumentPlugin + +from suds.sudsobject import asdict + + +class Filter(MessagePlugin): + + def marshalled(self, context): + context.envelope.set('xmlns:xm', 'http://www.w3.org/2005/05/xmlmime') + + def received(self, context): + reply = context.reply + context.reply = reply[reply.find("") + 1] + +class Handlewsdl(DocumentPlugin): + + def loaded(self, context): + # unknown types, so present them as strings + context.document = context.document.replace('type="iph:DossierID"', 'type="xsd:string"') + context.document = context.document.replace('type="iph:TypeTechnique"', 'type="xsd:string"') + + +class Transport(HttpAuthenticated): + def __init__(self, model, **kwargs): + self.model = model + HttpAuthenticated.__init__(self, **kwargs) + + def get_requests_kwargs(self): + kwargs = {} + if self.model.username: + kwargs['auth'] = (self.model.username, self.model.password) + if self.model.keystore: + kwargs['cert'] = (self.model.keystore.path, self.model.keystore.path) + if not self.model.verify_cert: + kwargs['verify'] = False + return kwargs + + def open(self, request): + # only use our custom handler to fetch service resources, not schemas + # from other namespaces + if 'www.w3.org' in request.url: + return HttpAuthenticated.open(self, request) + resp = self.model.requests.get(request.url, headers=request.headers, + **self.get_requests_kwargs()) + return StringIO.StringIO(resp.content) + + def send(self, request): + request.message = request.message.replace("contentType", "xm:contentType") + self.addcredentials(request) + resp = self.model.requests.post(request.url, data=request.message, + headers=request.headers, **self.get_requests_kwargs()) + return Reply(resp.status_code, resp.headers, resp.content) + +# plugins=[Handlewsdl(), Filter()] +def get_client(instance): + transport = Transport(instance) + return Client(instance.wsdl_url, transport=transport, cache=None) diff --git a/passerelle_imio_ia_delib/templates/iimioiadelib/iimioiadelib_detail.html b/passerelle_imio_ia_delib/templates/iimioiadelib/iimioiadelib_detail.html new file mode 100644 index 0000000..0aa5570 --- /dev/null +++ b/passerelle_imio_ia_delib/templates/iimioiadelib/iimioiadelib_detail.html @@ -0,0 +1,12 @@ +{% extends "passerelle/manage/service_view.html" %} +{% load i18n passerelle %} + +{% block description %} +{% endblock %} + +{% block security %} +

+{% trans 'Access is limited to the following API users:' %} +

+{% access_rights_table resource=object permission='can_access' %} +{% endblock %} diff --git a/passerelle_imio_ia_delib/templates/passerelle_imio_ia_delib/iimioiadelib_detail.html b/passerelle_imio_ia_delib/templates/passerelle_imio_ia_delib/iimioiadelib_detail.html new file mode 100644 index 0000000..956e6be --- /dev/null +++ b/passerelle_imio_ia_delib/templates/passerelle_imio_ia_delib/iimioiadelib_detail.html @@ -0,0 +1,22 @@ +{% extends "passerelle/manage/service_view.html" %} +{% load i18n passerelle %} + +{% block description %} +{% endblock %} + +{% block security %} +TEST +

+{% trans 'Access is limited to the following API users:' %} +

+{% access_rights_table resource=object permission='can_access' %} +{%endblock %} + +{% block endpoints %} +
    + {% url 'generic-endpoint' connector='iimioiadelib' slug=object.slug endpoint='testConnection' as is_connected %} +
  • {% trans 'Check Connection with IA Delib:' %} +

    {{ is_connected }}

    +
  • +
+{% endblock %} diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8af93ac --- /dev/null +++ b/setup.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +import glob +import os +import re +import subprocess +import sys + +from setuptools.command.install_lib import install_lib as _install_lib +from distutils.command.build import build as _build +from distutils.command.sdist import sdist +from distutils.cmd import Command +from setuptools import setup, find_packages + +class eo_sdist(sdist): + def run(self): + if os.path.exists('VERSION'): + os.remove('VERSION') + version = get_version() + version_file = open('VERSION', 'w') + version_file.write(version) + version_file.close() + sdist.run(self) + if os.path.exists('VERSION'): + os.remove('VERSION') + +def get_version(): + if os.path.exists('VERSION'): + version_file = open('VERSION', 'r') + version = version_file.read() + version_file.close() + return version + if os.path.exists('.git'): + p = subprocess.Popen(['git', 'describe', '--dirty', '--match=v*'], stdout=subprocess.PIPE) + result = p.communicate()[0] + if p.returncode == 0: + version = result.split()[0][1:] + version = version.replace('-', '.') + return version + return '0' + + +class compile_translations(Command): + description = 'compile message catalogs to MO files via django compilemessages' + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + try: + from django.core.management import call_command + for path, dirs, files in os.walk('passerelle_imio_ia_delib'): + if 'locale' not in dirs: + continue + curdir = os.getcwd() + os.chdir(os.path.realpath(path)) + call_command('compilemessages') + os.chdir(curdir) + except ImportError: + sys.stderr.write('!!! Please install Django >= 1.4 to build translations\n') + + +class build(_build): + sub_commands = [('compile_translations', None)] + _build.sub_commands + + +class install_lib(_install_lib): + def run(self): + self.run_command('compile_translations') + _install_lib.run(self) + + +setup( + name='passerelle-imio-ia-delib', + version=get_version(), + author='Christophe Boulanger', + author_email='christophe.boulanger@imio.be', + packages=find_packages(), + include_package_data=True, + url='https://dev.entrouvert.org/projects/imio/', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + ], + install_requires=['django>=1.7, <1.8', + ], + zip_safe=False, + cmdclass={ + 'build': build, + 'compile_translations': compile_translations, + 'install_lib': install_lib, + 'sdist': eo_sdist, + } +)