Compare commits

...

89 Commits

Author SHA1 Message Date
Roberto Rosario
971d643759 Merge branch 'feature/multi-tenant' of gitlab.com:mayan-edms/mayan-edms into feature/multi-tenant 2016-06-13 03:14:24 -04:00
Roberto Rosario
ba493cf984 Update document_states app to support organizations. 2016-06-10 19:59:09 -04:00
Roberto Rosario
9ffcfeb49b Use simple password hasher to speed up tests.
Before: Ran 346 tests in 156.928s
After: Ran 346 tests in 144.324s
2016-06-08 19:30:45 -04:00
Roberto Rosario
41f68cf435 Remove unused import. 2016-06-08 19:30:34 -04:00
Roberto Rosario
072bfcf2aa Use organization manager. 2016-06-08 19:30:20 -04:00
Roberto Rosario
6a258e4a02 Remove remarked imports. 2016-06-08 19:30:04 -04:00
Roberto Rosario
59fbe2d409 Turn on all warnings when running tests. 2016-06-08 19:29:45 -04:00
Roberto Rosario
aa0f48b1a0 Update OCR app to use organizations. 2016-06-08 19:29:20 -04:00
Roberto Rosario
a2f8e8b8d8 Finish updating checkouts app to support organizations. 2016-06-08 16:58:57 -04:00
Roberto Rosario
7ae0917564 Fix document app tests. 2016-06-08 02:02:25 -04:00
Roberto Rosario
ff30268a4b Fix folder api tests. 2016-06-08 01:59:32 -04:00
Roberto Rosario
5466dcdad3 Convert metadata app to use organizations. 2016-06-08 01:37:15 -04:00
Roberto Rosario
2035602836 Make the organization and rest api tests cleaner to import from other modules. 2016-06-08 01:26:01 -04:00
Roberto Rosario
326919271a Merge branch 'development' into feature/multi-tenant 2016-06-06 05:20:43 -04:00
Roberto Rosario
331a4da3b3 Don't create a default organization on each initialization of the organization app. GitLab issue #297. 2016-06-06 02:54:32 -04:00
Roberto Rosario
017dc67d3c Fix trashed document list view url name. 2016-06-01 12:19:41 -04:00
Roberto Rosario
aea00db37e Replace imports of Group for MayanGroup. 2016-05-31 01:23:24 -04:00
Roberto Rosario
629cc24090 Add OrganizationAdminMixin. Enable OrganizationAdminMixin for the tags, folders and user_management apps. 2016-05-31 01:22:15 -04:00
Roberto Rosario
f799d379d1 Remove print statement. 2016-05-30 23:55:25 -04:00
Roberto Rosario
8221f90fd3 Improve tags app organization test. 2016-05-30 23:55:02 -04:00
Roberto Rosario
f6dfff5949 Use the OrganizationTestCase class for the tags app model tests. 2016-05-30 23:54:32 -04:00
Roberto Rosario
38eb65151a Use the GenericAPITestCase for the documents and tags api tests. 2016-05-30 23:52:23 -04:00
Roberto Rosario
8435b8444a Add REST API base test case class. 2016-05-30 23:51:28 -04:00
Roberto Rosario
e5c51749da The return value is an user instance not an username. 2016-05-30 23:50:54 -04:00
Roberto Rosario
132d66fff8 Use the plural form 'required_permissions' to make it all uniform thruough the project. 2016-05-30 23:47:49 -04:00
Roberto Rosario
0a545f4b33 Return the created organization instance when creating the default organization. 2016-05-30 23:46:11 -04:00
Roberto Rosario
305aad0bfa Invalidate caches when creating organization admins. 2016-05-30 23:45:26 -04:00
Roberto Rosario
ccf3795601 Update the acls, common and documents tests to use OrganizationTestCase class. 2016-05-30 19:33:49 -04:00
Roberto Rosario
18bd82ba55 Fix authentication app view tests. 2016-05-30 19:23:27 -04:00
Roberto Rosario
3ebadf763b Add clean up method to OrganizationModelTestCase 2016-05-30 19:18:23 -04:00
Roberto Rosario
f9670ea7c2 Add a base organization test case class. 2016-05-30 19:16:58 -04:00
Roberto Rosario
de40977f5f Convert checkouts app to use organizations. 2016-05-30 19:14:34 -04:00
Roberto Rosario
aca93a0deb Update the Message of the day (motd) app to support organizations. 2016-05-30 17:51:24 -04:00
Roberto Rosario
e943588ba2 Move the OrganizationSourceManager manager to it's own module. Cleanups. 2016-05-30 15:42:33 -04:00
Roberto Rosario
db1673dd0a Convert sources app to use organizations. 2016-05-30 15:37:58 -04:00
Roberto Rosario
2dcad10805 Add missing .objects. 2016-05-30 06:49:52 -04:00
Roberto Rosario
ead86806d4 Add explicit organization deletion to the common generic test. 2016-05-30 06:18:26 -04:00
Roberto Rosario
cc360be4a4 Finish converting the tags app to user organizations. 2016-05-30 06:18:06 -04:00
Roberto Rosario
69bd6cc308 Finish conversion of the folders app to organizations. 2016-05-30 06:12:57 -04:00
Roberto Rosario
395fe0cb98 Finish conversion of the documents app to support organizations. 2016-05-30 06:10:08 -04:00
Roberto Rosario
d9137b4361 Folder model no longer links to the user model. 2016-05-30 06:00:24 -04:00
Roberto Rosario
e5c9e91104 Rename view and models from "deleted document" to "trashed document". Implement document level organization support via custom 'on_organization' manager. Unfilter default 'objects' manager to operate on all documents in the database. 2016-05-30 03:58:30 -04:00
Roberto Rosario
67f88b79c6 Fix queryset filter. 2016-05-27 23:03:52 -04:00
Roberto Rosario
266cf5c8a3 Import from the permissions app, not the permission module. 2016-05-27 18:03:25 -04:00
Roberto Rosario
e228dfc8c5 Add initial organization view tests for the documents app. 2016-05-27 18:01:31 -04:00
Roberto Rosario
fc86abe951 Make generic organization test class inherit from GenericViewTestCase. 2016-05-27 18:00:57 -04:00
Roberto Rosario
bc7682ab8a Trying to create a default organization in the ready() method of the app interfieres the organization app migration. 2016-05-27 00:38:35 -04:00
Roberto Rosario
ba1c5c1b17 Add organization view tests for the tags app. 2016-05-27 00:16:46 -04:00
Roberto Rosario
48d88d39e7 The folder creation is meant to succeed. 2016-05-26 23:52:24 -04:00
Roberto Rosario
de81fc58dd Generalize organization test. Add more organization tests to the folders app. 2016-05-26 21:58:31 -04:00
Roberto Rosario
b71ebe45f9 Initial sample a organization view test. 2016-05-26 14:52:25 -04:00
Roberto Rosario
05a46445d9 Don't use on_organization as we may be operating on an organization that is not our own. Return the username and password to be used for tests. 2016-05-26 14:47:41 -04:00
Roberto Rosario
907744cf18 Document page has to reference to document model. 2016-05-25 03:11:55 -04:00
Roberto Rosario
f7fd9634df Change group->user related_name to 'users'. Update all references. 2016-05-25 03:07:45 -04:00
Roberto Rosario
796e4cea04 Test are completely broken, disable CI for now. 2016-05-25 02:55:46 -04:00
Roberto Rosario
b2304119fc Improve the organization app tests. 2016-05-25 02:51:58 -04:00
Roberto Rosario
07a124187f Organization related improvements to the permissions app tests. 2016-05-25 02:51:32 -04:00
Roberto Rosario
eab5296c7a Organization related improvements to the user management app tests. 2016-05-25 02:51:06 -04:00
Roberto Rosario
ab83e23f27 Use the new group accessor for the user groups. 2016-05-25 02:50:39 -04:00
Roberto Rosario
5e7a62e022 Use MayanGroup in the user management views. Filter user for the current organization. 2016-05-25 02:49:19 -04:00
Roberto Rosario
e34dffb176 Make a hybrid user manager class to allow calling 'create_user' and 'create_superuser' from the on_organization manager. 2016-05-25 02:48:35 -04:00
Roberto Rosario
3a2d8bac33 Use the organization aware group model in the user serializer. Filter users for the current organization. 2016-05-25 02:47:58 -04:00
Roberto Rosario
58d634a395 Organization related improvements for the tags app. 2016-05-25 02:47:26 -04:00
Roberto Rosario
a0df9d260d Organization support improvements in the common app tests. 2016-05-25 02:46:32 -04:00
Roberto Rosario
626cc1cd07 Move the organizations.managment module to .utils to avoid class with /management/ folder. 2016-05-25 02:45:42 -04:00
Roberto Rosario
c49f8b1def Improve organization support for the folders app. 2016-05-25 02:45:20 -04:00
Roberto Rosario
2ca3a67c9c Don't make the default organization name hardcoded. 2016-05-25 02:44:24 -04:00
Roberto Rosario
7be1d76f62 Move import. Improve comment. 2016-05-25 02:44:05 -04:00
Roberto Rosario
c5f64d4805 Make the acls app organizations aware. 2016-05-25 02:43:12 -04:00
Roberto Rosario
27c1a33762 Call the command to create an organization admin during the initial setup stage. 2016-05-25 01:08:20 -04:00
Roberto Rosario
ce0b0a9a79 Add management command to create an organization admin. 2016-05-25 01:08:03 -04:00
Roberto Rosario
90778c709c Style cleanups 2016-05-25 01:07:42 -04:00
Roberto Rosario
6e6a6073d2 Relate roles to organizations 2016-05-25 01:06:57 -04:00
Roberto Rosario
a3a03ec095 Add custom Group model that relates to organization. 2016-05-25 01:06:20 -04:00
Roberto Rosario
433e295d07 Fix folder document list. GitLab issue #273 2016-05-24 18:18:40 -04:00
Roberto Rosario
c37430ff12 Folders no longer have an user field. GitLab issue #272. 2016-05-24 18:09:01 -04:00
Roberto Rosario
0e5521160b Move basic tests from the Site convertion to tests/test_middleware 2016-05-24 18:08:33 -04:00
Roberto Rosario
83046882b1 Remove repeated migrations. 2016-05-24 17:53:33 -04:00
Baptiste GAILLET
56f1a7d537 Add initial tests for organizations 2016-05-24 17:48:29 -04:00
Baptiste GAILLET
fba20b0a91 Fix postgresql organization creation id error after the first migration 2016-05-24 17:47:25 -04:00
Baptiste GAILLET
ab69031662 Add data migration for organizations apps and change documents migration 35 dependencies to this new migration 2016-05-24 17:46:50 -04:00
Roberto Rosario
14bd599387 Merge branch 'development' into feature/multi-tenant 2016-05-22 17:47:12 -04:00
Roberto Rosario
d71357cf81 Merge branch 'development' into feature/multi-tenant 2016-05-17 17:02:00 -04:00
Roberto Rosario
4aea55339f Merge branch 'development' into feature/multi-tenant 2016-05-07 20:13:52 -04:00
Roberto Rosario
9013793b5c Fix migrations conflicts with development branch. 2016-05-04 02:40:28 -04:00
Roberto Rosario
bd3bdb9b13 Merge branch 'development' into feature/multi-tenant 2016-05-04 02:22:38 -04:00
Roberto Rosario
9efa7c9543 Fix typo in document image generation query. Support multiple logins using different sessions iDS. 2016-03-07 04:13:14 -04:00
Roberto Rosario
8a5a26c0b4 Update authenthication code to support multitenants. Replace all remaining instances of hardcoded User model. 2016-03-07 03:08:11 -04:00
Roberto Rosario
6492908c59 Make folders and tags apps multitenant. 2016-03-07 01:53:13 -04:00
175 changed files with 3552 additions and 1194 deletions

View File

@@ -20,16 +20,16 @@ variables:
# - bash <(curl https://raw.githubusercontent.com/codecov/codecov-bash/master/codecov) -t $CODECOV_TOKEN # - bash <(curl https://raw.githubusercontent.com/codecov/codecov-bash/master/codecov) -t $CODECOV_TOKEN
# tags: # tags:
# - mysql # - mysql
test:postgres: #test:postgres:
script: # script:
- pip install -r requirements/testing.txt # - pip install -r requirements/testing.txt
- pip install -q psycopg2 # - pip install -q psycopg2
- coverage run manage.py runtests --settings=mayan.settings.testing.gitlab-ci.db_postgres --nomigrations # - coverage run manage.py runtests --settings=mayan.settings.testing.gitlab-ci.db_postgres --nomigrations
- bash <(curl https://raw.githubusercontent.com/codecov/codecov-bash/master/codecov) -t $CODECOV_TOKEN # - bash <(curl https://raw.githubusercontent.com/codecov/codecov-bash/master/codecov) -t $CODECOV_TOKEN
tags: # tags:
- postgres # - postgres
test:sqlite: #test:sqlite:
script: # script:
- pip install -r requirements/testing.txt # - pip install -r requirements/testing.txt
- coverage run manage.py runtests --settings=mayan.settings.testing.gitlab-ci --nomigrations # - coverage run manage.py runtests --settings=mayan.settings.testing.gitlab-ci --nomigrations
- bash <(curl https://raw.githubusercontent.com/codecov/codecov-bash/master/codecov) -t $CODECOV_TOKEN # - bash <(curl https://raw.githubusercontent.com/codecov/codecov-bash/master/codecov) -t $CODECOV_TOKEN

View File

@@ -52,10 +52,10 @@ clean-pyc:
# Testing # Testing
test: test:
./manage.py test $(MODULE) --settings=mayan.settings.testing --nomigrations python -Wall ./manage.py test $(MODULE) --settings=mayan.settings.testing --nomigrations
test-all: test-all:
./manage.py runtests --settings=mayan.settings.testing --nomigrations python -Wall ./manage.py runtests --settings=mayan.settings.testing --nomigrations
# Documentation # Documentation

View File

@@ -74,14 +74,14 @@ class AccessControlListManager(models.Manager):
pass pass
user_roles = [] user_roles = []
for group in user.groups.all(): for group in user.organization_groups.all():
for role in group.roles.all(): for role in group.roles.all():
if set(stored_permissions).intersection(set(self.get_inherited_permissions(role=role, obj=obj))): if set(stored_permissions).intersection(set(self.get_inherited_permissions(role=role, obj=obj))):
return True return True
user_roles.append(role) user_roles.append(role)
if not self.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles).exists(): if not self.model.on_organization.filter(content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk, permissions__in=stored_permissions, role__in=user_roles).exists():
raise PermissionDenied(ugettext('Insufficient access.')) raise PermissionDenied(ugettext('Insufficient access.'))
def filter_by_access(self, permission, user, queryset): def filter_by_access(self, permission, user, queryset):
@@ -89,7 +89,7 @@ class AccessControlListManager(models.Manager):
return queryset return queryset
user_roles = [] user_roles = []
for group in user.groups.all(): for group in user.organization_groups.all():
for role in group.roles.all(): for role in group.roles.all():
user_roles.append(role) user_roles.append(role)

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import organizations.shortcuts
class Migration(migrations.Migration):
dependencies = [
('organizations', '0002_add_data_default_organization'),
('acls', '0002_auto_20150703_0513'),
]
operations = [
migrations.AddField(
model_name='accesscontrollist',
name='organization',
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'),
),
]

View File

@@ -8,6 +8,9 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from organizations.models import Organization
from organizations.managers import CurrentOrganizationManager
from organizations.shortcuts import get_current_organization
from permissions.models import Role, StoredPermission from permissions.models import Role, StoredPermission
from .managers import AccessControlListManager from .managers import AccessControlListManager
@@ -30,6 +33,9 @@ class AccessControlList(models.Model):
ct_field='content_type', ct_field='content_type',
fk_field='object_id', fk_field='object_id',
) )
organization = models.ForeignKey(
Organization, default=get_current_organization
)
# TODO: limit choices to the permissions valid for the content_object # TODO: limit choices to the permissions valid for the content_object
permissions = models.ManyToManyField( permissions = models.ManyToManyField(
StoredPermission, blank=True, related_name='acls', StoredPermission, blank=True, related_name='acls',
@@ -38,6 +44,7 @@ class AccessControlList(models.Model):
role = models.ForeignKey(Role, related_name='acls', verbose_name=_('Role')) role = models.ForeignKey(Role, related_name='acls', verbose_name=_('Role'))
objects = AccessControlListManager() objects = AccessControlListManager()
on_organization = CurrentOrganizationManager()
class Meta: class Meta:
unique_together = ('content_type', 'object_id', 'role') unique_together = ('content_type', 'object_id', 'role')

View File

@@ -1,16 +1,17 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from documents.models import Document, DocumentType from documents.models import Document, DocumentType
from documents.permissions import permission_document_view from documents.permissions import permission_document_view
from documents.tests import TEST_SMALL_DOCUMENT_PATH, TEST_DOCUMENT_TYPE from documents.tests import TEST_SMALL_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
from organizations.tests.base import OrganizationTestCase
from permissions.classes import Permission from permissions.classes import Permission
from permissions.models import Role from permissions.models import Role
from permissions.tests.literals import TEST_ROLE_LABEL from permissions.tests.literals import TEST_ROLE_LABEL
from user_management.models import MayanGroup
from user_management.tests.literals import TEST_USER_USERNAME, TEST_GROUP from user_management.tests.literals import TEST_USER_USERNAME, TEST_GROUP
from ..models import AccessControlList from ..models import AccessControlList
@@ -19,13 +20,15 @@ TEST_DOCUMENT_TYPE_2 = 'test document type 2'
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class PermissionTestCase(TestCase): class PermissionTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type_1 = DocumentType.objects.create( super(PermissionTestCase, self).setUp()
self.document_type_1 = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
self.document_type_2 = DocumentType.objects.create( self.document_type_2 = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2 label=TEST_DOCUMENT_TYPE_2
) )
@@ -44,21 +47,23 @@ class PermissionTestCase(TestCase):
file_object=file_object file_object=file_object
) )
self.user = get_user_model().objects.create( self.user = get_user_model().on_organization.create(
username=TEST_USER_USERNAME username=TEST_USER_USERNAME
) )
self.group = Group.objects.create(name=TEST_GROUP) self.group = MayanGroup.on_organization.create(name=TEST_GROUP)
self.role = Role.objects.create(label=TEST_ROLE_LABEL) self.role = Role.on_organization.create(label=TEST_ROLE_LABEL)
self.group.user_set.add(self.user) self.group.users.add(self.user)
self.role.groups.add(self.group) self.role.organization_groups.add(self.group)
Permission.invalidate_cache() Permission.invalidate_cache()
def tearDown(self): def tearDown(self):
for document_type in DocumentType.objects.all(): for document_type in DocumentType.on_organization.all():
document_type.delete() document_type.delete()
super(PermissionTestCase, self).tearDown()
def test_check_access_without_permissions(self): def test_check_access_without_permissions(self):
with self.assertRaises(PermissionDenied): with self.assertRaises(PermissionDenied):
AccessControlList.objects.check_access( AccessControlList.objects.check_access(
@@ -75,7 +80,7 @@ class PermissionTestCase(TestCase):
) )
def test_check_access_with_acl(self): def test_check_access_with_acl(self):
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_1, role=self.role content_object=self.document_1, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
@@ -91,7 +96,7 @@ class PermissionTestCase(TestCase):
def test_filtering_with_permissions(self): def test_filtering_with_permissions(self):
self.role.permissions.add(permission_document_view.stored_permission) self.role.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_1, role=self.role content_object=self.document_1, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
@@ -104,7 +109,7 @@ class PermissionTestCase(TestCase):
) )
def test_check_access_with_inherited_acl(self): def test_check_access_with_inherited_acl(self):
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_type_1, role=self.role content_object=self.document_type_1, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
@@ -118,12 +123,12 @@ class PermissionTestCase(TestCase):
self.fail('PermissionDenied exception was not expected.') self.fail('PermissionDenied exception was not expected.')
def test_check_access_with_inherited_acl_and_local_acl(self): def test_check_access_with_inherited_acl_and_local_acl(self):
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_type_1, role=self.role content_object=self.document_type_1, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_3, role=self.role content_object=self.document_3, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
@@ -139,7 +144,7 @@ class PermissionTestCase(TestCase):
def test_filtering_with_inherited_permissions(self): def test_filtering_with_inherited_permissions(self):
self.role.permissions.add(permission_document_view.stored_permission) self.role.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_type_1, role=self.role content_object=self.document_type_1, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
@@ -155,12 +160,12 @@ class PermissionTestCase(TestCase):
def test_filtering_with_inherited_permissions_and_local_acl(self): def test_filtering_with_inherited_permissions_and_local_acl(self):
self.role.permissions.add(permission_document_view.stored_permission) self.role.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_type_1, role=self.role content_object=self.document_type_1, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document_3, role=self.role content_object=self.document_3, role=self.role
) )
acl.permissions.add(permission_document_view.stored_permission) acl.permissions.add(permission_document_view.stored_permission)

View File

@@ -33,7 +33,7 @@ class AccessControlListViewTestCase(GenericDocumentViewTestCase):
) )
self.assertEquals(response.status_code, 403) self.assertEquals(response.status_code, 403)
self.assertEqual(AccessControlList.objects.count(), 0) self.assertEqual(AccessControlList.on_organization.count(), 0)
def test_acl_create_view_with_permission(self): def test_acl_create_view_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
@@ -49,7 +49,7 @@ class AccessControlListViewTestCase(GenericDocumentViewTestCase):
) )
self.assertContains(response, text='created', status_code=200) self.assertContains(response, text='created', status_code=200)
self.assertEqual(AccessControlList.objects.count(), 1) self.assertEqual(AccessControlList.on_organization.count(), 1)
def test_acl_create_duplicate_view_with_permission(self): def test_acl_create_duplicate_view_with_permission(self):
""" """
@@ -57,7 +57,7 @@ class AccessControlListViewTestCase(GenericDocumentViewTestCase):
Result: Should redirect to existing ACL for object + role combination Result: Should redirect to existing ACL for object + role combination
""" """
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document, role=self.role content_object=self.document, role=self.role
) )
@@ -76,8 +76,8 @@ class AccessControlListViewTestCase(GenericDocumentViewTestCase):
self.assertContains( self.assertContains(
response, text='vailable permissions', status_code=200 response, text='vailable permissions', status_code=200
) )
self.assertEqual(AccessControlList.objects.count(), 1) self.assertEqual(AccessControlList.on_organization.count(), 1)
self.assertEqual(AccessControlList.objects.first().pk, acl.pk) self.assertEqual(AccessControlList.on_organization.first().pk, acl.pk)
def test_orphan_acl_create_view_with_permission(self): def test_orphan_acl_create_view_with_permission(self):
""" """
@@ -108,4 +108,4 @@ class AccessControlListViewTestCase(GenericDocumentViewTestCase):
) )
self.assertNotContains(response, text='optgroup', status_code=200) self.assertNotContains(response, text='optgroup', status_code=200)
self.assertEqual(AccessControlList.objects.count(), 1) self.assertEqual(AccessControlList.on_organization.count(), 1)

View File

@@ -26,7 +26,6 @@ logger = logging.getLogger(__name__)
class ACLCreateView(SingleObjectCreateView): class ACLCreateView(SingleObjectCreateView):
fields = ('role',) fields = ('role',)
model = AccessControlList
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.content_type = get_object_or_404( self.content_type = get_object_or_404(
@@ -52,14 +51,9 @@ class ACLCreateView(SingleObjectCreateView):
return super(ACLCreateView, self).dispatch(request, *args, **kwargs) return super(ACLCreateView, self).dispatch(request, *args, **kwargs)
def get_instance_extra_data(self):
return {
'content_object': self.content_object
}
def form_valid(self, form): def form_valid(self, form):
try: try:
acl = AccessControlList.objects.get( acl = AccessControlList.on_organization.get(
content_type=self.content_type, content_type=self.content_type,
object_id=self.content_object.pk, object_id=self.content_object.pk,
role=form.cleaned_data['role'] role=form.cleaned_data['role']
@@ -79,6 +73,14 @@ class ACLCreateView(SingleObjectCreateView):
) % self.content_object ) % self.content_object
} }
def get_instance_extra_data(self):
return {
'content_object': self.content_object
}
def get_queryset(self):
return AccessControlList.on_organization.all()
def get_success_url(self): def get_success_url(self):
if self.object.pk: if self.object.pk:
return reverse('acls:acl_permissions', args=(self.object.pk,)) return reverse('acls:acl_permissions', args=(self.object.pk,))
@@ -87,8 +89,6 @@ class ACLCreateView(SingleObjectCreateView):
class ACLDeleteView(SingleObjectDeleteView): class ACLDeleteView(SingleObjectDeleteView):
model = AccessControlList
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk']) acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
@@ -118,6 +118,9 @@ class ACLDeleteView(SingleObjectDeleteView):
) )
) )
def get_queryset(self):
return AccessControlList.on_organization.all()
class ACLListView(SingleObjectListView): class ACLListView(SingleObjectListView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@@ -152,7 +155,7 @@ class ACLListView(SingleObjectListView):
} }
def get_queryset(self): def get_queryset(self):
return AccessControlList.objects.filter( return AccessControlList.on_organization.filter(
content_type=self.content_type, object_id=self.content_object.pk content_type=self.content_type, object_id=self.content_object.pk
) )

View File

