Compare commits

...

96 Commits

Author SHA1 Message Date
Roberto Rosario
71ede69c3f Update linking app to use organizations. 2016-07-01 02:02:34 -04:00
Roberto Rosario
b6161e9d92 PEP8 and code style cleanups. 2016-06-30 21:30:01 -04:00
Roberto Rosario
aa56e8ae14 Update dynamic_search app to work with organizations. Remove Recent search feature. 2016-06-30 18:46:26 -04:00
Roberto Rosario
1b8436add7 Merge remote-tracking branch 'origin/master' into feature/multi-tenant 2016-06-30 16:59:46 -04:00
Roberto Rosario
883e1cc510 Merge remote-tracking branch 'origin/master' into feature/merge_test 2016-06-30 01:00:55 -04:00
Roberto Rosario
debec7b4a2 Update the django_gpg app to support organizations. 2016-06-24 04:22:49 -04:00
Roberto Rosario
0be3e130c1 Merge branch 'master' into feature/merge2 2016-06-24 03:28:36 -04:00
Roberto Rosario
dd28e3dc09 Allow superusers to login from any organization. 2016-06-24 03:26:24 -04:00
Roberto Rosario
09e375654d Improve verbose name of custom group model. Allow user to be created without belonging to an organization, this is used for superusers. 2016-06-24 03:24:35 -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
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
208 changed files with 3955 additions and 1553 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 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,25 @@
from __future__ import unicode_literals
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:
# Check for superadmins, they can login from any organization.
try:
user = UserModel.objects.filter(is_superuser=True).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

@@ -3,9 +3,10 @@ from __future__ import absolute_import, unicode_literals
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase, override_settings from django.test import 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

@@ -4,7 +4,7 @@ import datetime
import time import time
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings from django.test import override_settings
from django.utils.timezone import now from django.utils.timezone import now
from documents.exceptions import NewDocumentVersionNotAllowed from documents.exceptions import NewDocumentVersionNotAllowed
@@ -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

@@ -12,9 +12,16 @@ class Command(management.BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
management.call_command('createsettings', interactive=False) management.call_command('createsettings', interactive=False)
try: try:
result = management.call_command('migrate', interactive=False) management.call_command('migrate', interactive=False)
except OperationalError as exception: except OperationalError:
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,42 +2,42 @@ 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 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
) )
from .base import BaseTestCase
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
class GenericViewTestCase(BaseTestCase): class GenericViewTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(GenericViewTestCase, self).setUp() 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):
@@ -80,7 +80,7 @@ class GenericViewTestCase(BaseTestCase):
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())
@@ -94,6 +94,12 @@ class GenericViewTestCase(BaseTestCase):
data=data, follow=follow data=data, follow=follow
) )
def login_user(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
def login_superuser(self):
self.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
class CommonViewTestCase(GenericViewTestCase): class CommonViewTestCase(GenericViewTestCase):
def test_about_view(self): def test_about_view(self):

View File

@@ -10,6 +10,7 @@ import gnupg
from django.db import models from django.db import models
from common.utils import mkdtemp, mkstemp from common.utils import mkdtemp, mkstemp
from organizations.managers import CurrentOrganizationManager
from .classes import KeyStub, SignatureVerification from .classes import KeyStub, SignatureVerification
from .exceptions import ( from .exceptions import (
@@ -193,3 +194,7 @@ class KeyManager(models.Manager):
else: else:
logger.debug('file not signed') logger.debug('file not signed')
raise VerificationError('File not signed') raise VerificationError('File not signed')
class OrganizationKeyManager(KeyManager, CurrentOrganizationManager):
pass

View File

@@ -14,6 +14,9 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='key', model_name='key',
name='key_data', name='key_data',
field=models.TextField(help_text='ASCII armored version of the key.', verbose_name='Key data'), field=models.TextField(
help_text='ASCII armored version of the key.',
verbose_name='Key data'
),
), ),
] ]

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'),
('django_gpg', '0006_auto_20160510_0025'),
]
operations = [
migrations.AddField(
model_name='key',
name='organization',
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, to='organizations.Organization'),
),
]

View File

