Projet

Général

Profil

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

Jean-Baptiste Jaillet, 17 mai 2017 15:01

Télécharger (12,1 ko)

Voir les différences:

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

 debian/control                                     |   1 +
 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                     |  91 ++++++++++++++++++
 .../cmis/templates/cmis/cmisconnector_detail.html  |  21 +++++
 passerelle/settings.py                             |   1 +
 tests/test_cmis.py                                 | 105 +++++++++++++++++++++
 8 files changed, 250 insertions(+)
 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 tests/test_cmis.py
debian/control
22 22
    python-django-jsonfield,
23 23
    python-magic,
24 24
    python-suds,
25
    python-cmislib,
25 26
Recommends: python-soappy, python-phpserialize
26 27
Suggests: python-sqlalchemy, python-mako
27 28
Description: Uniform access to multiple data sources and services (Python module)
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', '0005_resourcelog'),
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'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')])),
22
                ('cmis_endpoint', models.CharField(help_text='URL of the CMIS endpoint', max_length=250, verbose_name='CMIS endpoint')),
23
                ('username', models.CharField(max_length=128, verbose_name='Service username')),
24
                ('password', models.CharField(max_length=128, verbose_name='Password')),
25
                ('users', models.ManyToManyField(to='base.ApiUser', blank=True)),
26
            ],
27
            options={
28
                'verbose_name': 'CMIS connector',
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,
19
                           UpdateConflictException)
20
import json
21
import base64
22
import logging
23

  
24
from django.db import models
25
from django.utils.translation import ugettext_lazy as _
26

  
27
from passerelle.base.models import BaseResource
28
from passerelle.utils.api import endpoint
29
from passerelle.utils.jsonresponse import APIError
30

  
31

  
32
class CmisConnector(BaseResource):
33
    cmis_endpoint = models.CharField(max_length=250, verbose_name=_('CMIS endpoint'),
34
                                     help_text=_('URL of the CMIS endpoint'))
35
    username = models.CharField(max_length=128, verbose_name=_('Service username'))
36
    password = models.CharField(max_length=128, verbose_name=_('Password'))
37

  
38
    category = _('Business Process Connectors')
39

  
40
    class Meta:
41
        verbose_name = _('CMIS connector')
42

  
43

  
44
    @endpoint(serializer_type='json-api', methods=['post'], perm='can_upload_file')
45
    def upload_file(self, request, **kwargs):
46
        try:
47
            data = json.loads(request.body)
48
        except ValueError:
49
            raise APIError('CmisError : Invalid JSON string sent.')
50

  
51
        if not set(data.keys()) >= set(['filename', 'path', 'content', 'contentType']):
52
            raise APIError('CmisError : missing keys %s' % (set(['filename', 'path', 'content', 'contentType']) -
53
                                                     set(data.keys())))
54
        title = data['filename']
55
        path = data['path'].encode('utf-8')
56
        if not path:
57
            path = '/'
58
        try:
59
            content = base64.b64decode(data['content'])
60
        except (ValueError, TypeError) as e:
61
            raise APIError('CmisError : content must be a base64 string')
62

  
63
        content_type = data['contentType']
64

  
65
        repo_name = kwargs.get('repo', False)
66
        repo = self.cmis_connection_repository(repo_name)
67
        try:
68
            folder = repo.getObjectByPath(path)
69
        except ObjectNotFoundException:
70
            raise APIError('CmisError : Path not found on platform.')
71

  
72
        try:
73
            doc = folder.createDocumentFromString(title, contentString=content, contentType=content_type)
74
        except UpdateConflictException:
75
            raise APIError('CmisError : The document already exists on platform.')
76

  
77
        return doc.properties
78

  
79
    def cmis_connection_repository(self, repo_name):
80
        try:
81
            cmis_client = CmisClient(self.cmis_endpoint, self.username, self.password)
82
        except PermissionDeniedException:
83
            raise APIError('CmisError : Wrong username or password to connect to platform.')
84
        except ObjectNotFoundException:
85
            raise APIError('CmisError : Platform endpoint not found.')
86

  
87
        if repo_name:
88
            for repo in cmis_client.getRepositories():
89
                if repo_name == repo['repositoryName']:
90
                    return cmis_client.getRepository(repo['repositoryId'])
91
        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 description %}