@@ -71,7 +71,7 @@
{% if not request.user.is_authenticated %} {% if not request.user.is_authenticated %}
{% trans 'Anonymous' %} {% trans 'Anonymous' %}
{% else %} {% else %}
<li><a href="{% url 'common:current_user_details' %}" title="{% trans 'User details' %}">{{ request.user.get_full_name|default:request.user }} <i class="fa fa-user"></i></a></li> <li><a href="{% url 'common:current_user_details' %}" title="{% trans 'User details' %}">{{ request.organization }}: {{ request.user.get_full_name|default:request.user }} <i class="fa fa-user"></i></a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>

View File

@@ -13,7 +13,7 @@ class EmailAuthBackend(ModelBackend):
def authenticate(self, email=None, password=None): def authenticate(self, email=None, password=None):
UserModel = get_user_model() UserModel = get_user_model()
try: try:
user = UserModel.objects.get(email=email) user = UserModel.on_organization.get(email=email)
if user.check_password(password): if user.check_password(password):
return user return user
except UserModel.DoesNotExist: except UserModel.DoesNotExist:

View File

@@ -0,0 +1,20 @@
from __future__ import unicode_literals
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class UsernameModelBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel.on_organization.get(username=username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)

View File

@@ -6,6 +6,7 @@ from django.core.urlresolvers import reverse
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.test.client import Client from django.test.client import Client
from organizations.tests.base import OrganizationTestCase
from user_management.tests.literals import ( from user_management.tests.literals import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
) )
@@ -13,18 +14,22 @@ from user_management.tests.literals import (
from .literals import TEST_EMAIL_AUTHENTICATION_BACKEND from .literals import TEST_EMAIL_AUTHENTICATION_BACKEND
class UserLoginTestCase(TestCase): class UserLoginTestCase(OrganizationTestCase):
""" """
Test that users can login via the supported authentication methods Test that users can login via the supported authentication methods
""" """
def setUp(self): def setUp(self):
super(UserLoginTestCase, self).setUp()
self.admin_user = get_user_model().objects.create_superuser( self.admin_user = get_user_model().objects.create_superuser(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD password=TEST_ADMIN_PASSWORD
) )
self.client = Client() self.client = Client()
def tearDown(self):
super(UserLoginTestCase, self).tearDown()
@override_settings(AUTHENTICATION_LOGIN_METHOD='username') @override_settings(AUTHENTICATION_LOGIN_METHOD='username')
def test_normal_behaviour(self): def test_normal_behaviour(self):
response = self.client.get(reverse('documents:document_list')) response = self.client.get(reverse('documents:document_list'))

View File

@@ -31,7 +31,7 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
return DocumentCheckoutSerializer return DocumentCheckoutSerializer
def get_queryset(self): def get_queryset(self):
documents = DocumentCheckout.objects.checked_out_documents() documents = DocumentCheckout.on_organization.checked_out_documents()
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -44,7 +44,7 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
else: else:
filtered_documents = documents filtered_documents = documents
return DocumentCheckout.objects.filter( return DocumentCheckout.on_organization.filter(
document__pk__in=filtered_documents.values_list('pk', flat=True) document__pk__in=filtered_documents.values_list('pk', flat=True)
) )
@@ -66,7 +66,7 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
if serializer.is_valid(): if serializer.is_valid():
document = get_object_or_404( document = get_object_or_404(
Document, pk=serializer.data['document'] Document.on_organization, pk=serializer.data['document']
) )
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -80,7 +80,7 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
timezone = pytz.utc timezone = pytz.utc
try: try:
DocumentCheckout.objects.create( DocumentCheckout.on_organization.create(
document=document, document=document,
expiration_datetime=timezone.localize( expiration_datetime=timezone.localize(
serializer.data['expiration_datetime'] serializer.data['expiration_datetime']
@@ -104,7 +104,7 @@ class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView):
def get_queryset(self): def get_queryset(self):
if self.request.method == 'GET': if self.request.method == 'GET':
documents = DocumentCheckout.objects.checked_out_documents() documents = DocumentCheckout.on_organization.checked_out_documents()
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -117,13 +117,13 @@ class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView):
else: else:
filtered_documents = documents filtered_documents = documents
return DocumentCheckout.objects.filter( return DocumentCheckout.on_organization.filter(
document__pk__in=filtered_documents.values_list( document__pk__in=filtered_documents.values_list(
'pk', flat=True 'pk', flat=True
) )
) )
elif self.request.method == 'DELETE': elif self.request.method == 'DELETE':
return DocumentCheckout.objects.all() return DocumentCheckout.on_organization.all()
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" """

View File

@@ -43,23 +43,23 @@ class CheckoutsApp(MayanAppConfig):
Document.add_to_class( Document.add_to_class(
'check_in', 'check_in',
lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user) lambda document, user=None: DocumentCheckout.on_organization.check_in_document(document, user)
) )
Document.add_to_class( Document.add_to_class(
'checkout_info', 'checkout_info',
lambda document: DocumentCheckout.objects.document_checkout_info( lambda document: DocumentCheckout.on_organization.document_checkout_info(
document document
) )
) )
Document.add_to_class( Document.add_to_class(
'checkout_state', 'checkout_state',
lambda document: DocumentCheckout.objects.document_checkout_state( lambda document: DocumentCheckout.on_organization.document_checkout_state(
document document
) )
) )
Document.add_to_class( Document.add_to_class(
'is_checked_out', 'is_checked_out',
lambda document: DocumentCheckout.objects.is_document_checked_out( lambda document: DocumentCheckout.on_organization.is_document_checked_out(
document document
) )
) )

View File

@@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals
import logging import logging
from django.apps import apps
from django.db import models from django.db import models
from django.utils.timezone import now from django.utils.timezone import now
@@ -25,15 +26,15 @@ class DocumentCheckoutManager(models.Manager):
) )
def checked_out_documents(self): def checked_out_documents(self):
return Document.objects.filter( return Document.on_organization.filter(
pk__in=self.model.objects.all().values_list( pk__in=self.all().values_list(
'document__pk', flat=True 'document__pk', flat=True
) )
) )
def expired_check_outs(self): def expired_check_outs(self):
expired_list = Document.objects.filter( expired_list = Document.on_organization.filter(
pk__in=self.model.objects.filter( pk__in=self.filter(
expiration_datetime__lte=now() expiration_datetime__lte=now()
).values_list('document__pk', flat=True) ).values_list('document__pk', flat=True)
) )
@@ -45,14 +46,14 @@ class DocumentCheckoutManager(models.Manager):
document.check_in() document.check_in()
def is_document_checked_out(self, document): def is_document_checked_out(self, document):
if self.model.objects.filter(document=document): if self.model.on_organization.filter(document=document):
return True return True
else: else:
return False return False
def check_in_document(self, document, user=None): def check_in_document(self, document, user=None):
try: try:
document_checkout = self.model.objects.get(document=document) document_checkout = self.get(document=document)
except self.model.DoesNotExist: except self.model.DoesNotExist:
raise DocumentNotCheckedOut raise DocumentNotCheckedOut
else: else:
@@ -70,7 +71,7 @@ class DocumentCheckoutManager(models.Manager):
def document_checkout_info(self, document): def document_checkout_info(self, document):
try: try:
return self.model.objects.get(document=document) return self.get(document=document)
except self.model.DoesNotExist: except self.model.DoesNotExist:
raise DocumentNotCheckedOut raise DocumentNotCheckedOut
@@ -87,3 +88,14 @@ class DocumentCheckoutManager(models.Manager):
return True return True
else: else:
return not checkout_info.block_new_version return not checkout_info.block_new_version
class OrganizationDocumentCheckoutManager(DocumentCheckoutManager):
def get_queryset(self):
Document = apps.get_model('documents', 'Document')
return super(
OrganizationDocumentCheckoutManager, self
).get_queryset().filter(
document__in=Document.on_organization.all()
)

View File

@@ -14,7 +14,9 @@ from documents.models import Document, NewVersionBlock
from .events import event_document_check_out from .events import event_document_check_out
from .exceptions import DocumentAlreadyCheckedOut from .exceptions import DocumentAlreadyCheckedOut
from .managers import DocumentCheckoutManager from .managers import (
DocumentCheckoutManager, OrganizationDocumentCheckoutManager
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -44,6 +46,11 @@ class DocumentCheckout(models.Model):
) )
objects = DocumentCheckoutManager() objects = DocumentCheckoutManager()
on_organization = OrganizationDocumentCheckoutManager()
class Meta:
verbose_name = _('Document checkout')
verbose_name_plural = _('Document checkouts')
def __str__(self): def __str__(self):
return unicode(self.document) return unicode(self.document)
@@ -82,7 +89,3 @@ class DocumentCheckout(models.Model):
) )
return result return result
class Meta:
verbose_name = _('Document checkout')
verbose_name_plural = _('Document checkouts')

View File

@@ -2,14 +2,13 @@ from __future__ import unicode_literals
from rest_framework import serializers from rest_framework import serializers
from documents.serializers import DocumentSerializer
from .models import DocumentCheckout from .models import DocumentCheckout
class DocumentCheckoutSerializer(serializers.ModelSerializer): class DocumentCheckoutSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Hide this import otherwise strange circular import error occur
from documents.serializers import DocumentSerializer
super(DocumentCheckoutSerializer, self).__init__(*args, **kwargs) super(DocumentCheckoutSerializer, self).__init__(*args, **kwargs)
self.fields['document'] = DocumentSerializer() self.fields['document'] = DocumentSerializer()

View File

@@ -29,7 +29,7 @@ def task_check_expired_check_outs():
name=lock_id, timeout=CHECKOUT_EXPIRATION_LOCK_EXPIRE name=lock_id, timeout=CHECKOUT_EXPIRATION_LOCK_EXPIRE
) )
logger.debug('acquired lock: %s', lock_id) logger.debug('acquired lock: %s', lock_id)
DocumentCheckout.objects.check_in_expired_check_outs() DocumentCheckout.on_organization.check_in_expired_check_outs()
lock.release() lock.release()
except LockError: except LockError:
logger.debug('unable to obtain lock') logger.debug('unable to obtain lock')

View File

@@ -12,6 +12,7 @@ from documents.models import DocumentType
from documents.tests.literals import ( from documents.tests.literals import (
TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
) )
from organizations.tests.base import OrganizationTestCase
from user_management.tests.literals import ( from user_management.tests.literals import (
TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD
) )
@@ -21,14 +22,15 @@ from ..models import DocumentCheckout
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentCheckoutTestCase(TestCase): class DocumentCheckoutTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(DocumentCheckoutTestCase, self).setUp()
self.admin_user = get_user_model().on_organization.create_superuser(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD password=TEST_ADMIN_PASSWORD
) )
self.document_type = DocumentType.objects.create( self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -39,18 +41,19 @@ class DocumentCheckoutTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(DocumentCheckoutTestCase, self).tearDown()
def test_document_checkout(self): def test_document_checkout(self):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
self.assertTrue(self.document.is_checked_out()) self.assertTrue(self.document.is_checked_out())
self.assertTrue( self.assertTrue(
DocumentCheckout.objects.is_document_checked_out( DocumentCheckout.on_organization.is_document_checked_out(
document=self.document document=self.document
) )
) )
@@ -58,7 +61,7 @@ class DocumentCheckoutTestCase(TestCase):
def test_version_creation_blocking(self): def test_version_creation_blocking(self):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
@@ -70,7 +73,7 @@ class DocumentCheckoutTestCase(TestCase):
def test_checkin_in(self): def test_checkin_in(self):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
@@ -79,7 +82,7 @@ class DocumentCheckoutTestCase(TestCase):
self.assertFalse(self.document.is_checked_out()) self.assertFalse(self.document.is_checked_out())
self.assertFalse( self.assertFalse(
DocumentCheckout.objects.is_document_checked_out( DocumentCheckout.on_organization.is_document_checked_out(
document=self.document document=self.document
) )
) )
@@ -87,13 +90,13 @@ class DocumentCheckoutTestCase(TestCase):
def test_double_checkout(self): def test_double_checkout(self):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
with self.assertRaises(DocumentAlreadyCheckedOut): with self.assertRaises(DocumentAlreadyCheckedOut):
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, document=self.document,
expiration_datetime=expiration_datetime, user=self.admin_user, expiration_datetime=expiration_datetime, user=self.admin_user,
block_new_version=True block_new_version=True
@@ -106,13 +109,13 @@ class DocumentCheckoutTestCase(TestCase):
def test_auto_checkin(self): def test_auto_checkin(self):
expiration_datetime = now() + datetime.timedelta(seconds=1) expiration_datetime = now() + datetime.timedelta(seconds=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
time.sleep(2) time.sleep(2)
DocumentCheckout.objects.check_in_expired_check_outs() DocumentCheckout.on_organization.check_in_expired_check_outs()
self.assertFalse(self.document.is_checked_out()) self.assertFalse(self.document.is_checked_out())

View File

@@ -0,0 +1,97 @@
from __future__ import unicode_literals
import datetime
from django.utils.timezone import now
from common.literals import TIME_DELTA_UNIT_DAYS
from documents.models import DocumentType
from documents.tests.literals import (
TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
)
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import DocumentCheckout
class OrganizationDocumentCheckoutTestCase(OrganizationViewTestCase):
def create_document(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE
)
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document = self.document_type.new_document(
file_object=file_object
)
def check_out_document(self):
self.create_document()
expiration_datetime = now() + datetime.timedelta(days=1)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
DocumentCheckout.on_organization.checkout_document(
document=self.document,
expiration_datetime=expiration_datetime, user=self.user,
block_new_version=True
)
def test_checkout_info(self):
self.check_out_document()
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.get(
'checkouts:checkout_info', args=(self.document.pk,),
follow=True
)
self.assertEquals(response.status_code, 200)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.get(
'checkouts:checkout_info', args=(self.document.pk,),
follow=True
)
self.assertEquals(response.status_code, 404)
def test_check_in(self):
self.check_out_document()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.get(
'checkouts:checkin_document', args=(self.document.pk,),
follow=True
)
self.assertEquals(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.assertTrue(self.document.is_checked_out())
def test_check_out(self):
self.create_document()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'checkouts:checkout_document', args=(self.document.pk,), data={
'expiration_datetime_0': 2,
'expiration_datetime_1': TIME_DELTA_UNIT_DAYS,
'block_new_version': True
}, follow=True
)
self.assertEquals(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.assertFalse(self.document.is_checked_out())
def test_checkout_list(self):
self.check_out_document()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.get('checkouts:checkout_list')
self.assertNotContains(
response, text=self.document.label, status_code=200
)

View File

@@ -27,7 +27,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.user, block_new_version=True user=self.user, block_new_version=True
) )
@@ -49,7 +49,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.user, block_new_version=True user=self.user, block_new_version=True
) )
@@ -72,7 +72,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
self.assertFalse(self.document.is_checked_out()) self.assertFalse(self.document.is_checked_out())
self.assertFalse( self.assertFalse(
DocumentCheckout.objects.is_document_checked_out( DocumentCheckout.on_organization.is_document_checked_out(
document=self.document document=self.document
) )
) )
@@ -134,7 +134,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
@@ -169,7 +169,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )
@@ -200,7 +200,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
def test_forcefull_check_in_document_view_with_permission(self): def test_forcefull_check_in_document_view_with_permission(self):
expiration_datetime = now() + datetime.timedelta(days=1) expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document( DocumentCheckout.on_organization.checkout_document(
document=self.document, expiration_datetime=expiration_datetime, document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True user=self.admin_user, block_new_version=True
) )

View File

@@ -30,7 +30,7 @@ class CheckoutDocumentView(SingleObjectCreateView):
form_class = DocumentCheckoutForm form_class = DocumentCheckoutForm
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.document = get_object_or_404(Document, pk=self.kwargs['pk']) self.document = get_object_or_404(Document.on_organization, pk=self.kwargs['pk'])
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -103,12 +103,11 @@ class CheckoutListView(DocumentListView):
} }
def get_document_queryset(self): def get_document_queryset(self):
return DocumentCheckout.objects.checked_out_documents() return DocumentCheckout.on_organization.checked_out_documents()
class CheckoutDetailView(SingleObjectDetailView): class CheckoutDetailView(SingleObjectDetailView):
form_class = DocumentCheckoutDefailForm form_class = DocumentCheckoutDefailForm
model = Document
object_permission = permission_document_checkout_detail_view object_permission = permission_document_checkout_detail_view
def get_extra_context(self): def get_extra_context(self):
@@ -120,7 +119,7 @@ class CheckoutDetailView(SingleObjectDetailView):
} }
def get_object(self): def get_object(self):
return get_object_or_404(Document, pk=self.kwargs['pk']) return get_object_or_404(Document.on_organization, pk=self.kwargs['pk'])
class DocumentCheckinView(ConfirmView): class DocumentCheckinView(ConfirmView):
@@ -142,7 +141,7 @@ class DocumentCheckinView(ConfirmView):
return context return context
def get_object(self): def get_object(self):
return get_object_or_404(Document, pk=self.kwargs['pk']) return get_object_or_404(Document.on_organization, pk=self.kwargs['pk'])
def get_post_action_redirect(self): def get_post_action_redirect(self):
return reverse('checkouts:checkout_info', args=(self.get_object().pk,)) return reverse('checkouts:checkout_info', args=(self.get_object().pk,))

View File

@@ -17,4 +17,5 @@ class Command(management.BaseCommand):
self.stderr.write(self.style.NOTICE('Unable to migrate the database. The initialsetup command is to be used only on new installations. To upgrade existing installations use the performupgrade command.')) self.stderr.write(self.style.NOTICE('Unable to migrate the database. The initialsetup command is to be used only on new installations. To upgrade existing installations use the performupgrade command.'))
raise raise
management.call_command('createautoadmin', interactive=False) management.call_command('createautoadmin', interactive=False)
management.call_command('createorganizationadmin', interactive=False)
post_initial_setup.send(sender=self) post_initial_setup.send(sender=self)

View File

@@ -2,15 +2,16 @@ from __future__ import absolute_import, unicode_literals
from django.conf.urls import url from django.conf.urls import url
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.urlresolvers import clear_url_caches, reverse from django.core.urlresolvers import clear_url_caches, reverse
from django.http import HttpResponse from django.http import HttpResponse
from django.template import Context, Template from django.template import Context, Template
from django.test import TestCase from django.test import TestCase
from organizations.tests.base import OrganizationTestCase
from permissions import Permission from permissions import Permission
from permissions.models import Role from permissions.models import Role
from permissions.tests.literals import TEST_ROLE_LABEL from permissions.tests.literals import TEST_ROLE_LABEL
from user_management.models import MayanGroup
from user_management.tests import ( from user_management.tests import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL, TEST_GROUP, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL, TEST_GROUP,
TEST_USER_EMAIL, TEST_USER_USERNAME, TEST_USER_PASSWORD TEST_USER_EMAIL, TEST_USER_USERNAME, TEST_USER_PASSWORD
@@ -19,23 +20,24 @@ from user_management.tests import (
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
class GenericViewTestCase(TestCase): class GenericViewTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(GenericViewTestCase, self).setUp()
self.has_test_view = False self.has_test_view = False
self.admin_user = get_user_model().objects.create_superuser( self.admin_user = get_user_model().on_organization.create_superuser(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD password=TEST_ADMIN_PASSWORD
) )
self.user = get_user_model().objects.create_user( self.user = get_user_model().on_organization.create_user(
username=TEST_USER_USERNAME, email=TEST_USER_EMAIL, username=TEST_USER_USERNAME, email=TEST_USER_EMAIL,
password=TEST_USER_PASSWORD password=TEST_USER_PASSWORD
) )
self.group = Group.objects.create(name=TEST_GROUP) self.group = MayanGroup.on_organization.create(name=TEST_GROUP)
self.role = Role.objects.create(label=TEST_ROLE_LABEL) self.role = Role.on_organization.create(label=TEST_ROLE_LABEL)
self.group.user_set.add(self.user) self.group.users.add(self.user)
self.role.groups.add(self.group) self.role.organization_groups.add(self.group)
Permission.invalidate_cache() Permission.invalidate_cache()
def tearDown(self): def tearDown(self):
@@ -45,6 +47,8 @@ class GenericViewTestCase(TestCase):
if self.has_test_view: if self.has_test_view:
urlpatterns.pop(0) urlpatterns.pop(0)
super(GenericViewTestCase, self).tearDown()
def add_test_view(self, test_object): def add_test_view(self, test_object):
from mayan.urls import urlpatterns from mayan.urls import urlpatterns
@@ -77,7 +81,7 @@ class GenericViewTestCase(TestCase):
def login(self, username, password): def login(self, username, password):
logged_in = self.client.login(username=username, password=password) logged_in = self.client.login(username=username, password=password)
user = get_user_model().objects.get(username=username) user = get_user_model().on_organization.get(username=username)
self.assertTrue(logged_in) self.assertTrue(logged_in)
self.assertTrue(user.is_authenticated()) self.assertTrue(user.is_authenticated())

View File

@@ -2,6 +2,8 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from organizations.admin import OrganizationAdminMixin
from .models import ( from .models import (
Workflow, WorkflowInstance, WorkflowInstanceLogEntry, WorkflowState, Workflow, WorkflowInstance, WorkflowInstanceLogEntry, WorkflowState,
WorkflowTransition WorkflowTransition
@@ -22,7 +24,7 @@ class WorkflowTransitionInline(admin.TabularInline):
@admin.register(Workflow) @admin.register(Workflow)
class WorkflowAdmin(admin.ModelAdmin): class WorkflowAdmin(OrganizationAdminMixin, admin.ModelAdmin):
def document_types_list(self, instance): def document_types_list(self, instance):
return ','.join( return ','.join(
instance.document_types.values_list('label', flat=True) instance.document_types.values_list('label', flat=True)

View File

@@ -7,4 +7,4 @@ def launch_workflow(sender, instance, created, **kwargs):
Workflow = get_model('document_states', 'Workflow') Workflow = get_model('document_states', 'Workflow')
if created: if created:
Workflow.objects.launch_for(instance) Workflow.on_organization.launch_for(instance)

View File

@@ -1,7 +1,62 @@
from __future__ import unicode_literals
from django.apps import apps
from django.db import models from django.db import models
from organizations.managers import CurrentOrganizationManager
class OrganizationWorkflowStateManager(models.Manager):
def get_queryset(self):
Workflow = apps.get_model('document_states', 'Workflow')
return super(
OrganizationWorkflowStateManager, self
).get_queryset().filter(
workflow__in=Workflow.on_organization.all(),
)
class OrganizationWorkflowTransitionManager(models.Manager):
def get_queryset(self):
Workflow = apps.get_model('document_states', 'Workflow')
return super(
OrganizationWorkflowTransitionManager, self
).get_queryset().filter(
workflow__in=Workflow.on_organization.all(),
)
class OrganizationWorkflowInstanceManager(models.Manager):
def get_queryset(self):
Document = apps.get_model('documents', 'Document')
Workflow = apps.get_model('document_states', 'Workflow')
return super(
OrganizationWorkflowInstanceManager, self
).get_queryset().filter(
workflow__in=Workflow.on_organization.all(),
document__in=Document.on_organization.all(),
)
class OrganizationWorkflowInstanceLogEntryManager(models.Manager):
def get_queryset(self):
Workflow = apps.get_model('document_states', 'Workflow')
return super(
OrganizationWorkflowInstanceLogEntryManager, self
).get_queryset().filter(
workflow_instance__workflow__in=Workflow.on_organization.all(),
)
class WorkflowManager(models.Manager): class WorkflowManager(models.Manager):
def launch_for(self, document): def launch_for(self, document):
for workflow in document.document_type.workflows.all(): for workflow in document.document_type.workflows.all():
workflow.launch_for(document) workflow.launch_for(document)
class OrganizationWorkflowManager(WorkflowManager, CurrentOrganizationManager):
pass

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import organizations.shortcuts
class Migration(migrations.Migration):
dependencies = [
('organizations', '0002_add_data_default_organization'),
('document_states', '0002_workflowstate_completion'),
]
operations = [
migrations.AddField(
model_name='workflow',
name='organization',
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'),
),
]

View File

@@ -9,14 +9,23 @@ from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from documents.models import Document, DocumentType from documents.models import Document, DocumentType
from organizations.models import Organization
from organizations.shortcuts import get_current_organization
from .managers import WorkflowManager from .managers import (
OrganizationWorkflowManager, OrganizationWorkflowStateManager,
OrganizationWorkflowTransitionManager, OrganizationWorkflowInstanceManager,
OrganizationWorkflowInstanceLogEntryManager, WorkflowManager
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@python_2_unicode_compatible @python_2_unicode_compatible
class Workflow(models.Model): class Workflow(models.Model):
organization = models.ForeignKey(
Organization, default=get_current_organization
)
label = models.CharField( label = models.CharField(
max_length=255, unique=True, verbose_name=_('Label') max_length=255, unique=True, verbose_name=_('Label')
) )
@@ -26,12 +35,19 @@ class Workflow(models.Model):
) )
objects = WorkflowManager() objects = WorkflowManager()
on_organization = OrganizationWorkflowManager()
class Meta:
verbose_name = _('Workflow')
verbose_name_plural = _('Workflows')
def __str__(self): def __str__(self):
return self.label return self.label
def get_document_types_not_in_workflow(self): def get_document_types_not_in_workflow(self):
return DocumentType.objects.exclude(pk__in=self.document_types.all()) return DocumentType.on_organization.exclude(
pk__in=self.document_types.all()
)
def get_initial_state(self): def get_initial_state(self):
try: try:
@@ -54,10 +70,6 @@ class Workflow(models.Model):
'Workflow %s launched for document %s', self, document 'Workflow %s launched for document %s', self, document
) )
class Meta:
verbose_name = _('Workflow')
verbose_name_plural = _('Workflows')
@python_2_unicode_compatible @python_2_unicode_compatible
class WorkflowState(models.Model): class WorkflowState(models.Model):
@@ -79,6 +91,14 @@ class WorkflowState(models.Model):
), verbose_name=_('Completion') ), verbose_name=_('Completion')
) )
objects = models.Manager()
on_organization = OrganizationWorkflowStateManager()
class Meta:
unique_together = ('workflow', 'label')
verbose_name = _('Workflow state')
verbose_name_plural = _('Workflow states')
def __str__(self): def __str__(self):
return self.label return self.label
@@ -87,11 +107,6 @@ class WorkflowState(models.Model):
self.workflow.states.all().update(initial=False) self.workflow.states.all().update(initial=False)
return super(WorkflowState, self).save(*args, **kwargs) return super(WorkflowState, self).save(*args, **kwargs)
class Meta:
unique_together = ('workflow', 'label')
verbose_name = _('Workflow state')
verbose_name_plural = _('Workflow states')
@python_2_unicode_compatible @python_2_unicode_compatible
class WorkflowTransition(models.Model): class WorkflowTransition(models.Model):
@@ -109,8 +124,8 @@ class WorkflowTransition(models.Model):
verbose_name=_('Destination state') verbose_name=_('Destination state')
) )
def __str__(self): objects = models.Manager()
return self.label on_organization = OrganizationWorkflowTransitionManager()
class Meta: class Meta:
unique_together = ( unique_together = (
@@ -119,6 +134,9 @@ class WorkflowTransition(models.Model):
verbose_name = _('Workflow transition') verbose_name = _('Workflow transition')
verbose_name_plural = _('Workflow transitions') verbose_name_plural = _('Workflow transitions')
def __str__(self):
return self.label
@python_2_unicode_compatible @python_2_unicode_compatible
class WorkflowInstance(models.Model): class WorkflowInstance(models.Model):
@@ -129,6 +147,14 @@ class WorkflowInstance(models.Model):
Document, related_name='workflows', verbose_name=_('Document') Document, related_name='workflows', verbose_name=_('Document')
) )
objects = models.Manager()
on_organization = OrganizationWorkflowInstanceManager()
class Meta:
unique_together = ('document', 'workflow')
verbose_name = _('Workflow instance')
verbose_name_plural = _('Workflow instances')
def __str__(self): def __str__(self):
return unicode(self.workflow) return unicode(self.workflow)
@@ -168,11 +194,6 @@ class WorkflowInstance(models.Model):
def get_transition_choices(self): def get_transition_choices(self):
return self.get_current_state().origin_transitions.all() return self.get_current_state().origin_transitions.all()
class Meta:
unique_together = ('document', 'workflow')
verbose_name = _('Workflow instance')
verbose_name_plural = _('Workflow instances')
@python_2_unicode_compatible @python_2_unicode_compatible
class WorkflowInstanceLogEntry(models.Model): class WorkflowInstanceLogEntry(models.Model):
@@ -189,9 +210,12 @@ class WorkflowInstanceLogEntry(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User')) user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User'))
comment = models.TextField(blank=True, verbose_name=_('Comment')) comment = models.TextField(blank=True, verbose_name=_('Comment'))
def __str__(self): objects = models.Manager()
return unicode(self.transition) on_organization = OrganizationWorkflowInstanceLogEntryManager()
class Meta: class Meta:
verbose_name = _('Workflow instance log entry') verbose_name = _('Workflow instance log entry')
verbose_name_plural = _('Workflow instance log entries') verbose_name_plural = _('Workflow instance log entries')
def __str__(self):
return unicode(self.transition)

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
TEST_WORKFLOW_LABEL = 'test workflow' TEST_WORKFLOW_LABEL = 'test workflow'
TEST_WORKFLOW_LABEL_EDITED = 'test workflow edited'
TEST_WORKFLOW_INITIAL_STATE_LABEL = 'test initial state' TEST_WORKFLOW_INITIAL_STATE_LABEL = 'test initial state'
TEST_WORKFLOW_INITIAL_STATE_COMPLETION = 33 TEST_WORKFLOW_INITIAL_STATE_COMPLETION = 33
TEST_WORKFLOW_STATE_LABEL = 'test state' TEST_WORKFLOW_STATE_LABEL = 'test state'