@@ -14,6 +14,8 @@ 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 common.utils import mkdtemp from common.utils import mkdtemp
from organizations.models import Organization
from organizations.shortcuts import get_current_organization
from .exceptions import NeedPassphrase, PassphraseError from .exceptions import NeedPassphrase, PassphraseError
from .literals import ( from .literals import (
@@ -21,7 +23,7 @@ from .literals import (
ERROR_MSG_GOOD_PASSPHRASE, KEY_TYPE_CHOICES, KEY_TYPE_SECRET, ERROR_MSG_GOOD_PASSPHRASE, KEY_TYPE_CHOICES, KEY_TYPE_SECRET,
OUTPUT_MESSAGE_CONTAINS_PRIVATE_KEY OUTPUT_MESSAGE_CONTAINS_PRIVATE_KEY
) )
from .managers import KeyManager from .managers import KeyManager, OrganizationKeyManager
from .settings import setting_gpg_path from .settings import setting_gpg_path
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -44,6 +46,9 @@ def gpg_command(function):
@python_2_unicode_compatible @python_2_unicode_compatible
class Key(models.Model): class Key(models.Model):
organization = models.ForeignKey(
Organization, default=get_current_organization
)
key_data = models.TextField( key_data = models.TextField(
help_text=_('ASCII armored version of the key.'), help_text=_('ASCII armored version of the key.'),
verbose_name=_('Key data') verbose_name=_('Key data')
@@ -72,11 +77,15 @@ class Key(models.Model):
) )
objects = KeyManager() objects = KeyManager()
on_organization = OrganizationKeyManager()
class Meta: class Meta:
verbose_name = _('Key') verbose_name = _('Key')
verbose_name_plural = _('Keys') verbose_name_plural = _('Keys')
def __str__(self):
return '{} - {}'.format(self.key_id, self.user_id)
def clean(self): def clean(self):
def import_key(gpg): def import_key(gpg):
return gpg.import_keys(key_data=self.key_data) return gpg.import_keys(key_data=self.key_data)
@@ -86,7 +95,7 @@ class Key(models.Model):
if not import_results.count: if not import_results.count:
raise ValidationError(_('Invalid key data')) raise ValidationError(_('Invalid key data'))
if Key.objects.filter(fingerprint=import_results.fingerprints[0]).exists(): if Key.on_organization.filter(fingerprint=import_results.fingerprints[0]).exists():
raise ValidationError(_('Key already exists.')) raise ValidationError(_('Key already exists.'))
def get_absolute_url(self): def get_absolute_url(self):
@@ -123,9 +132,6 @@ class Key(models.Model):
super(Key, self).save(*args, **kwargs) super(Key, self).save(*args, **kwargs)
def __str__(self):
return '{} - {}'.format(self.key_id, self.user_id)
def sign_file(self, file_object, passphrase=None, clearsign=False, detached=False, binary=False, output=None): def sign_file(self, file_object, passphrase=None, clearsign=False, detached=False, binary=False, output=None):
# WARNING: using clearsign=True and subsequent decryption corrupts the # WARNING: using clearsign=True and subsequent decryption corrupts the
# file. Appears to be a problem in python-gnupg or gpg itself. # file. Appears to be a problem in python-gnupg or gpg itself.

View File

@@ -5,8 +5,8 @@ import StringIO
import gnupg import gnupg
import mock import mock
from common.tests import BaseTestCase
from common.utils import TemporaryFile from common.utils import TemporaryFile
from organizations.tests import OrganizationTestCase
from ..exceptions import ( from ..exceptions import (
DecryptionError, KeyDoesNotExist, NeedPassphrase, PassphraseError, DecryptionError, KeyDoesNotExist, NeedPassphrase, PassphraseError,
@@ -43,7 +43,7 @@ def mock_recv_keys(self, keyserver, *keyids):
return ImportResult() return ImportResult()
class KeyTestCase(BaseTestCase): class KeyTestCase(OrganizationTestCase):
def test_key_instance_creation(self): def test_key_instance_creation(self):
# Creating a Key instance is analogous to importing a key # Creating a Key instance is analogous to importing a key
key = Key.objects.create(key_data=TEST_KEY_DATA) key = Key.objects.create(key_data=TEST_KEY_DATA)

View File

@@ -0,0 +1,83 @@
from __future__ import unicode_literals
from django.test import override_settings
from django_downloadview.test import assert_download_response
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import Key
from .literals import TEST_KEY_DATA, TEST_KEY_FINGERPRINT
@override_settings(OCR_AUTO_OCR=False)
class OrganizationKeyViewsTestCase(OrganizationViewTestCase):
def create_key(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.key = Key.on_organization.create(key_data=TEST_KEY_DATA)
def test_key_delete_view(self):
self.create_key()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'django_gpg:key_delete',
args=(self.key.pk,), follow=True
)
self.assertEqual(response.status_code, 404)
def test_key_download_view(self):
self.create_key()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.get(
viewname='django_gpg:key_download', args=(self.key.pk,)
)
self.assertEqual(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.get(
viewname='django_gpg:key_download', args=(self.key.pk,)
)
assert_download_response(
self, response=response, content=self.key.key_data,
basename=self.key.key_id,
)
def test_key_upload_view(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
viewname='django_gpg:key_upload',
data={'key_data': TEST_KEY_DATA}, follow=True
)
self.assertContains(response, 'created', status_code=200)
self.assertEqual(Key.on_organization.count(), 1)
self.assertEqual(
Key.on_organization.first().fingerprint, TEST_KEY_FINGERPRINT
)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
self.assertEqual(Key.on_organization.count(), 0)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.assertEqual(Key.on_organization.count(), 1)
def test_key_private_list_view(self):
self.create_key()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.get(viewname='django_gpg:key_private_list')
self.assertNotContains(
response, text=self.key.key_id, status_code=200
)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.get(viewname='django_gpg:key_private_list')
self.assertContains(
response, text=self.key.key_id, status_code=200
)

View File

@@ -3,9 +3,7 @@ from __future__ import absolute_import, unicode_literals
from django_downloadview.test import assert_download_response from django_downloadview.test import assert_download_response
from common.tests.test_views import GenericViewTestCase from common.tests.test_views import GenericViewTestCase
from user_management.tests import ( from user_management.tests import TEST_USER_USERNAME, TEST_USER_PASSWORD
TEST_USER_USERNAME, TEST_USER_PASSWORD
)
from ..models import Key from ..models import Key
from ..permissions import permission_key_download, permission_key_upload from ..permissions import permission_key_download, permission_key_upload

View File

@@ -25,7 +25,6 @@ logger = logging.getLogger(__name__)
class KeyDeleteView(SingleObjectDeleteView): class KeyDeleteView(SingleObjectDeleteView):
model = Key
object_permission = permission_key_delete object_permission = permission_key_delete
def get_post_action_redirect(self): def get_post_action_redirect(self):
@@ -37,10 +36,12 @@ class KeyDeleteView(SingleObjectDeleteView):
def get_extra_context(self): def get_extra_context(self):
return {'title': _('Delete key: %s') % self.get_object()} return {'title': _('Delete key: %s') % self.get_object()}
def get_queryset(self):
return Key.on_organization.all()
class KeyDetailView(SingleObjectDetailView): class KeyDetailView(SingleObjectDetailView):
form_class = KeyDetailForm form_class = KeyDetailForm
model = Key
object_permission = permission_key_view object_permission = permission_key_view
def get_extra_context(self): def get_extra_context(self):
@@ -48,9 +49,11 @@ class KeyDetailView(SingleObjectDetailView):
'title': _('Details for key: %s') % self.get_object(), 'title': _('Details for key: %s') % self.get_object(),
} }
def get_queryset(self):
return Key.on_organization.all()
class KeyDownloadView(SingleObjectDownloadView): class KeyDownloadView(SingleObjectDownloadView):
model = Key
object_permission = permission_key_download object_permission = permission_key_download
def get_file(self): def get_file(self):
@@ -58,6 +61,9 @@ class KeyDownloadView(SingleObjectDownloadView):
return ContentFile(key.key_data, name=key.key_id) return ContentFile(key.key_data, name=key.key_id)
def get_queryset(self):
return Key.on_organization.all()
class KeyReceive(ConfirmView): class KeyReceive(ConfirmView):
post_action_redirect = reverse_lazy('django_gpg:key_public_list') post_action_redirect = reverse_lazy('django_gpg:key_public_list')
@@ -71,7 +77,7 @@ class KeyReceive(ConfirmView):
def view_action(self): def view_action(self):
try: try:
Key.objects.receive_key(key_id=self.kwargs['key_id']) Key.on_organization.receive_key(key_id=self.kwargs['key_id'])
except Exception as exception: except Exception as exception:
messages.error( messages.error(
self.request, self.request,
@@ -122,14 +128,13 @@ class KeyQueryResultView(SingleObjectListView):
def get_queryset(self): def get_queryset(self):
term = self.request.GET.get('term') term = self.request.GET.get('term')
if term: if term:
return Key.objects.search(query=term) return Key.on_organization.search(query=term)
else: else:
return () return ()
class KeyUploadView(SingleObjectCreateView): class KeyUploadView(SingleObjectCreateView):
fields = ('key_data',) fields = ('key_data',)
model = Key
post_action_redirect = reverse_lazy('django_gpg:key_public_list') post_action_redirect = reverse_lazy('django_gpg:key_public_list')
view_permission = permission_key_upload view_permission = permission_key_upload
@@ -138,10 +143,12 @@ class KeyUploadView(SingleObjectCreateView):
'title': _('Upload new key'), 'title': _('Upload new key'),
} }
def get_queryset(self):
return Key.on_organization.all()
class PublicKeyListView(SingleObjectListView): class PublicKeyListView(SingleObjectListView):
object_permission = permission_key_view object_permission = permission_key_view
queryset = Key.objects.public_keys()
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -149,13 +156,18 @@ class PublicKeyListView(SingleObjectListView):
'title': _('Public keys') 'title': _('Public keys')
} }
def get_queryset(self):
return Key.on_organization.public_keys()
class PrivateKeyListView(SingleObjectListView): class PrivateKeyListView(SingleObjectListView):
object_permission = permission_key_view object_permission = permission_key_view
queryset = Key.objects.private_keys()
def get_extra_context(self): def get_extra_context(self):
return { return {
'hide_object': True, 'hide_object': True,
'title': _('Private keys') 'title': _('Private keys')
} }
def get_queryset(self):
return Key.on_organization.private_keys()

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,54 @@
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
@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

