Projet

Général

Profil

0001-cmis-add-cmis-connector-to-upload-file-12876.patch

Jean-Baptiste Jaillet, 14 septembre 2016 18:43

Télécharger (12,5 ko)

Voir les différences:

Subject: [PATCH] cmis: add cmis connector to upload file (#12876)

 passerelle/apps/cmis/__init__.py                   |  0
 passerelle/apps/cmis/migrations/0001_initial.py    | 31 ++++++++
 passerelle/apps/cmis/migrations/__init__.py        |  0
 passerelle/apps/cmis/models.py                     | 82 ++++++++++++++++++++++
 .../cmis/templates/cmis/cmisconnector_detail.html  | 18 +++++
 passerelle/apps/cmis/urls.py                       | 22 ++++++
 passerelle/apps/cmis/views.py                      | 23 ++++++
 passerelle/settings.py                             |  2 +-
 tests/test_cmis.py                                 | 80 +++++++++++++++++++++
 9 files changed, 257 insertions(+), 1 deletion(-)
 create mode 100644 passerelle/apps/cmis/__init__.py
 create mode 100644 passerelle/apps/cmis/migrations/0001_initial.py
 create mode 100644 passerelle/apps/cmis/migrations/__init__.py
 create mode 100644 passerelle/apps/cmis/models.py
 create mode 100644 passerelle/apps/cmis/templates/cmis/cmisconnector_detail.html
 create mode 100644 passerelle/apps/cmis/urls.py
 create mode 100644 passerelle/apps/cmis/views.py
 create mode 100644 tests/test_cmis.py
passerelle/apps/cmis/migrations/0001_initial.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import models, migrations
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('base', '0002_auto_20151009_0326'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='CmisConnector',
16
            fields=[
17
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
                ('title', models.CharField(max_length=50)),
19
                ('slug', models.SlugField()),
20
                ('description', models.TextField()),
21
                ('log_level', models.CharField(default=b'NOTSET', 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')])),
22
                ('cmis_endpoint', models.CharField(help_text='URL of the CMIS endpoint', max_length=250, verbose_name='CMIS endpoint')),
23
                ('username', models.CharField(help_text='Username on DMS platform', max_length=128, verbose_name='Service username')),
24
                ('password', models.CharField(help_text='Password on DMS platform', max_length=128, verbose_name='Password')),
25
                ('users', models.ManyToManyField(to='base.ApiUser', blank=True)),
26
            ],
27
            options={
28
                'abstract': False,
29
            },
30
        ),
31
    ]
passerelle/apps/cmis/models.py
1
# passerelle.apps.cmis
2
# Copyright (C) 2016  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

  
17
from cmislib import CmisClient
18
from cmislib.model import PermissionDeniedException, ObjectNotFoundException, UpdateConflictException, CmisException as CmisLibException
19
import json
20
import base64
21
import logging
22

  
23
from django.db import models
24
from passerelle.base.models import BaseResource
25
from passerelle.utils.api import endpoint
26
from django.utils.translation import ugettext_lazy as _
27
from django.http import HttpResponse
28

  
29

  
30
class CmisException(Exception):
31
    http_status = 200
32
    log_error = False
33

  
34

  
35
class CmisConnector(BaseResource):
36
    cmis_endpoint = models.CharField(max_length=250, verbose_name=_('CMIS endpoint'),
37
                                     help_text=_('URL of the CMIS endpoint'))
38
    username = models.CharField(max_length=128, verbose_name=_('Service username'), help_text=_('Username on DMS platform'))
39
    password = models.CharField(max_length=128,
40
                                verbose_name=_('Password'), help_text=_('Password on DMS platform'))
41

  
42
    category = _('Business Process Connectors')
43

  
44
    @classmethod
45
    def get_icon_class(cls):
46
        return 'ressources'
47

  
48
    @endpoint(serializer_type='json-api', methods=['post'], perm='can_upload_file')
49
    def upload_file(self, request, **kwargs):
50
        data = json.loads(request.body)
51

  
52
        title = data['filename']
53
        path = data['path'].encode('utf-8')
54
        content = base64.b64decode(data['content'])
55
        content_type = data['contentType']
56

  
57
        repo = self.cmis_connection()
58
        try:
59
            folder = repo.getObjectByPath(path)
60
        except ObjectNotFoundException:
61
            raise CmisException('Path not found on platform.')
62
        except Exception as e:
63
            raise CmisException(str(e))
64

  
65
        try:
66
            doc = folder.createDocumentFromString(title, contentString=content, contentType=content_type)
67
        except UpdateConflictException:
68
            raise CmisException('The document already exists on platform.')
69
        except Exception as e:
70
            raise CmisException(str(e))
71

  
72
        return doc.properties
73

  
74
    def cmis_connection(self):
75
        try:
76
            cmis_client = CmisClient(self.cmis_endpoint, self.username, self.password)
77
        except PermissionDeniedException:
78
            raise CmisException('Wrong username or password to connect to platform.')
79
        except ObjectNotFoundException:
80
            raise CmisException('Platform endpoint not found.')
81

  
82
        return cmis_client.defaultRepository
passerelle/apps/cmis/templates/cmis/cmisconnector_detail.html
1
{% extends "passerelle/manage/service_view.html" %}
2
{% load i18n passerelle %}
3

  
4
{% block endpoints %}
5
<p>
6
{% blocktrans %}
7
The API currently doesn't support all parameters and is limited to the JSON
8
format.
9
{% endblocktrans %}
10
</p>
11
{% endblock %}
12

  
13
{% block security %}
14
<p>
15
{% trans 'Upload is limited to the following API users:' %}
16
</p>
17
{% access_rights_table resource=object permission='can_upload_file' %}
18
{% endblock %}
passerelle/apps/cmis/urls.py
1
# passerelle.apps.cmis
2
# Copyright (C) 2016  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
from django.conf.urls import patterns, url
17

  
18
from .views import *
19

  
20
urlpatterns = patterns('',
21
    url(r'^(?P<slug>[\w-]+)/$', CmisConnectorDetailView.as_view(),
22
        name='cmisconnector-view'))
passerelle/apps/cmis/views.py
1
# passerelle.apps.cmis
2
# Copyright (C) 2016  Entr'ouvert
3
#
4
# This program is free software: you can redistribute it and/or modify it
5
# under the terms of the GNU Affero General Public License as published
6
# by the Free Software Foundation, either version 3 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU Affero General Public License for more details.
13
#
14
# You should have received a copy of the GNU Affero General Public License
15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
from django.views.generic import DetailView
17

  
18
from .models import CmisConnector
19

  
20

  
21
class CmisConnectorDetailView(DetailView):
22
    model = CmisConnector
23
    template_name = 'cmis/cmisconnector_detail.html'
passerelle/settings.py
113 113
    'csvdatasource',
114 114
    'orange',
115 115
    'family',
116
    # backoffice templates and static
116
    'cmis',
117 117
    'gadjo',
118 118
)
119 119

  
tests/test_cmis.py
1
import pytest
2
import mock
3
import base64
4
import ast
5

  
6
from cmis.models import CmisConnector
7
from django.contrib.contenttypes.models import ContentType
8
from passerelle.base.models import ApiUser, AccessRight
9
from django.core.urlresolvers import reverse
10

  
11

  
12
@pytest.fixture()
13
def setup(db):
14
    api = ApiUser.objects.create(username='all', keytype='', key='')
15

  
16
    conn = CmisConnector.objects.create(cmis_endpoint='http://cmis_endpoint.com/cmis', username='admin', password='admin', slug='cmis')
17
    obj_type = ContentType.objects.get_for_model(conn)
18

  
19
    AccessRight.objects.create(codename='can_access', apiuser=api,
20
                               resource_type=obj_type, resource_pk=conn.pk)
21

  
22
    return conn
23

  
24
@pytest.fixture()
25
def url():
26
    return reverse('generic-endpoint', kwargs={
27
                  'connector': 'cmis', 'slug': 'cmis', 'endpoint': 'upload_file'})
28

  
29

  
30
fake_file = {
31
    'filename': 'test.txt',
32
    'path': '/a/path',
33
    'content': base64.b64encode('fake file content.'),
34
    'contentType': 'text/plain'
35
}
36

  
37

  
38
class MockedCmisDocument(mock.Mock):
39

  
40
    @property
41
    def properties(self):
42
        return {
43
            'cmis:baseTypeId': 'cmis:document',
44
            'cmis:contentStreamFileName': self.title,
45
            'cmis:contentStreamMimeType': self.contentType,
46
        }
47

  
48

  
49
class MockedCmisFolder(mock.Mock):
50

  
51
    def createDocumentFromString(self, title, contentString, contentType):
52
        return MockedCmisDocument(title=title, contentString=contentString, contentType=contentType)
53

  
54

  
55
class MockedCmisRepository(mock.Mock):
56

  
57
    def getObjectByPath(self, path):
58
        return MockedCmisFolder()
59

  
60

  
61
class MockedCmisClient(mock.Mock):
62

  
63
    @property
64
    def defaultRepository(self):
65
        return MockedCmisRepository()
66

  
67
@mock.patch('cmis.models.CmisClient')
68
def test_cmis_upload(mock_connection, app, setup, url):
69
    mock_connection.return_value = MockedCmisClient()
70
    response = app.post_json(url, fake_file, status=200)
71
    body = ast.literal_eval(response.body)
72
    assert 'data' in body
73
    assert body['err'] == 0
74
    data = body['data']
75
    assert 'cmis:baseTypeId' in data
76
    assert 'cmis:contentStreamFileName' in data
77
    assert 'cmis:contentStreamMimeType' in data
78
    assert data['cmis:baseTypeId'] == 'cmis:document'
79
    assert data['cmis:contentStreamFileName'] == fake_file['filename']
80
    assert data['cmis:contentStreamMimeType'] == fake_file['contentType']
0
-