View File

@@ -0,0 +1,59 @@
from __future__ import unicode_literals
from django.test import override_settings
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import Workflow
from .literals import (
TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED,
TEST_WORKFLOW_INITIAL_STATE_LABEL, TEST_WORKFLOW_INITIAL_STATE_COMPLETION,
TEST_WORKFLOW_STATE_LABEL, TEST_WORKFLOW_STATE_COMPLETION,
TEST_WORKFLOW_TRANSITION_LABEL
)
@override_settings(OCR_AUTO_OCR=False)
class OrganizationWorkflowiewTestCase(OrganizationViewTestCase):
def create_workflow(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.workflow = Workflow.on_organization.create(label=TEST_WORKFLOW_LABEL)
def test_workflow_create_view(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
'document_states:setup_workflow_create',
data={'label': TEST_WORKFLOW_LABEL_EDITED}, follow=True
)
self.assertEqual(response.status_code, 200)
self.assertEqual(Workflow.on_organization.count(), 1)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
self.assertEqual(Workflow.on_organization.count(), 0)
def test_workflow_delete_view(self):
self.create_workflow()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'document_states:setup_workflow_delete',
args=(self.workflow.pk,), follow=True
)
self.assertEqual(response.status_code, 404)
def test_workflow_edit_view(self):
self.create_workflow()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'document_states:setup_workflow_edit',
args=(self.workflow.pk,),
data={'label': TEST_WORKFLOW_LABEL_EDITED}, follow=True
)
self.assertEqual(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.assertEqual(
Workflow.on_organization.first().label, TEST_WORKFLOW_LABEL
)

View File

@@ -1,19 +1,15 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth import get_user_model from documents.tests.test_views import GenericDocumentViewTestCase
from django.core.urlresolvers import reverse from user_management.tests.literals import (
from django.test.client import Client TEST_USER_PASSWORD, TEST_USER_USERNAME
from django.test import TestCase
from documents.models import DocumentType
from documents.tests.literals import (
TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
)
from user_management.tests import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL
) )
from ..models import Workflow, WorkflowState, WorkflowTransition from ..models import Workflow, WorkflowState, WorkflowTransition
from ..permissions import (
permission_workflow_create, permission_workflow_delete,
permission_workflow_edit
)
from .literals import ( from .literals import (
TEST_WORKFLOW_LABEL, TEST_WORKFLOW_INITIAL_STATE_LABEL, TEST_WORKFLOW_LABEL, TEST_WORKFLOW_INITIAL_STATE_LABEL,
@@ -22,70 +18,67 @@ from .literals import (
) )
class DocumentStateViewTestCase(TestCase): class DocumentStateViewTestCase(GenericDocumentViewTestCase):
def setUp(self): def create_workflow(self):
self.admin_user = get_user_model().objects.create_superuser( self.workflow = Workflow.on_organization.create(
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, label=TEST_WORKFLOW_LABEL
password=TEST_ADMIN_PASSWORD
)
self.client = Client()
# Login the admin user
logged_in = self.client.login(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
self.assertTrue(logged_in)
self.assertTrue(self.admin_user.is_authenticated())
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE
) )
with open(TEST_SMALL_DOCUMENT_PATH) as file_object: def test_workflow_create_view_with_permission(self):
self.document = self.document_type.new_document( self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
file_object=file_object
self.role.permissions.add(
permission_workflow_create.stored_permission
) )
def tearDown(self): response = self.post(
self.document_type.delete() 'document_states:setup_workflow_create', data={
def test_creating_workflow(self):
response = self.client.post(
reverse(
'document_states:setup_workflow_create'
), data={
'label': TEST_WORKFLOW_LABEL, 'label': TEST_WORKFLOW_LABEL,
}, follow=True }, follow=True
) )
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(Workflow.objects.count(), 1) self.assertEquals(Workflow.on_organization.count(), 1)
self.assertEquals(Workflow.objects.all()[0].label, TEST_WORKFLOW_LABEL) self.assertEquals(
Workflow.on_organization.all()[0].label, TEST_WORKFLOW_LABEL
)
def test_delete_workflow(self): def test_workflow_delete_view_with_permission(self):
workflow = Workflow.objects.create(label=TEST_WORKFLOW_LABEL) self.create_workflow()
self.assertEquals(Workflow.objects.count(), 1) self.assertEquals(Workflow.on_organization.count(), 1)
self.assertEquals(Workflow.objects.all()[0].label, TEST_WORKFLOW_LABEL) self.assertEquals(
Workflow.on_organization.all()[0].label, TEST_WORKFLOW_LABEL
)
response = self.client.post( self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
reverse(
'document_states:setup_workflow_delete', args=(workflow.pk,) self.role.permissions.add(
), follow=True permission_workflow_delete.stored_permission
)
response = self.post(
'document_states:setup_workflow_delete', args=(self.workflow.pk,),
follow=True
) )
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(Workflow.objects.count(), 0) self.assertEquals(Workflow.on_organization.count(), 0)
def test_create_workflow_state(self): def test_workflow_state_create_view_with_permission(self):
workflow = Workflow.objects.create(label=TEST_WORKFLOW_LABEL) self.create_workflow()
response = self.client.post( self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
reverse(
self.role.permissions.add(
permission_workflow_edit.stored_permission
)
response = self.post(
'document_states:setup_workflow_state_create', 'document_states:setup_workflow_state_create',
args=(workflow.pk,) args=(self.workflow.pk,), data={
), data={
'label': TEST_WORKFLOW_STATE_LABEL, 'label': TEST_WORKFLOW_STATE_LABEL,
'completion': TEST_WORKFLOW_STATE_COMPLETION, 'completion': TEST_WORKFLOW_STATE_COMPLETION,
}, follow=True }, follow=True
@@ -93,50 +86,61 @@ class DocumentStateViewTestCase(TestCase):
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(WorkflowState.objects.count(), 1) self.assertEquals(WorkflowState.on_organization.count(), 1)
self.assertEquals( self.assertEquals(
WorkflowState.objects.all()[0].label, TEST_WORKFLOW_STATE_LABEL WorkflowState.on_organization.all()[0].label,
TEST_WORKFLOW_STATE_LABEL
) )
self.assertEquals( self.assertEquals(
WorkflowState.objects.all()[0].completion, WorkflowState.on_organization.all()[0].completion,
TEST_WORKFLOW_STATE_COMPLETION TEST_WORKFLOW_STATE_COMPLETION
) )
def test_delete_workflow_state(self): def test_workflow_state_delete_view_with_permission(self):
workflow = Workflow.objects.create(label=TEST_WORKFLOW_LABEL) self.create_workflow()
workflow_state = WorkflowState.objects.create(
workflow=workflow, label=TEST_WORKFLOW_STATE_LABEL, workflow_state = WorkflowState.on_organization.create(
workflow=self.workflow, label=TEST_WORKFLOW_STATE_LABEL,
completion=TEST_WORKFLOW_STATE_COMPLETION completion=TEST_WORKFLOW_STATE_COMPLETION
) )
response = self.client.post( self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
reverse(
self.role.permissions.add(
permission_workflow_edit.stored_permission
)
response = self.post(
'document_states:setup_workflow_state_delete', 'document_states:setup_workflow_state_delete',
args=(workflow_state.pk,) args=(workflow_state.pk,), follow=True
), follow=True
) )
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(WorkflowState.objects.count(), 0) self.assertEquals(WorkflowState.on_organization.count(), 0)
self.assertEquals(Workflow.objects.count(), 1) self.assertEquals(Workflow.on_organization.count(), 1)
def test_create_workflow_transition(self): def test_workflow_transition_create_view_with_permission(self):
workflow = Workflow.objects.create(label=TEST_WORKFLOW_LABEL) self.create_workflow()
workflow_initial_state = WorkflowState.objects.create(
workflow=workflow, label=TEST_WORKFLOW_INITIAL_STATE_LABEL, workflow_initial_state = WorkflowState.on_organization.create(
workflow=self.workflow, label=TEST_WORKFLOW_INITIAL_STATE_LABEL,
completion=TEST_WORKFLOW_INITIAL_STATE_COMPLETION, initial=True completion=TEST_WORKFLOW_INITIAL_STATE_COMPLETION, initial=True
) )
workflow_state = WorkflowState.objects.create( workflow_state = WorkflowState.on_organization.create(
workflow=workflow, label=TEST_WORKFLOW_STATE_LABEL, workflow=self.workflow, label=TEST_WORKFLOW_STATE_LABEL,
completion=TEST_WORKFLOW_STATE_COMPLETION completion=TEST_WORKFLOW_STATE_COMPLETION
) )
response = self.client.post( self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
reverse(
self.role.permissions.add(
permission_workflow_edit.stored_permission
)
response = self.post(
'document_states:setup_workflow_transition_create', 'document_states:setup_workflow_transition_create',
args=(workflow.pk,) args=(self.workflow.pk,), data={
), data={
'label': TEST_WORKFLOW_TRANSITION_LABEL, 'label': TEST_WORKFLOW_TRANSITION_LABEL,
'origin_state': workflow_initial_state.pk, 'origin_state': workflow_initial_state.pk,
'destination_state': workflow_state.pk, 'destination_state': workflow_state.pk,
@@ -145,47 +149,52 @@ class DocumentStateViewTestCase(TestCase):
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(WorkflowTransition.objects.count(), 1) self.assertEquals(WorkflowTransition.on_organization.count(), 1)
self.assertEquals( self.assertEquals(
WorkflowTransition.objects.all()[0].label, WorkflowTransition.on_organization.all()[0].label,
TEST_WORKFLOW_TRANSITION_LABEL TEST_WORKFLOW_TRANSITION_LABEL
) )
self.assertEquals( self.assertEquals(
WorkflowTransition.objects.all()[0].origin_state, WorkflowTransition.on_organization.all()[0].origin_state,
workflow_initial_state workflow_initial_state
) )
self.assertEquals( self.assertEquals(
WorkflowTransition.objects.all()[0].destination_state, WorkflowTransition.on_organization.all()[0].destination_state,
workflow_state workflow_state
) )
def test_delete_workflow_transition(self): def test_workflow_transition_delete_view_with_permission(self):
workflow = Workflow.objects.create(label=TEST_WORKFLOW_LABEL) self.create_workflow()
workflow_initial_state = WorkflowState.objects.create(
workflow=workflow, label=TEST_WORKFLOW_INITIAL_STATE_LABEL, workflow_initial_state = WorkflowState.on_organization.create(
workflow=self.workflow, label=TEST_WORKFLOW_INITIAL_STATE_LABEL,
completion=TEST_WORKFLOW_INITIAL_STATE_COMPLETION, initial=True completion=TEST_WORKFLOW_INITIAL_STATE_COMPLETION, initial=True
) )
workflow_state = WorkflowState.objects.create( workflow_state = WorkflowState.on_organization.create(
workflow=workflow, label=TEST_WORKFLOW_STATE_LABEL, workflow=self.workflow, label=TEST_WORKFLOW_STATE_LABEL,
completion=TEST_WORKFLOW_STATE_COMPLETION completion=TEST_WORKFLOW_STATE_COMPLETION
) )
workflow_transition = WorkflowTransition.objects.create( workflow_transition = WorkflowTransition.on_organization.create(
workflow=workflow, label=TEST_WORKFLOW_TRANSITION_LABEL, workflow=self.workflow, label=TEST_WORKFLOW_TRANSITION_LABEL,
origin_state=workflow_initial_state, origin_state=workflow_initial_state,
destination_state=workflow_state destination_state=workflow_state
) )
self.assertEquals(WorkflowTransition.objects.count(), 1) self.assertEquals(WorkflowTransition.on_organization.count(), 1)
response = self.client.post( self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
reverse(
self.role.permissions.add(
permission_workflow_edit.stored_permission
)
response = self.post(
'document_states:setup_workflow_transition_delete', 'document_states:setup_workflow_transition_delete',
args=(workflow_transition.pk,) args=(workflow_transition.pk,), follow=True
), follow=True
) )
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(WorkflowState.objects.count(), 2) self.assertEquals(WorkflowState.on_organization.count(), 2)
self.assertEquals(Workflow.objects.count(), 1) self.assertEquals(Workflow.on_organization.count(), 1)
self.assertEquals(WorkflowTransition.objects.count(), 0) self.assertEquals(WorkflowTransition.on_organization.count(), 0)

View File

@@ -47,7 +47,7 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
).dispatch(request, *args, **kwargs) ).dispatch(request, *args, **kwargs)
def get_document(self): def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['pk']) return get_object_or_404(Document.on_organization, pk=self.kwargs['pk'])
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -64,7 +64,7 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
class WorkflowDocumentListView(DocumentListView): class WorkflowDocumentListView(DocumentListView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.workflow = get_object_or_404(Workflow, pk=self.kwargs['pk']) self.workflow = get_object_or_404(Workflow.on_organization, pk=self.kwargs['pk'])
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -80,7 +80,7 @@ class WorkflowDocumentListView(DocumentListView):
).dispatch(request, *args, **kwargs) ).dispatch(request, *args, **kwargs)
def get_document_queryset(self): def get_document_queryset(self):
return Document.objects.filter( return Document.on_organization.filter(
document_type__in=self.workflow.document_types.all() document_type__in=self.workflow.document_types.all()
) )
@@ -123,7 +123,7 @@ class WorkflowInstanceDetailView(SingleObjectListView):
return self.get_workflow_instance().log_entries.order_by('-datetime') return self.get_workflow_instance().log_entries.order_by('-datetime')
def get_workflow_instance(self): def get_workflow_instance(self):
return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk']) return get_object_or_404(WorkflowInstance.on_organization, pk=self.kwargs['pk'])
class WorkflowInstanceTransitionView(FormView): class WorkflowInstanceTransitionView(FormView):
@@ -175,7 +175,7 @@ class WorkflowInstanceTransitionView(FormView):
return self.get_workflow_instance().get_absolute_url() return self.get_workflow_instance().get_absolute_url()
def get_workflow_instance(self): def get_workflow_instance(self):
return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk']) return get_object_or_404(WorkflowInstance.on_organization, pk=self.kwargs['pk'])
# Setup # Setup
@@ -185,29 +185,37 @@ class SetupWorkflowListView(SingleObjectListView):
'title': _('Workflows'), 'title': _('Workflows'),
'hide_link': True, 'hide_link': True,
} }
model = Workflow
view_permission = permission_workflow_view view_permission = permission_workflow_view
def get_queryset(self):
return Workflow.on_organization.all()
class SetupWorkflowCreateView(SingleObjectCreateView): class SetupWorkflowCreateView(SingleObjectCreateView):
form_class = WorkflowForm form_class = WorkflowForm
model = Workflow
view_permission = permission_workflow_create view_permission = permission_workflow_create
post_action_redirect = reverse_lazy('document_states:setup_workflow_list') post_action_redirect = reverse_lazy('document_states:setup_workflow_list')
def get_queryset(self):
return Workflow.on_organization.all()
class SetupWorkflowEditView(SingleObjectEditView): class SetupWorkflowEditView(SingleObjectEditView):
form_class = WorkflowForm form_class = WorkflowForm
model = Workflow
view_permission = permission_workflow_edit view_permission = permission_workflow_edit
post_action_redirect = reverse_lazy('document_states:setup_workflow_list') post_action_redirect = reverse_lazy('document_states:setup_workflow_list')
def get_queryset(self):
return Workflow.on_organization.all()
class SetupWorkflowDeleteView(SingleObjectDeleteView): class SetupWorkflowDeleteView(SingleObjectDeleteView):
model = Workflow
view_permission = permission_workflow_delete view_permission = permission_workflow_delete
post_action_redirect = reverse_lazy('document_states:setup_workflow_list') post_action_redirect = reverse_lazy('document_states:setup_workflow_list')
def get_queryset(self):
return Workflow.on_organization.all()
class SetupWorkflowDocumentTypesView(AssignRemoveView): class SetupWorkflowDocumentTypesView(AssignRemoveView):
decode_content_type = True decode_content_type = True
@@ -229,7 +237,7 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
} }
def get_object(self): def get_object(self):
return get_object_or_404(Workflow, pk=self.kwargs['pk']) return get_object_or_404(Workflow.on_organization, pk=self.kwargs['pk'])
def left_list(self): def left_list(self):
return AssignRemoveView.generate_choices( return AssignRemoveView.generate_choices(
@@ -273,7 +281,7 @@ class SetupWorkflowStateListView(SingleObjectListView):
return self.get_workflow().states.all() return self.get_workflow().states.all()
def get_workflow(self): def get_workflow(self):
return get_object_or_404(Workflow, pk=self.kwargs['pk']) return get_object_or_404(Workflow.on_organization, pk=self.kwargs['pk'])
class SetupWorkflowStateCreateView(SingleObjectCreateView): class SetupWorkflowStateCreateView(SingleObjectCreateView):
@@ -289,7 +297,7 @@ class SetupWorkflowStateCreateView(SingleObjectCreateView):
} }
def get_workflow(self): def get_workflow(self):
return get_object_or_404(Workflow, pk=self.kwargs['pk']) return get_object_or_404(Workflow.on_organization, pk=self.kwargs['pk'])
def get_queryset(self): def get_queryset(self):
return self.get_workflow().states.all() return self.get_workflow().states.all()
@@ -350,7 +358,7 @@ class SetupWorkflowTransitionListView(SingleObjectListView):
view_permission = permission_workflow_view view_permission = permission_workflow_view
def get_workflow(self): def get_workflow(self):
return get_object_or_404(Workflow, pk=self.kwargs['pk']) return get_object_or_404(Workflow.on_organization, pk=self.kwargs['pk'])
def get_queryset(self): def get_queryset(self):
return self.get_workflow().transitions.all() return self.get_workflow().transitions.all()
@@ -385,7 +393,7 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
return kwargs return kwargs
def get_workflow(self): def get_workflow(self):
return get_object_or_404(Workflow, pk=self.kwargs['pk']) return get_object_or_404(Workflow.on_organization, pk=self.kwargs['pk'])
def get_queryset(self): def get_queryset(self):
return self.get_workflow().transitions.all() return self.get_workflow().transitions.all()

View File

@@ -3,8 +3,8 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from .models import ( from .models import (
DeletedDocument, Document, DocumentPage, DocumentType, Document, DocumentPage, DocumentType, DocumentTypeFilename,
DocumentTypeFilename, DocumentVersion, RecentDocument DocumentVersion, RecentDocument, TrashedDocument
) )
@@ -29,14 +29,6 @@ class DocumentVersionInline(admin.StackedInline):
allow_add = True allow_add = True
@admin.register(DeletedDocument)
class DeletedDocumentAdmin(admin.ModelAdmin):
date_hierarchy = 'deleted_date_time'
list_filter = ('document_type',)
list_display = ('uuid', 'label', 'document_type', 'deleted_date_time')
readonly_fields = ('uuid', 'document_type')
@admin.register(Document) @admin.register(Document)
class DocumentAdmin(admin.ModelAdmin): class DocumentAdmin(admin.ModelAdmin):
date_hierarchy = 'date_added' date_hierarchy = 'date_added'
@@ -62,3 +54,11 @@ class RecentDocumentAdmin(admin.ModelAdmin):
list_display_links = ('document', 'datetime_accessed') list_display_links = ('document', 'datetime_accessed')
list_filter = ('user',) list_filter = ('user',)
readonly_fields = ('user', 'document', 'datetime_accessed') readonly_fields = ('user', 'document', 'datetime_accessed')
@admin.register(TrashedDocument)
class TrashedDocumentAdmin(admin.ModelAdmin):
date_hierarchy = 'deleted_date_time'
list_filter = ('document_type',)
list_display = ('uuid', 'label', 'document_type', 'deleted_date_time')
readonly_fields = ('uuid', 'document_type')

View File