@@ -22,9 +22,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 +49,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 +109,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 +169,35 @@ 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):
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,29 @@ 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 user_management.tests.literals import ( from rest_api.tests import GenericAPITestCase
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 +34,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 +107,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 +142,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 +245,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

@@ -3,12 +3,13 @@ from __future__ import unicode_literals
from datetime import timedelta from datetime import timedelta
import time import time
from common.tests import BaseTestCase from django.test import 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,
@@ -17,11 +18,10 @@ from .literals import (
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentTestCase(BaseTestCase): class DocumentTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(DocumentTestCase, self).setUp() super(DocumentTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -61,30 +61,30 @@ class DocumentTestCase(BaseTestCase):
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):
""" """
@@ -101,13 +101,13 @@ class DocumentTestCase(BaseTestCase):
# 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):
""" """
@@ -120,13 +120,13 @@ class DocumentTestCase(BaseTestCase):
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
@@ -134,16 +134,16 @@ class DocumentTestCase(BaseTestCase):
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(BaseTestCase): class OfficeDocumentTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(OfficeDocumentTestCase, self).setUp() super(OfficeDocumentTestCase, self).setUp()
self.document_type = DocumentType.objects.create( self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -169,10 +169,11 @@ class OfficeDocumentTestCase(BaseTestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class MultiPageTiffTestCase(BaseTestCase): class MultiPageTiffTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(MultiPageTiffTestCase, self).setUp() super(MultiPageTiffTestCase, self).setUp()
self.document_type = DocumentType.objects.create(
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -196,10 +197,11 @@ class MultiPageTiffTestCase(BaseTestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentVersionTestCase(BaseTestCase): class DocumentVersionTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(DocumentVersionTestCase, self).setUp() super(DocumentVersionTestCase, self).setUp()
self.document_type = DocumentType.objects.create(
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -210,7 +212,7 @@ class DocumentVersionTestCase(BaseTestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(DocumentVersionTestCase, self).setUp() 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)
@@ -247,10 +249,10 @@ class DocumentVersionTestCase(BaseTestCase):
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class DocumentManagerTestCase(BaseTestCase): class DocumentManagerTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(DocumentManagerTestCase, self).setUp() super(DocumentManagerTestCase, self).setUp()
self.document_type = DocumentType.objects.create( self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -259,13 +261,13 @@ class DocumentManagerTestCase(BaseTestCase):
super(DocumentManagerTestCase, self).tearDown() 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
@@ -274,15 +276,14 @@ class DocumentManagerTestCase(BaseTestCase):
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(BaseTestCase): class NewVersionBlockTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
super(NewVersionBlockTestCase, self).setUp() super(NewVersionBlockTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
self.document_type = DocumentType.objects.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )

View File

@@ -0,0 +1,60 @@
from __future__ import unicode_literals
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import DocumentType
from .literals import (
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_2_LABEL, 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

@@ -16,7 +16,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,
@@ -29,22 +29,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
) )
@@ -116,7 +112,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
) )
@@ -129,7 +125,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
) )
@@ -142,7 +138,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
) )
@@ -161,7 +157,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
) )
@@ -171,10 +167,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
) )
@@ -189,7 +185,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):
@@ -198,10 +194,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
) )
@@ -223,7 +219,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
) )
@skip_file_descriptor_check @skip_file_descriptor_check
@@ -235,7 +231,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,)
@@ -271,7 +267,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',
@@ -309,7 +305,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=(
@@ -371,7 +367,6 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
self.assertContains(response, text='queued', status_code=200) self.assertContains(response, text='queued', status_code=200)
self.assertEqual(self.document.pages.count(), page_count) self.assertEqual(self.document.pages.count(), page_count)
def test_document_multiple_update_page_count_view_no_permission(self): def test_document_multiple_update_page_count_view_no_permission(self):
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD) self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
@@ -500,20 +495,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
@@ -523,8 +518,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
@@ -574,7 +569,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',
@@ -587,7 +582,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(
@@ -596,7 +591,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
@@ -616,9 +611,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):
@@ -632,7 +627,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(
@@ -652,7 +647,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(
@@ -672,7 +667,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
) )
@@ -701,7 +696,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
) )
@@ -742,27 +737,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
) )
@@ -771,8 +766,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(
@@ -784,8 +779,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(
@@ -802,8 +797,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(
@@ -811,15 +806,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(
@@ -827,8 +822,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
@@ -840,8 +835,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()
@@ -850,7 +845,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)
@@ -864,6 +859,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

@@ -1,14 +0,0 @@
from __future__ import unicode_literals
from django.contrib import admin
from .models import RecentSearch
@admin.register(RecentSearch)
class RecentSearchAdmin(admin.ModelAdmin):
date_hierarchy = 'datetime_created'
list_display = ('user', 'query', 'datetime_created', 'hits')
list_display_links = ('user', 'query', 'datetime_created', 'hits')
list_filter = ('user',)
readonly_fields = ('user', 'query', 'datetime_created', 'hits')

View File

@@ -6,29 +6,6 @@ from rest_framework.exceptions import ParseError
from rest_api.filters import MayanObjectPermissionsFilter from rest_api.filters import MayanObjectPermissionsFilter
from .classes import SearchModel from .classes import SearchModel
from .filters import RecentSearchUserFilter
from .models import RecentSearch
from .serializers import RecentSearchSerializer
class APIRecentSearchListView(generics.ListAPIView):
"""
Returns a list of all the recent searches for the logged user.
"""
filter_backends = (RecentSearchUserFilter,)
queryset = RecentSearch.objects.all()
serializer_class = RecentSearchSerializer
class APIRecentSearchView(generics.RetrieveAPIView):
"""
Returns the selected recent search details.
"""
filter_backends = (RecentSearchUserFilter,)
queryset = RecentSearch.objects.all()
serializer_class = RecentSearchSerializer
class APISearchView(generics.ListAPIView): class APISearchView(generics.ListAPIView):

View File

@@ -2,10 +2,10 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common import MayanAppConfig, menu_facet, menu_sidebar from common import MayanAppConfig, menu_facet
from rest_api.classes import APIEndPoint from rest_api.classes import APIEndPoint
from .links import link_search, link_search_advanced, link_search_again from .links import link_search, link_search_advanced
class DynamicSearchApp(MayanAppConfig): class DynamicSearchApp(MayanAppConfig):
@@ -26,6 +26,3 @@ class DynamicSearchApp(MayanAppConfig):
'search:search', 'search:search_advanced', 'search:results' 'search:search', 'search:search_advanced', 'search:results'
) )
) )
menu_sidebar.bind_links(
links=(link_search_again,), sources=('search:results',)
)

