Merge branch '2_1_10-hotfix' into feature/hotfix

This commit is contained in:
Roberto Rosario
2017-02-20 02:44:00 -04:00
30 changed files with 1266 additions and 105 deletions

View File

@@ -1,3 +1,25 @@
2.1.10 (2017-02-13)
==================
- Update Makefile to use twine for releases.
- Add Makefile target to make test releases.
2.1.9 (2017-02-13)
==================
- Update make file to Workaround long standing pypa wheel bug #99
2.1.8 (2017-02-12)
==================
- Fixes in the trashed document API endpoints.
- Improved tags API PUT and PATCH endpoints.
- Bulk document adding when creating and editing tags.
- The version of django-mptt is preserved in case mayan-cabinets is installed.
- Add Django GPG API endpoints for singing keys.
- Add API endpoints for the document states (workflows) app.
- Add API endpoints for the messsage of the day (MOTD) app.
- Add Smart link API endpoints.
- Add writable versions of the Document and Document Type serializers (GitLab issues #348 and #349).
- Close GitLab issue #310 "Metadata's lookup with chinese messages when new document"
2.1.7 (2017-02-01)
==================
- Improved user management API endpoints.

View File

@@ -1,3 +1,3 @@
include README.rst LICENSE HISTORY.rst
include README.md LICENSE HISTORY.rst
recursive-include mayan *.txt *.html *.css *.ico *.png *.jpg *.js *.po *.mo *.ttf *.woff *.woff2 LICENSE
global-exclude mayan/settings/local.py mayan/settings/travis/* mayan/media/*

View File

@@ -87,15 +87,20 @@ requirements_testing:
# Releases
release: clean
python setup.py sdist bdist_wheel upload
test_release: clean wheel
twine upload dist/* -r testpypi
@echo "Test with: pip install -i https://testpypi.python.org/pypi mayan-edms"
release: clean wheel
twine upload dist/* -r pypi
sdist: clean
python setup.py sdist
ls -l dist
wheel: clean
python setup.py bdist_wheel
wheel: clean sdist
pip wheel --no-index --no-deps --wheel-dir dist dist/*.tar.gz
ls -l dist
@@ -106,5 +111,3 @@ runserver:
shell_plus:
./manage.py shell_plus --settings=mayan.settings.development

76
README.md Normal file
View File

@@ -0,0 +1,76 @@
[![pypi][pypi]][pypi-url]
[![builds][builds]][builds-url]
[![coverage][cover]][cover-url]
![python][python]
![license][license]
[pypi]: http://img.shields.io/pypi/v/mayan-edms.svg
[pypi-url]: http://badge.fury.io/py/mayan-edms
[builds]: https://gitlab.com/mayan-edms/mayan-edms/badges/master/build.svg
[builds-url]: https://gitlab.com/mayan-edms/mayan-edms/pipelines
[cover]: https://codecov.io/gitlab/mayan-edms/mayan-edms/coverage.svg?branch=master
[cover-url]: https://codecov.io/gitlab/mayan-edms/mayan-edms?branch=master
[python]: https://img.shields.io/pypi/pyversions/mayan-edms.svg
[python-url]: https://img.shields.io/pypi/l/mayan-edms.svg?style=flat
[license]: https://img.shields.io/pypi/l/mayan-edms.svg?style=flat
[license-url]: https://img.shields.io/pypi/l/mayan-edms.svg?style=flat
<div align="center">
<a href="http://www.mayan-edms.com">
<img width="200" heigth="200" src="https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/mayan_logo.png">
</a>
<br>
<br>
<p>
Mayan EDMS is a document management system. Its main purpose is to store,
introspect, and categorize files, with a strong emphasis on preserving the
contextual and business information of documents. It can also OCR, preview,
label, sign, send, and receive thoses files. Other features of interest
are its workflow system, role based access control, and REST API.
<p>
<p align="center">
<img src="https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/overview.gif">
</p>
</div>
<h2 align="center">Installation</h2>
The installation procedure uses the <a href="https://www.docker.com">Docker container manager (docker.com)</a>. Make sure Docker is properly installed and working before attempting to install Mayan EDMS.
Step 1- Initialize the installation
```bash
docker run --rm -v mayan_media:/var/lib/mayan \
-v mayan_settings:/etc/mayan mayanedms/mayanedms mayan:init
```
Step 2- Deploy a container
```bash
docker run -d --name mayan-edms --restart=always -p 80:80 \
-v mayan_media:/var/lib/mayan -v mayan_settings:/etc/mayan mayanedms/mayanedms
```
Step 3- Open a browser and go to http://localhost
<h2 align="center">Important links</h2>
- [Homepage](http://www.mayan-edms.com)
- [Videos](https://www.youtube.com/channel/UCJOOXHP1MJ9lVA7d8ZTlHPw)
- [Documentation](http://mayan.readthedocs.io/en/stable/)
- [Paid support](http://www.mayan-edms.com/providers/)
- [Community forum](https://groups.google.com/forum/#!forum/mayan-edms)
- [Community forum archive](http://mayan-edms.1003.x6.nabble.com/)
- [Source code, issues, bugs](https://gitlab.com/mayan-edms/mayan-edms)
- [Plug-ins, other related projects](https://gitlab.com/mayan-edms/)
- [Translations](https://www.transifex.com/rosarior/mayan-edms/)

View File

@@ -1,65 +0,0 @@
|PyPI badge| |Build Status| |Coverage badge| |Documentation| |License badge| |Python version|
|Logo|
Description
-----------
Free Open Source Electronic Document Management System.
`Website`_
`Video demostration`_
`Documentation`_
`Translations`_
`Mailing list (via Google Groups)`_
|Animation|
License
-------
This project is open sourced under `Apache 2.0 License`_.
Installation
------------
To install Mayan EDMS, simply do:
.. code-block:: bash
$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install mayan-edms
(venv) $ mayan-edms.py initialsetup
(venv) $ mayan-edms.py runserver
Point your browser to 127.0.0.1:8000 and use the automatically created admin
account.
.. _Website: http://www.mayan-edms.com
.. _Video demostration: http://bit.ly/pADNXv
.. _Documentation: http://readthedocs.org/docs/mayan/en/latest/
.. _Translations: https://www.transifex.com/projects/p/mayan-edms/
.. _Mailing list (via Google Groups): http://groups.google.com/group/mayan-edms
.. _Apache 2.0 License: https://www.apache.org/licenses/LICENSE-2.0.txt
.. |Build Status| image:: https://gitlab.com/mayan-edms/mayan-edms/badges/master/build.svg
:target: https://gitlab.com/mayan-edms/mayan-edms/commits/master
.. |Logo| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/mayan_logo.png
.. |Animation| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/overview.gif
.. |PyPI badge| image:: http://img.shields.io/pypi/v/mayan-edms.svg?style=flat
:target: http://badge.fury.io/py/mayan-edms
.. |License badge| image:: https://img.shields.io/pypi/l/mayan-edms.svg?style=flat
.. |Analytics| image:: https://ga-beacon.appspot.com/UA-52965619-2/mayan-edms/readme?pixel
.. |Coverage badge| image:: https://codecov.io/gitlab/mayan-edms/mayan-edms/coverage.svg?branch=master
:target: https://codecov.io/gitlab/mayan-edms/mayan-edms?branch=master
.. |Documentation| image:: https://readthedocs.org/projects/mayan/badge/?version=latest
:target: http://mayan.readthedocs.io/en/latest
.. |Python version| image:: https://img.shields.io/pypi/pyversions/mayan-edms.svg
|Analytics|

75
docs/releases/2.1.10.rst Normal file
View File

@@ -0,0 +1,75 @@
===============================
Mayan EDMS v2.1.10 release notes
===============================
Released: February 13, 2017
What's new
==========
This is a micro release equal to the previews version from the user's point of view.
The version number was increase to workaround some issues with the Python
Package Index not allowing re-uploads.
Changes
-------------
- Update Makefile to use twine for releases.
- Add Makefile target to make test releases.
Removals
--------
* None
Upgrading from a previous version
---------------------------------
Using PIP
~~~~~~~~~
Type in the console::
$ pip install -U mayan-edms
the requirements will also be updated automatically.
Using Git
~~~~~~~~~
If you installed Mayan EDMS by cloning the Git repository issue the commands::
$ git reset --hard HEAD
$ git pull
otherwise download the compressed archived and uncompress it overriding the
existing installation.
Next upgrade/add the new requirements::
$ pip install --upgrade -r requirements.txt
Common steps
~~~~~~~~~~~~
Migrate existing database schema with::
$ mayan-edms.py performupgrade
Add new static media::
$ mayan-edms.py collectstatic --noinput
The upgrade procedure is now complete.
Backward incompatible changes
=============================
* None
Bugs fixed or issues closed
===========================
* None
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/

View File

@@ -2,7 +2,7 @@
Mayan EDMS v2.1.8 release notes
===============================
Released: February XX, 2017
Released: February 12, 2017
What's new
==========
@@ -20,7 +20,8 @@ Changes
- Add Django GPG API endpoints for singing keys.
- Add API endpoints for the document states app.
- Add API endpoints for the messsage of the day (MOTD) app.
- Add Smart link API endpoints.
- Add writable versions of the Document and Document Type serializers (GitLab issues #348 and #349).
Removals
--------
@@ -75,6 +76,8 @@ Backward incompatible changes
Bugs fixed or issues closed
===========================
* None
* `GitLab issue #310 <https://gitlab.com/mayan-edms/mayan-edms/issues/310>`_ Metadata's lookup with chinese messages when new document
* `GitLab issue #348 <https://gitlab.com/mayan-edms/mayan-edms/issues/348>`_ REST API: Document version comments are not getting updated
* `GitLab issue #349 <https://gitlab.com/mayan-edms/mayan-edms/issues/349>`_ REST API: Document Label, Description are not able to update
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/

74
docs/releases/2.1.9.rst Normal file
View File

@@ -0,0 +1,74 @@
===============================
Mayan EDMS v2.1.9 release notes
===============================
Released: February 13, 2017
What's new
==========
This is a micro release equal to the previews version from the user's point of view.
The version number was increase to workaround some issues with the Python
Package Index not allowing re-uploads.
Changes
-------------
- Update make file to Workaround long standing pypa wheel bug #99
Removals
--------
* None
Upgrading from a previous version
---------------------------------
Using PIP
~~~~~~~~~
Type in the console::
$ pip install -U mayan-edms
the requirements will also be updated automatically.
Using Git
~~~~~~~~~
If you installed Mayan EDMS by cloning the Git repository issue the commands::
$ git reset --hard HEAD
$ git pull
otherwise download the compressed archived and uncompress it overriding the
existing installation.
Next upgrade/add the new requirements::
$ pip install --upgrade -r requirements.txt
Common steps
~~~~~~~~~~~~
Migrate existing database schema with::
$ mayan-edms.py performupgrade
Add new static media::
$ mayan-edms.py collectstatic --noinput
The upgrade procedure is now complete.
Backward incompatible changes
=============================
* None
Bugs fixed or issues closed
===========================
* None
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/

View File

@@ -22,6 +22,8 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
2.1.10
2.1.9
2.1.8
2.1.7
2.1.6

View File

@@ -1,8 +1,8 @@
from __future__ import unicode_literals
__title__ = 'Mayan EDMS'
__version__ = '2.1.7'
__build__ = 0x020107
__version__ = '2.1.10'
__build__ = 0x020110
__author__ = 'Roberto Rosario'
__author_email__ = 'roberto.rosario@mayan-edms.com'
__description__ = 'Free Open Source Electronic Document Management System'

View File

@@ -31,7 +31,9 @@ from .serializers import (
DocumentPageSerializer, DocumentSerializer,
DocumentTypeSerializer, DocumentVersionSerializer,
DocumentVersionRevertSerializer, NewDocumentSerializer,
NewDocumentVersionSerializer, RecentDocumentSerializer
NewDocumentVersionSerializer, RecentDocumentSerializer,
WritableDocumentSerializer, WritableDocumentTypeSerializer,
WritableDocumentVersionSerializer
)
logger = logging.getLogger(__name__)
@@ -190,7 +192,6 @@ class APIDocumentView(generics.RetrieveUpdateDestroyAPIView):
}
permission_classes = (MayanPermission,)
queryset = Document.objects.all()
serializer_class = DocumentSerializer
def delete(self, *args, **kwargs):
"""
@@ -206,6 +207,12 @@ class APIDocumentView(generics.RetrieveUpdateDestroyAPIView):
return super(APIDocumentView, self).get(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentSerializer
else:
return WritableDocumentSerializer
def patch(self, *args, **kwargs):
"""
Edit the properties of the selected document.
@@ -289,6 +296,12 @@ class APIDocumentTypeListView(generics.ListCreateAPIView):
return super(APIDocumentTypeListView, self).get(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentTypeSerializer
else:
return WritableDocumentTypeSerializer
def post(self, *args, **kwargs):
"""
Create a new document type.
@@ -310,7 +323,6 @@ class APIDocumentTypeView(generics.RetrieveUpdateDestroyAPIView):
}
permission_classes = (MayanPermission,)
queryset = DocumentType.objects.all()
serializer_class = DocumentTypeSerializer
def delete(self, *args, **kwargs):
"""
@@ -326,6 +338,12 @@ class APIDocumentTypeView(generics.RetrieveUpdateDestroyAPIView):
return super(APIDocumentTypeView, self).get(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentTypeSerializer
else:
return WritableDocumentTypeSerializer
def patch(self, *args, **kwargs):
"""
Edit the properties of the selected document type.
@@ -436,7 +454,12 @@ class APIDocumentVersionView(generics.RetrieveUpdateAPIView):
mayan_permission_attribute_check = 'document'
permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all()
serializer_class = DocumentVersionSerializer
def get_serializer_class(self):
if self.request.method == 'GET':
return DocumentVersionSerializer
else:
return WritableDocumentVersionSerializer
def patch(self, *args, **kwargs):
"""

View File

@@ -6,7 +6,8 @@ from common.models import SharedUploadedFile
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from .models import (
Document, DocumentVersion, DocumentPage, DocumentType, RecentDocument
Document, DocumentVersion, DocumentPage, DocumentType,
DocumentTypeFilename, RecentDocument
)
from .settings import setting_language
from .tasks import task_get_document_page_image, task_upload_new_version
@@ -45,15 +46,40 @@ class DocumentPageSerializer(serializers.HyperlinkedModelSerializer):
model = DocumentPage
class DocumentTypeFilenameSerializer(serializers.ModelSerializer):
class Meta:
model = DocumentTypeFilename
fields = ('filename',)
class DocumentTypeSerializer(serializers.HyperlinkedModelSerializer):
documents_url = serializers.HyperlinkedIdentityField(
view_name='rest_api:documenttype-document-list',
)
documents_count = serializers.SerializerMethodField()
filenames = DocumentTypeFilenameSerializer(many=True, read_only=True)
class Meta:
extra_kwargs = {
'url': {'view_name': 'rest_api:documenttype-detail'},
}
fields = (
'delete_time_period', 'delete_time_unit', 'documents_url',
'documents_count', 'id', 'label', 'filenames', 'trash_time_period',
'trash_time_unit', 'url'
)
model = DocumentType
def get_documents_count(self, obj):
return obj.documents.count()
class WritableDocumentTypeSerializer(serializers.ModelSerializer):
documents_url = serializers.HyperlinkedIdentityField(
view_name='rest_api:documenttype-document-list',
)
documents_count = serializers.SerializerMethodField()
class Meta:
extra_kwargs = {
'url': {'view_name': 'rest_api:documenttype-detail'},
@@ -65,6 +91,9 @@ class DocumentTypeSerializer(serializers.HyperlinkedModelSerializer):
)
model = DocumentType
def get_documents_count(self, obj):
return obj.documents.count()
class DocumentVersionSerializer(serializers.HyperlinkedModelSerializer):
pages = DocumentPageSerializer(many=True, required=False, read_only=True)
@@ -82,6 +111,26 @@ class DocumentVersionSerializer(serializers.HyperlinkedModelSerializer):
read_only_fields = ('document', 'file')
class WritableDocumentVersionSerializer(serializers.ModelSerializer):
document = serializers.HyperlinkedIdentityField(
view_name='rest_api:document-detail'
)
pages = DocumentPageSerializer(many=True, required=False, read_only=True)
revert = serializers.HyperlinkedIdentityField(
view_name='rest_api:documentversion-revert'
)
url = serializers.HyperlinkedIdentityField(
view_name='rest_api:documentversion-detail'
)
class Meta:
extra_kwargs = {
'file': {'use_url': False},
}
model = DocumentVersion
read_only_fields = ('document', 'file')
class DocumentVersionRevertSerializer(DocumentVersionSerializer):
class Meta(DocumentVersionSerializer.Meta):
read_only_fields = ('comment', 'document',)
@@ -136,9 +185,6 @@ class DocumentSerializer(serializers.HyperlinkedModelSerializer):
view_name='rest_api:document-version-list',
)
def get_document_type_label(self, instance):
return instance.document_type.label
class Meta:
extra_kwargs = {
'document_type': {'view_name': 'rest_api:documenttype-detail'},
@@ -152,6 +198,32 @@ class DocumentSerializer(serializers.HyperlinkedModelSerializer):
model = Document
read_only_fields = ('document_type',)
def get_document_type_label(self, instance):
return instance.document_type.label
class WritableDocumentSerializer(serializers.ModelSerializer):
document_type_label = serializers.SerializerMethodField()
latest_version = DocumentVersionSerializer(many=False, read_only=True)
versions = serializers.HyperlinkedIdentityField(
view_name='rest_api:document-version-list',
)
url = serializers.HyperlinkedIdentityField(
view_name='rest_api:document-detail',
)
class Meta:
fields = (
'date_added', 'description', 'document_type',
'document_type_label', 'id', 'label', 'language',
'latest_version', 'url', 'uuid', 'versions',
)
model = Document
read_only_fields = ('document_type',)
def get_document_type_label(self, instance):
return instance.document_type.label
class NewDocumentSerializer(serializers.ModelSerializer):
file = serializers.FileField(write_only=True)

View File

@@ -49,12 +49,13 @@ class DocumentTypeAPITestCase(APITestCase):
def test_document_type_create(self):
self.assertEqual(DocumentType.objects.all().count(), 0)
self.client.post(
response = self.client.post(
reverse('rest_api:documenttype-list'), data={
'label': TEST_DOCUMENT_TYPE
}
)
self.assertEqual(response.status_code, 201)
self.assertEqual(DocumentType.objects.all().count(), 1)
self.assertEqual(
DocumentType.objects.all().first().label, TEST_DOCUMENT_TYPE

View File

@@ -0,0 +1,360 @@
from __future__ import absolute_import, unicode_literals
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from rest_framework import generics
from acls.models import AccessControlList
from documents.models import Document
from documents.permissions import permission_document_view
from permissions import Permission
from rest_api.filters import MayanObjectPermissionsFilter
from rest_api.permissions import MayanPermission
from .models import SmartLink
from .permissions import (
permission_smart_link_create, permission_smart_link_delete,
permission_smart_link_edit, permission_smart_link_view
)
from .serializers import (
ResolvedSmartLinkDocumentSerializer, ResolvedSmartLinkSerializer,
SmartLinkConditionSerializer, SmartLinkSerializer,
WritableSmartLinkSerializer
)
class APIResolvedSmartLinkDocumentListView(generics.ListAPIView):
filter_backends = (MayanObjectPermissionsFilter,)
mayan_object_permissions = {'GET': (permission_document_view,)}
permission_classes = (MayanPermission,)
serializer_class = ResolvedSmartLinkDocumentSerializer
def get(self, *args, **kwargs):
"""
Returns a list of the smart link documents that apply to the document.
"""
return super(APIResolvedSmartLinkDocumentListView, self).get(
*args, **kwargs
)
def get_document(self):
document = get_object_or_404(Document, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_document_view,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_document_view, self.request.user, document
)
return document
def get_smart_link(self):
smart_link = get_object_or_404(
SmartLink.objects.get_for(document=self.get_document()),
pk=self.kwargs['smart_link_pk']
)
try:
Permission.check_permissions(
self.request.user, (permission_smart_link_view,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_smart_link_view, self.request.user, smart_link
)
return smart_link
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'document': self.get_document(),
'format': self.format_kwarg,
'request': self.request,
'smart_link': self.get_smart_link(),
'view': self
}
def get_queryset(self):
return self.get_smart_link().get_linked_document_for(
document=self.get_document()
)
class APIResolvedSmartLinkView(generics.RetrieveAPIView):
filter_backends = (MayanObjectPermissionsFilter,)
lookup_url_kwarg = 'smart_link_pk'
mayan_object_permissions = {'GET': (permission_smart_link_view,)}
permission_classes = (MayanPermission,)
serializer_class = ResolvedSmartLinkSerializer
def get(self, *args, **kwargs):
"""
Return the details of the selected resolved smart link.
"""
return super(APIResolvedSmartLinkView, self).get(*args, **kwargs)
def get_document(self):
document = get_object_or_404(Document, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_document_view,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_document_view, self.request.user, document
)
return document
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'document': self.get_document(),
'format': self.format_kwarg,
'request': self.request,
'view': self
}
def get_queryset(self):
return SmartLink.objects.get_for(document=self.get_document())
class APIResolvedSmartLinkListView(generics.ListAPIView):
filter_backends = (MayanObjectPermissionsFilter,)
mayan_object_permissions = {'GET': (permission_smart_link_view,)}
permission_classes = (MayanPermission,)
serializer_class = ResolvedSmartLinkSerializer
def get(self, *args, **kwargs):
"""
Returns a list of the smart links that apply to the document.
"""
return super(APIResolvedSmartLinkListView, self).get(*args, **kwargs)
def get_document(self):
document = get_object_or_404(Document, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_document_view,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_document_view, self.request.user, document
)
return document
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'document': self.get_document(),
'format': self.format_kwarg,
'request': self.request,
'view': self
}
def get_queryset(self):
return SmartLink.objects.filter(
document_types=self.get_document().document_type
)
class APISmartLinkConditionListView(generics.ListCreateAPIView):
serializer_class = SmartLinkConditionSerializer
def get(self, *args, **kwargs):
"""
Returns a list of all the smart link conditions.
"""
return super(APISmartLinkConditionListView, self).get(*args, **kwargs)
def get_queryset(self):
return self.get_smart_link().conditions.all()
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'format': self.format_kwarg,
'request': self.request,
'smart_link': self.get_smart_link(),
'view': self
}
def get_smart_link(self):
if self.request.method == 'GET':
permission_required = permission_smart_link_view
else:
permission_required = permission_smart_link_edit
smart_link = get_object_or_404(SmartLink, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_required,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_required, self.request.user, smart_link
)
return smart_link
def post(self, *args, **kwargs):
"""
Create a new smart link condition.
"""
return super(APISmartLinkConditionListView, self).post(*args, **kwargs)
class APISmartLinkConditionView(generics.RetrieveUpdateDestroyAPIView):
lookup_url_kwarg = 'condition_pk'
serializer_class = SmartLinkConditionSerializer
def delete(self, *args, **kwargs):
"""
Delete the selected smart link condition.
"""
return super(APISmartLinkConditionView, self).delete(*args, **kwargs)
def get(self, *args, **kwargs):
"""
Return the details of the selected smart link condition.
"""
return super(APISmartLinkConditionView, self).get(*args, **kwargs)
def get_queryset(self):
return self.get_smart_link().conditions.all()
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'format': self.format_kwarg,
'request': self.request,
'smart_link': self.get_smart_link(),
'view': self
}
def get_smart_link(self):
if self.request.method == 'GET':
permission_required = permission_smart_link_view
else:
permission_required = permission_smart_link_edit
smart_link = get_object_or_404(SmartLink, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_required,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_required, self.request.user, smart_link
)
return smart_link
def patch(self, *args, **kwargs):
"""
Edit the selected smart link condition.
"""
return super(APISmartLinkConditionView, self).patch(*args, **kwargs)
def put(self, *args, **kwargs):
"""
Edit the selected smart link condition.
"""
return super(APISmartLinkConditionView, self).put(*args, **kwargs)
class APISmartLinkListView(generics.ListCreateAPIView):
filter_backends = (MayanObjectPermissionsFilter,)
mayan_object_permissions = {'GET': (permission_smart_link_view,)}
mayan_view_permissions = {'POST': (permission_smart_link_create,)}
permission_classes = (MayanPermission,)
queryset = SmartLink.objects.all()
def get(self, *args, **kwargs):
"""
Returns a list of all the smart links.
"""
return super(APISmartLinkListView, self).get(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return SmartLinkSerializer
else:
return WritableSmartLinkSerializer
def post(self, *args, **kwargs):
"""
Create a new smart link.
"""
return super(APISmartLinkListView, self).post(*args, **kwargs)
class APISmartLinkView(generics.RetrieveUpdateDestroyAPIView):
filter_backends = (MayanObjectPermissionsFilter,)
mayan_object_permissions = {
'DELETE': (permission_smart_link_delete,),
'GET': (permission_smart_link_view,),
'PATCH': (permission_smart_link_edit,),
'PUT': (permission_smart_link_edit,)
}
queryset = SmartLink.objects.all()
def delete(self, *args, **kwargs):
"""
Delete the selected smart link.
"""
return super(APISmartLinkView, self).delete(*args, **kwargs)
def get(self, *args, **kwargs):
"""
Return the details of the selected smart ink.
"""
return super(APISmartLinkView, self).get(*args, **kwargs)
def get_serializer_class(self):
if self.request.method == 'GET':
return SmartLinkSerializer
else:
return WritableSmartLinkSerializer
def patch(self, *args, **kwargs):
"""
Edit the selected smart link.
"""
return super(APISmartLinkView, self).patch(*args, **kwargs)
def put(self, *args, **kwargs):
"""
Edit the selected smart link.
"""
return super(APISmartLinkView, self).put(*args, **kwargs)

View File

@@ -12,6 +12,7 @@ from common import (
)
from common.widgets import two_state_template
from navigation import SourceColumn
from rest_api.classes import APIEndPoint
from .links import (
link_smart_link_create, link_smart_link_condition_create,
@@ -35,6 +36,8 @@ class LinkingApp(MayanAppConfig):
def ready(self):
super(LinkingApp, self).ready()
APIEndPoint(app=self, version_string='1')
Document = apps.get_model(
app_label='documents', model_name='Document'
)

View File

@@ -0,0 +1,8 @@
from django.db import models
class SmartLinkManager(models.Manager):
def get_for(self, document):
return self.filter(
document_types=document.document_type, enabled=True
)

View File

@@ -11,6 +11,7 @@ from documents.models import Document, DocumentType
from .literals import (
INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES
)
from .managers import SmartLinkManager
@python_2_unicode_compatible
@@ -29,6 +30,8 @@ class SmartLink(models.Model):
DocumentType, verbose_name=_('Document types')
)
objects = SmartLinkManager()
def __str__(self):
return self.label

View File

@@ -0,0 +1,115 @@
from __future__ import absolute_import, unicode_literals
from rest_framework import serializers
from rest_framework.reverse import reverse
from documents.serializers import DocumentSerializer
from .models import SmartLink, SmartLinkCondition
class SmartLinkConditionSerializer(serializers.HyperlinkedModelSerializer):
smart_link_url = serializers.SerializerMethodField()
url = serializers.SerializerMethodField()
class Meta:
fields = (
'enabled', 'expression', 'foreign_document_data', 'inclusion',
'id', 'negated', 'operator', 'smart_link_url', 'url'
)
model = SmartLinkCondition
def create(self, validated_data):
validated_data['smart_link'] = self.context['smart_link']
return super(SmartLinkConditionSerializer, self).create(validated_data)
def get_smart_link_url(self, instance):
return reverse(
'rest_api:smartlink-detail', args=(instance.smart_link.pk,),
request=self.context['request'], format=self.context['format']
)
def get_url(self, instance):
return reverse(
'rest_api:smartlinkcondition-detail', args=(
instance.smart_link.pk, instance.pk,
), request=self.context['request'], format=self.context['format']
)
class SmartLinkSerializer(serializers.HyperlinkedModelSerializer):
conditions_url = serializers.HyperlinkedIdentityField(
view_name='rest_api:smartlinkcondition-list'
)
class Meta:
extra_kwargs = {
'url': {'view_name': 'rest_api:smartlink-detail'},
}
fields = (
'conditions_url', 'dynamic_label', 'enabled', 'label', 'id', 'url'
)
model = SmartLink
class ResolvedSmartLinkDocumentSerializer(DocumentSerializer):
resolved_smart_link_url = serializers.SerializerMethodField()
class Meta(DocumentSerializer.Meta):
fields = DocumentSerializer.Meta.fields + (
'resolved_smart_link_url',
)
read_only_fields = DocumentSerializer.Meta.fields
def get_resolved_smart_link_url(self, instance):
return reverse(
'rest_api:resolvedsmartlink-detail', args=(
self.context['document'].pk, self.context['smart_link'].pk
), request=self.context['request'],
format=self.context['format']
)
class ResolvedSmartLinkSerializer(SmartLinkSerializer):
resolved_dynamic_label = serializers.SerializerMethodField()
resolved_smart_link_url = serializers.SerializerMethodField()
resolved_documents_url = serializers.SerializerMethodField()
class Meta(SmartLinkSerializer.Meta):
fields = SmartLinkSerializer.Meta.fields + (
'resolved_dynamic_label', 'resolved_smart_link_url',
'resolved_documents_url'
)
read_only_fields = SmartLinkSerializer.Meta.fields
def get_resolved_documents_url(self, instance):
return reverse(
'rest_api:resolvedsmartlinkdocument-list',
args=(self.context['document'].pk, instance.pk,),
request=self.context['request'], format=self.context['format']
)
def get_resolved_dynamic_label(self, instance):
return instance.get_dynamic_label(document=self.context['document'])
def get_resolved_smart_link_url(self, instance):
return reverse(
'rest_api:resolvedsmartlink-detail',
args=(self.context['document'].pk, instance.pk,),
request=self.context['request'], format=self.context['format']
)
class WritableSmartLinkSerializer(serializers.ModelSerializer):
conditions_url = serializers.HyperlinkedIdentityField(
view_name='rest_api:smartlinkcondition-list'
)
class Meta:
extra_kwargs = {
'url': {'view_name': 'rest_api:smartlink-detail'},
}
fields = (
'conditions_url', 'dynamic_label', 'enabled', 'label', 'id', 'url'
)
model = SmartLink

View File

@@ -1,5 +1,9 @@
from __future__ import unicode_literals
TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA = 'label'
TEST_SMART_LINK_CONDITION_EXPRESSION = 'sample'
TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED = '\'test edited\''
TEST_SMART_LINK_CONDITION_OPERATOR = 'icontains'
TEST_SMART_LINK_DYNAMIC_LABEL = '{{ document.label }}'
TEST_SMART_LINK_EDITED_LABEL = 'test edited label'
TEST_SMART_LINK_LABEL_EDITED = 'test edited label'
TEST_SMART_LINK_LABEL = 'test label'

View File

@@ -0,0 +1,315 @@
from __future__ import unicode_literals
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import override_settings
from rest_framework.test import APITestCase
from documents.models import DocumentType
from documents.tests.literals import (
TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
)
from user_management.tests.literals import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
)
from ..models import SmartLink, SmartLinkCondition
from .literals import (
TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA,
TEST_SMART_LINK_CONDITION_EXPRESSION,
TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED,
TEST_SMART_LINK_CONDITION_OPERATOR, TEST_SMART_LINK_DYNAMIC_LABEL,
TEST_SMART_LINK_LABEL_EDITED, TEST_SMART_LINK_LABEL
)
@override_settings(OCR_AUTO_OCR=False)
class SmartLinkAPITestCase(APITestCase):
def setUp(self):
self.admin_user = get_user_model().objects.create_superuser(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD
)
self.client.login(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
def tearDown(self):
if hasattr(self, 'document_type'):
self.document_type.delete()
def _create_document_type(self):
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE
)
def _create_document(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document = self.document_type.new_document(
file_object=file_object
)
def _create_smart_link(self):
return SmartLink.objects.create(
label=TEST_SMART_LINK_LABEL,
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
)
def test_smart_link_create_view(self):
response = self.client.post(
reverse('rest_api:smartlink-list'), {
'label': TEST_SMART_LINK_LABEL
}
)
smart_link = SmartLink.objects.first()
self.assertEqual(response.data['id'], smart_link.pk)
self.assertEqual(response.data['label'], TEST_SMART_LINK_LABEL)
self.assertEqual(SmartLink.objects.count(), 1)
self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL)
def test_smart_link_delete_view(self):
smart_link = self._create_smart_link()
self.client.delete(
reverse('rest_api:smartlink-detail', args=(smart_link.pk,))
)
self.assertEqual(SmartLink.objects.count(), 0)
def test_smart_link_detail_view(self):
smart_link = self._create_smart_link()
response = self.client.get(
reverse('rest_api:smartlink-detail', args=(smart_link.pk,))
)
self.assertEqual(
response.data['label'], TEST_SMART_LINK_LABEL
)
def test_smart_link_patch_view(self):
smart_link = self._create_smart_link()
self.client.patch(
reverse('rest_api:smartlink-detail', args=(smart_link.pk,)),
data={
'label': TEST_SMART_LINK_LABEL_EDITED,
}
)
smart_link.refresh_from_db()
self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL_EDITED)
def test_smart_link_put_view(self):
smart_link = self._create_smart_link()
self.client.put(
reverse('rest_api:smartlink-detail', args=(smart_link.pk,)),
data={
'label': TEST_SMART_LINK_LABEL_EDITED,
}
)
smart_link.refresh_from_db()
self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL_EDITED)
@override_settings(OCR_AUTO_OCR=False)
class SmartLinkConditionAPITestCase(APITestCase):
def setUp(self):
self.admin_user = get_user_model().objects.create_superuser(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD
)
self.client.login(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
def tearDown(self):
if hasattr(self, 'document_type'):
self.document_type.delete()
def _create_document_type(self):
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE
)
def _create_document(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document = self.document_type.new_document(
file_object=file_object
)
def _create_smart_link(self):
self.smart_link = SmartLink.objects.create(
label=TEST_SMART_LINK_LABEL,
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
)
self.smart_link.document_types.add(self.document_type)
def _create_smart_link_condition(self):
self.smart_link_condition = SmartLinkCondition.objects.create(
smart_link=self.smart_link,
foreign_document_data=TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA,
expression=TEST_SMART_LINK_CONDITION_EXPRESSION,
operator=TEST_SMART_LINK_CONDITION_OPERATOR
)
def test_resolved_smart_link_detail_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
self._create_document()
response = self.client.get(
reverse(
'rest_api:resolvedsmartlink-detail',
args=(self.document.pk, self.smart_link.pk)
)
)
self.assertEqual(
response.data['label'], TEST_SMART_LINK_LABEL
)
def test_resolved_smart_link_list_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
self._create_document()
response = self.client.get(
reverse(
'rest_api:resolvedsmartlink-list', args=(self.document.pk,)
)
)
self.assertEqual(
response.data['results'][0]['label'], TEST_SMART_LINK_LABEL
)
def test_resolved_smart_link_document_list_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
self._create_document()
response = self.client.get(
reverse(
'rest_api:resolvedsmartlinkdocument-list',
args=(self.document.pk, self.smart_link.pk)
)
)
self.assertEqual(
response.data['results'][0]['label'], self.document.label
)
def test_smart_link_condition_create_view(self):
self._create_document_type()
self._create_smart_link()
response = self.client.post(
reverse(
'rest_api:smartlinkcondition-list', args=(self.smart_link.pk,)
), {
'foreign_document_data': TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA,
'expression': TEST_SMART_LINK_CONDITION_EXPRESSION,
'operator': TEST_SMART_LINK_CONDITION_OPERATOR
}
)
smart_link_condition = SmartLinkCondition.objects.first()
self.assertEqual(response.data['id'], smart_link_condition.pk)
self.assertEqual(
response.data['operator'], TEST_SMART_LINK_CONDITION_OPERATOR
)
self.assertEqual(SmartLinkCondition.objects.count(), 1)
self.assertEqual(
smart_link_condition.operator, TEST_SMART_LINK_CONDITION_OPERATOR
)
def test_smart_link_condition_delete_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
self.client.delete(
reverse(
'rest_api:smartlinkcondition-detail',
args=(self.smart_link.pk, self.smart_link_condition.pk)
)
)
self.assertEqual(SmartLinkCondition.objects.count(), 0)
def test_smart_link_condition_detail_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
response = self.client.get(
reverse(
'rest_api:smartlinkcondition-detail',
args=(self.smart_link.pk, self.smart_link_condition.pk)
)
)
self.assertEqual(
response.data['operator'], TEST_SMART_LINK_CONDITION_OPERATOR
)
def test_smart_link_condition_patch_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
self.client.patch(
reverse(
'rest_api:smartlinkcondition-detail',
args=(self.smart_link.pk, self.smart_link_condition.pk)
),
data={
'expression': TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED,
}
)
self.smart_link_condition.refresh_from_db()
self.assertEqual(
self.smart_link_condition.expression,
TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED
)
def test_smart_link_condition_put_view(self):
self._create_document_type()
self._create_smart_link()
self._create_smart_link_condition()
self.client.put(
reverse(
'rest_api:smartlinkcondition-detail',
args=(self.smart_link.pk, self.smart_link_condition.pk)
),
data={
'expression': TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED,
'foreign_document_data': TEST_SMART_LINK_CONDITION_FOREIGN_DOCUMENT_DATA,
'operator': TEST_SMART_LINK_CONDITION_OPERATOR,
}
)
self.smart_link_condition.refresh_from_db()
self.assertEqual(
self.smart_link_condition.expression,
TEST_SMART_LINK_CONDITION_EXPRESSION_EDITED
)

View File

@@ -13,7 +13,7 @@ from ..permissions import (
)
from .literals import (
TEST_SMART_LINK_DYNAMIC_LABEL, TEST_SMART_LINK_EDITED_LABEL,
TEST_SMART_LINK_DYNAMIC_LABEL, TEST_SMART_LINK_LABEL_EDITED,
TEST_SMART_LINK_LABEL
)
@@ -83,7 +83,7 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
response = self.post(
'linking:smart_link_edit', args=(smart_link.pk,), data={
'label': TEST_SMART_LINK_EDITED_LABEL
'label': TEST_SMART_LINK_LABEL_EDITED
}
)
self.assertEqual(response.status_code, 403)
@@ -101,13 +101,13 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
response = self.post(
'linking:smart_link_edit', args=(smart_link.pk,), data={
'label': TEST_SMART_LINK_EDITED_LABEL
'label': TEST_SMART_LINK_LABEL_EDITED
}, follow=True
)
smart_link = SmartLink.objects.get(pk=smart_link.pk)
self.assertContains(response, text='update', status_code=200)
self.assertEqual(smart_link.label, TEST_SMART_LINK_EDITED_LABEL)
self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL_EDITED)
def setup_smart_links(self):
smart_link = SmartLink.objects.create(

View File

@@ -2,6 +2,11 @@ from __future__ import unicode_literals
from django.conf.urls import patterns, url
from .api_views import (
APIResolvedSmartLinkView, APIResolvedSmartLinkDocumentListView,
APIResolvedSmartLinkListView, APISmartLinkListView, APISmartLinkView,
APISmartLinkConditionListView, APISmartLinkConditionView
)
from .views import (
DocumentSmartLinkListView, ResolvedSmartLinkView,
SetupSmartLinkDocumentTypesView, SmartLinkConditionListView,
@@ -61,3 +66,38 @@ urlpatterns = patterns(
name='smart_link_condition_delete'
),
)
api_urls = [
url(
r'^smart_links/$', APISmartLinkListView.as_view(),
name='smartlink-list'
),
url(
r'^smart_links/(?P<pk>[0-9]+)/$', APISmartLinkView.as_view(),
name='smartlink-detail'
),
url(
r'^smart_links/(?P<pk>[0-9]+)/conditions/$',
APISmartLinkConditionListView.as_view(), name='smartlinkcondition-list'
),
url(
r'^smart_links/(?P<pk>[0-9]+)/conditions/(?P<condition_pk>[0-9]+)/$',
APISmartLinkConditionView.as_view(),
name='smartlinkcondition-detail'
),
url(
r'^documents/(?P<pk>[0-9]+)/resolved_smart_links/$',
APIResolvedSmartLinkListView.as_view(),
name='resolvedsmartlink-list'
),
url(
r'^documents/(?P<pk>[0-9]+)/resolved_smart_links/(?P<smart_link_pk>[0-9]+)/$',
APIResolvedSmartLinkView.as_view(),
name='resolvedsmartlink-detail'
),
url(
r'^documents/(?P<pk>[0-9]+)/resolved_smart_links/(?P<smart_link_pk>[0-9]+)/documents/$',
APIResolvedSmartLinkDocumentListView.as_view(),
name='resolvedsmartlinkdocument-list'
),
]

View File

@@ -174,9 +174,7 @@ class DocumentSmartLinkListView(SmartLinkListView):
}
def get_smart_link_queryset(self):
return ResolvedSmartLink.objects.filter(
document_types=self.document.document_type, enabled=True
)
return ResolvedSmartLink.objects.get_for(document=self.document)
class SmartLinkCreateView(SingleObjectCreateView):

View File

@@ -266,7 +266,7 @@ class APIDocumentTypeMetadataTypeOptionalListView(generics.ListCreateAPIView):
document_type
)
serializer = self.get_serializer(data=self.request.POST)
serializer = self.get_serializer(data=self.request.data)
if serializer.is_valid():
metadata_type = get_object_or_404(

View File

@@ -5,7 +5,7 @@ import shlex
from django.core.exceptions import ValidationError
from django.db import models
from django.template import Context, Template
from django.utils.encoding import python_2_unicode_compatible
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
@@ -97,7 +97,7 @@ class MetadataType(models.Model):
splitter.whitespace = ','.encode('utf-8')
splitter.whitespace_split = True
splitter.commenters = ''.encode('utf-8')
return list(splitter)
return [force_text(e) for e in splitter]
def get_default_value(self):
template = Template(self.default)
@@ -126,6 +126,7 @@ class MetadataType(models.Model):
if self.lookup:
lookup_options = self.get_lookup_values()
if value and value not in lookup_options:
raise ValidationError(
_('Value is not one of the provided options.')

View File

@@ -1,8 +1,10 @@
from __future__ import unicode_literals
from django.db import IntegrityError
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import DocumentMetadata, MetadataType, DocumentTypeMetadataType
@@ -26,7 +28,7 @@ class DocumentMetadataSerializer(serializers.ModelSerializer):
class DocumentTypeMetadataTypeSerializer(serializers.ModelSerializer):
class Meta:
fields = ('metadata_type', )
fields = ('metadata_type',)
model = DocumentTypeMetadataType
@@ -52,10 +54,15 @@ class DocumentNewMetadataSerializer(serializers.Serializer):
metadata_type = MetadataType.objects.get(
pk=validated_data['metadata_type_pk']
)
instance = self.document.metadata.create(
metadata_type=metadata_type, value=validated_data['value']
)
return instance
try:
instance = self.document.metadata.create(
metadata_type=metadata_type, value=validated_data['value']
)
return instance
except IntegrityError:
detail = 'Metadata type with pk {} is already defined for Document with pk {}'.format(metadata_type.pk,
self.document.pk)
raise ValidationError(detail)
class DocumentTypeNewMetadataTypeSerializer(serializers.Serializer):

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.files.base import File
@@ -175,3 +176,15 @@ class MetadataTestCase(TestCase):
self.assertTrue(
self.metadata_type.get_required_for(self.document_type)
)
def test_unicode_lookup(self):
# Should NOT return a ValidationError, otherwise test fails
self.metadata_type.lookup = '测试1,测试2,test1,test2'
self.metadata_type.save()
self.metadata_type.validate_value(document_type=None, value='测试1')
def test_non_unicode_lookup(self):
# Should NOT return a ValidationError, otherwise test fails
self.metadata_type.lookup = 'test1,test2'
self.metadata_type.save()
self.metadata_type.validate_value(document_type=None, value='test1')

View File

@@ -70,7 +70,7 @@ class MOTDAPITestCase(APITestCase):
response.data['label'], TEST_LABEL
)
def test_message_path_view(self):
def test_message_patch_view(self):
message = self._create_message()
self.client.patch(

View File

@@ -8,7 +8,10 @@ django-rosetta==0.7.8
ipython==4.0.3
pypandoc==1.3.3
transifex-client==0.12.2
twine==1.8.1
wheel==0.26.0

View File

@@ -91,8 +91,13 @@ pytz==2015.4
sh==1.11
""".split()
with open('README.rst') as f:
readme = f.read()
try:
import pypandoc
readme = pypandoc.convert_file('README.md', 'rst')
except (IOError, ImportError):
with open('README.md') as f:
readme = f.read()
with open('HISTORY.rst') as f:
history = f.read()