@@ -27,7 +27,7 @@ from .permissions import (
permission_document_type_edit, permission_document_type_view permission_document_type_edit, permission_document_type_view
) )
from .serializers import ( from .serializers import (
DeletedDocumentSerializer, DocumentPageImageSerializer, TrashedDocumentSerializer, DocumentPageImageSerializer,
DocumentPageSerializer, DocumentSerializer, DocumentPageSerializer, DocumentSerializer,
DocumentTypeSerializer, DocumentVersionSerializer, DocumentTypeSerializer, DocumentVersionSerializer,
DocumentVersionRevertSerializer, NewDocumentSerializer, DocumentVersionRevertSerializer, NewDocumentSerializer,
@@ -37,7 +37,7 @@ from .serializers import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class APIDeletedDocumentListView(generics.ListAPIView): class APITrashedDocumentListView(generics.ListAPIView):
""" """
Returns a list of all the trashed documents. Returns a list of all the trashed documents.
""" """
@@ -46,10 +46,10 @@ class APIDeletedDocumentListView(generics.ListAPIView):
mayan_object_permissions = {'GET': (permission_document_view,)} mayan_object_permissions = {'GET': (permission_document_view,)}
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.trash.all() queryset = Document.trash.all()
serializer_class = DeletedDocumentSerializer serializer_class = TrashedDocumentSerializer
class APIDeletedDocumentView(generics.RetrieveDestroyAPIView): class APITrashedDocumentView(generics.RetrieveDestroyAPIView):
""" """
Returns the selected trashed document details. Returns the selected trashed document details.
""" """
@@ -59,17 +59,17 @@ class APIDeletedDocumentView(generics.RetrieveDestroyAPIView):
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.trash.all() queryset = Document.trash.all()
serializer_class = DeletedDocumentSerializer serializer_class = TrashedDocumentSerializer
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" """
Delete the trashed document. Delete the trashed document.
""" """
return super(APIDeletedDocumentView, self).delete(*args, **kwargs) return super(APITrashedDocumentView, self).delete(*args, **kwargs)
class APIDeletedDocumentRestoreView(generics.GenericAPIView): class APITrashedDocumentRestoreView(generics.GenericAPIView):
""" """
Restore a trashed document. Restore a trashed document.
""" """
@@ -80,7 +80,7 @@ class APIDeletedDocumentRestoreView(generics.GenericAPIView):
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.trash.all() queryset = Document.trash.all()
serializer_class = DeletedDocumentSerializer serializer_class = TrashedDocumentSerializer
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
self.get_object().restore() self.get_object().restore()
@@ -103,7 +103,7 @@ class APIDocumentDownloadView(generics.RetrieveAPIView):
'GET': (permission_document_download,) 'GET': (permission_document_download,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.objects.all() queryset = Document.on_organization.all()
def get_serializer_class(self): def get_serializer_class(self):
return None return None
@@ -127,7 +127,7 @@ class APIDocumentListView(generics.ListCreateAPIView):
mayan_object_permissions = {'GET': (permission_document_view,)} mayan_object_permissions = {'GET': (permission_document_view,)}
mayan_view_permissions = {'POST': (permission_document_create,)} mayan_view_permissions = {'POST': (permission_document_create,)}
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.objects.all() queryset = Document.on_organization.all()
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method == 'GET': if self.request.method == 'GET':
@@ -162,7 +162,7 @@ class APIDocumentVersionDownloadView(generics.RetrieveAPIView):
'GET': (permission_document_download,) 'GET': (permission_document_download,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all() queryset = DocumentVersion.on_organization.all()
def get_serializer_class(self): def get_serializer_class(self):
return None return None
@@ -189,7 +189,7 @@ class APIDocumentView(generics.RetrieveUpdateDestroyAPIView):
'DELETE': (permission_document_trash,) 'DELETE': (permission_document_trash,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.objects.all() queryset = Document.on_organization.all()
serializer_class = DocumentSerializer serializer_class = DocumentSerializer
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
@@ -233,7 +233,7 @@ class APIDocumentPageImageView(generics.RetrieveAPIView):
} }
mayan_permission_attribute_check = 'document' mayan_permission_attribute_check = 'document'
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentPage.objects.all() queryset = DocumentPage.on_organization.all()
serializer_class = DocumentPageImageSerializer serializer_class = DocumentPageImageSerializer
@@ -249,7 +249,7 @@ class APIDocumentPageView(generics.RetrieveUpdateAPIView):
} }
mayan_permission_attribute_check = 'document' mayan_permission_attribute_check = 'document'
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentPage.objects.all() queryset = DocumentPage.on_organization.all()
serializer_class = DocumentPageSerializer serializer_class = DocumentPageSerializer
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
@@ -279,7 +279,7 @@ class APIDocumentTypeListView(generics.ListCreateAPIView):
mayan_object_permissions = {'GET': (permission_document_type_view,)} mayan_object_permissions = {'GET': (permission_document_type_view,)}
mayan_view_permissions = {'POST': (permission_document_type_create,)} mayan_view_permissions = {'POST': (permission_document_type_create,)}
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentType.objects.all() queryset = DocumentType.on_organization.all()
serializer_class = DocumentTypeSerializer serializer_class = DocumentTypeSerializer
def get(self, *args, **kwargs): def get(self, *args, **kwargs):
@@ -309,7 +309,7 @@ class APIDocumentTypeView(generics.RetrieveUpdateDestroyAPIView):
'DELETE': (permission_document_type_delete,) 'DELETE': (permission_document_type_delete,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentType.objects.all() queryset = DocumentType.on_organization.all()
serializer_class = DocumentTypeSerializer serializer_class = DocumentTypeSerializer
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
@@ -435,7 +435,7 @@ class APIDocumentVersionView(generics.RetrieveUpdateAPIView):
} }
mayan_permission_attribute_check = 'document' mayan_permission_attribute_check = 'document'
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all() queryset = DocumentVersion.on_organization.all()
serializer_class = DocumentVersionSerializer serializer_class = DocumentVersionSerializer
def patch(self, *args, **kwargs): def patch(self, *args, **kwargs):
@@ -463,7 +463,7 @@ class APIDocumentVersionRevertView(generics.GenericAPIView):
} }
mayan_permission_attribute_check = 'document' mayan_permission_attribute_check = 'document'
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all() queryset = DocumentVersion.on_organization.all()
serializer_class = DocumentVersionRevertSerializer serializer_class = DocumentVersionRevertSerializer
def post(self, *args, **kwargs): def post(self, *args, **kwargs):

View File

@@ -36,7 +36,7 @@ from .links import (
link_clear_image_cache, link_document_clear_transformations, link_clear_image_cache, link_document_clear_transformations,
link_document_delete, link_document_document_type_edit, link_document_delete, link_document_document_type_edit,
link_document_multiple_document_type_edit, link_document_download, link_document_multiple_document_type_edit, link_document_download,
link_document_edit, link_document_list, link_document_list_deleted, link_document_edit, link_document_list, link_document_list_trashed,
link_document_list_recent, link_document_multiple_delete, link_document_list_recent, link_document_multiple_delete,
link_document_multiple_trash, link_document_multiple_clear_transformations, link_document_multiple_trash, link_document_multiple_clear_transformations,
link_document_multiple_download, link_document_multiple_restore, link_document_multiple_download, link_document_multiple_restore,
@@ -87,19 +87,19 @@ class DocumentsApp(MayanAppConfig):
APIEndPoint(app=self, version_string='1') APIEndPoint(app=self, version_string='1')
DeletedDocument = self.get_model('DeletedDocument')
Document = self.get_model('Document') Document = self.get_model('Document')
DocumentPage = self.get_model('DocumentPage') DocumentPage = self.get_model('DocumentPage')
DocumentType = self.get_model('DocumentType') DocumentType = self.get_model('DocumentType')
DocumentTypeFilename = self.get_model('DocumentTypeFilename') DocumentTypeFilename = self.get_model('DocumentTypeFilename')
DocumentVersion = self.get_model('DocumentVersion') DocumentVersion = self.get_model('DocumentVersion')
TrashedDocument = self.get_model('TrashedDocument')
MissingItem( MissingItem(
label=_('Create a document type'), label=_('Create a document type'),
description=_( description=_(
'Every uploaded document must be assigned a document type, ' 'Every uploaded document must be assigned a document type, '
'it is the basic way Mayan EDMS categorizes documents.' 'it is the basic way Mayan EDMS categorizes documents.'
), condition=lambda: not DocumentType.objects.exists(), ), condition=lambda: not DocumentType.on_organization.exists(),
view='documents:document_type_list' view='documents:document_type_list'
) )
@@ -172,20 +172,20 @@ class DocumentsApp(MayanAppConfig):
) )
SourceColumn( SourceColumn(
source=DeletedDocument, label=_('Thumbnail'), source=TrashedDocument, label=_('Thumbnail'),
func=lambda context: document_thumbnail( func=lambda context: document_thumbnail(
context['object'], context['object'],
gallery_name='documents:delete_document_list', gallery_name='documents:trashed_document_list',
size=setting_thumbnail_size.value, size=setting_thumbnail_size.value,
title=getattr(context['object'], 'label', None), title=getattr(context['object'], 'label', None),
disable_title_link=True disable_title_link=True
) )
) )
SourceColumn( SourceColumn(
source=DeletedDocument, label=_('Type'), attribute='document_type' source=TrashedDocument, label=_('Type'), attribute='document_type'
) )
SourceColumn( SourceColumn(
source=DeletedDocument, label=_('Date time trashed'), source=TrashedDocument, label=_('Date time trashed'),
attribute='deleted_date_time' attribute='deleted_date_time'
) )
@@ -268,7 +268,7 @@ class DocumentsApp(MayanAppConfig):
menu_front_page.bind_links( menu_front_page.bind_links(
links=( links=(
link_document_list_recent, link_document_list, link_document_list_recent, link_document_list,
link_document_list_deleted link_document_list_trashed
) )
) )
menu_setup.bind_links(links=(link_document_type_setup,)) menu_setup.bind_links(links=(link_document_type_setup,))
@@ -304,7 +304,7 @@ class DocumentsApp(MayanAppConfig):
menu_sidebar.bind_links( menu_sidebar.bind_links(
links=(link_trash_can_empty,), links=(link_trash_can_empty,),
sources=( sources=(
'documents:document_list_deleted', 'documents:trash_can_empty' 'documents:document_list_trashed', 'documents:trash_can_empty'
) )
) )
@@ -319,7 +319,7 @@ class DocumentsApp(MayanAppConfig):
) )
menu_object.bind_links( menu_object.bind_links(
links=(link_document_restore, link_document_delete), links=(link_document_restore, link_document_delete),
sources=(DeletedDocument,) sources=(TrashedDocument,)
) )
# Document facet links # Document facet links
@@ -354,7 +354,7 @@ class DocumentsApp(MayanAppConfig):
menu_multi_item.bind_links( menu_multi_item.bind_links(
links=( links=(
link_document_multiple_restore, link_document_multiple_delete link_document_multiple_restore, link_document_multiple_delete
), sources=(DeletedDocument,) ), sources=(TrashedDocument,)
) )
# Document pages # Document pages
@@ -430,5 +430,5 @@ class DocumentsApp(MayanAppConfig):
dispatch_uid='create_default_document_type' dispatch_uid='create_default_document_type'
) )
registry.register(DeletedDocument) registry.register(TrashedDocument)
registry.register(Document) registry.register(Document)

View File

@@ -162,7 +162,7 @@ class DocumentTypeSelectForm(forms.Form):
logger.debug('user: %s', user) logger.debug('user: %s', user)
super(DocumentTypeSelectForm, self).__init__(*args, **kwargs) super(DocumentTypeSelectForm, self).__init__(*args, **kwargs)
queryset = DocumentType.objects.all() queryset = DocumentType.on_organization.all()
try: try:
Permission.check_permissions(user, (permission_document_create,)) Permission.check_permissions(user, (permission_document_create,))
except PermissionDenied: except PermissionDenied:

View File

@@ -11,7 +11,7 @@ def create_default_document_type(sender, **kwargs):
app_label='documents', model_name='DocumentType' app_label='documents', model_name='DocumentType'
) )
if not DocumentType.objects.count(): if not DocumentType.on_organization.count():
document_type = DocumentType.objects.create( document_type = DocumentType.objects.create(
label=DEFAULT_DOCUMENT_TYPE_LABEL label=DEFAULT_DOCUMENT_TYPE_LABEL
) )

View File

@@ -140,9 +140,9 @@ link_document_list_recent = Link(
icon='fa fa-clock-o', text=_('Recent documents'), icon='fa fa-clock-o', text=_('Recent documents'),
view='documents:document_list_recent' view='documents:document_list_recent'
) )
link_document_list_deleted = Link( link_document_list_trashed = Link(
icon='fa fa-trash', text=_('Trash'), icon='fa fa-trash', text=_('Trash'),
view='documents:document_list_deleted' view='documents:document_list_trashed'
) )
# Tools # Tools

View File

@@ -4,6 +4,7 @@ from datetime import timedelta
import logging import logging
from django.apps import apps from django.apps import apps
from django.conf import settings
from django.db import models from django.db import models
from django.utils.timezone import now from django.utils.timezone import now
@@ -22,9 +23,9 @@ class DocumentManager(models.Manager):
return self.get(uuid=uuid) return self.get(uuid=uuid)
def get_queryset(self): def get_queryset(self):
return TrashCanQuerySet( return TrashedDocumentQuerySet(
self.model, using=self._db self.model, using=self._db
).filter(in_trash=False) )
def invalidate_cache(self): def invalidate_cache(self):
for document in self.model.objects.all(): for document in self.model.objects.all():
@@ -49,7 +50,7 @@ class DocumentTypeManager(models.Manager):
'Document type: %s, has a deletion period delta of: %s', 'Document type: %s, has a deletion period delta of: %s',
document_type, delta document_type, delta
) )
for document in document_type.deleted_documents.filter(deleted_date_time__lt=now() - delta): for document in document_type.trashed_documents.filter(deleted_date_time__lt=now() - delta):
logger.info( logger.info(
'Document "%s" with id: %d, trashed on: %s, exceded ' 'Document "%s" with id: %d, trashed on: %s, exceded '
'delete period', document, document.pk, 'delete period', document, document.pk,
@@ -109,8 +110,48 @@ class NewVersionBlockManager(models.Manager):
return self.filter(document=document).exists() return self.filter(document=document).exists()
class PassthroughManager(models.Manager): class OrganizationDocumentManager(models.Manager):
pass def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return TrashedDocumentQuerySet(
self.model, using=self._db
).filter(in_trash=False).filter(
document_type__in=DocumentType.on_organization.all()
)
class OrganizationDocumentVersionManager(models.Manager):
def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return super(
OrganizationDocumentVersionManager, self
).get_queryset().filter(
document__document_type__in=DocumentType.on_organization.all()
)
class OrganizationDocumentTypeFilenameManager(models.Manager):
def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return super(
OrganizationDocumentTypeFilenameManager, self
).get_queryset().filter(
document_type__in=DocumentType.on_organization.all()
)
class OrganizationDocumentPage(models.Manager):
def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return super(
OrganizationDocumentPage, self
).get_queryset().filter(
document_version__document__document_type__in=DocumentType.on_organization.all()
)
class RecentDocumentManager(models.Manager): class RecentDocumentManager(models.Manager):
@@ -129,24 +170,37 @@ class RecentDocumentManager(models.Manager):
return new_recent return new_recent
def get_for_user(self, user): def get_for_user(self, user):
document_model = apps.get_model('documents', 'document') Document = apps.get_model('documents', 'Document')
if user.is_authenticated(): if user.is_authenticated():
return document_model.objects.filter( return Document.objects.filter(
recentdocument__user=user recentdocument__user=user,
).order_by('-recentdocument__datetime_accessed') ).order_by('-recentdocument__datetime_accessed')
else: else:
return document_model.objects.none() return Document.objects.none()
class TrashCanManager(models.Manager): class TrashedDocumentManager(models.Manager):
def get_queryset(self): def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return super( return super(
TrashCanManager, self TrashedDocumentManager, self
).get_queryset().filter(in_trash=True) ).get_queryset().filter(in_trash=True)
class TrashCanQuerySet(models.QuerySet): class OrganizationTrashedDocumentManager(models.Manager):
def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return super(
OrganizationTrashedDocumentManager, self
).get_queryset().filter(in_trash=True).filter(
document_type__in=DocumentType.on_organization.all()
)
class TrashedDocumentQuerySet(models.QuerySet):
def delete(self, to_trash=True): def delete(self, to_trash=True):
for instance in self: for instance in self:
instance.delete(to_trash=to_trash) instance.delete(to_trash=to_trash)

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import organizations.shortcuts
class Migration(migrations.Migration):
dependencies = [
('organizations', '0002_add_data_default_organization'),
('documents', '0034_auto_20160509_2321'),
]
operations = [
migrations.AddField(
model_name='documenttype',
name='organization',
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'),
),
]

View File

@@ -23,6 +23,9 @@ from converter.exceptions import InvalidOfficeFormat, PageCountError
from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION from converter.literals import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION
from converter.models import Transformation from converter.models import Transformation
from mimetype.api import get_mimetype from mimetype.api import get_mimetype
from organizations.models import Organization
from organizations.managers import CurrentOrganizationManager
from organizations.shortcuts import get_current_organization
from permissions import Permission from permissions import Permission
from .events import ( from .events import (
@@ -34,7 +37,10 @@ from .exceptions import NewDocumentVersionNotAllowed
from .literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT from .literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
from .managers import ( from .managers import (
DocumentManager, DocumentTypeManager, NewVersionBlockManager, DocumentManager, DocumentTypeManager, NewVersionBlockManager,
PassthroughManager, RecentDocumentManager, TrashCanManager OrganizationDocumentManager, OrganizationDocumentVersionManager,
OrganizationTrashedDocumentManager,
OrganizationDocumentTypeFilenameManager, OrganizationDocumentPage,
RecentDocumentManager, TrashedDocumentManager
) )
from .permissions import permission_document_view from .permissions import permission_document_view
from .runtime import cache_storage_backend, storage_backend from .runtime import cache_storage_backend, storage_backend
@@ -61,6 +67,9 @@ class DocumentType(models.Model):
Define document types or classes to which a specific set of Define document types or classes to which a specific set of
properties can be attached properties can be attached
""" """
organization = models.ForeignKey(
Organization, default=get_current_organization
)
label = models.CharField( label = models.CharField(
max_length=32, unique=True, verbose_name=_('Label') max_length=32, unique=True, verbose_name=_('Label')
) )
@@ -87,12 +96,13 @@ class DocumentType(models.Model):
) )
objects = DocumentTypeManager() objects = DocumentTypeManager()
on_organization = CurrentOrganizationManager()
def __str__(self): def __str__(self):
return self.label return self.label
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
for document in Document.passthrough.filter(document_type=self): for document in Document.objects.filter(document_type=self):
document.delete(to_trash=False) document.delete(to_trash=False)
return super(DocumentType, self).delete(*args, **kwargs) return super(DocumentType, self).delete(*args, **kwargs)
@@ -106,8 +116,8 @@ class DocumentType(models.Model):
verbose_name_plural = _('Documents types') verbose_name_plural = _('Documents types')
@property @property
def deleted_documents(self): def trashed_documents(self):
return DeletedDocument.objects.filter(document_type=self) return TrashedDocument.objects.filter(document_type=self)
def get_document_count(self, user): def get_document_count(self, user):
queryset = self.documents queryset = self.documents
@@ -185,9 +195,14 @@ class Document(models.Model):
), verbose_name=_('Is stub?') ), verbose_name=_('Is stub?')
) )
# objects manager needs to go first otherwise
# objects becomes equal to ActiveDocumentManager(), which it shouldn't.
# The first manager becomes the default manager.
# https://docs.djangoproject.com/en/1.9/topics/db/managers/#default-managers
objects = DocumentManager() objects = DocumentManager()
passthrough = PassthroughManager() on_organization = OrganizationDocumentManager()
trash = TrashCanManager() trash = TrashedDocumentManager()
def __str__(self): def __str__(self):
return self.label or ugettext('Document stub, id: %d') % self.pk return self.label or ugettext('Document stub, id: %d') % self.pk
@@ -324,8 +339,9 @@ class Document(models.Model):
return 0 return 0
class DeletedDocument(Document): class TrashedDocument(Document):
objects = TrashCanManager() objects = TrashedDocumentManager()
on_organization = OrganizationTrashedDocumentManager()
class Meta: class Meta:
proxy = True proxy = True
@@ -372,6 +388,9 @@ class DocumentVersion(models.Model):
blank=True, editable=False, null=True, verbose_name=_('Checksum') blank=True, editable=False, null=True, verbose_name=_('Checksum')
) )
objects = models.Manager()
on_organization = OrganizationDocumentVersionManager()
def __str__(self): def __str__(self):
return '{0} - {1}'.format(self.document, self.timestamp) return '{0} - {1}'.format(self.document, self.timestamp)
@@ -627,6 +646,9 @@ class DocumentTypeFilename(models.Model):
) )
enabled = models.BooleanField(default=True, verbose_name=_('Enabled')) enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
objects = models.Manager()
on_organization = OrganizationDocumentTypeFilenameManager()
class Meta: class Meta:
ordering = ('filename',) ordering = ('filename',)
unique_together = ('document_type', 'filename') unique_together = ('document_type', 'filename')
@@ -651,6 +673,9 @@ class DocumentPage(models.Model):
verbose_name=_('Page number') verbose_name=_('Page number')
) )
objects = models.Manager()
on_organization = OrganizationDocumentPage()
def __str__(self): def __str__(self):
return _( return _(
'Page %(page_num)d out of %(total_pages)d of %(document)s' 'Page %(page_num)d out of %(total_pages)d of %(document)s'

View File

@@ -103,32 +103,6 @@ class NewDocumentVersionSerializer(serializers.Serializer):
) )
class DeletedDocumentSerializer(serializers.HyperlinkedModelSerializer):
document_type_label = serializers.SerializerMethodField()
restore = serializers.HyperlinkedIdentityField(
view_name='rest_api:deleteddocument-restore'
)
def get_document_type_label(self, instance):
return instance.document_type.label
class Meta:
extra_kwargs = {
'document_type': {'view_name': 'rest_api:documenttype-detail'},
'url': {'view_name': 'rest_api:deleteddocument-detail'}
}
fields = (
'date_added', 'deleted_date_time', 'description', 'document_type',
'document_type_label', 'id', 'label', 'language', 'restore',
'url', 'uuid',
)
model = Document
read_only_fields = (
'deleted_date_time', 'description', 'document_type', 'label',
'language'
)
class DocumentSerializer(serializers.HyperlinkedModelSerializer): class DocumentSerializer(serializers.HyperlinkedModelSerializer):
document_type_label = serializers.SerializerMethodField() document_type_label = serializers.SerializerMethodField()
latest_version = DocumentVersionSerializer(many=False, read_only=True) latest_version = DocumentVersionSerializer(many=False, read_only=True)
@@ -156,7 +130,7 @@ class NewDocumentSerializer(serializers.ModelSerializer):
file = serializers.FileField(write_only=True) file = serializers.FileField(write_only=True)
def save(self, _user): def save(self, _user):
document = Document.objects.create( document = Document.on_organization.create(
description=self.validated_data.get('description', ''), description=self.validated_data.get('description', ''),
document_type=self.validated_data['document_type'], document_type=self.validated_data['document_type'],
label=self.validated_data.get( label=self.validated_data.get(
@@ -191,3 +165,29 @@ class RecentDocumentSerializer(serializers.ModelSerializer):
class Meta: class Meta:
fields = ('document', 'datetime_accessed') fields = ('document', 'datetime_accessed')
model = RecentDocument model = RecentDocument
class TrashedDocumentSerializer(serializers.HyperlinkedModelSerializer):
document_type_label = serializers.SerializerMethodField()
restore = serializers.HyperlinkedIdentityField(
view_name='rest_api:deleteddocument-restore'
)
def get_document_type_label(self, instance):
return instance.document_type.label
class Meta:
extra_kwargs = {
'document_type': {'view_name': 'rest_api:documenttype-detail'},
'url': {'view_name': 'rest_api:deleteddocument-detail'}
}
fields = (
'date_added', 'deleted_date_time', 'description', 'document_type',
'document_type_label', 'id', 'label', 'language', 'restore',
'url', 'uuid',
)
model = Document
read_only_fields = (
'deleted_date_time', 'description', 'document_type', 'label',
'language'
)

View File

@@ -62,7 +62,7 @@ def task_get_document_page_image(document_page_id, *args, **kwargs):
app_label='documents', model_name='DocumentPage' app_label='documents', model_name='DocumentPage'
) )
document_page = DocumentPage.objects.get(pk=document_page_id) document_page = DocumentPage.on_organization.get(pk=document_page_id)
return document_page.get_image(*args, **kwargs) return document_page.get_image(*args, **kwargs)
@@ -72,7 +72,7 @@ def task_update_page_count(self, version_id):
app_label='documents', model_name='DocumentVersion' app_label='documents', model_name='DocumentVersion'
) )
document_version = DocumentVersion.objects.get(pk=version_id) document_version = DocumentVersion.on_organization.get(pk=version_id)
try: try:
document_version.update_page_count() document_version.update_page_count()
except OperationalError as exception: except OperationalError as exception:
@@ -95,12 +95,12 @@ def task_upload_new_document(self, document_type_id, shared_uploaded_file_id, de
) )
try: try:
document_type = DocumentType.objects.get(pk=document_type_id) document_type = DocumentType.on_organization.get(pk=document_type_id)
shared_file = SharedUploadedFile.objects.get( shared_file = SharedUploadedFile.objects.get(
pk=shared_uploaded_file_id pk=shared_uploaded_file_id
) )
if user_id: if user_id:
user = get_user_model().objects.get(pk=user_id) user = get_user_model().on_organization.get(pk=user_id)
else: else:
user = None user = None
@@ -149,12 +149,12 @@ def task_upload_new_version(self, document_id, shared_uploaded_file_id, user_id,
) )
try: try:
document = Document.objects.get(pk=document_id) document = Document.on_organization.get(pk=document_id)
shared_file = SharedUploadedFile.objects.get( shared_file = SharedUploadedFile.objects.get(
pk=shared_uploaded_file_id pk=shared_uploaded_file_id
) )
if user_id: if user_id:
user = get_user_model().objects.get(pk=user_id) user = get_user_model().on_organization.get(pk=user_id)
else: else:
user = None user = None

View File