View File

@@ -12,7 +12,6 @@ from django.utils.module_loading import import_string
from acls.models import AccessControlList from acls.models import AccessControlList
from permissions import Permission from permissions import Permission
from .models import RecentSearch
from .settings import setting_limit from .settings import setting_limit
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -148,7 +147,7 @@ class SearchModel(object):
for query in field_query_list: for query in field_query_list:
logger.debug('query: %s', query) logger.debug('query: %s', query)
term_query_result_set = set( term_query_result_set = set(
model.objects.filter(query).values_list( model.on_organization.filter(query).values_list(
data['return_value'], flat=True data['return_value'], flat=True
) )
) )
@@ -183,7 +182,7 @@ class SearchModel(object):
datetime.datetime.now() - start_time datetime.datetime.now() - start_time
).split(':')[2] ).split(':')[2]
queryset = self.model.objects.filter( queryset = self.model.on_organization.filter(
pk__in=list(result_set)[:setting_limit.value] pk__in=list(result_set)[:setting_limit.value]
) )
@@ -195,10 +194,6 @@ class SearchModel(object):
self.permission, user, queryset self.permission, user, queryset
) )
RecentSearch.objects.add_query_for_user(
user, query_string, len(result_set)
)
return queryset, result_set, elapsed_time return queryset, result_set, elapsed_time
def assemble_query(self, terms, search_fields): def assemble_query(self, terms, search_fields):

View File

@@ -1,11 +0,0 @@
from __future__ import unicode_literals
from rest_framework.filters import BaseFilterBackend
class RecentSearchUserFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
if request.user.is_staff or request.user.is_superuser:
return queryset
else:
return queryset.filter(user=self.request.user)

View File

@@ -8,4 +8,3 @@ link_search = Link(text=_('Search'), view='search:search')
link_search_advanced = Link( link_search_advanced = Link(
text=_('Advanced search'), view='search:search_advanced' text=_('Advanced search'), view='search:search_advanced'
) )
link_search_again = Link(text=_('Search again'), view='search:search_again')

View File

@@ -1,40 +0,0 @@
import urlparse
from django.contrib.auth.models import AnonymousUser
from django.db import models
from django.utils.http import urlencode
from .settings import setting_recent_count
class RecentSearchManager(models.Manager):
def add_query_for_user(self, user, query_string, hits):
parsed_query = urlparse.parse_qs(
urlencode(dict(query_string.items()))
)
for key, value in parsed_query.items():
parsed_query[key] = ' '.join(value)
if 'q' in query_string:
# Is a simple query
if not query_string['q']:
# Don't store empty simple searches
return
else:
# Cleanup query string and only store the q parameter
parsed_query = {'q': parsed_query['q']}
if parsed_query and not isinstance(user, AnonymousUser):
# If the URL query has at least one variable with a value
new_recent, created = self.model.objects.get_or_create(
user=user, query=urlencode(parsed_query),
defaults={'hits': hits}
)
if not created:
new_recent.hits = hits
new_recent.save()
for recent_to_delete in self.model.objects.filter(user=user)[setting_recent_count.value:]:
recent_to_delete.delete()