5
{% endblock %}
6

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

  
16
{% block security %}
17
<p>
18
{% trans 'Upload is limited to the following API users:' %}
19
</p>
20
{% access_rights_table resource=object permission='can_upload_file' %}
21
{% endblock %}
passerelle/settings.py
112 112
    'family',
113 113
    'passerelle.apps.opengis',
114 114
    'passerelle.apps.airquality',
115
    'passerelle.apps.cmis',
115 116
    # backoffice templates and static
116 117
    'gadjo',
117 118
)
tests/test_cmis.py
1
import pytest
2
import mock
3
import base64
4
import json
5

  
6
from django.contrib.contenttypes.models import ContentType
7
from django.core.urlresolvers import reverse
8

  
9
from passerelle.base.models import ApiUser, AccessRight
10
from passerelle.apps.cmis.models import CmisConnector
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',
17
                                        password='admin', slug='slug-cmis')
18
    obj_type = ContentType.objects.get_for_model(conn)
19

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

  
23
    return conn
24

  
25
fake_file = {
26
    'filename': 'test.txt',
27
    'path': '/a/path',
28
    'content': base64.b64encode('fake file content.'),
29
    'contentType': 'text/plain'
30
}
31

  
32
fake_file_error_one = {
33
    'path': '/a/path',
34
    'content': base64.b64encode('fake file content.'),
35
    'contentType': 'text/plain'
36
}
37

  
38
fake_file_error_two = {
39
    'filename': 'test.txt',
40
    'path': '/a/path',
41
    'content': 'fake file content.',
42
    'contentType': 'text/plain'
43
}
44

  
45
class MockedCmisDocument(mock.Mock):
46

  
47
    @property
48
    def properties(self):
49
        return {
50
            'cmis:baseTypeId': 'cmis:document',
51
            'cmis:contentStreamFileName': self.title,
52
            'cmis:contentStreamMimeType': self.contentType,
53
        }
54

  
55

  
56
class MockedCmisFolder(mock.Mock):
57

  
58
    def createDocumentFromString(self, title, contentString, contentType):
59
        return MockedCmisDocument(title=title, contentString=contentString, contentType=contentType)
60

  
61

  
62
class MockedCmisRepository(mock.Mock):
63

  
64
    def getObjectByPath(self, path):
65
        return MockedCmisFolder()
66

  
67

  
68
class MockedCmisClient(mock.Mock):
69

  
70
    @property
71
    def defaultRepository(self):
72
        return MockedCmisRepository()
73

  
74
@mock.patch('passerelle.apps.cmis.models.CmisClient')
75
def test_cmis_upload(mock_connection, app, setup):
76
    mock_connection.return_value = MockedCmisClient()
77
    response = app.post_json('/cmis/slug-cmis/upload_file', fake_file)
78
    body = json.loads(response.body)
79
    assert 'data' in body
80
    assert body['err'] == 0
81
    data = body['data']
82
    assert 'cmis:baseTypeId' in data
83
    assert 'cmis:contentStreamFileName' in data
84
    assert 'cmis:contentStreamMimeType' in data
85
    assert data['cmis:baseTypeId'] == 'cmis:document'
86
    assert data['cmis:contentStreamFileName'] == fake_file['filename']
87
    assert data['cmis:contentStreamMimeType'] == fake_file['contentType']
88

  
89
@mock.patch('passerelle.apps.cmis.models.CmisClient')
90
def test_cmis_upload_error_key(mock_connection, app, setup):
91
    mock_connection.return_value = MockedCmisClient()
92
    resp = app.post_json('/cmis/slug-cmis/upload_file', fake_file_error_one)
93
    assert 'err' in resp.json
94
    assert 'err_desc' in resp.json
95
    assert resp.json['err'] == 1
96
    assert 'CmisError : missing keys' in resp.json['err_desc']
97

  
98
@mock.patch('passerelle.apps.cmis.models.CmisClient')
99
def test_cmis_upload_error_base64(mock_connection, app, setup):
100
    mock_connection.return_value = MockedCmisClient()
101
    resp = app.post_json('/cmis/slug-cmis/upload_file', fake_file_error_two)
102
    assert 'err' in resp.json
103
    assert 'err_desc' in resp.json
104
    assert resp.json['err'] == 1
105
    assert 'CmisError : content must be a base64 string' in resp.json['err_desc']
0
-