@@ -21,10 +21,13 @@ __all__ = (
# Filenames # Filenames
TEST_COMPRESSED_DOCUMENTS_FILENAME = 'compressed_documents.zip' TEST_COMPRESSED_DOCUMENTS_FILENAME = 'compressed_documents.zip'
TEST_DEU_DOCUMENT_FILENAME = 'deu_website.png' TEST_DEU_DOCUMENT_FILENAME = 'deu_website.png'
TEST_DOCUMENT_DESCRIPTION = 'test description' TEST_DOCUMENT_DESCRIPTION = 'test description'
TEST_DOCUMENT_FILENAME = 'mayan_11_1.pdf' TEST_DOCUMENT_FILENAME = 'mayan_11_1.pdf'
TEST_DOCUMENT_TYPE = 'test_document_type' TEST_DOCUMENT_TYPE = 'test_document_type'
TEST_DOCUMENT_TYPE_2 = 'test document type 2' TEST_DOCUMENT_TYPE_2 = 'test document type 2'
TEST_DOCUMENT_TYPE_EDITED_LABEL = 'test document type edited label'
TEST_DOCUMENT_TYPE_2_LABEL = 'test document type 2 label'
TEST_HYBRID_DOCUMENT = 'hybrid_text_and_image.pdf' TEST_HYBRID_DOCUMENT = 'hybrid_text_and_image.pdf'
TEST_MULTI_PAGE_TIFF = 'multi_page.tiff' TEST_MULTI_PAGE_TIFF = 'multi_page.tiff'
TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME = 'I18N_title_áéíóúüñÑ.png.zip' TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME = 'I18N_title_áéíóúüñÑ.png.zip'
@@ -70,3 +73,5 @@ TEST_SMALL_DOCUMENT_PATH = os.path.join(
settings.BASE_DIR, 'contrib', 'sample_documents', settings.BASE_DIR, 'contrib', 'sample_documents',
TEST_SMALL_DOCUMENT_FILENAME TEST_SMALL_DOCUMENT_FILENAME
) )
TEST_TRANSFORMATION_NAME = 'rotate'
TEST_TRANSFORMATION_ARGUMENT = 'degrees: 180'

View File