View File

@@ -15,7 +15,10 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='recentsearch', model_name='recentsearch',
name='user', name='user',
field=models.ForeignKey(editable=False, to=settings.AUTH_USER_MODEL, verbose_name='User'), field=models.ForeignKey(
editable=False, to=settings.AUTH_USER_MODEL,
verbose_name='User'
),
preserve_default=True, preserve_default=True,
), ),
] ]

View File

@@ -1,75 +0,0 @@
from __future__ import unicode_literals
import urllib
import urlparse
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import (
python_2_unicode_compatible, smart_str, smart_unicode
)
from django.utils.translation import ugettext_lazy as _
from .managers import RecentSearchManager
@python_2_unicode_compatible
class RecentSearch(models.Model):
"""
Keeps a list of the [n] most recent search keywords for a given user
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL, editable=False, verbose_name=_('User')
)
query = models.TextField(editable=False, verbose_name=_('Query'))
datetime_created = models.DateTimeField(
auto_now=True, db_index=True, verbose_name=_('Datetime created')
)
hits = models.IntegerField(editable=False, verbose_name=_('Hits'))
objects = RecentSearchManager()
def __str__(self):
# TODO: Fix this hack, store the search model name in the recent
# search entry
from .classes import SearchModel
document_search = SearchModel.get('documents.Document')
query_dict = urlparse.parse_qs(
urllib.unquote_plus(smart_str(self.query))
)
if self.is_advanced():
# Advanced search
advanced_string = []
for key, value in query_dict.items():
search_field = document_search.get_search_field(key)
advanced_string.append(
'%s: %s' % (
search_field.label, smart_unicode(' '.join(value))
)
)
display_string = ', '.join(advanced_string)
else:
# Is a simple search
display_string = smart_unicode(' '.join(query_dict['q']))
return '%s (%s)' % (display_string, self.hits)
def save(self, *args, **kwargs):
super(RecentSearch, self).save(*args, **kwargs)
def url(self):
view = 'search:results' if self.is_advanced() else 'search:search'
return '%s?%s' % (reverse(view), self.query)
def is_advanced(self):
return 'q' not in urlparse.parse_qs(self.query)
class Meta:
ordering = ('-datetime_created',)
verbose_name = _('Recent search')
verbose_name_plural = _('Recent searches')

View File

@@ -1,19 +0,0 @@
from __future__ import unicode_literals
from rest_framework import serializers
from user_management.serializers import UserSerializer
from .models import RecentSearch
class RecentSearchSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='rest_api:recentsearch-detail'
)
user = UserSerializer()
class Meta:
fields = ('datetime_created', 'hits', 'query', 'url', 'user')
model = RecentSearch
read_only_fields = ('datetime_created', 'hits', 'query', 'user')

View File

@@ -1,38 +1,21 @@
from __future__ import unicode_literals from __future__ import unicode_literals
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 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
)
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class SearchAPITestCase(APITestCase): class SearchAPITestCase(GenericAPITestCase):
""" """
Test the search API endpoints Test the search 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_search(self): def test_search(self):
document_type = DocumentType.objects.create( document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -45,6 +28,5 @@ class SearchAPITestCase(APITestCase):
'{}?q={}'.format(reverse('rest_api:search-view'), document.label) '{}?q={}'.format(reverse('rest_api:search-view'), document.label)
) )
content = loads(response.content) self.assertEqual(response.data['results'][0]['label'], document.label)
self.assertEqual(content['results'][0]['label'], document.label) self.assertEqual(response.data['count'], 1)
self.assertEqual(content['count'], 1)

View File

@@ -1,23 +1,25 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.test import TestCase
from documents.models import DocumentType from documents.models import DocumentType
from documents.search import document_search from documents.search import document_search
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
from organizations.tests import OrganizationTestCase
from user_management.tests import ( from user_management.tests import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL
) )
class DocumentSearchTestCase(TestCase): class DocumentSearchTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(DocumentSearchTestCase, 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
) )
@@ -28,6 +30,7 @@ class DocumentSearchTestCase(TestCase):
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(DocumentSearchTestCase, self).tearDown()
def test_simple_search_after_related_name_change(self): def test_simple_search_after_related_name_change(self):
""" """

View File

@@ -1,39 +1,26 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import Client
from common.tests.test_views import GenericViewTestCase
from documents.models import DocumentType from documents.models import DocumentType
from documents.search import document_search from documents.search import document_search
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 (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL
)
class Issue46TestCase(TestCase): class Issue46TestCase(GenericViewTestCase):
""" """
Functional tests to make sure issue 46 is fixed Functional tests to make sure issue 46 is fixed
""" """
def setUp(self): def setUp(self):
self.admin_user = get_user_model().objects.create_superuser( super(Issue46TestCase, self).setUp()
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
password=TEST_ADMIN_PASSWORD self.login_superuser()
)
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_count = 4 self.document_count = 4
self.document_type = DocumentType.objects.create( self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -46,9 +33,11 @@ class Issue46TestCase(TestCase):
) )
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(Issue46TestCase, self).tearDown()
def test_advanced_search_past_first_page(self): def test_advanced_search_past_first_page(self):
# Make sure all documents are returned by the search # Make sure all documents are returned by the search
model_list, result_set, elapsed_time = document_search.search( model_list, result_set, elapsed_time = document_search.search(

View File

@@ -2,28 +2,17 @@ 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 APISearchView
APIRecentSearchListView, APIRecentSearchView, APISearchView
)
from .views import AdvancedSearchView, ResultsView, SearchView from .views import AdvancedSearchView, ResultsView, SearchView
urlpatterns = patterns( urlpatterns = patterns(
'dynamic_search.views', 'dynamic_search.views',
url(r'^$', SearchView.as_view(), name='search'), url(r'^$', SearchView.as_view(), name='search'),
url(r'^advanced/$', AdvancedSearchView.as_view(), name='search_advanced'), url(r'^advanced/$', AdvancedSearchView.as_view(), name='search_advanced'),
url(r'^again/$', 'search_again', name='search_again'),
url(r'^results/$', ResultsView.as_view(), name='results'), url(r'^results/$', ResultsView.as_view(), name='results'),
) )
api_urls = patterns( api_urls = patterns(
'', '',
url(
r'^recent_searches/$', APIRecentSearchListView.as_view(),
name='recentsearch-list'
),
url(
r'^recent_searches/(?P<pk>[0-9]+)/$', APIRecentSearchView.as_view(),
name='recentsearch-detail'
),
url(r'^search/$', APISearchView.as_view(), name='search-view'), url(r'^search/$', APISearchView.as_view(), name='search-view'),
) )

View File

@@ -1,11 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import logging import logging
import urlparse
from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.generics import SimpleView, SingleObjectListView from common.generics import SimpleView, SingleObjectListView
@@ -82,12 +79,3 @@ class AdvancedSearchView(SearchView):
return AdvancedSearchForm( return AdvancedSearchForm(
data=self.request.GET, search_model=document_search data=self.request.GET, search_model=document_search
) )
def search_again(request):
query = urlparse.urlparse(
request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))
).query
return HttpResponseRedirect(
'{}?{}'.format(reverse('search:search_advanced'), query)
)

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

@@ -1,76 +1,61 @@
from __future__ import unicode_literals from __future__ import unicode_literals
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 user_management.tests.literals import ( from rest_api.tests import GenericAPITestCase
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
)
from ..models import Folder 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 +73,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):
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,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 SmartLink, SmartLinkCondition from .models import SmartLink, SmartLinkCondition
@@ -13,7 +15,7 @@ class SmartLinkConditionInline(admin.StackedInline):
@admin.register(SmartLink) @admin.register(SmartLink)
class SmartLinkAdmin(admin.ModelAdmin): class SmartLinkAdmin(OrganizationAdminMixin, admin.ModelAdmin):
def document_type_list(self, instance): def document_type_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

@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from django.apps import apps
from django.db import models
class OrganizationSmartLinkConditionManager(models.Manager):
def get_queryset(self):
SmartLink = apps.get_model('linking', 'SmartLink')
return super(
OrganizationSmartLinkConditionManager, self
).get_queryset().filter(
smart_link__in=SmartLink.on_organization.all(),
)

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'),
('linking', '0005_auto_20150729_2344'),
]
operations = [
migrations.AddField(
model_name='smartlink',
name='organization',
field=models.ForeignKey(
default=organizations.shortcuts.get_current_organization,
to='organizations.Organization'
),
),
]

View File

@@ -7,14 +7,21 @@ 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.managers import CurrentOrganizationManager
from organizations.shortcuts import get_current_organization
from .literals import ( from .literals import (
INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES
) )
from .managers import OrganizationSmartLinkConditionManager
@python_2_unicode_compatible @python_2_unicode_compatible
class SmartLink(models.Model): class SmartLink(models.Model):
organization = models.ForeignKey(
Organization, default=get_current_organization
)
label = models.CharField(max_length=96, verbose_name=_('Label')) label = models.CharField(max_length=96, verbose_name=_('Label'))
dynamic_label = models.CharField( dynamic_label = models.CharField(
blank=True, max_length=96, help_text=_( blank=True, max_length=96, help_text=_(
@@ -29,6 +36,9 @@ class SmartLink(models.Model):
DocumentType, verbose_name=_('Document types') DocumentType, verbose_name=_('Document types')
) )
objects = models.Manager()
on_organization = CurrentOrganizationManager()
def __str__(self): def __str__(self):
return self.label return self.label
@@ -75,9 +85,9 @@ class SmartLink(models.Model):
smart_link_query |= condition_query smart_link_query |= condition_query
if smart_link_query: if smart_link_query:
return Document.objects.filter(smart_link_query) return Document.on_organization.filter(smart_link_query)
else: else:
return Document.objects.none() return Document.on_organization.none()
def resolve_for(self, document): def resolve_for(self, document):
return ResolvedSmartLink( return ResolvedSmartLink(
@@ -123,6 +133,9 @@ class SmartLinkCondition(models.Model):
) )
enabled = models.BooleanField(default=True, verbose_name=_('Enabled')) enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
objects = models.Manager()
on_organization = OrganizationSmartLinkConditionManager()
def __str__(self): def __str__(self):
return '%s foreign %s %s %s %s' % ( return '%s foreign %s %s %s %s' % (
self.get_inclusion_display(), self.get_inclusion_display(),

View File

@@ -3,10 +3,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
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_DOCUMENT_PATH, TEST_DOCUMENT_TYPE from documents.tests import TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
from organizations.tests 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
) )
@@ -17,9 +18,11 @@ from .literals import TEST_SMART_LINK_LABEL, TEST_SMART_LINK_DYNAMIC_LABEL
@override_settings(OCR_AUTO_OCR=False) @override_settings(OCR_AUTO_OCR=False)
class SmartLinkTestCase(TestCase): class SmartLinkTestCase(OrganizationTestCase):
def setUp(self): def setUp(self):
self.document_type = DocumentType.objects.create( super(SmartLinkTestCase, self).setUp()
self.document_type = DocumentType.on_organization.create(
label=TEST_DOCUMENT_TYPE label=TEST_DOCUMENT_TYPE
) )
@@ -28,16 +31,17 @@ class SmartLinkTestCase(TestCase):
file_object=file_object file_object=file_object
) )
self.user = get_user_model().objects.create_superuser( self.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
) )
def tearDown(self): def tearDown(self):
self.document_type.delete() self.document_type.delete()
super(SmartLinkTestCase, self).tearDown()
def test_dynamic_label(self): def test_dynamic_label(self):
smart_link = SmartLink.objects.create( smart_link = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL, label=TEST_SMART_LINK_LABEL,
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
) )

View File

@@ -0,0 +1,94 @@
from __future__ import unicode_literals
from organizations.tests.test_organization_views import OrganizationViewTestCase
from ..models import SmartLink
from .literals import (
TEST_SMART_LINK_DYNAMIC_LABEL, TEST_SMART_LINK_EDITED_LABEL,
TEST_SMART_LINK_LABEL
)
class OrganizationIndexViewTestCase(OrganizationViewTestCase):
def create_smart_link(self):
self.smart_link = SmartLink.on_organization.create(
organization=self.organization_a, label=TEST_SMART_LINK_LABEL
)
def test_smart_link_create_view(self):
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.post(
'linking:smart_link_create', data={
'label': TEST_SMART_LINK_LABEL
}
)
self.assertEqual(SmartLink.on_organization.count(), 1)
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
self.assertEqual(SmartLink.on_organization.count(), 0)
def test_smart_link_delete_view(self):
self.create_smart_link()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'linking:smart_link_delete', args=(self.smart_link.pk,)
)
self.assertEqual(response.status_code, 404)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
self.assertEqual(SmartLink.on_organization.count(), 1)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
'linking:smart_link_delete', args=(self.smart_link.pk,)
)
self.assertEqual(response.status_code, 302)
self.assertEqual(SmartLink.on_organization.count(), 0)
def test_smart_link_edit_view(self):
self.create_smart_link()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.post(
'linking:smart_link_edit', args=(self.smart_link.pk,),
data={
'label': TEST_SMART_LINK_EDITED_LABEL
}
)
self.assertEqual(response.status_code, 404)
self.assertEqual(self.smart_link.label, TEST_SMART_LINK_LABEL)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.post(
'linking:smart_link_edit', args=(self.smart_link.pk,),
data={
'label': TEST_SMART_LINK_EDITED_LABEL
}
)
self.assertEqual(response.status_code, 302)
self.smart_link.refresh_from_db()
self.assertEqual(
self.smart_link.label, TEST_SMART_LINK_EDITED_LABEL
)
def test_smart_link_list_view(self):
self.create_smart_link()
with self.settings(ORGANIZATION_ID=self.organization_b.pk):
response = self.get('linking:smart_link_list')
self.assertNotContains(
response, text=self.smart_link.label, status_code=200
)
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
response = self.get('linking:smart_link_list')
self.assertContains(
response, text=self.smart_link.label, status_code=200
)

View File

@@ -29,7 +29,7 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
) )
self.assertEquals(response.status_code, 403) self.assertEquals(response.status_code, 403)
self.assertEqual(SmartLink.objects.count(), 0) self.assertEqual(SmartLink.on_organization.count(), 0)
def test_smart_link_create_view_with_permission(self): def test_smart_link_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)
@@ -44,21 +44,23 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
self.assertContains(response, text='created', status_code=200) self.assertContains(response, text='created', status_code=200)
self.assertEqual(SmartLink.objects.count(), 1) self.assertEqual(SmartLink.on_organization.count(), 1)
self.assertEqual( self.assertEqual(
SmartLink.objects.first().label, TEST_SMART_LINK_LABEL SmartLink.on_organization.first().label, TEST_SMART_LINK_LABEL
) )
def test_smart_link_delete_view_no_permission(self): def test_smart_link_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)
smart_link = SmartLink.objects.create(label=TEST_SMART_LINK_LABEL) smart_link = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL
)
response = self.post( response = self.post(
'linking:smart_link_delete', args=(smart_link.pk,) 'linking:smart_link_delete', args=(smart_link.pk,)
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
self.assertEqual(SmartLink.objects.count(), 1) self.assertEqual(SmartLink.on_organization.count(), 1)
def test_smart_link_delete_view_with_permission(self): def test_smart_link_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)
@@ -67,19 +69,23 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
permission_smart_link_delete.stored_permission permission_smart_link_delete.stored_permission
) )
smart_link = SmartLink.objects.create(label=TEST_SMART_LINK_LABEL) smart_link = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL
)
response = self.post( response = self.post(
'linking:smart_link_delete', args=(smart_link.pk,), follow=True 'linking:smart_link_delete', args=(smart_link.pk,), follow=True
) )
self.assertContains(response, text='deleted', status_code=200) self.assertContains(response, text='deleted', status_code=200)
self.assertEqual(SmartLink.objects.count(), 0) self.assertEqual(SmartLink.on_organization.count(), 0)
def test_smart_link_edit_view_no_permission(self): def test_smart_link_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)
smart_link = SmartLink.objects.create(label=TEST_SMART_LINK_LABEL) smart_link = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL
)
response = self.post( response = self.post(
'linking:smart_link_edit', args=(smart_link.pk,), data={ 'linking:smart_link_edit', args=(smart_link.pk,), data={
@@ -87,7 +93,7 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
} }
) )
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
smart_link = SmartLink.objects.get(pk=smart_link.pk) smart_link = SmartLink.on_organization.get(pk=smart_link.pk)
self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL) self.assertEqual(smart_link.label, TEST_SMART_LINK_LABEL)
def test_smart_link_edit_view_with_permission(self): def test_smart_link_edit_view_with_permission(self):
@@ -97,7 +103,9 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
permission_smart_link_edit.stored_permission permission_smart_link_edit.stored_permission
) )
smart_link = SmartLink.objects.create(label=TEST_SMART_LINK_LABEL) smart_link = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL
)
response = self.post( response = self.post(
'linking:smart_link_edit', args=(smart_link.pk,), data={ 'linking:smart_link_edit', args=(smart_link.pk,), data={
@@ -105,18 +113,18 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
}, follow=True }, follow=True
) )
smart_link = SmartLink.objects.get(pk=smart_link.pk) smart_link = SmartLink.on_organization.get(pk=smart_link.pk)
self.assertContains(response, text='update', status_code=200) self.assertContains(response, text='update', status_code=200)
self.assertEqual(smart_link.label, TEST_SMART_LINK_EDITED_LABEL) self.assertEqual(smart_link.label, TEST_SMART_LINK_EDITED_LABEL)
def setup_smart_links(self): def setup_smart_links(self):
smart_link = SmartLink.objects.create( smart_link = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL, label=TEST_SMART_LINK_LABEL,
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
) )
smart_link.document_types.add(self.document_type) smart_link.document_types.add(self.document_type)
smart_link_2 = SmartLink.objects.create( smart_link_2 = SmartLink.on_organization.create(
label=TEST_SMART_LINK_LABEL, label=TEST_SMART_LINK_LABEL,
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
) )

View File

@@ -31,10 +31,10 @@ logger = logging.getLogger(__name__)
class ResolvedSmartLinkView(DocumentListView): class ResolvedSmartLinkView(DocumentListView):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.document = get_object_or_404( self.document = get_object_or_404(
Document, pk=self.kwargs['document_pk'] Document.on_organization, pk=self.kwargs['document_pk']
) )
self.smart_link = get_object_or_404( self.smart_link = get_object_or_404(
SmartLink, pk=self.kwargs['smart_link_pk'] SmartLink.on_organization, pk=self.kwargs['smart_link_pk']
) )
try: try:
@@ -63,7 +63,7 @@ class ResolvedSmartLinkView(DocumentListView):
try: try:
queryset = self.smart_link.get_linked_document_for(self.document) queryset = self.smart_link.get_linked_document_for(self.document)
except Exception as exception: except Exception as exception:
queryset = Document.objects.none() queryset = Document.on_organization.none()
if self.request.user.is_staff or self.request.user.is_superuser: if self.request.user.is_staff or self.request.user.is_superuser:
messages.error( messages.error(
@@ -110,12 +110,14 @@ class SetupSmartLinkDocumentTypesView(AssignRemoveView):
} }
def get_object(self): def get_object(self):
return get_object_or_404(SmartLink, pk=self.kwargs['pk']) return get_object_or_404(
SmartLink.on_organization, pk=self.kwargs['pk']
)
def left_list(self): def left_list(self):
# TODO: filter document type list by user ACL # TODO: filter document type list by user ACL
return AssignRemoveView.generate_choices( return AssignRemoveView.generate_choices(
DocumentType.objects.exclude( DocumentType.on_organization.exclude(
pk__in=self.get_object().document_types.all() pk__in=self.get_object().document_types.all()
) )
) )
@@ -144,12 +146,14 @@ class SmartLinkListView(SingleObjectListView):
return super(SmartLinkListView, self).get_queryset() return super(SmartLinkListView, self).get_queryset()
def get_smart_link_queryset(self): def get_smart_link_queryset(self):
return SmartLink.objects.all() return SmartLink.on_organization.all()
class DocumentSmartLinkListView(SmartLinkListView): class DocumentSmartLinkListView(SmartLinkListView):
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(
@@ -174,7 +178,7 @@ class DocumentSmartLinkListView(SmartLinkListView):
} }
def get_smart_link_queryset(self): def get_smart_link_queryset(self):
return ResolvedSmartLink.objects.filter( return ResolvedSmartLink.on_organization.filter(
document_types=self.document.document_type, enabled=True document_types=self.document.document_type, enabled=True
) )
@@ -188,7 +192,6 @@ class SmartLinkCreateView(SingleObjectCreateView):
class SmartLinkEditView(SingleObjectEditView): class SmartLinkEditView(SingleObjectEditView):
form_class = SmartLinkForm form_class = SmartLinkForm
model = SmartLink
post_action_redirect = reverse_lazy('linking:smart_link_list') post_action_redirect = reverse_lazy('linking:smart_link_list')
view_permission = permission_smart_link_edit view_permission = permission_smart_link_edit
@@ -198,9 +201,11 @@ class SmartLinkEditView(SingleObjectEditView):
'title': _('Edit smart link: %s') % self.get_object() 'title': _('Edit smart link: %s') % self.get_object()
} }
def get_queryset(self):
return SmartLink.on_organization.all()
class SmartLinkDeleteView(SingleObjectDeleteView): class SmartLinkDeleteView(SingleObjectDeleteView):
model = SmartLink
post_action_redirect = reverse_lazy('linking:smart_link_list') post_action_redirect = reverse_lazy('linking:smart_link_list')
view_permission = permission_smart_link_delete view_permission = permission_smart_link_delete
@@ -210,6 +215,9 @@ class SmartLinkDeleteView(SingleObjectDeleteView):
'title': _('Delete smart link: %s') % self.get_object() 'title': _('Delete smart link: %s') % self.get_object()
} }
def get_queryset(self):
return SmartLink.on_organization.all()
class SmartLinkConditionListView(SingleObjectListView): class SmartLinkConditionListView(SingleObjectListView):
view_permission = permission_smart_link_edit view_permission = permission_smart_link_edit
@@ -227,7 +235,9 @@ class SmartLinkConditionListView(SingleObjectListView):
return self.get_smart_link().conditions.all() return self.get_smart_link().conditions.all()
def get_smart_link(self): def get_smart_link(self):
return get_object_or_404(SmartLink, pk=self.kwargs['pk']) return get_object_or_404(
SmartLink.on_organization, pk=self.kwargs['pk']
)
class SmartLinkConditionCreateView(SingleObjectCreateView): class SmartLinkConditionCreateView(SingleObjectCreateView):
@@ -269,12 +279,13 @@ class SmartLinkConditionCreateView(SingleObjectCreateView):
return self.get_smart_link().conditions.all() return self.get_smart_link().conditions.all()
def get_smart_link(self): def get_smart_link(self):
return get_object_or_404(SmartLink, pk=self.kwargs['pk']) return get_object_or_404(
SmartLink.on_organization, pk=self.kwargs['pk']
)
class SmartLinkConditionEditView(SingleObjectEditView): class SmartLinkConditionEditView(SingleObjectEditView):
form_class = SmartLinkConditionForm form_class = SmartLinkConditionForm
model = SmartLinkCondition
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
try: try:
@@ -306,10 +317,11 @@ class SmartLinkConditionEditView(SingleObjectEditView):
) )
) )
def get_queryset(self):
return SmartLinkCondition.on_organization.all()
class SmartLinkConditionDeleteView(SingleObjectDeleteView): class SmartLinkConditionDeleteView(SingleObjectDeleteView):
model = SmartLinkCondition
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
try: try:
Permission.check_permissions( Permission.check_permissions(
@@ -341,3 +353,6 @@ class SmartLinkConditionDeleteView(SingleObjectDeleteView):
self.get_object().smart_link.pk, self.get_object().smart_link.pk,
) )
) )
def get_queryset(self):
return SmartLinkCondition.on_organization.all()

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

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