@@ -4,48 +4,32 @@ from __future__ import unicode_literals
import time import time
from json import loads
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import override_settings from django.test import override_settings
from django.utils.six import BytesIO from django.utils.six import BytesIO
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase
from rest_api.tests import GenericAPITestCase
from user_management.tests.literals import ( from user_management.tests.literals import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
) )
from .literals import ( from .literals import (
TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE, TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
TEST_SMALL_DOCUMENT_CHECKSUM, TEST_SMALL_DOCUMENT_PATH, TEST_DOCUMENT_TYPE_EDITED_LABEL, TEST_SMALL_DOCUMENT_CHECKSUM,
TEST_SMALL_DOCUMENT_PATH,
) )
from ..models import Document, DocumentType, HASH_FUNCTION from ..models import Document, DocumentType, HASH_FUNCTION
class DocumentTypeAPITestCase(APITestCase): class DocumentTypeAPITestCase(GenericAPITestCase):
""" """
Test the document type API endpoints Test the document type API endpoints
""" """
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):
self.admin_user.delete()
def test_document_type_create(self): def test_document_type_create(self):
self.assertEqual(DocumentType.objects.all().count(), 0) self.assertEqual(DocumentType.on_organization.all().count(), 0)
self.client.post( self.client.post(
reverse('rest_api:documenttype-list'), data={ reverse('rest_api:documenttype-list'), data={
@@ -53,66 +37,69 @@ class DocumentTypeAPITestCase(APITestCase):
} }
) )
self.assertEqual(DocumentType.objects.all().count(), 1) self.assertEqual(DocumentType.on_organization.all().count(), 1)
self.assertEqual( self.assertEqual(
DocumentType.objects.all().first().label, TEST_DOCUMENT_TYPE DocumentType.on_organization.all().first().label,
TEST_DOCUMENT_TYPE
) )
def test_document_type_edit_via_put(self): def test_document_type_edit_via_put(self):
document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE) document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE
self.client.put(
reverse('rest_api:documenttype-detail', args=(document_type.pk,)),
{'label': TEST_DOCUMENT_TYPE + 'edited'}
) )
document_type = DocumentType.objects.get(pk=document_type.pk) response = self.client.put(
self.assertEqual(document_type.label, TEST_DOCUMENT_TYPE + 'edited') reverse('rest_api:documenttype-detail', args=(document_type.pk,)),
{'label': TEST_DOCUMENT_TYPE_EDITED_LABEL}
)
self.assertEqual(response.status_code, 200)
document_type = DocumentType.on_organization.get(pk=document_type.pk)
self.assertEqual(document_type.label, TEST_DOCUMENT_TYPE_EDITED_LABEL)
def test_document_type_edit_via_patch(self): def test_document_type_edit_via_patch(self):
document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE) document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE
self.client.patch(
reverse('rest_api:documenttype-detail', args=(document_type.pk,)),
{'label': TEST_DOCUMENT_TYPE + 'edited'}
) )
document_type = DocumentType.objects.get(pk=document_type.pk) response = self.client.patch(
self.assertEqual(document_type.label, TEST_DOCUMENT_TYPE + 'edited') reverse('rest_api:documenttype-detail', args=(document_type.pk,)),
{'label': TEST_DOCUMENT_TYPE_EDITED_LABEL}
)
self.assertEqual(response.status_code, 200)
document_type = DocumentType.on_organization.get(pk=document_type.pk)
self.assertEqual(document_type.label, TEST_DOCUMENT_TYPE_EDITED_LABEL)
document_type.delete()
def test_document_type_delete(self): def test_document_type_delete(self):
document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE) document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE
)
self.client.delete( self.client.delete(
reverse('rest_api:documenttype-detail', args=(document_type.pk,)) reverse('rest_api:documenttype-detail', args=(document_type.pk,))
) )
self.assertEqual(DocumentType.objects.all().count(), 0) self.assertEqual(DocumentType.on_organization.all().count(), 0)
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentAPITestCase(APITestCase): class DocumentAPITestCase(GenericAPITestCase):
""" """
Test document API endpoints Test document API endpoints
""" """
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(DocumentAPITestCase, self).setUp()
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, self.document_type = DocumentType.on_organization.create(
password=TEST_ADMIN_PASSWORD
)
self.client.login(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
def tearDown(self): def tearDown(self):
self.admin_user.delete()
self.document_type.delete() self.document_type.delete()
super(DocumentAPITestCase, self).tearDown()
def test_document_upload(self): def test_document_upload(self):
with open(TEST_DOCUMENT_PATH) as file_descriptor: with open(TEST_DOCUMENT_PATH) as file_descriptor:
@@ -123,14 +110,14 @@ class DocumentAPITestCase(APITestCase):
} }
) )
document_data = loads(response.content) document_data = response.data
self.assertEqual( self.assertEqual(
response.status_code, status.HTTP_201_CREATED response.status_code, status.HTTP_201_CREATED
) )
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
document = Document.objects.first() document = Document.on_organization.first()
self.assertEqual(document.pk, document_data['id']) self.assertEqual(document.pk, document_data['id'])
@@ -158,41 +145,9 @@ class DocumentAPITestCase(APITestCase):
reverse('rest_api:document-detail', args=(document.pk,)) reverse('rest_api:document-detail', args=(document.pk,))
) )
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(Document.trash.count(), 1) self.assertEqual(Document.trash.count(), 1)
def test_deleted_document_delete_from_trash(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
document = self.document_type.new_document(
file_object=file_object,
)
document.delete()
self.assertEqual(Document.objects.count(), 0)
self.assertEqual(Document.trash.count(), 1)
self.client.delete(
reverse('rest_api:trasheddocument-detail', args=(document.pk,))
)
self.assertEqual(Document.trash.count(), 0)
def test_deleted_document_restore(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
document = self.document_type.new_document(
file_object=file_object,
)
document.delete()
self.client.post(
reverse('rest_api:trasheddocument-restore', args=(document.pk,))
)
self.assertEqual(Document.trash.count(), 0)
self.assertEqual(Document.objects.count(), 1)
def test_document_new_version_upload(self): def test_document_new_version_upload(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object: with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
document = self.document_type.new_document( document = self.document_type.new_document(
@@ -293,5 +248,34 @@ class DocumentAPITestCase(APITestCase):
del(buf) del(buf)
# TODO: def test_document_set_document_type(self): def test_trashed_document_delete_from_trash(self):
# pass with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
document = self.document_type.new_document(
file_object=file_object,
)
document.delete()
self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(Document.trash.count(), 1)
self.client.delete(
reverse('rest_api:trasheddocument-detail', args=(document.pk,))
)
self.assertEqual(Document.trash.count(), 0)
def test_trashed_document_restore(self):
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
document = self.document_type.new_document(
file_object=file_object,
)
document.delete()
self.client.post(
reverse('rest_api:trasheddocument-restore', args=(document.pk,))
)
self.assertEqual(Document.trash.count(), 0)
self.assertEqual(Document.on_organization.count(), 1)

View File

@@ -49,7 +49,7 @@ class DocumentsLinksTestCase(GenericDocumentViewTestCase):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document, role=self.role content_object=self.document, role=self.role
) )
acl.permissions.add( acl.permissions.add(
@@ -81,7 +81,7 @@ class DocumentsLinksTestCase(GenericDocumentViewTestCase):
def test_document_version_download_link_with_permission(self): def test_document_version_download_link_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
acl = AccessControlList.objects.create( acl = AccessControlList.on_organization.create(
content_object=self.document, role=self.role content_object=self.document, role=self.role
) )
acl.permissions.add(permission_document_download.stored_permission) acl.permissions.add(permission_document_download.stored_permission)

View File

@@ -5,9 +5,11 @@ import time
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from organizations.tests.base import OrganizationTestCase
from ..exceptions import NewDocumentVersionNotAllowed from ..exceptions import NewDocumentVersionNotAllowed
from ..literals import STUB_EXPIRATION_INTERVAL from ..literals import STUB_EXPIRATION_INTERVAL
from ..models import DeletedDocument, Document, DocumentType, NewVersionBlock from ..models import Document, DocumentType, NewVersionBlock, TrashedDocument
from .literals import ( from .literals import (
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_PATH, TEST_MULTI_PAGE_TIFF_PATH, TEST_DOCUMENT_TYPE, TEST_DOCUMENT_PATH, TEST_MULTI_PAGE_TIFF_PATH,
@@ -16,9 +18,10 @@ from .literals import (
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentTestCase(TestCase): class DocumentTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(DocumentTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -29,6 +32,7 @@ class DocumentTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(DocumentTestCase, self).tearDown()
def test_document_creation(self): def test_document_creation(self):
self.assertEqual(self.document_type.label, TEST_DOCUMENT_TYPE) self.assertEqual(self.document_type.label, TEST_DOCUMENT_TYPE)
@@ -57,30 +61,30 @@ class DocumentTestCase(TestCase):
self.assertEqual(self.document.versions.count(), 3) self.assertEqual(self.document.versions.count(), 3)
def test_restoring_documents(self): def test_restoring_documents(self):
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
# Trash the document # Trash the document
self.document.delete() self.document.delete()
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
# Restore the document # Restore the document
self.document.restore() self.document.restore()
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
def test_trashing_documents(self): def test_trashing_documents(self):
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
# Trash the document # Trash the document
self.document.delete() self.document.delete()
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
# Delete the document # Delete the document
self.document.delete() self.document.delete()
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
def test_auto_trashing(self): def test_auto_trashing(self):
""" """
@@ -97,13 +101,13 @@ class DocumentTestCase(TestCase):
# field # field
time.sleep(2) time.sleep(2)
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
DocumentType.objects.check_trash_periods() DocumentType.objects.check_trash_periods()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
def test_auto_delete(self): def test_auto_delete(self):
""" """
@@ -116,13 +120,13 @@ class DocumentTestCase(TestCase):
self.document_type.delete_time_unit = 'seconds' self.document_type.delete_time_unit = 'seconds'
self.document_type.save() self.document_type.save()
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.document.delete() self.document.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
# Needed by MySQL as milliseconds value is not store in timestamp # Needed by MySQL as milliseconds value is not store in timestamp
# field # field
@@ -130,14 +134,16 @@ class DocumentTestCase(TestCase):
DocumentType.objects.check_delete_periods() DocumentType.objects.check_delete_periods()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class OfficeDocumentTestCase(TestCase): class OfficeDocumentTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(OfficeDocumentTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -148,6 +154,7 @@ class OfficeDocumentTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(OfficeDocumentTestCase, self).tearDown()
def test_document_creation(self): def test_document_creation(self):
self.assertEqual(self.document.file_mimetype, 'application/msword') self.assertEqual(self.document.file_mimetype, 'application/msword')
@@ -162,9 +169,11 @@ class OfficeDocumentTestCase(TestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class MultiPageTiffTestCase(TestCase): class MultiPageTiffTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(MultiPageTiffTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -175,6 +184,7 @@ class MultiPageTiffTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(MultiPageTiffTestCase, self).tearDown()
def test_document_creation(self): def test_document_creation(self):
self.assertEqual(self.document.file_mimetype, 'image/tiff') self.assertEqual(self.document.file_mimetype, 'image/tiff')
@@ -187,9 +197,11 @@ class MultiPageTiffTestCase(TestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentVersionTestCase(TestCase): class DocumentVersionTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(DocumentVersionTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -200,6 +212,7 @@ class DocumentVersionTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(DocumentVersionTestCase, self).tearDown()
def test_add_new_version(self): def test_add_new_version(self):
self.assertEqual(self.document.versions.count(), 1) self.assertEqual(self.document.versions.count(), 1)
@@ -236,23 +249,25 @@ class DocumentVersionTestCase(TestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentManagerTestCase(TestCase): class DocumentManagerTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(DocumentManagerTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(DocumentManagerTestCase, self).tearDown()
def test_document_stubs_deletion(self): def test_document_stubs_deletion(self):
document_stub = Document.objects.create( document_stub = Document.on_organization.create(
document_type=self.document_type document_type=self.document_type
) )
Document.objects.delete_stubs() Document.objects.delete_stubs()
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
document_stub.date_added = document_stub.date_added - timedelta( document_stub.date_added = document_stub.date_added - timedelta(
seconds=STUB_EXPIRATION_INTERVAL + 1 seconds=STUB_EXPIRATION_INTERVAL + 1
@@ -261,13 +276,14 @@ class DocumentManagerTestCase(TestCase):
Document.objects.delete_stubs() Document.objects.delete_stubs()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class NewVersionBlockTestCase(TestCase): class NewVersionBlockTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(NewVersionBlockTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -279,6 +295,7 @@ class NewVersionBlockTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document.delete() self.document.delete()
self.document_type.delete() self.document_type.delete()
super(NewVersionBlockTestCase, self).tearDown()
def test_blocking(self): def test_blocking(self):
NewVersionBlock.objects.block(document=self.document) NewVersionBlock.objects.block(document=self.document)

View File

@@ -0,0 +1,62 @@
from __future__ import unicode_literals
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import Document,DocumentType
from .literals import (
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_2_LABEL,
TEST_DOCUMENT_TYPE_QUICK_LABEL, TEST_SMALL_DOCUMENT_CHECKSUM,
TEST_SMALL_DOCUMENT_PATH
)
class DocumentOrganizationViewTestCase(OrganizationViewTestCase):
def setUp(self):
super(DocumentOrganizationViewTestCase, self).setUp()
# Create a document for organization A
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE
)
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
self.document = self.document_type.new_document(
file_object=file_object
)
def tearDown(self):
super(DocumentOrganizationViewTestCase, self).tearDown()
if self.document_type.pk:
self.document_type.delete()
def test_document_to_trash_view(self):
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'documents:document_trash', args=(self.document.pk,)
)
self.assertEqual(response.status_code, 404)
def test_document_view_view(self):
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
# Make sure admin for organization B cannot find the document for
# organization A
response = self.get(
'documents:document_properties', args=(self.document.pk,),
)
self.assertEqual(response.status_code, 404)
def test_document_document_type_change_view(self):
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2_LABEL
)
response = self.post(
'documents:document_document_type_edit',
args=(self.document.pk,),
data={'document_type': document_type.pk}
)
self.assertEqual(response.status_code, 403)

View File

@@ -15,7 +15,7 @@ from user_management.tests.literals import (
from ..literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT from ..literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
from ..models import ( from ..models import (
DeletedDocument, Document, DocumentType, HASH_FUNCTION Document, DocumentType, TrashedDocument, HASH_FUNCTION
) )
from ..permissions import ( from ..permissions import (
permission_document_create, permission_document_delete, permission_document_create, permission_document_delete,
@@ -28,22 +28,18 @@ from ..permissions import (
) )
from .literals import ( from .literals import (
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_QUICK_LABEL, TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_EDITED_LABEL,
TEST_SMALL_DOCUMENT_CHECKSUM, TEST_SMALL_DOCUMENT_PATH TEST_DOCUMENT_TYPE_2_LABEL, TEST_DOCUMENT_TYPE_QUICK_LABEL,
TEST_SMALL_DOCUMENT_CHECKSUM, TEST_SMALL_DOCUMENT_PATH,
TEST_TRANSFORMATION_ARGUMENT, TEST_TRANSFORMATION_NAME
) )
TEST_DOCUMENT_TYPE_EDITED_LABEL = 'test document type edited label'
TEST_DOCUMENT_TYPE_2_LABEL = 'test document type 2 label'
TEST_TRANSFORMATION_NAME = 'rotate'
TEST_TRANSFORMATION_ARGUMENT = 'degrees: 180'
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class GenericDocumentViewTestCase(GenericViewTestCase): class GenericDocumentViewTestCase(GenericViewTestCase):
def setUp(self): def setUp(self):
super(GenericDocumentViewTestCase, self).setUp() super(GenericDocumentViewTestCase, self).setUp()
self.document_type = DocumentType.objects.create( self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -115,7 +111,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.document.document_type, self.document_type self.document.document_type, self.document_type
) )
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
@@ -128,7 +124,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual( self.assertEqual(
Document.objects.get(pk=self.document.pk).document_type, Document.on_organization.get(pk=self.document.pk).document_type,
self.document_type self.document_type
) )
@@ -141,7 +137,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.document.document_type, self.document_type self.document.document_type, self.document_type
) )
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
@@ -160,7 +156,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertContains(response, text='success', status_code=200) self.assertContains(response, text='success', status_code=200)
self.assertEqual( self.assertEqual(
Document.objects.get(pk=self.document.pk).document_type, Document.on_organization.get(pk=self.document.pk).document_type,
document_type document_type
) )
@@ -170,10 +166,10 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
) )
self.assertEqual( self.assertEqual(
Document.objects.first().document_type, self.document_type Document.on_organization.first().document_type, self.document_type
) )
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
@@ -188,7 +184,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
self.assertEqual( self.assertEqual(
Document.objects.first().document_type, self.document_type Document.on_organization.first().document_type, self.document_type
) )
def test_document_multiple_document_type_change_view_with_permission(self): def test_document_multiple_document_type_change_view_with_permission(self):
@@ -197,10 +193,10 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
) )
self.assertEqual( self.assertEqual(
Document.objects.first().document_type, self.document_type Document.on_organization.first().document_type, self.document_type
) )
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2_LABEL label=TEST_DOCUMENT_TYPE_2_LABEL
) )
@@ -222,7 +218,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual( self.assertEqual(
Document.objects.first().document_type, document_type Document.on_organization.first().document_type, document_type
) )
def test_document_download_user_view(self): def test_document_download_user_view(self):
@@ -230,7 +226,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
response = self.post( response = self.post(
'documents:document_download', args=(self.document.pk,) 'documents:document_download', args=(self.document.pk,)
@@ -262,7 +258,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
response = self.post( response = self.post(
'documents:document_multiple_download', 'documents:document_multiple_download',
@@ -296,7 +292,7 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
response = self.post( response = self.post(
'documents:document_version_download', args=( 'documents:document_version_download', args=(
@@ -486,20 +482,20 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
def test_trash_can_empty_view_no_permissions(self): def test_trash_can_empty_view_no_permissions(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
self.document.delete() self.document.delete()
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
response = self.post('documents:trash_can_empty') response = self.post('documents:trash_can_empty')
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
def test_trash_can_empty_view_with_permission(self): def test_trash_can_empty_view_with_permission(self):
self.login( self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
self.document.delete() self.document.delete()
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
self.role.permissions.add( self.role.permissions.add(
permission_empty_trash.stored_permission permission_empty_trash.stored_permission
@@ -509,8 +505,8 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertContains( self.assertContains(
response, text='emptied successfully', status_code=200 response, text='emptied successfully', status_code=200
) )
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
def test_document_version_revert_no_permission(self): def test_document_version_revert_no_permission(self):
first_version = self.document.latest_version first_version = self.document.latest_version
@@ -560,7 +556,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.document_type.delete() self.document_type.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
response = self.post( response = self.post(
'documents:document_type_create', 'documents:document_type_create',
@@ -573,7 +569,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(DocumentType.objects.count(), 0) self.assertEqual(DocumentType.on_organization.count(), 0)
def test_document_type_create_view_with_permission(self): def test_document_type_create_view_with_permission(self):
self.login( self.login(
@@ -582,7 +578,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.document_type.delete() self.document_type.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.role.permissions.add( self.role.permissions.add(
permission_document_type_create.stored_permission permission_document_type_create.stored_permission
@@ -602,9 +598,9 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.assertContains(response, text='successfully', status_code=200) self.assertContains(response, text='successfully', status_code=200)
self.assertEqual(DocumentType.objects.count(), 1) self.assertEqual(DocumentType.on_organization.count(), 1)
self.assertEqual( self.assertEqual(
DocumentType.objects.first().label, TEST_DOCUMENT_TYPE DocumentType.on_organization.first().label, TEST_DOCUMENT_TYPE
) )
def test_document_type_delete_view_no_permission(self): def test_document_type_delete_view_no_permission(self):
@@ -618,7 +614,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(DocumentType.objects.count(), 1) self.assertEqual(DocumentType.on_organization.count(), 1)
def test_document_type_delete_view_with_permission(self): def test_document_type_delete_view_with_permission(self):
self.login( self.login(
@@ -638,7 +634,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
) )
self.assertContains(response, 'successfully', status_code=200) self.assertContains(response, 'successfully', status_code=200)
self.assertEqual(DocumentType.objects.count(), 0) self.assertEqual(DocumentType.on_organization.count(), 0)
def test_document_type_edit_view_no_permission(self): def test_document_type_edit_view_no_permission(self):
self.login( self.login(
@@ -658,7 +654,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual( self.assertEqual(
DocumentType.objects.get(pk=self.document_type.pk).label, DocumentType.on_organization.get(pk=self.document_type.pk).label,
TEST_DOCUMENT_TYPE TEST_DOCUMENT_TYPE
) )
@@ -687,7 +683,7 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.assertContains(response, 'successfully', status_code=200) self.assertContains(response, 'successfully', status_code=200)
self.assertEqual( self.assertEqual(
DocumentType.objects.get(pk=self.document_type.pk).label, DocumentType.on_organization.get(pk=self.document_type.pk).label,
TEST_DOCUMENT_TYPE_EDITED_LABEL TEST_DOCUMENT_TYPE_EDITED_LABEL
) )
@@ -728,27 +724,27 @@ class DocumentTypeViewsTestCase(GenericDocumentViewTestCase):
self.assertEqual(self.document_type.filenames.count(), 1) self.assertEqual(self.document_type.filenames.count(), 1)
class DeletedDocumentTestCase(GenericDocumentViewTestCase): class TrashedDocumentTestCase(GenericDocumentViewTestCase):
def test_document_restore_view_no_permission(self): def test_document_restore_view_no_permission(self):
self.login( self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
self.document.delete() self.document.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
response = self.post( response = self.post(
'documents:document_restore', args=(self.document.pk,) 'documents:document_restore', args=(self.document.pk,)
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
def test_document_restore_view_with_permission(self): def test_document_restore_view_with_permission(self):
self.login( self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
self.document.delete() self.document.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.role.permissions.add( self.role.permissions.add(
permission_document_restore.stored_permission permission_document_restore.stored_permission
) )
@@ -757,8 +753,8 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
follow=True follow=True
) )
self.assertContains(response, text='restored', status_code=200) self.assertContains(response, text='restored', status_code=200)
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
def test_document_trash_no_permissions(self): def test_document_trash_no_permissions(self):
self.login( self.login(
@@ -770,8 +766,8 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.assertEqual(Document.objects.count(), 1) self.assertEqual(Document.on_organization.count(), 1)
def test_document_trash_with_permissions(self): def test_document_trash_with_permissions(self):
self.login( self.login(
@@ -788,8 +784,8 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
) )
self.assertContains(response, text='success', status_code=200) self.assertContains(response, text='success', status_code=200)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
def test_document_delete_no_permissions(self): def test_document_delete_no_permissions(self):
self.login( self.login(
@@ -797,15 +793,15 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
) )
self.document.delete() self.document.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
response = self.post( response = self.post(
'documents:document_delete', args=(self.document.pk,), 'documents:document_delete', args=(self.document.pk,),
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
def test_document_delete_with_permissions(self): def test_document_delete_with_permissions(self):
self.login( self.login(
@@ -813,8 +809,8 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
) )
self.document.delete() self.document.delete()
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
self.assertEqual(DeletedDocument.objects.count(), 1) self.assertEqual(TrashedDocument.on_organization.count(), 1)
self.role.permissions.add( self.role.permissions.add(
permission_document_delete.stored_permission permission_document_delete.stored_permission
@@ -826,8 +822,8 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
) )
self.assertContains(response, text='success', status_code=200) self.assertContains(response, text='success', status_code=200)
self.assertEqual(DeletedDocument.objects.count(), 0) self.assertEqual(TrashedDocument.on_organization.count(), 0)
self.assertEqual(Document.objects.count(), 0) self.assertEqual(Document.on_organization.count(), 0)
def test_deleted_document_list_view_no_permissions(self): def test_deleted_document_list_view_no_permissions(self):
self.document.delete() self.document.delete()
@@ -836,7 +832,7 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
) )
response = self.get('documents:document_list_deleted') response = self.get('documents:document_list_trashed')
self.assertNotContains(response, self.document.label, status_code=200) self.assertNotContains(response, self.document.label, status_code=200)
@@ -850,6 +846,6 @@ class DeletedDocumentTestCase(GenericDocumentViewTestCase):
self.role.permissions.add( self.role.permissions.add(
permission_document_view.stored_permission permission_document_view.stored_permission
) )
response = self.get('documents:document_list_deleted') response = self.get('documents:document_list_trashed')
self.assertContains(response, self.document.label, status_code=200) self.assertContains(response, self.document.label, status_code=200)

View File

@@ -3,8 +3,8 @@ from __future__ import unicode_literals
from django.conf.urls import patterns, url from django.conf.urls import patterns, url
from .api_views import ( from .api_views import (
APIDeletedDocumentListView, APIDeletedDocumentRestoreView, APITrashedDocumentListView, APITrashedDocumentRestoreView,
APIDeletedDocumentView, APIDocumentDownloadView, APIDocumentView, APITrashedDocumentView, APIDocumentDownloadView, APIDocumentView,
APIDocumentListView, APIDocumentVersionDownloadView, APIDocumentListView, APIDocumentVersionDownloadView,
APIDocumentPageImageView, APIDocumentPageView, APIDocumentPageImageView, APIDocumentPageView,
APIDocumentTypeDocumentListView, APIDocumentTypeListView, APIDocumentTypeDocumentListView, APIDocumentTypeListView,
@@ -14,17 +14,16 @@ from .api_views import (
) )
from .settings import setting_print_size, setting_display_size from .settings import setting_print_size, setting_display_size
from .views import ( from .views import (
ClearImageCacheView, DeletedDocumentDeleteView, ClearImageCacheView, DocumentEditView, DocumentListView, DocumentPageView,
DeletedDocumentDeleteManyView, DeletedDocumentListView, DocumentEditView, DocumentPageListView, DocumentPageViewResetView, DocumentPreviewView,
DocumentListView, DocumentPageView, DocumentPageListView, DocumentRestoreView, DocumentRestoreManyView, DocumentTrashView,
DocumentPageViewResetView, DocumentPreviewView, DocumentRestoreView, DocumentTrashManyView, DocumentTypeCreateView, DocumentTypeDeleteView,
DocumentRestoreManyView, DocumentTrashView, DocumentTrashManyView,
DocumentTypeCreateView, DocumentTypeDeleteView,
DocumentTypeDocumentListView, DocumentTypeFilenameCreateView, DocumentTypeDocumentListView, DocumentTypeFilenameCreateView,
DocumentTypeFilenameDeleteView, DocumentTypeFilenameEditView, DocumentTypeFilenameDeleteView, DocumentTypeFilenameEditView,
DocumentTypeFilenameListView, DocumentTypeListView, DocumentTypeEditView, DocumentTypeFilenameListView, DocumentTypeListView, DocumentTypeEditView,
DocumentVersionListView, DocumentVersionRevertView, DocumentView, DocumentVersionListView, DocumentVersionRevertView, DocumentView,
EmptyTrashCanView, RecentDocumentListView EmptyTrashCanView, RecentDocumentListView, TrashedDocumentDeleteView,
TrashedDocumentDeleteManyView, TrashedDocumentListView
) )
urlpatterns = patterns( urlpatterns = patterns(
@@ -35,8 +34,8 @@ urlpatterns = patterns(
name='document_list_recent' name='document_list_recent'
), ),
url( url(
r'^list/deleted/$', DeletedDocumentListView.as_view(), r'^list/deleted/$', TrashedDocumentListView.as_view(),
name='document_list_deleted' name='document_list_trashed'
), ),
url( url(
@@ -56,11 +55,11 @@ urlpatterns = patterns(
name='document_multiple_restore' name='document_multiple_restore'
), ),
url( url(
r'^(?P<pk>\d+)/delete/$', DeletedDocumentDeleteView.as_view(), r'^(?P<pk>\d+)/delete/$', TrashedDocumentDeleteView.as_view(),
name='document_delete' name='document_delete'
), ),
url( url(
r'^multiple/delete/$', DeletedDocumentDeleteManyView.as_view(), r'^multiple/delete/$', TrashedDocumentDeleteManyView.as_view(),
name='document_multiple_delete' name='document_multiple_delete'
), ),
url( url(
@@ -242,18 +241,6 @@ urlpatterns = patterns(
api_urls = patterns( api_urls = patterns(
'', '',
url(
r'^trashed_documents/$', APIDeletedDocumentListView.as_view(),
name='trasheddocument-list'
),
url(
r'^trashed_documents/(?P<pk>[0-9]+)/$',
APIDeletedDocumentView.as_view(), name='trasheddocument-detail'
),
url(
r'^trashed_documents/(?P<pk>[0-9]+)/restore/$',
APIDeletedDocumentRestoreView.as_view(), name='trasheddocument-restore'
),
url(r'^documents/$', APIDocumentListView.as_view(), name='document-list'), url(r'^documents/$', APIDocumentListView.as_view(), name='document-list'),
url( url(
r'^documents/recent/$', APIRecentDocumentListView.as_view(), r'^documents/recent/$', APIRecentDocumentListView.as_view(),
@@ -304,4 +291,16 @@ api_urls = patterns(
r'^document_types/$', APIDocumentTypeListView.as_view(), r'^document_types/$', APIDocumentTypeListView.as_view(),
name='documenttype-list' name='documenttype-list'
), ),
url(
r'^trashed_documents/$', APITrashedDocumentListView.as_view(),
name='trasheddocument-list'
),
url(
r'^trashed_documents/(?P<pk>[0-9]+)/$',
APITrashedDocumentView.as_view(), name='trasheddocument-detail'
),
url(
r'^trashed_documents/(?P<pk>[0-9]+)/restore/$',
APITrashedDocumentRestoreView.as_view(), name='trasheddocument-restore'
),
) )

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,13 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from organizations.admin import OrganizationAdminMixin
from .models import Folder from .models import Folder
@admin.register(Folder) @admin.register(Folder)
class FolderAdmin(admin.ModelAdmin): class FolderAdmin(OrganizationAdminMixin, admin.ModelAdmin):
filter_horizontal = ('documents',) filter_horizontal = ('documents',)
list_display = ('label', 'datetime_created') list_display = ('label', 'datetime_created')
list_display_links = ('label',)

View File

@@ -55,7 +55,7 @@ class APIFolderListView(generics.ListCreateAPIView):
mayan_object_permissions = {'GET': (permission_folder_view,)} mayan_object_permissions = {'GET': (permission_folder_view,)}
mayan_view_permissions = {'POST': (permission_folder_create,)} mayan_view_permissions = {'POST': (permission_folder_create,)}
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Folder.objects.all() queryset = Folder.on_organization.all()
def get_serializer_class(self): def get_serializer_class(self):
if self.request.method == 'GET': if self.request.method == 'GET':
@@ -85,7 +85,7 @@ class APIFolderView(generics.RetrieveUpdateDestroyAPIView):
'DELETE': (permission_folder_delete,) 'DELETE': (permission_folder_delete,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Folder.objects.all() queryset = Folder.on_organization.all()
serializer_class = FolderSerializer serializer_class = FolderSerializer
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):

View File

@@ -21,7 +21,7 @@ class FolderListForm(forms.Form):
logger.debug('user: %s', user) logger.debug('user: %s', user)
super(FolderListForm, self).__init__(*args, **kwargs) super(FolderListForm, self).__init__(*args, **kwargs)
queryset = Folder.objects.all() queryset = Folder.on_organization.all()
try: try:
Permission.check_permissions(user, (permission_folder_view,)) Permission.check_permissions(user, (permission_folder_view,))
except PermissionDenied: except PermissionDenied:

View File

@@ -1,10 +1,6 @@
from __future__ import absolute_import
from django.contrib.auth import get_user_model
from django.db import models from django.db import models
class FolderManager(models.Manager): class FolderManager(models.Manager):
def get_by_natural_key(self, label, *user_key): def get_by_natural_key(self, label, *user_key):
user = get_user_model().objects.get_by_natural_key(*user_key) return self.get(label=label)
return self.get(label=label, user=user)

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import organizations.shortcuts
class Migration(migrations.Migration):
dependencies = [
('organizations', '0001_initial'),
('folders', '0006_auto_20160308_0445'),
]
operations = [
migrations.AddField(
model_name='folder',
name='organization',
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'),
),
]

View File

@@ -9,6 +9,9 @@ from django.utils.translation import ugettext_lazy as _
from acls.models import AccessControlList from acls.models import AccessControlList
from documents.models import Document from documents.models import Document
from documents.permissions import permission_document_view from documents.permissions import permission_document_view
from organizations.models import Organization
from organizations.managers import CurrentOrganizationManager
from organizations.shortcuts import get_current_organization
from permissions import Permission from permissions import Permission
from .managers import FolderManager from .managers import FolderManager
@@ -16,6 +19,9 @@ from .managers import FolderManager
@python_2_unicode_compatible @python_2_unicode_compatible
class Folder(models.Model): class Folder(models.Model):
organization = models.ForeignKey(
Organization, default=get_current_organization
)
label = models.CharField( label = models.CharField(
db_index=True, max_length=128, verbose_name=_('Label') db_index=True, max_length=128, verbose_name=_('Label')
) )
@@ -27,6 +33,7 @@ class Folder(models.Model):
) )
objects = FolderManager() objects = FolderManager()
on_organization = CurrentOrganizationManager()
def __str__(self): def __str__(self):
return self.label return self.label

View File

@@ -60,7 +60,9 @@ class NewFolderDocumentSerializer(serializers.Serializer):
def create(self, validated_data): def create(self, validated_data):
try: try:
document = Document.objects.get(pk=validated_data['document']) document = Document.on_organization.get(
pk=validated_data['document']
)
validated_data['folder'].documents.add(document) validated_data['folder'].documents.add(document)
except Exception as exception: except Exception as exception:
raise ValidationError(exception) raise ValidationError(exception)

View File

@@ -4,10 +4,9 @@ from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import override_settings from django.test import override_settings
from rest_framework.test import APITestCase
from documents.models import DocumentType from documents.models import DocumentType
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
from rest_api.tests import GenericAPITestCase
from user_management.tests.literals import ( from user_management.tests.literals import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
) )
@@ -17,60 +16,50 @@ from ..models import Folder
from .literals import TEST_FOLDER_EDITED_LABEL, TEST_FOLDER_LABEL from .literals import TEST_FOLDER_EDITED_LABEL, TEST_FOLDER_LABEL
class FolderAPITestCase(APITestCase): class FolderAPITestCase(GenericAPITestCase):
""" """
Test the folder API endpoints Test the folder API endpoints
""" """
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 test_folder_create(self): def test_folder_create(self):
response = self.client.post( response = self.client.post(
reverse('rest_api:folder-list'), {'label': TEST_FOLDER_LABEL} reverse('rest_api:folder-list'), {'label': TEST_FOLDER_LABEL}
) )
folder = Folder.objects.first() folder = Folder.on_organization.first()
self.assertEqual(response.data['id'], folder.pk) self.assertEqual(response.data['id'], folder.pk)
self.assertEqual(response.data['label'], TEST_FOLDER_LABEL) self.assertEqual(response.data['label'], TEST_FOLDER_LABEL)
self.assertEqual(Folder.objects.count(), 1) self.assertEqual(Folder.on_organization.count(), 1)
self.assertEqual(folder.label, TEST_FOLDER_LABEL) self.assertEqual(folder.label, TEST_FOLDER_LABEL)
def test_folder_delete(self): def test_folder_delete(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
self.client.delete( self.client.delete(
reverse('rest_api:folder-detail', args=(folder.pk,)) reverse('rest_api:folder-detail', args=(folder.pk,))
) )
self.assertEqual(Folder.objects.count(), 0) self.assertEqual(Folder.on_organization.count(), 0)
def test_folder_edit(self): def test_folder_edit(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
self.client.put( self.client.put(
reverse('rest_api:folder-detail', args=(folder.pk,)), reverse('rest_api:folder-detail', args=(folder.pk,)),
{'label': TEST_FOLDER_EDITED_LABEL} {'label': TEST_FOLDER_EDITED_LABEL}
) )
folder = Folder.objects.first() folder = Folder.on_organization.first()
self.assertEqual(folder.label, TEST_FOLDER_EDITED_LABEL) self.assertEqual(folder.label, TEST_FOLDER_EDITED_LABEL)
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
def test_folder_add_document(self): def test_folder_add_document(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -88,9 +77,9 @@ class FolderAPITestCase(APITestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
def test_folder_remove_document(self): def test_folder_remove_document(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )

View File

@@ -4,6 +4,7 @@ from django.test import TestCase, override_settings
from documents.models import DocumentType from documents.models import DocumentType
from documents.tests import TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE from documents.tests import TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
from organizations.utils import create_default_organization
from ..models import Folder from ..models import Folder
@@ -13,7 +14,9 @@ from .literals import TEST_FOLDER_LABEL
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class FolderTestCase(TestCase): class FolderTestCase(TestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( create_default_organization()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -26,20 +29,20 @@ class FolderTestCase(TestCase):
self.document_type.delete() self.document_type.delete()
def test_folder_creation(self): def test_folder_creation(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
self.assertEqual(Folder.objects.all().count(), 1) self.assertEqual(Folder.on_organization.all().count(), 1)
self.assertEqual(list(Folder.objects.all()), [folder]) self.assertEqual(list(Folder.on_organization.all()), [folder])
def test_addition_of_documents(self): def test_addition_of_documents(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
folder.documents.add(self.document) folder.documents.add(self.document)
self.assertEqual(folder.documents.count(), 1) self.assertEqual(folder.documents.count(), 1)
self.assertEqual(list(folder.documents.all()), [self.document]) self.assertEqual(list(folder.documents.all()), [self.document])
def test_addition_and_deletion_of_documents(self): def test_addition_and_deletion_of_documents(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
folder.documents.add(self.document) folder.documents.add(self.document)
self.assertEqual(folder.documents.count(), 1) self.assertEqual(folder.documents.count(), 1)

View File

@@ -0,0 +1,62 @@
from __future__ import unicode_literals
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import Folder
from .literals import TEST_FOLDER_LABEL, TEST_FOLDER_EDITED_LABEL
class FolderOrganizationViewTestCase(OrganizationViewTestCase):
def test_folder_create_view(self):
# Create a folder for organization A
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
'folders:folder_create', data={
'label': TEST_FOLDER_LABEL
}
)
self.assertEqual(Folder.on_organization.count(), 1)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
self.assertEqual(Folder.on_organization.count(), 0)
def test_folder_delete_view(self):
# Create a folder for organization A
folder = Folder.objects.create(
organization=self.organization_a, label=TEST_FOLDER_LABEL
)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post('folders:folder_delete', args=(folder.pk,))
self.assertEqual(response.status_code, 404)
def test_folder_edit_view(self):
# Create a folder for organization A
folder = Folder.objects.create(
organization=self.organization_a, label=TEST_FOLDER_LABEL
)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
# Make sure admin for organization B cannot edit the folder
response = self.post(
'folders:folder_edit', args=(folder.pk,), data={
'label': TEST_FOLDER_EDITED_LABEL
}
)
self.assertEqual(response.status_code, 404)
def test_folder_view_view(self):
# Create a folder for organization A
folder = Folder.objects.create(
organization=self.organization_a, label=TEST_FOLDER_LABEL
)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
# Make sure admin for organization B cannot find the folder for
# organization A
response = self.get(
'folders:folder_view', args=(folder.pk,),
)
self.assertEqual(response.status_code, 404)

View File

@@ -26,7 +26,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
) )
self.assertEquals(response.status_code, 403) self.assertEquals(response.status_code, 403)
self.assertEqual(Folder.objects.count(), 0) self.assertEqual(Folder.on_organization.count(), 0)
def test_folder_create_view_with_permission(self): def test_folder_create_view_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
@@ -41,11 +41,13 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
self.assertContains(response, text='created', status_code=200) self.assertContains(response, text='created', status_code=200)
self.assertEqual(Folder.objects.count(), 1) self.assertEqual(Folder.on_organization.count(), 1)
self.assertEqual(Folder.objects.first().label, TEST_FOLDER_LABEL) self.assertEqual(
Folder.on_organization.first().label, TEST_FOLDER_LABEL
)
def test_folder_create_duplicate_view_with_permission(self): def test_folder_create_duplicate_view_with_permission(self):
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
@@ -60,17 +62,17 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
) )
self.assertContains(response, text='exists', status_code=200) self.assertContains(response, text='exists', status_code=200)
self.assertEqual(Folder.objects.count(), 1) self.assertEqual(Folder.on_organization.count(), 1)
self.assertEqual(Folder.objects.first().pk, folder.pk) self.assertEqual(Folder.on_organization.first().pk, folder.pk)
def test_folder_delete_view_no_permission(self): def test_folder_delete_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post('folders:folder_delete', args=(folder.pk,)) response = self.post('folders:folder_delete', args=(folder.pk,))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(Folder.objects.count(), 1) self.assertEqual(Folder.on_organization.count(), 1)
def test_folder_delete_view_with_permission(self): def test_folder_delete_view_with_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
@@ -79,19 +81,19 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
permission_folder_delete.stored_permission permission_folder_delete.stored_permission
) )
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_delete', args=(folder.pk,), follow=True 'folders:folder_delete', args=(folder.pk,), follow=True
) )
self.assertContains(response, text='deleted', status_code=200) self.assertContains(response, text='deleted', status_code=200)
self.assertEqual(Folder.objects.count(), 0) self.assertEqual(Folder.on_organization.count(), 0)
def test_folder_edit_view_no_permission(self): def test_folder_edit_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_edit', args=(folder.pk,), data={ 'folders:folder_edit', args=(folder.pk,), data={
@@ -99,7 +101,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
} }
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
folder = Folder.objects.get(pk=folder.pk) folder = Folder.on_organization.get(pk=folder.pk)
self.assertEqual(folder.label, TEST_FOLDER_LABEL) self.assertEqual(folder.label, TEST_FOLDER_LABEL)
def test_folder_edit_view_with_permission(self): def test_folder_edit_view_with_permission(self):
@@ -109,7 +111,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
permission_folder_edit.stored_permission permission_folder_edit.stored_permission
) )
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_edit', args=(folder.pk,), data={ 'folders:folder_edit', args=(folder.pk,), data={
@@ -117,7 +119,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
folder = Folder.objects.get(pk=folder.pk) folder = Folder.on_organization.get(pk=folder.pk)
self.assertContains(response, text='update', status_code=200) self.assertContains(response, text='update', status_code=200)
self.assertEqual(folder.label, TEST_FOLDER_EDITED_LABEL) self.assertEqual(folder.label, TEST_FOLDER_EDITED_LABEL)
@@ -126,7 +128,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
self.role.permissions.add(permission_folder_view.stored_permission) self.role.permissions.add(permission_folder_view.stored_permission)
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_add_document', args=(self.document.pk,), data={ 'folders:folder_add_document', args=(self.document.pk,), data={
@@ -150,7 +152,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
permission_document_view.stored_permission permission_document_view.stored_permission
) )
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_add_document', args=(self.document.pk,), data={ 'folders:folder_add_document', args=(self.document.pk,), data={
@@ -158,7 +160,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
folder = Folder.objects.get(pk=folder.pk) folder = Folder.on_organization.get(pk=folder.pk)
self.assertContains(response, text='added', status_code=200) self.assertContains(response, text='added', status_code=200)
self.assertEqual(folder.documents.count(), 1) self.assertEqual(folder.documents.count(), 1)
self.assertQuerysetEqual( self.assertQuerysetEqual(
@@ -170,7 +172,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
self.role.permissions.add(permission_folder_view.stored_permission) self.role.permissions.add(permission_folder_view.stored_permission)
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_add_multiple_documents', data={ 'folders:folder_add_multiple_documents', data={
@@ -190,7 +192,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
permission_folder_add_document.stored_permission permission_folder_add_document.stored_permission
) )
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
response = self.post( response = self.post(
'folders:folder_add_multiple_documents', data={ 'folders:folder_add_multiple_documents', data={
@@ -198,7 +200,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
folder = Folder.objects.get(pk=folder.pk) folder = Folder.on_organization.get(pk=folder.pk)
self.assertContains(response, text='added', status_code=200) self.assertContains(response, text='added', status_code=200)
self.assertEqual(folder.documents.count(), 1) self.assertEqual(folder.documents.count(), 1)
self.assertQuerysetEqual( self.assertQuerysetEqual(
@@ -208,7 +210,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
def test_folder_remove_document_view_no_permission(self): def test_folder_remove_document_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
folder.documents.add(self.document) folder.documents.add(self.document)
@@ -223,7 +225,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
folder = Folder.objects.get(pk=folder.pk) folder = Folder.on_organization.get(pk=folder.pk)
self.assertEqual(folder.documents.count(), 1) self.assertEqual(folder.documents.count(), 1)
def test_folder_remove_document_view_with_permission(self): def test_folder_remove_document_view_with_permission(self):
@@ -233,7 +235,7 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
permission_folder_remove_document.stored_permission permission_folder_remove_document.stored_permission
) )
folder = Folder.objects.create(label=TEST_FOLDER_LABEL) folder = Folder.on_organization.create(label=TEST_FOLDER_LABEL)
folder.documents.add(self.document) folder.documents.add(self.document)
self.assertEqual(folder.documents.count(), 1) self.assertEqual(folder.documents.count(), 1)
@@ -245,6 +247,6 @@ class FolderViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
folder = Folder.objects.get(pk=folder.pk) folder = Folder.on_organization.get(pk=folder.pk)
self.assertContains(response, text='removed', status_code=200) self.assertContains(response, text='removed', status_code=200)
self.assertEqual(folder.documents.count(), 0) self.assertEqual(folder.documents.count(), 0)

View File

@@ -34,14 +34,34 @@ logger = logging.getLogger(__name__)
class FolderCreateView(SingleObjectCreateView): class FolderCreateView(SingleObjectCreateView):
fields = ('label',) fields = ('label',)
model = Folder post_action_redirect = reverse_lazy('folders:folder_list')
view_permission = permission_folder_create view_permission = permission_folder_create
def form_valid(self, form):
try:
Folder.on_organization.get(label=form.cleaned_data['label'])
except Folder.DoesNotExist:
instance = form.save(commit=False)
instance.user = self.request.user
instance.save()
return super(FolderCreateView, self).form_valid(form)
else:
messages.error(
self.request,
_(
'A folder named: %s, already exists.'
) % form.cleaned_data['label']
)
return super(FolderCreateView, self).form_invalid(form)
def get_extra_context(self): def get_extra_context(self):
return { return {
'title': _('Create folder'), 'title': _('Create folder'),
} }
def get_queryset(self):
return Folder.on_organization.all()
class FolderDeleteView(SingleObjectDeleteView): class FolderDeleteView(SingleObjectDeleteView):
model = Folder model = Folder
@@ -54,6 +74,9 @@ class FolderDeleteView(SingleObjectDeleteView):
'title': _('Delete the folder: %s?') % self.get_object(), 'title': _('Delete the folder: %s?') % self.get_object(),
} }
def get_queryset(self):
return Folder.on_organization.all()
class FolderDetailView(DocumentListView): class FolderDetailView(DocumentListView):
def get_document_queryset(self): def get_document_queryset(self):
@@ -67,23 +90,11 @@ class FolderDetailView(DocumentListView):
} }
def get_folder(self): def get_folder(self):
folder = get_object_or_404(Folder, pk=self.kwargs['pk']) return get_object_or_404(Folder.on_organization, pk=self.kwargs['pk'])
try:
Permission.check_permissions(
self.request.user, (permission_folder_view,)
)
except PermissionDenied:
AccessControlList.objects.check_access(
permission_folder_view, self.request.user, folder
)
return folder
class FolderEditView(SingleObjectEditView): class FolderEditView(SingleObjectEditView):
fields = ('label',) fields = ('label',)
model = Folder
object_permission = permission_folder_edit object_permission = permission_folder_edit
post_action_redirect = reverse_lazy('folders:folder_list') post_action_redirect = reverse_lazy('folders:folder_list')
@@ -93,9 +104,11 @@ class FolderEditView(SingleObjectEditView):
'title': _('Edit folder: %s') % self.get_object(), 'title': _('Edit folder: %s') % self.get_object(),
} }
def get_queryset(self):
return Folder.on_organization.all()
class FolderListView(SingleObjectListView): class FolderListView(SingleObjectListView):
model = Folder
object_permission = permission_folder_view object_permission = permission_folder_view
def get_extra_context(self): def get_extra_context(self):
@@ -104,10 +117,19 @@ class FolderListView(SingleObjectListView):
'title': _('Folders'), 'title': _('Folders'),
} }
def get_folder_queryset(self):
return Folder.on_organization.all()
def get_queryset(self):
self.queryset = self.get_folder_queryset()
return super(FolderListView, self).get_queryset()
class DocumentFolderListView(FolderListView): class DocumentFolderListView(FolderListView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.document = get_object_or_404(Document, pk=self.kwargs['pk']) self.document = get_object_or_404(
Document.on_organization, pk=self.kwargs['pk']
)
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -132,10 +154,12 @@ class DocumentFolderListView(FolderListView):
def folder_add_document(request, document_id=None, document_id_list=None): def folder_add_document(request, document_id=None, document_id_list=None):
queryset = Document.on_organization.all()
if document_id: if document_id:
queryset = Document.objects.filter(pk=document_id) queryset = queryset.filter(pk=document_id)
elif document_id_list: elif document_id_list:
queryset = Document.objects.filter(pk__in=document_id_list) queryset = queryset.filter(pk__in=document_id_list)
if not queryset: if not queryset:
messages.error(request, _('Must provide at least one document.')) messages.error(request, _('Must provide at least one document.'))
@@ -216,12 +240,12 @@ def folder_add_document(request, document_id=None, document_id_list=None):
def folder_document_remove(request, folder_id, document_id=None, document_id_list=None): def folder_document_remove(request, folder_id, document_id=None, document_id_list=None):
post_action_redirect = None post_action_redirect = None
folder = get_object_or_404(Folder, pk=folder_id) folder = get_object_or_404(Folder.on_organization, pk=folder_id)
if document_id: if document_id:
queryset = Document.objects.filter(pk=document_id) queryset = Document.on_organization.filter(pk=document_id)
elif document_id_list: elif document_id_list:
queryset = Document.objects.filter(pk__in=document_id_list) queryset = Document.on_organization.filter(pk__in=document_id_list)
if not queryset: if not queryset:
messages.error(request, _('Must provide at least one folder document.')) messages.error(request, _('Must provide at least one folder document.'))

View File

@@ -2,11 +2,13 @@ from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from organizations.admin import OrganizationAdminMixin
from .models import MetadataType from .models import MetadataType
@admin.register(MetadataType) @admin.register(MetadataType)
class MetadataTypeAdmin(admin.ModelAdmin): class MetadataTypeAdmin(OrganizationAdminMixin, admin.ModelAdmin):
list_display = ( list_display = (
'name', 'label', 'default', 'lookup', 'validation', 'parser' 'name', 'label', 'default', 'lookup', 'validation', 'parser'
) )

View File

@@ -49,7 +49,7 @@ def save_metadata(metadata_dict, document, create=False):
""" """
if create: if create:
# Use matched metadata now to create document metadata # Use matched metadata now to create document metadata
document_metadata, created = DocumentMetadata.objects.get_or_create( document_metadata, created = DocumentMetadata.on_organization.get_or_create(
document=document, document=document,
metadata_type=get_object_or_404( metadata_type=get_object_or_404(
MetadataType, MetadataType,
@@ -57,7 +57,7 @@ def save_metadata(metadata_dict, document, create=False):
) )
else: else:
try: try:
document_metadata = DocumentMetadata.objects.get( document_metadata = DocumentMetadata.on_organization.get(
document=document, document=document,
metadata_type=get_object_or_404( metadata_type=get_object_or_404(
MetadataType, MetadataType,
@@ -92,7 +92,7 @@ def metadata_repr_as_list(metadata_list):
output = [] output = []
for metadata_dict in metadata_list: for metadata_dict in metadata_list:
try: try:
output.append('%s - %s' % (MetadataType.objects.get( output.append('%s - %s' % (MetadataType.on_organization.get(
pk=metadata_dict['id']), metadata_dict.get('value', ''))) pk=metadata_dict['id']), metadata_dict.get('value', '')))
except: except:
pass pass
@@ -107,9 +107,9 @@ def set_bulk_metadata(document, metadata_dictionary):
] ]
for metadata_type_name, value in metadata_dictionary.items(): for metadata_type_name, value in metadata_dictionary.items():
metadata_type = MetadataType.objects.get(name=metadata_type_name) metadata_type = MetadataType.on_organization.get(name=metadata_type_name)
if metadata_type in document_type_metadata_types: if metadata_type in document_type_metadata_types:
DocumentMetadata.objects.get_or_create( DocumentMetadata.on_organization.get_or_create(
document=document, metadata_type=metadata_type, value=value document=document, metadata_type=metadata_type, value=value
) )

View File

@@ -31,7 +31,7 @@ from .serializers import (
class APIMetadataTypeListView(generics.ListCreateAPIView): class APIMetadataTypeListView(generics.ListCreateAPIView):
serializer_class = MetadataTypeSerializer serializer_class = MetadataTypeSerializer
queryset = MetadataType.objects.all() queryset = MetadataType.on_organization.all()
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
filter_backends = (MayanObjectPermissionsFilter,) filter_backends = (MayanObjectPermissionsFilter,)
@@ -53,7 +53,7 @@ class APIMetadataTypeListView(generics.ListCreateAPIView):
class APIMetadataTypeView(generics.RetrieveUpdateDestroyAPIView): class APIMetadataTypeView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = MetadataTypeSerializer serializer_class = MetadataTypeSerializer
queryset = MetadataType.objects.all() queryset = MetadataType.on_organization.all()
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
mayan_object_permissions = { mayan_object_permissions = {
@@ -92,7 +92,7 @@ class APIDocumentMetadataListView(generics.ListCreateAPIView):
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
def get_document(self): def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['pk']) return get_object_or_404(Document.on_organization, pk=self.kwargs['pk'])
def get_queryset(self): def get_queryset(self):
document = self.get_document() document = self.get_document()
@@ -151,7 +151,7 @@ class APIDocumentMetadataListView(generics.ListCreateAPIView):
class APIDocumentMetadataView(generics.RetrieveUpdateDestroyAPIView): class APIDocumentMetadataView(generics.RetrieveUpdateDestroyAPIView):
serializer_class = DocumentMetadataSerializer serializer_class = DocumentMetadataSerializer
queryset = DocumentMetadata.objects.all() queryset = DocumentMetadata.on_organization.all()
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
mayan_object_permissions = { mayan_object_permissions = {
@@ -220,7 +220,7 @@ class APIDocumentTypeMetadataTypeOptionalListView(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
document_type = get_object_or_404( document_type = get_object_or_404(
DocumentType, pk=self.kwargs['document_type_pk'] DocumentType.on_organization, pk=self.kwargs['document_type_pk']
) )
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -253,7 +253,7 @@ class APIDocumentTypeMetadataTypeOptionalListView(generics.ListCreateAPIView):
Add an optional metadata type to a document type. Add an optional metadata type to a document type.
""" """
document_type = get_object_or_404( document_type = get_object_or_404(
DocumentType, pk=self.kwargs['document_type_pk'] DocumentType.on_organization, pk=self.kwargs['document_type_pk']
) )
try: try:
@@ -270,7 +270,7 @@ class APIDocumentTypeMetadataTypeOptionalListView(generics.ListCreateAPIView):
if serializer.is_valid(): if serializer.is_valid():
metadata_type = get_object_or_404( metadata_type = get_object_or_404(
MetadataType, pk=serializer.data['metadata_type_pk'] MetadataType.on_organization, pk=serializer.data['metadata_type_pk']
) )
document_type_metadata_type = document_type.metadata.create( document_type_metadata_type = document_type.metadata.create(
metadata_type=metadata_type, required=self.required_metadata metadata_type=metadata_type, required=self.required_metadata
@@ -313,7 +313,7 @@ class APIDocumentTypeMetadataTypeView(views.APIView):
""" """
document_type_metadata_type = get_object_or_404( document_type_metadata_type = get_object_or_404(
DocumentTypeMetadataType, pk=self.kwargs['pk'] DocumentTypeMetadataType.on_organization, pk=self.kwargs['pk']
) )
try: try:

View File

@@ -109,13 +109,13 @@ MetadataFormSet = formset_factory(MetadataForm, extra=0)
class AddMetadataForm(forms.Form): class AddMetadataForm(forms.Form):
metadata_type = forms.ModelChoiceField( metadata_type = forms.ModelChoiceField(
queryset=MetadataType.objects.all(), label=_('Metadata type') queryset=MetadataType.on_organization.all(), label=_('Metadata type')
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
document_type = kwargs.pop('document_type') document_type = kwargs.pop('document_type')
super(AddMetadataForm, self).__init__(*args, **kwargs) super(AddMetadataForm, self).__init__(*args, **kwargs)
self.fields['metadata_type'].queryset = MetadataType.objects.filter( self.fields['metadata_type'].queryset = MetadataType.on_organization.filter(
pk__in=document_type.metadata.values_list( pk__in=document_type.metadata.values_list(
'metadata_type', flat=True 'metadata_type', flat=True
) )

View File

@@ -44,7 +44,7 @@ def post_post_document_type_change_metadata(sender, instance, **kwargs):
# Add new document type metadata types to document # Add new document type metadata types to document
for document_type_metadata_type in instance.document_type.metadata.filter(required=True): for document_type_metadata_type in instance.document_type.metadata.filter(required=True):
DocumentMetadata.objects.create( DocumentMetadata.on_organization.create(
document=instance, document=instance,
metadata_type=document_type_metadata_type.metadata_type, metadata_type=document_type_metadata_type.metadata_type,
value=None value=None

View File

@@ -1,6 +1,35 @@
from __future__ import unicode_literals
from django.apps import apps
from django.db import models from django.db import models
class MetadataTypeManager(models.Manager): class MetadataTypeManager(models.Manager):
def get_by_natural_key(self, name): def get_by_natural_key(self, name):
return self.get(name=name) return self.get(name=name)
class OrganizationDocumentMetadataManager(models.Manager):
def get_queryset(self):
Document = apps.get_model('documents', 'Document')
MetadataType = apps.get_model('metadata', 'MetadataType')
return super(
OrganizationDocumentMetadataManager, self
).get_queryset().filter(
document__in=Document.on_organization.all(),
metadata_type__in=MetadataType.on_organization.all()
)
class OrganizationDocumentTypeMetadataTypeManager(models.Manager):
def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
MetadataType = apps.get_model('metadata', 'MetadataType')
return super(
OrganizationDocumentTypeMetadataTypeManager, self
).get_queryset().filter(
document_type__in=DocumentType.on_organization.all(),
metadata_type__in=MetadataType.on_organization.all()
)

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import organizations.shortcuts
class Migration(migrations.Migration):
dependencies = [
('organizations', '0002_add_data_default_organization'),
('metadata', '0007_auto_20150918_0800'),
]
operations = [
migrations.AddField(
model_name='metadatatype',
name='organization',
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'),
),
]

View File

@@ -10,9 +10,15 @@ from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from documents.models import Document, DocumentType from documents.models import Document, DocumentType
from organizations.models import Organization
from organizations.managers import CurrentOrganizationManager
from organizations.shortcuts import get_current_organization
from .classes import MetadataLookup from .classes import MetadataLookup
from .managers import MetadataTypeManager from .managers import (
MetadataTypeManager, OrganizationDocumentMetadataManager,
OrganizationDocumentTypeMetadataTypeManager
)
from .settings import setting_available_parsers, setting_available_validators from .settings import setting_available_parsers, setting_available_validators
@@ -35,7 +41,9 @@ class MetadataType(models.Model):
""" """
Define a type of metadata Define a type of metadata
""" """
organization = models.ForeignKey(
Organization, default=get_current_organization
)
name = models.CharField( name = models.CharField(
max_length=48, max_length=48,
help_text=_( help_text=_(
@@ -79,6 +87,12 @@ class MetadataType(models.Model):
) )
objects = MetadataTypeManager() objects = MetadataTypeManager()
on_organization = CurrentOrganizationManager()
class Meta:
ordering = ('label',)
verbose_name = _('Metadata type')
verbose_name_plural = _('Metadata types')
def __str__(self): def __str__(self):
return self.label return self.label
@@ -86,11 +100,6 @@ class MetadataType(models.Model):
def natural_key(self): def natural_key(self):
return (self.name,) return (self.name,)
class Meta:
ordering = ('label',)
verbose_name = _('Metadata type')
verbose_name_plural = _('Metadata types')
@staticmethod @staticmethod
def comma_splitter(string): def comma_splitter(string):
splitter = shlex.shlex(string.encode('utf-8'), posix=True) splitter = shlex.shlex(string.encode('utf-8'), posix=True)
@@ -158,6 +167,14 @@ class DocumentMetadata(models.Model):
verbose_name=_('Value') verbose_name=_('Value')
) )
objects = models.Manager()
on_organization = OrganizationDocumentMetadataManager()
class Meta:
unique_together = ('document', 'metadata_type')
verbose_name = _('Document metadata')
verbose_name_plural = _('Document metadata')
def __str__(self): def __str__(self):
return unicode(self.metadata_type) return unicode(self.metadata_type)
@@ -184,11 +201,6 @@ class DocumentMetadata(models.Model):
document_type=self.document.document_type, value=self.value document_type=self.document.document_type, value=self.value
) )
class Meta:
unique_together = ('document', 'metadata_type')
verbose_name = _('Document metadata')
verbose_name_plural = _('Document metadata')
@property @property
def is_required(self): def is_required(self):
return self.metadata_type.get_required_for( return self.metadata_type.get_required_for(
@@ -207,10 +219,13 @@ class DocumentTypeMetadataType(models.Model):
) )
required = models.BooleanField(default=False, verbose_name=_('Required')) required = models.BooleanField(default=False, verbose_name=_('Required'))
def __str__(self): objects = models.Manager()
return unicode(self.metadata_type) on_organization = OrganizationDocumentTypeMetadataTypeManager()
class Meta: class Meta:
unique_together = ('document_type', 'metadata_type') unique_together = ('document_type', 'metadata_type')
verbose_name = _('Document type metadata type options') verbose_name = _('Document type metadata type options')
verbose_name_plural = _('Document type metadata types options') verbose_name_plural = _('Document type metadata types options')
def __str__(self):
return unicode(self.metadata_type)

View File

@@ -47,7 +47,7 @@ class DocumentNewMetadataSerializer(serializers.Serializer):
) )
def create(self, validated_data): def create(self, validated_data):
metadata_type = MetadataType.objects.get( metadata_type = MetadataType.on_organization.get(
pk=validated_data['metadata_type_pk'] pk=validated_data['metadata_type_pk']
) )
instance = self.document.metadata.create( instance = self.document.metadata.create(

View File

@@ -15,7 +15,7 @@ def task_remove_metadata_type(document_type_id, metadata_type_id):
app_label='metadata', model_name='DocumentMetadata' app_label='metadata', model_name='DocumentMetadata'
) )
DocumentMetadata.objects.filter( DocumentMetadata.on_organization.filter(
document__document_type__id=document_type_id, document__document_type__id=document_type_id,
metadata_type__id=metadata_type_id metadata_type__id=metadata_type_id
).delete() ).delete()
@@ -31,7 +31,7 @@ def task_add_required_metadata_type(document_type_id, metadata_type_id):
app_label='metadata', model_name='MetadataType' app_label='metadata', model_name='MetadataType'
) )
metadata_type = MetadataType.objects.get(pk=metadata_type_id) metadata_type = MetadataType.on_organization.get(pk=metadata_type_id)
for document in DocumentType.objects.get(pk=document_type_id).documents.all(): for document in DocumentType.on_organization.get(pk=document_type_id).documents.all():
document.metadata.create(metadata_type=metadata_type) document.metadata.create(metadata_type=metadata_type)

View File

@@ -4,10 +4,9 @@ from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import override_settings from django.test import override_settings
from rest_framework.test import APITestCase
from documents.models import DocumentType from documents.models import DocumentType
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
from rest_api.tests import GenericAPITestCase
from user_management.tests.literals import ( from user_management.tests.literals import (
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
) )
@@ -23,19 +22,12 @@ TEST_METADATA_VALUE = 'test value'
TEST_METADATA_VALUE_EDITED = 'test value edited' TEST_METADATA_VALUE_EDITED = 'test value edited'
class MetadataTypeAPITestCase(APITestCase): class MetadataTypeAPITestCase(GenericAPITestCase):
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(MetadataTypeAPITestCase, self).setUp()
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): def tearDown(self):
self.admin_user.delete() super(MetadataTypeAPITestCase, self).tearDown()
def test_metadata_type_create(self): def test_metadata_type_create(self):
response = self.client.post( response = self.client.post(
@@ -45,7 +37,7 @@ class MetadataTypeAPITestCase(APITestCase):
} }
) )
metadata_type = MetadataType.objects.first() metadata_type = MetadataType.on_organization.first()
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(response.data['id'], metadata_type.pk) self.assertEqual(response.data['id'], metadata_type.pk)
@@ -56,7 +48,7 @@ class MetadataTypeAPITestCase(APITestCase):
self.assertEqual(metadata_type.name, TEST_METADATA_TYPE_NAME) self.assertEqual(metadata_type.name, TEST_METADATA_TYPE_NAME)
def test_metadata_type_delete(self): def test_metadata_type_delete(self):
metadata_type = MetadataType.objects.create( metadata_type = MetadataType.on_organization.create(
label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME
) )
@@ -66,10 +58,10 @@ class MetadataTypeAPITestCase(APITestCase):
self.assertEqual(response.status_code, 204) self.assertEqual(response.status_code, 204)
self.assertEqual(MetadataType.objects.count(), 0) self.assertEqual(MetadataType.on_organization.count(), 0)
def test_metadata_type_edit(self): def test_metadata_type_edit(self):
metadata_type = MetadataType.objects.create( metadata_type = MetadataType.on_organization.create(
label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME
) )
@@ -89,28 +81,21 @@ class MetadataTypeAPITestCase(APITestCase):
self.assertEqual(metadata_type.name, TEST_METADATA_TYPE_NAME_2) self.assertEqual(metadata_type.name, TEST_METADATA_TYPE_NAME_2)
class DocumentTypeMetadataTypeAPITestCase(APITestCase): class DocumentTypeMetadataTypeAPITestCase(GenericAPITestCase):
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(DocumentTypeMetadataTypeAPITestCase, self).setUp()
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD
)
self.client.login( self.document_type = DocumentType.on_organization.create(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
self.metadata_type = MetadataType.objects.create( self.metadata_type = MetadataType.on_organization.create(
label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME
) )
def tearDown(self): def tearDown(self):
self.admin_user.delete()
self.document_type.delete() self.document_type.delete()
super(DocumentTypeMetadataTypeAPITestCase, self).tearDown()
def test_document_type_metadata_type_optional_create(self): def test_document_type_metadata_type_optional_create(self):
response = self.client.post( response = self.client.post(
@@ -122,7 +107,7 @@ class DocumentTypeMetadataTypeAPITestCase(APITestCase):
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
document_type_metadata_type = DocumentTypeMetadataType.objects.filter( document_type_metadata_type = DocumentTypeMetadataType.on_organization.filter(
document_type=self.document_type, required=False document_type=self.document_type, required=False
).first() ).first()
@@ -142,7 +127,7 @@ class DocumentTypeMetadataTypeAPITestCase(APITestCase):
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
document_type_metadata_type = DocumentTypeMetadataType.objects.filter( document_type_metadata_type = DocumentTypeMetadataType.on_organization.filter(
document_type=self.document_type, required=True document_type=self.document_type, required=True
).first() ).first()
@@ -169,23 +154,16 @@ class DocumentTypeMetadataTypeAPITestCase(APITestCase):
self.assertEqual(self.document_type.metadata.all().count(), 0) self.assertEqual(self.document_type.metadata.all().count(), 0)
class DocumentMetadataAPITestCase(APITestCase): class DocumentMetadataAPITestCase(GenericAPITestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(DocumentMetadataAPITestCase, self).setUp()
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD
)
self.client.login( self.document_type = DocumentType.on_organization.create(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
self.metadata_type = MetadataType.objects.create( self.metadata_type = MetadataType.on_organization.create(
label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME label=TEST_METADATA_TYPE_LABEL, name=TEST_METADATA_TYPE_NAME
) )
@@ -199,8 +177,8 @@ class DocumentMetadataAPITestCase(APITestCase):
) )
def tearDown(self): def tearDown(self):
self.admin_user.delete()
self.document_type.delete() self.document_type.delete()
super(DocumentMetadataAPITestCase, self).tearDown()
def test_document_metadata_create(self): def test_document_metadata_create(self):
response = self.client.post( response = self.client.post(
@@ -213,7 +191,7 @@ class DocumentMetadataAPITestCase(APITestCase):
} }
) )
document_metadata = DocumentMetadata.objects.get( document_metadata = DocumentMetadata.on_organization.get(
document=self.document document=self.document
) )

View File

@@ -2,10 +2,11 @@ from __future__ import unicode_literals
from django.core.files.base import File from django.core.files.base import File
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings from django.test import override_settings
from documents.models import DocumentType from documents.models import DocumentType
from documents.tests import TEST_SMALL_DOCUMENT_PATH, TEST_DOCUMENT_TYPE from documents.tests import TEST_SMALL_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
from organizations.tests import OrganizationTestCase
from ..models import MetadataType, DocumentMetadata from ..models import MetadataType, DocumentMetadata
@@ -18,13 +19,14 @@ from .literals import (
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class MetadataTestCase(TestCase): class MetadataTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(MetadataTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
self.metadata_type = MetadataType.objects.create( self.metadata_type = MetadataType.on_organization.create(
name=TEST_METADATA_TYPE_NAME, label=TEST_METADATA_TYPE_LABEL name=TEST_METADATA_TYPE_NAME, label=TEST_METADATA_TYPE_LABEL
) )
@@ -37,6 +39,7 @@ class MetadataTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(MetadataTestCase, self).tearDown()
def test_no_default(self): def test_no_default(self):
document_metadata = DocumentMetadata( document_metadata = DocumentMetadata(

View File

@@ -0,0 +1,65 @@
from __future__ import unicode_literals
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import MetadataType
from .literals import (
TEST_DOCUMENT_METADATA_VALUE_2, TEST_METADATA_TYPE_LABEL,
TEST_METADATA_TYPE_LABEL_2, TEST_METADATA_TYPE_NAME,
TEST_METADATA_TYPE_NAME_2
)
class MetadataOrganizationViewTestCase(OrganizationViewTestCase):
def create_metadata_type(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.metadata_type = MetadataType.on_organization.create(
name=TEST_METADATA_TYPE_NAME, label=TEST_METADATA_TYPE_LABEL
)
def test_metadata_type_creation(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
'metadata:setup_metadata_type_create', data={
'label': TEST_METADATA_TYPE_LABEL,
'name': TEST_METADATA_TYPE_NAME
}, follow=True
)
self.assertContains(response, text='created', status_code=200)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
self.assertEqual(MetadataType.on_organization.count(), 0)
def test_metadata_type_delete(self):
self.create_metadata_type()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'metadata:setup_metadata_type_delete',
args=(self.metadata_type.pk,), follow=True
)
self.assertEqual(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.assertEqual(MetadataType.on_organization.count(), 1)
def test_metadata_type_edit(self):
self.create_metadata_type()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'metadata:setup_metadata_type_edit',
args=(self.metadata_type.pk,), data={
'label': TEST_METADATA_TYPE_LABEL_2,
}
)
self.assertEqual(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.metadata_type.refresh_from_db()
self.assertEqual(
self.metadata_type.label, TEST_METADATA_TYPE_LABEL
)

View File

@@ -30,7 +30,7 @@ class DocumentMetadataTestCase(GenericDocumentViewTestCase):
def setUp(self): def setUp(self):
super(DocumentMetadataTestCase, self).setUp() super(DocumentMetadataTestCase, self).setUp()
self.metadata_type = MetadataType.objects.create( self.metadata_type = MetadataType.on_organization.create(
name=TEST_METADATA_TYPE_NAME, label=TEST_METADATA_TYPE_LABEL name=TEST_METADATA_TYPE_NAME, label=TEST_METADATA_TYPE_LABEL
) )
@@ -94,11 +94,11 @@ class DocumentMetadataTestCase(GenericDocumentViewTestCase):
permission_metadata_document_edit.stored_permission permission_metadata_document_edit.stored_permission
) )
document_type_2 = DocumentType.objects.create( document_type_2 = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE_2 label=TEST_DOCUMENT_TYPE_2
) )
metadata_type_2 = MetadataType.objects.create( metadata_type_2 = MetadataType.on_organization.create(
name=TEST_METADATA_TYPE_NAME_2, label=TEST_METADATA_TYPE_LABEL_2 name=TEST_METADATA_TYPE_NAME_2, label=TEST_METADATA_TYPE_LABEL_2
) )

View File

@@ -36,11 +36,11 @@ from .permissions import (
def metadata_edit(request, document_id=None, document_id_list=None): def metadata_edit(request, document_id=None, document_id_list=None):
if document_id: if document_id:
documents = Document.objects.filter(pk=document_id) documents = Document.on_organization.filter(pk=document_id)
if not documents: if not documents:
raise Document.DoesNotExist raise Document.DoesNotExist
elif document_id_list: elif document_id_list:
documents = Document.objects.filter(pk__in=document_id_list) documents = Document.on_organization.filter(pk__in=document_id_list)
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -186,11 +186,11 @@ def metadata_multiple_edit(request):
def metadata_add(request, document_id=None, document_id_list=None): def metadata_add(request, document_id=None, document_id_list=None):
if document_id: if document_id:
documents = Document.objects.filter(pk=document_id) documents = Document.on_organization.filter(pk=document_id)
if not documents: if not documents:
raise Document.DoesNotExist raise Document.DoesNotExist
elif document_id_list: elif document_id_list:
documents = Document.objects.select_related('document_type').filter(pk__in=document_id_list) documents = Document.on_organization.select_related('document_type').filter(pk__in=document_id_list)
if len(set([document.document_type.pk for document in documents])) > 1: if len(set([document.document_type.pk for document in documents])) > 1:
messages.error( messages.error(
request, _('Only select documents of the same type.') request, _('Only select documents of the same type.')
@@ -241,7 +241,7 @@ def metadata_add(request, document_id=None, document_id_list=None):
metadata_type = form.cleaned_data['metadata_type'] metadata_type = form.cleaned_data['metadata_type']
for document in documents: for document in documents:
try: try:
document_metadata, created = DocumentMetadata.objects.get_or_create( document_metadata, created = DocumentMetadata.on_organization.get_or_create(
document=document, document=document,
metadata_type=metadata_type, metadata_type=metadata_type,
defaults={'value': ''} defaults={'value': ''}
@@ -331,11 +331,11 @@ def metadata_multiple_add(request):
def metadata_remove(request, document_id=None, document_id_list=None): def metadata_remove(request, document_id=None, document_id_list=None):
if document_id: if document_id:
documents = Document.objects.filter(pk=document_id) documents = Document.on_organization.filter(pk=document_id)
if not documents: if not documents:
raise Document.DoesNotExist raise Document.DoesNotExist
elif document_id_list: elif document_id_list:
documents = Document.objects.filter(pk__in=document_id_list) documents = Document.on_organization.filter(pk__in=document_id_list)
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -416,10 +416,10 @@ def metadata_remove(request, document_id=None, document_id_list=None):
for form in formset.forms: for form in formset.forms:
if form.cleaned_data['update']: if form.cleaned_data['update']:
metadata_type = get_object_or_404( metadata_type = get_object_or_404(
MetadataType, pk=form.cleaned_data['id'] MetadataType.on_organization, pk=form.cleaned_data['id']
) )
try: try:
document_metadata = DocumentMetadata.objects.get( document_metadata = DocumentMetadata.on_organization.get(
document=document, metadata_type=metadata_type document=document, metadata_type=metadata_type
) )
document_metadata.delete() document_metadata.delete()
@@ -494,7 +494,7 @@ class DocumentMetadataListView(SingleObjectListView):
) )
def get_document(self): def get_document(self):
return get_object_or_404(Document, pk=self.kwargs['pk']) return get_object_or_404(Document.on_organization, pk=self.kwargs['pk'])
def get_extra_context(self): def get_extra_context(self):
document = self.get_document() document = self.get_document()
@@ -512,13 +512,14 @@ class DocumentMetadataListView(SingleObjectListView):
class MetadataTypeCreateView(SingleObjectCreateView): class MetadataTypeCreateView(SingleObjectCreateView):
extra_context = {'title': _('Create metadata type')} extra_context = {'title': _('Create metadata type')}
form_class = MetadataTypeForm form_class = MetadataTypeForm
model = MetadataType
post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list') post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
view_permission = permission_metadata_type_create view_permission = permission_metadata_type_create
def get_queryset(self):
return MetadataType.on_organization.all()
class MetadataTypeDeleteView(SingleObjectDeleteView): class MetadataTypeDeleteView(SingleObjectDeleteView):
model = MetadataType
post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list') post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
view_permission = permission_metadata_type_delete view_permission = permission_metadata_type_delete
@@ -529,10 +530,12 @@ class MetadataTypeDeleteView(SingleObjectDeleteView):
'title': _('Delete the metadata type: %s?') % self.get_object(), 'title': _('Delete the metadata type: %s?') % self.get_object(),
} }
def get_queryset(self):
return MetadataType.on_organization.all()
class MetadataTypeEditView(SingleObjectEditView): class MetadataTypeEditView(SingleObjectEditView):
form_class = MetadataTypeForm form_class = MetadataTypeForm
model = MetadataType
post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list') post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
view_permission = permission_metadata_type_edit view_permission = permission_metadata_type_edit
@@ -542,13 +545,13 @@ class MetadataTypeEditView(SingleObjectEditView):
'title': _('Edit metadata type: %s') % self.get_object(), 'title': _('Edit metadata type: %s') % self.get_object(),
} }
def get_queryset(self):
return MetadataType.on_organization.all()
class MetadataTypeListView(SingleObjectListView): class MetadataTypeListView(SingleObjectListView):
view_permission = permission_metadata_type_view view_permission = permission_metadata_type_view
def get_queryset(self):
return MetadataType.objects.all()
def get_extra_context(self): def get_extra_context(self):
return { return {
'extra_columns': ( 'extra_columns': (
@@ -561,6 +564,9 @@ class MetadataTypeListView(SingleObjectListView):
'title': _('Metadata types'), 'title': _('Metadata types'),
} }
def get_queryset(self):
return MetadataType.on_organization.all()
class SetupDocumentTypeMetadataOptionalView(AssignRemoveView): class SetupDocumentTypeMetadataOptionalView(AssignRemoveView):
decode_content_type = True decode_content_type = True
@@ -572,12 +578,12 @@ class SetupDocumentTypeMetadataOptionalView(AssignRemoveView):
self.get_object().metadata.create(metadata_type=item, required=False) self.get_object().metadata.create(metadata_type=item, required=False)
def get_object(self): def get_object(self):
return get_object_or_404(DocumentType, pk=self.kwargs['pk']) return get_object_or_404(DocumentType.on_organization, pk=self.kwargs['pk'])
def left_list(self): def left_list(self):
return AssignRemoveView.generate_choices( return AssignRemoveView.generate_choices(
set(MetadataType.objects.all()) - set( set(MetadataType.on_organization.all()) - set(
MetadataType.objects.filter( MetadataType.on_organization.filter(
id__in=self.get_object().metadata.values_list( id__in=self.get_object().metadata.values_list(
'metadata_type', flat=True 'metadata_type', flat=True
) )

View File

@@ -1,7 +1,10 @@
from django.conf import settings
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from organizations.managers import CurrentOrganizationManager
class MessageManager(models.Manager): class MessageManager(models.Manager):
def get_for_now(self): def get_for_now(self):
@@ -9,3 +12,10 @@ class MessageManager(models.Manager):
return self.filter(enabled=True).filter( return self.filter(enabled=True).filter(
Q(start_datetime__isnull=True) | Q(start_datetime__lte=now) Q(start_datetime__isnull=True) | Q(start_datetime__lte=now)
).filter(Q(end_datetime__isnull=True) | Q(end_datetime__gte=now)) ).filter(Q(end_datetime__isnull=True) | Q(end_datetime__gte=now))
class OrganizationMessageManager(MessageManager, CurrentOrganizationManager):
def get_queryset(self):
return super(OrganizationMessageManager, self).get_queryset().filter(
**{self._get_field_name() + '__id': settings.ORGANIZATION_ID}
)

View File

@@ -13,12 +13,36 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='MessageOfTheDay', name='MessageOfTheDay',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('label', models.CharField(max_length=32, verbose_name='Label')), 'id', models.AutoField(
('message', models.TextField(verbose_name='Message', blank=True)), verbose_name='ID', serialize=False, auto_created=True,
('enabled', models.BooleanField(default=True, verbose_name='Enabled')), primary_key=True
('start_datetime', models.DateTimeField(verbose_name='Start date time', blank=True)), )
('end_datetime', models.DateTimeField(verbose_name='End date time', blank=True)), ),
(
'label',
models.CharField(max_length=32, verbose_name='Label')
),
(
'message',
models.TextField(verbose_name='Message', blank=True)
),
(
'enabled',
models.BooleanField(default=True, verbose_name='Enabled')
),
(
'start_datetime',
models.DateTimeField(
verbose_name='Start date time', blank=True
)
),
(
'end_datetime',
models.DateTimeField(
verbose_name='End date time', blank=True
)
),
], ],
options={ options={
'verbose_name': 'Message of the day', 'verbose_name': 'Message of the day',

View File

@@ -14,11 +14,15 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='messageoftheday', model_name='messageoftheday',
name='end_datetime', name='end_datetime',
field=models.DateTimeField(null=True, verbose_name='End date time', blank=True), field=models.DateTimeField(
null=True, verbose_name='End date time', blank=True
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='messageoftheday', model_name='messageoftheday',
name='start_datetime', name='start_datetime',
field=models.DateTimeField(null=True, verbose_name='Start date time', blank=True), field=models.DateTimeField(
null=True, verbose_name='Start date time', blank=True
),
), ),
] ]

View File

@@ -14,21 +14,35 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='message', model_name='message',
name='end_datetime', name='end_datetime',
field=models.DateTimeField(help_text='Date and time until when this message is to be displayed.', null=True, verbose_name='End date time', blank=True), field=models.DateTimeField(
help_text='Date and time until when this message is to be '
'displayed.', null=True, verbose_name='End date time',
blank=True
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='message', model_name='message',
name='label', name='label',
field=models.CharField(help_text='Short description of this message.', max_length=32, verbose_name='Label'), field=models.CharField(
help_text='Short description of this message.', max_length=32,
verbose_name='Label'
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='message', model_name='message',
name='message', name='message',
field=models.TextField(help_text='The actual message to be displayed.', verbose_name='Message'), field=models.TextField(
help_text='The actual message to be displayed.',
verbose_name='Message'
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='message', model_name='message',
name='start_datetime', name='start_datetime',
field=models.DateTimeField(help_text='Date and time after which this message will be displayed.', null=True, verbose_name='Start date time', blank=True), field=models.DateTimeField(
help_text='Date and time after which this message will be '
'displayed.', null=True, verbose_name='Start date time',
blank=True
),
), ),
] ]

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import organizations.shortcuts
class Migration(migrations.Migration):
dependencies = [
('organizations', '0002_add_data_default_organization'),
('motd', '0005_auto_20160510_0025'),
]
operations = [
migrations.AddField(
model_name='message',
name='organization',
field=models.ForeignKey(
default=organizations.shortcuts.get_current_organization,
to='organizations.Organization'
),
),
]

View File

@@ -4,11 +4,17 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .managers import MessageManager from organizations.models import Organization
from organizations.shortcuts import get_current_organization
from .managers import MessageManager, OrganizationMessageManager
@python_2_unicode_compatible @python_2_unicode_compatible
class Message(models.Model): class Message(models.Model):
organization = models.ForeignKey(
Organization, default=get_current_organization
)
label = models.CharField( label = models.CharField(
max_length=32, help_text=_('Short description of this message.'), max_length=32, help_text=_('Short description of this message.'),
verbose_name=_('Label') verbose_name=_('Label')
@@ -30,6 +36,7 @@ class Message(models.Model):
) )
objects = MessageManager() objects = MessageManager()
on_organization = OrganizationMessageManager()
class Meta: class Meta:
verbose_name = _('Message') verbose_name = _('Message')

View File

@@ -9,4 +9,4 @@ register = Library()
@register.inclusion_tag('motd/messages.html') @register.inclusion_tag('motd/messages.html')
def motd(): def motd():
return {'messages': Message.objects.get_for_now()} return {'messages': Message.on_organization.get_for_now()}

View File

@@ -0,0 +1,5 @@
from __future__ import unicode_literals
TEST_MESSAGE_LABEL = 'test message label'
TEST_MESSAGE_LABEL_EDITED = 'test message label edited'
TEST_MESSAGE_TEXT = 'test message text'

View File

@@ -5,20 +5,27 @@ from datetime import timedelta
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from organizations.models import Organization
from organizations.utils import create_default_organization
from ..models import Message from ..models import Message
TEST_LABEL = 'test label' from .literals import TEST_MESSAGE_LABEL, TEST_MESSAGE_TEXT
TEST_MESSAGE = 'test message'
class MOTDTestCase(TestCase): class MOTDTestCase(TestCase):
def setUp(self): def setUp(self):
self.motd = Message.objects.create( create_default_organization()
label=TEST_LABEL, message=TEST_MESSAGE self.motd = Message.on_organization.create(
label=TEST_MESSAGE_LABEL, message=TEST_MESSAGE_TEXT
) )
def tearDown(self):
Organization.objects.all().delete()
Organization.objects.clear_cache()
def test_basic(self): def test_basic(self):
queryset = Message.objects.get_for_now() queryset = Message.on_organization.get_for_now()
self.assertEqual(queryset.exists(), True) self.assertEqual(queryset.exists(), True)
@@ -26,7 +33,7 @@ class MOTDTestCase(TestCase):
self.motd.start_datetime = timezone.now() - timedelta(days=1) self.motd.start_datetime = timezone.now() - timedelta(days=1)
self.motd.save() self.motd.save()
queryset = Message.objects.get_for_now() queryset = Message.on_organization.get_for_now()
self.assertEqual(queryset.first(), self.motd) self.assertEqual(queryset.first(), self.motd)
@@ -35,7 +42,7 @@ class MOTDTestCase(TestCase):
self.motd.end_datetime = timezone.now() - timedelta(days=1) self.motd.end_datetime = timezone.now() - timedelta(days=1)
self.motd.save() self.motd.save()
queryset = Message.objects.get_for_now() queryset = Message.on_organization.get_for_now()
self.assertEqual(queryset.exists(), False) self.assertEqual(queryset.exists(), False)
@@ -43,6 +50,6 @@ class MOTDTestCase(TestCase):
self.motd.enabled = False self.motd.enabled = False
self.motd.save() self.motd.save()
queryset = Message.objects.get_for_now() queryset = Message.on_organization.get_for_now()
self.assertEqual(queryset.exists(), False) self.assertEqual(queryset.exists(), False)

View File

@@ -0,0 +1,67 @@
from __future__ import unicode_literals
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import Message
from .literals import (
TEST_MESSAGE_LABEL, TEST_MESSAGE_LABEL_EDITED, TEST_MESSAGE_TEXT
)
class MessageOrganizationViewTestCase(OrganizationViewTestCase):
def create_message(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.message = Message.on_organization.create(
label=TEST_MESSAGE_LABEL, message=TEST_MESSAGE_TEXT
)
def test_message_create_view(self):
# Create a message for organization A
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
'motd:message_create', data={
'label': TEST_MESSAGE_LABEL, 'message': TEST_MESSAGE_TEXT
}
)
self.assertNotContains(response, text='danger', status_code=302)
self.assertEqual(Message.on_organization.count(), 1)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
self.assertEqual(Message.on_organization.count(), 0)
def test_message_delete_view(self):
# Create a message for organization A
self.create_message()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'motd:message_delete', args=(self.message.pk,)
)
self.assertEqual(response.status_code, 404)
def test_message_edit_view(self):
# Create a message for organization A
self.create_message()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
# Make sure admin for organization B cannot edit the message
response = self.post(
'motd:message_edit', args=(self.message.pk,), data={
'label': TEST_MESSAGE_LABEL_EDITED
}
)
self.assertEqual(response.status_code, 404)
def test_message_messageged_item_list_view(self):
# Create a message for organization A
self.create_message()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
# Make sure admin for organization B cannot find the message for
# organization A
response = self.get('motd:message_list')
self.assertNotContains(
response, text=self.message.label, status_code=200
)

View File

@@ -10,7 +10,9 @@ urlpatterns = patterns(
'', '',
url(r'^list/$', MessageListView.as_view(), name='message_list'), url(r'^list/$', MessageListView.as_view(), name='message_list'),
url(r'^create/$', MessageCreateView.as_view(), name='message_create'), url(r'^create/$', MessageCreateView.as_view(), name='message_create'),
url(r'^(?P<pk>\d+)/edit/$', MessageEditView.as_view(), name='message_edit'), url(
r'^(?P<pk>\d+)/edit/$', MessageEditView.as_view(), name='message_edit'
),
url( url(
r'^(?P<pk>\d+)/delete/$', MessageDeleteView.as_view(), r'^(?P<pk>\d+)/delete/$', MessageDeleteView.as_view(),
name='message_delete' name='message_delete'

View File

@@ -21,7 +21,6 @@ logger = logging.getLogger(__name__)
class MessageCreateView(SingleObjectCreateView): class MessageCreateView(SingleObjectCreateView):
fields = ('label', 'message', 'enabled', 'start_datetime', 'end_datetime') fields = ('label', 'message', 'enabled', 'start_datetime', 'end_datetime')
model = Message
view_permission = permission_message_create view_permission = permission_message_create
def get_extra_context(self): def get_extra_context(self):
@@ -29,9 +28,11 @@ class MessageCreateView(SingleObjectCreateView):
'title': _('Create message'), 'title': _('Create message'),
} }
def get_queryset(self):
return Message.on_organization.all()
class MessageDeleteView(SingleObjectDeleteView): class MessageDeleteView(SingleObjectDeleteView):
model = Message
object_permission = permission_message_delete object_permission = permission_message_delete
post_action_redirect = reverse_lazy('motd:message_list') post_action_redirect = reverse_lazy('motd:message_list')
@@ -42,10 +43,12 @@ class MessageDeleteView(SingleObjectDeleteView):
'title': _('Delete the message: %s?') % self.get_object(), 'title': _('Delete the message: %s?') % self.get_object(),
} }
def get_queryset(self):
return Message.on_organization.all()
class MessageEditView(SingleObjectEditView): class MessageEditView(SingleObjectEditView):
fields = ('label', 'message', 'enabled', 'start_datetime', 'end_datetime') fields = ('label', 'message', 'enabled', 'start_datetime', 'end_datetime')
model = Message
object_permission = permission_message_edit object_permission = permission_message_edit
post_action_redirect = reverse_lazy('motd:message_list') post_action_redirect = reverse_lazy('motd:message_list')
@@ -55,9 +58,11 @@ class MessageEditView(SingleObjectEditView):
'title': _('Edit message: %s') % self.get_object(), 'title': _('Edit message: %s') % self.get_object(),
} }
def get_queryset(self):
return Message.on_organization.all()
class MessageListView(SingleObjectListView): class MessageListView(SingleObjectListView):
model = Message
object_permission = permission_message_view object_permission = permission_message_view
def get_extra_context(self): def get_extra_context(self):
@@ -65,3 +70,6 @@ class MessageListView(SingleObjectListView):
'hide_link': True, 'hide_link': True,
'title': _('Messages'), 'title': _('Messages'),
} }
def get_queryset(self):
return Message.on_organization.all()

View File

@@ -16,7 +16,7 @@ class APIDocumentOCRView(generics.GenericAPIView):
'POST': (permission_ocr_document,) 'POST': (permission_ocr_document,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = Document.objects.all() queryset = Document.on_organization.all()
def get_serializer_class(self): def get_serializer_class(self):
return None return None
@@ -44,7 +44,7 @@ class APIDocumentVersionOCRView(generics.GenericAPIView):
'POST': (permission_ocr_document,) 'POST': (permission_ocr_document,)
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
queryset = DocumentVersion.objects.all() queryset = DocumentVersion.on_organization.all()
def get_serializer_class(self): def get_serializer_class(self):
return None return None
@@ -83,7 +83,7 @@ class APIDocumentPageContentView(generics.RetrieveAPIView):
} }
permission_classes = (MayanPermission,) permission_classes = (MayanPermission,)
serializer_class = DocumentPageContentSerializer serializer_class = DocumentPageContentSerializer
queryset = DocumentPage.objects.all() queryset = DocumentPage.on_organization.all()
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()

View File

@@ -60,7 +60,7 @@ class OCRBackendBase(object):
image = document_page.get_image() image = document_page.get_image()
try: try:
document_page_content, created = DocumentPageContent.objects.get_or_create( document_page_content, created = DocumentPageContent.on_organization.get_or_create(
document_page=document_page document_page=document_page
) )
document_page_content.content = self.execute( document_page_content.content = self.execute(

View File

@@ -57,5 +57,5 @@ class DocumentContentForm(forms.Form):
class DocumentTypeSelectForm(forms.Form): class DocumentTypeSelectForm(forms.Form):
document_type = forms.ModelChoiceField( document_type = forms.ModelChoiceField(
queryset=DocumentType.objects.all(), label=('Document type') queryset=DocumentType.on_organization.all(), label=('Document type')
) )

View File

@@ -20,6 +20,6 @@ def initialize_new_ocr_settings(sender, instance, **kwargs):
DocumentTypeSettings = get_model('ocr', 'DocumentTypeSettings') DocumentTypeSettings = get_model('ocr', 'DocumentTypeSettings')
if kwargs['created']: if kwargs['created']:
DocumentTypeSettings.objects.create( DocumentTypeSettings.on_organization.create(
document_type=instance, auto_ocr=setting_auto_ocr.value document_type=instance, auto_ocr=setting_auto_ocr.value
) )

View File

@@ -0,0 +1,37 @@
from __future__ import unicode_literals
from django.apps import apps
from django.db import models
class OrganizationDocumentTypeSettingsManager(models.Manager):
def get_queryset(self):
DocumentType = apps.get_model('documents', 'DocumentType')
return super(
OrganizationDocumentTypeSettingsManager, self
).get_queryset().filter(
document_type__in=DocumentType.on_organization.all(),
)
class OrganizationDocumentVersionOCRErrorManager(models.Manager):
def get_queryset(self):
DocumentVersion = apps.get_model('documents', 'DocumentVersion')
return super(
OrganizationDocumentVersionOCRErrorManager, self
).get_queryset().filter(
document_version__in=DocumentVersion.on_organization.all(),
)
class OrganizationDocumentPageContentManager(models.Manager):
def get_queryset(self):
DocumentPage = apps.get_model('documents', 'DocumentPage')
return super(
OrganizationDocumentPageContentManager, self
).get_queryset().filter(
document_page__in=DocumentPage.on_organization.all(),
)

View File

@@ -6,6 +6,12 @@ from django.utils.translation import ugettext_lazy as _
from documents.models import DocumentPage, DocumentType, DocumentVersion from documents.models import DocumentPage, DocumentType, DocumentVersion
from .managers import (
OrganizationDocumentTypeSettingsManager,
OrganizationDocumentVersionOCRErrorManager,
OrganizationDocumentPageContentManager
)
class DocumentTypeSettings(models.Model): class DocumentTypeSettings(models.Model):
""" """
@@ -20,6 +26,9 @@ class DocumentTypeSettings(models.Model):
verbose_name=_('Automatically queue newly created documents for OCR.') verbose_name=_('Automatically queue newly created documents for OCR.')
) )
objects = models.Manager()
on_organization = OrganizationDocumentTypeSettingsManager()
class Meta: class Meta:
verbose_name = _('Document type settings') verbose_name = _('Document type settings')
verbose_name_plural = _('Document types settings') verbose_name_plural = _('Document types settings')
@@ -35,14 +44,17 @@ class DocumentVersionOCRError(models.Model):
) )
result = models.TextField(blank=True, null=True, verbose_name=_('Result')) result = models.TextField(blank=True, null=True, verbose_name=_('Result'))
def __str__(self): objects = models.Manager()
return unicode(self.document_version) on_organization = OrganizationDocumentVersionOCRErrorManager()
class Meta: class Meta:
ordering = ('datetime_submitted',) ordering = ('datetime_submitted',)
verbose_name = _('Document Version OCR Error') verbose_name = _('Document Version OCR Error')
verbose_name_plural = _('Document Version OCR Errors') verbose_name_plural = _('Document Version OCR Errors')
def __str__(self):
return unicode(self.document_version)
@python_2_unicode_compatible @python_2_unicode_compatible
class DocumentPageContent(models.Model): class DocumentPageContent(models.Model):
@@ -55,9 +67,12 @@ class DocumentPageContent(models.Model):
) )
content = models.TextField(blank=True, verbose_name=_('Content')) content = models.TextField(blank=True, verbose_name=_('Content'))
def __str__(self): objects = models.Manager()
return unicode(self.document_page) on_organization = OrganizationDocumentPageContentManager()
class Meta: class Meta:
verbose_name = _('Document page content') verbose_name = _('Document page content')
verbose_name_plural = _('Document pages contents') verbose_name_plural = _('Document pages contents')
def __str__(self):
return unicode(self.document_page)

View File

@@ -92,7 +92,7 @@ class Parser(object):
file_object = document_page.document_version.get_intermidiate_file() file_object = document_page.document_version.get_intermidiate_file()
try: try:
document_page_content, created = DocumentPageContent.objects.get_or_create( document_page_content, created = DocumentPageContent.on_organization.get_or_create(
document_page=document_page document_page=document_page
) )
document_page_content.content = self.execute( document_page_content.content = self.execute(

View File

@@ -35,7 +35,9 @@ def task_do_ocr(self, document_version_pk):
logger.debug('acquired lock: %s', lock_id) logger.debug('acquired lock: %s', lock_id)
document_version = None document_version = None
try: try:
document_version = DocumentVersion.objects.get(pk=document_version_pk) document_version = DocumentVersion.on_organization.get(
pk=document_version_pk
)
logger.info( logger.info(
'Starting document OCR for document version: %s', 'Starting document OCR for document version: %s',
document_version document_version
@@ -53,7 +55,7 @@ def task_do_ocr(self, document_version_pk):
exception exception
) )
if document_version: if document_version:
entry, created = DocumentVersionOCRError.objects.get_or_create( entry, created = DocumentVersionOCRError.on_organization.get_or_create(
document_version=document_version document_version=document_version
) )
@@ -72,7 +74,7 @@ def task_do_ocr(self, document_version_pk):
'OCR complete for document version: %s', document_version 'OCR complete for document version: %s', document_version
) )
try: try:
entry = DocumentVersionOCRError.objects.get( entry = DocumentVersionOCRError.on_organization.get(
document_version=document_version document_version=document_version
) )
except DocumentVersionOCRError.DoesNotExist: except DocumentVersionOCRError.DoesNotExist:

View File

@@ -1,36 +1,23 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import json
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase
from documents.models import DocumentType from documents.models import DocumentType
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
from user_management.tests import ( from rest_api.tests import GenericAPITestCase
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
)
class OCRAPITestCase(APITestCase): class OCRAPITestCase(GenericAPITestCase):
""" """
Test the OCR app API endpoints Test the OCR app API endpoints
""" """
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(OCRAPITestCase, self).setUp()
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD
)
self.client.login( self.document_type = DocumentType.on_organization.create(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -41,6 +28,7 @@ class OCRAPITestCase(APITestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(OCRAPITestCase, self).tearDown()
def test_submit_document(self): def test_submit_document(self):
response = self.client.post( response = self.client.post(
@@ -81,5 +69,5 @@ class OCRAPITestCase(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue( self.assertTrue(
'Mayan EDMS Documentation' in json.loads(response.content)['content'] 'Mayan EDMS Documentation' in response.data['content']
) )

Some files were not shown because too many files have changed in this diff Show More