Compare commits
27 Commits
feature/mu
...
feature/mu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ede69c3f | ||
|
|
b6161e9d92 | ||
|
|
aa56e8ae14 | ||
|
|
1b8436add7 | ||
|
|
ce8377da03 | ||
|
|
4b7c4335e1 | ||
|
|
4f2744f916 | ||
|
|
883e1cc510 | ||
|
|
9483309332 | ||
|
|
07ac1cbbbe | ||
|
|
e62e6a9a08 | ||
|
|
97089670ee | ||
|
|
063b325986 | ||
|
|
2ea3c08c97 | ||
|
|
f3f5cff36e | ||
|
|
d0aee4f72b | ||
|
|
5ac1276f25 | ||
|
|
113ad144e0 | ||
|
|
debec7b4a2 | ||
|
|
0be3e130c1 | ||
|
|
dd28e3dc09 | ||
|
|
09e375654d | ||
|
|
c531631a37 | ||
|
|
a76a55f70a | ||
|
|
98e6ba2b50 | ||
|
|
945158bd60 | ||
|
|
ab831aa493 |
16
HISTORY.rst
16
HISTORY.rst
@@ -1,3 +1,19 @@
|
|||||||
|
2.1.4 (2016-XX-XX)
|
||||||
|
==================
|
||||||
|
- Add missing link to the 2.1.3 release notes in the index file.
|
||||||
|
- Improve TempfileCheckMixin.
|
||||||
|
|
||||||
|
2.1.3 (2016-06-29)
|
||||||
|
==================
|
||||||
|
- Add help message when initialsetup migration phase fails. Relates to GitLab issue #296.
|
||||||
|
- Start using self.setdout instead of print as per documentation.
|
||||||
|
- Fix GitLab issue #295, "When editing a user the top bar jumps to the name of the user".
|
||||||
|
- Normalize handling of temporary file and directory creation.
|
||||||
|
- Fix GitLab issue #309, "Temp files quickly filling-up my /tmp (1GB tmpfs)".
|
||||||
|
- Explicitly check for residual temporary files in tests.
|
||||||
|
- Add missing temporary file cleanup for office documents.
|
||||||
|
- Fix file descriptor leak in the document signature download test.
|
||||||
|
|
||||||
2.1.2 (2016-05-20)
|
2.1.2 (2016-05-20)
|
||||||
==================
|
==================
|
||||||
- Sort document languages and user profile locale language lists. GitLab issue #292.
|
- Sort document languages and user profile locale language lists. GitLab issue #292.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|PyPI badge| |Build Status| |Coverage badge| |Installs badge| |License badge|
|
|PyPI badge| |Build Status| |Coverage badge| |License badge|
|
||||||
|
|
||||||
|Logo|
|
|Logo|
|
||||||
|
|
||||||
@@ -63,8 +63,6 @@ Contribute
|
|||||||
:target: https://gitlab.com/mayan-edms/mayan-edms/commits/master
|
:target: https://gitlab.com/mayan-edms/mayan-edms/commits/master
|
||||||
.. |Logo| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/mayan_logo.png
|
.. |Logo| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/mayan_logo.png
|
||||||
.. |Animation| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/overview.gif
|
.. |Animation| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/overview.gif
|
||||||
.. |Installs badge| image:: http://img.shields.io/pypi/dm/mayan-edms.svg?style=flat
|
|
||||||
:target: https://crate.io/packages/mayan-edms/
|
|
||||||
.. |PyPI badge| image:: http://img.shields.io/pypi/v/mayan-edms.svg?style=flat
|
.. |PyPI badge| image:: http://img.shields.io/pypi/v/mayan-edms.svg?style=flat
|
||||||
:target: http://badge.fury.io/py/mayan-edms
|
:target: http://badge.fury.io/py/mayan-edms
|
||||||
.. |License badge| image:: http://img.shields.io/badge/license-Apache%202.0-green.svg?style=flat
|
.. |License badge| image:: http://img.shields.io/badge/license-Apache%202.0-green.svg?style=flat
|
||||||
|
|||||||
87
docs/releases/2.1.3.rst
Normal file
87
docs/releases/2.1.3.rst
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
===============================
|
||||||
|
Mayan EDMS v2.1.3 release notes
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Released: June 29, 2016
|
||||||
|
|
||||||
|
What's new
|
||||||
|
==========
|
||||||
|
|
||||||
|
This is a bug-fix release and all users are encouraged to upgrade.
|
||||||
|
|
||||||
|
Temporary files cleanup
|
||||||
|
-----------------------
|
||||||
|
When uploading PDF files that had been OCRed by previous software, the text
|
||||||
|
parser backend that uses Poppler, would leave behind some temporary files in
|
||||||
|
the /tmp folder. The issue has been resolved and from the fix a test mixin
|
||||||
|
system check has been devised that will identify places in the codebase with
|
||||||
|
similar behaviors, reducing the recurrence of similar issues in the future.
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Add help message when initialsetup migration phase fails. Relates to GitLab issue #296
|
||||||
|
- Start using self.setdout instead of print as per documentation.
|
||||||
|
- Fix GitLab issue #295, "When editing a user the top bar jumps to the name of the user".
|
||||||
|
- Normalize handling of temporary file and directory creation.
|
||||||
|
- Explicitly check for residual temporary files in tests.
|
||||||
|
- Add missing temporary file cleanup for office documents.
|
||||||
|
- Fix file descriptor leak in the document signature download test.
|
||||||
|
|
||||||
|
Removals
|
||||||
|
--------
|
||||||
|
* None
|
||||||
|
|
||||||
|
Upgrading from a previous version
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Using PIP
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Type in the console::
|
||||||
|
|
||||||
|
$ pip install -U mayan-edms
|
||||||
|
|
||||||
|
the requirements will also be updated automatically.
|
||||||
|
|
||||||
|
Using Git
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
If you installed Mayan EDMS by cloning the Git repository issue the commands::
|
||||||
|
|
||||||
|
$ git reset --hard HEAD
|
||||||
|
$ git pull
|
||||||
|
|
||||||
|
otherwise download the compressed archived and uncompress it overriding the
|
||||||
|
existing installation.
|
||||||
|
|
||||||
|
Next upgrade/add the new requirements::
|
||||||
|
|
||||||
|
$ pip install --upgrade -r requirements.txt
|
||||||
|
|
||||||
|
Common steps
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Migrate existing database schema with::
|
||||||
|
|
||||||
|
$ mayan-edms.py performupgrade
|
||||||
|
|
||||||
|
Add new static media::
|
||||||
|
|
||||||
|
$ mayan-edms.py collectstatic --noinput
|
||||||
|
|
||||||
|
The upgrade procedure is now complete.
|
||||||
|
|
||||||
|
|
||||||
|
Backward incompatible changes
|
||||||
|
=============================
|
||||||
|
|
||||||
|
* None
|
||||||
|
|
||||||
|
Bugs fixed or issues closed
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* `GitLab issue #295 <https://gitlab.com/mayan-edms/mayan-edms/issues/295>`_ When editing a user the top bar jumps to the name of the user
|
||||||
|
* `GitLab issue #309 <https://gitlab.com/mayan-edms/mayan-edms/issues/309>`_ Temp files quickly filling-up my /tmp (1GB tmpfs)
|
||||||
|
|
||||||
|
|
||||||
|
.. _PyPI: https://pypi.python.org/pypi/mayan-edms/
|
||||||
@@ -22,6 +22,7 @@ versions of the documentation contain the release notes for any later releases.
|
|||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
|
2.1.3
|
||||||
2.1.2
|
2.1.2
|
||||||
2.1.1
|
2.1.1
|
||||||
2.1
|
2.1
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__title__ = 'Mayan EDMS'
|
__title__ = 'Mayan EDMS'
|
||||||
__version__ = '2.1.2'
|
__version__ = '2.1.3'
|
||||||
__build__ = 0x020102
|
__build__ = 0x020103
|
||||||
__author__ = 'Roberto Rosario'
|
__author__ = 'Roberto Rosario'
|
||||||
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
||||||
__description__ = 'Free Open Source Electronic Document Management System'
|
__description__ = 'Free Open Source Electronic Document Management System'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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.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
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
|
|
||||||
@@ -15,6 +14,12 @@ class UsernameModelBackend(ModelBackend):
|
|||||||
if user.check_password(password):
|
if user.check_password(password):
|
||||||
return user
|
return user
|
||||||
except UserModel.DoesNotExist:
|
except UserModel.DoesNotExist:
|
||||||
# Run the default password hasher once to reduce the timing
|
# Check for superadmins, they can login from any organization.
|
||||||
# difference between an existing and a non-existing user (#20760).
|
try:
|
||||||
UserModel().set_password(password)
|
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)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ 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 organizations.tests.base import OrganizationTestCase
|
||||||
|
|||||||
@@ -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,9 +12,15 @@ 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)
|
management.call_command('createorganizationadmin', interactive=False)
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ setting_temporary_directory = namespace.add_setting(
|
|||||||
global_name='COMMON_TEMPORARY_DIRECTORY', default=tempfile.gettempdir(),
|
global_name='COMMON_TEMPORARY_DIRECTORY', default=tempfile.gettempdir(),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
'Temporary directory used site wide to store thumbnails, previews '
|
'Temporary directory used site wide to store thumbnails, previews '
|
||||||
'and temporary files. If none is specified, one will be created '
|
'and temporary files.'
|
||||||
'using tempfile.mkdtemp().'
|
|
||||||
),
|
),
|
||||||
is_path=True
|
is_path=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from .base import BaseTestCase # NOQA
|
||||||
|
from .decorators import skip_file_descriptor_check # NOQA
|
||||||
|
|||||||
11
mayan/apps/common/tests/base.py
Normal file
11
mayan/apps/common/tests/base.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from .mixins import OpenFileCheckMixin, TempfileCheckMixin
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestCase(OpenFileCheckMixin, TempfileCheckMixin, TestCase):
|
||||||
|
"""
|
||||||
|
This is the most basic test case class any test in the project should use.
|
||||||
|
"""
|
||||||
5
mayan/apps/common/tests/decorators.py
Normal file
5
mayan/apps/common/tests/decorators.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
def skip_file_descriptor_check(func):
|
||||||
|
def func_wrapper(item):
|
||||||
|
item._skip_file_descriptor_test = True
|
||||||
|
return func(item)
|
||||||
|
return func_wrapper
|
||||||
54
mayan/apps/common/tests/mixins.py
Normal file
54
mayan/apps/common/tests/mixins.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
from ..settings import setting_temporary_directory
|
||||||
|
|
||||||
|
|
||||||
|
class TempfileCheckMixin(object):
|
||||||
|
def _get_temporary_entries(self):
|
||||||
|
return os.listdir(setting_temporary_directory.value)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TempfileCheckMixin, self).setUp()
|
||||||
|
self._temporary_entries = self._get_temporary_entries()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
for temporary_entry in self._get_temporary_entries():
|
||||||
|
self.assertFalse(
|
||||||
|
temporary_entry not in self._temporary_entries,
|
||||||
|
msg='Orphan temporary file. The number of temporary file and '
|
||||||
|
'directories at the start and at the end of the test are not the '
|
||||||
|
'same.'
|
||||||
|
)
|
||||||
|
|
||||||
|
super(TempfileCheckMixin, self).tearDown()
|
||||||
|
|
||||||
|
|
||||||
|
class OpenFileCheckMixin(object):
|
||||||
|
def _get_descriptor_count(self):
|
||||||
|
process = psutil.Process()
|
||||||
|
return process.num_fds()
|
||||||
|
|
||||||
|
def _get_open_files(self):
|
||||||
|
process = psutil.Process()
|
||||||
|
return process.open_files()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(OpenFileCheckMixin, self).setUp()
|
||||||
|
self._open_files = self._get_open_files()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
if not getattr(self, '_skip_file_descriptor_test', False):
|
||||||
|
for new_open_file in self._get_open_files():
|
||||||
|
self.assertFalse(
|
||||||
|
new_open_file not in self._open_files,
|
||||||
|
msg='File descriptor leak. The number of file descriptors '
|
||||||
|
'at the start and at the end of the test are not the same.'
|
||||||
|
)
|
||||||
|
|
||||||
|
self._skip_file_descriptor_test = False
|
||||||
|
|
||||||
|
super(OpenFileCheckMixin, self).tearDown()
|
||||||
@@ -5,7 +5,6 @@ from django.contrib.auth import get_user_model
|
|||||||
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 organizations.tests.base import OrganizationTestCase
|
||||||
from permissions import Permission
|
from permissions import Permission
|
||||||
@@ -23,6 +22,7 @@ from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
|
|||||||
class GenericViewTestCase(OrganizationTestCase):
|
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().on_organization.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,
|
||||||
@@ -46,7 +46,6 @@ class GenericViewTestCase(OrganizationTestCase):
|
|||||||
self.client.logout()
|
self.client.logout()
|
||||||
if self.has_test_view:
|
if self.has_test_view:
|
||||||
urlpatterns.pop(0)
|
urlpatterns.pop(0)
|
||||||
|
|
||||||
super(GenericViewTestCase, self).tearDown()
|
super(GenericViewTestCase, self).tearDown()
|
||||||
|
|
||||||
def add_test_view(self, test_object):
|
def add_test_view(self, test_object):
|
||||||
@@ -95,6 +94,12 @@ class GenericViewTestCase(OrganizationTestCase):
|
|||||||
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):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import types
|
import types
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ from django.utils.datastructures import MultiValueDict
|
|||||||
from django.utils.http import urlquote as django_urlquote
|
from django.utils.http import urlquote as django_urlquote
|
||||||
from django.utils.http import urlencode as django_urlencode
|
from django.utils.http import urlencode as django_urlencode
|
||||||
|
|
||||||
|
from .settings import setting_temporary_directory
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -42,17 +45,23 @@ def encapsulate(function):
|
|||||||
return lambda: function
|
return lambda: function
|
||||||
|
|
||||||
|
|
||||||
def fs_cleanup(filename, suppress_exceptions=True):
|
def fs_cleanup(filename, file_descriptor=None, suppress_exceptions=True):
|
||||||
"""
|
"""
|
||||||
Tries to remove the given filename. Ignores non-existent files
|
Tries to remove the given filename. Ignores non-existent files
|
||||||
"""
|
"""
|
||||||
|
if file_descriptor:
|
||||||
|
os.close(file_descriptor)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
except OSError:
|
except OSError:
|
||||||
if suppress_exceptions:
|
try:
|
||||||
pass
|
shutil.rmtree(filename)
|
||||||
else:
|
except OSError:
|
||||||
raise
|
if suppress_exceptions:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def get_descriptor(file_input, read=True):
|
def get_descriptor(file_input, read=True):
|
||||||
@@ -69,6 +78,21 @@ def get_descriptor(file_input, read=True):
|
|||||||
return file_input
|
return file_input
|
||||||
|
|
||||||
|
|
||||||
|
def TemporaryFile(*args, **kwargs):
|
||||||
|
kwargs.update({'dir': setting_temporary_directory.value})
|
||||||
|
return tempfile.TemporaryFile(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def mkdtemp(*args, **kwargs):
|
||||||
|
kwargs.update({'dir': setting_temporary_directory.value})
|
||||||
|
return tempfile.mkdtemp(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def mkstemp(*args, **kwargs):
|
||||||
|
kwargs.update({'dir': setting_temporary_directory.value})
|
||||||
|
return tempfile.mkstemp(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def return_attrib(obj, attrib, arguments=None):
|
def return_attrib(obj, attrib, arguments=None):
|
||||||
try:
|
try:
|
||||||
if isinstance(attrib, types.FunctionType):
|
if isinstance(attrib, types.FunctionType):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
import io
|
import io
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
@@ -16,7 +15,7 @@ import sh
|
|||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.utils import fs_cleanup
|
from common.utils import fs_cleanup, mkstemp
|
||||||
|
|
||||||
from ..classes import ConverterBase
|
from ..classes import ConverterBase
|
||||||
from ..exceptions import PageCountError
|
from ..exceptions import PageCountError
|
||||||
@@ -50,7 +49,7 @@ class Python(ConverterBase):
|
|||||||
|
|
||||||
if self.mime_type == 'application/pdf' and pdftoppm:
|
if self.mime_type == 'application/pdf' and pdftoppm:
|
||||||
|
|
||||||
new_file_object, input_filepath = tempfile.mkstemp()
|
new_file_object, input_filepath = mkstemp()
|
||||||
self.file_object.seek(0)
|
self.file_object.seek(0)
|
||||||
os.write(new_file_object, self.file_object.read())
|
os.write(new_file_object, self.file_object.read())
|
||||||
self.file_object.seek(0)
|
self.file_object.seek(0)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
@@ -16,7 +15,7 @@ import sh
|
|||||||
from django.utils.translation import string_concat, ugettext_lazy as _
|
from django.utils.translation import string_concat, ugettext_lazy as _
|
||||||
|
|
||||||
from common.settings import setting_temporary_directory
|
from common.settings import setting_temporary_directory
|
||||||
from common.utils import fs_cleanup
|
from common.utils import fs_cleanup, mkstemp
|
||||||
from mimetype.api import get_mimetype
|
from mimetype.api import get_mimetype
|
||||||
|
|
||||||
from .exceptions import InvalidOfficeFormat, OfficeConversionError
|
from .exceptions import InvalidOfficeFormat, OfficeConversionError
|
||||||
@@ -122,7 +121,7 @@ class ConverterBase(object):
|
|||||||
) % setting_libreoffice_path.value
|
) % setting_libreoffice_path.value
|
||||||
)
|
)
|
||||||
|
|
||||||
new_file_object, input_filepath = tempfile.mkstemp()
|
new_file_object, input_filepath = mkstemp()
|
||||||
self.file_object.seek(0)
|
self.file_object.seek(0)
|
||||||
os.write(new_file_object, self.file_object.read())
|
os.write(new_file_object, self.file_object.read())
|
||||||
self.file_object.seek(0)
|
self.file_object.seek(0)
|
||||||
@@ -168,6 +167,7 @@ class ConverterBase(object):
|
|||||||
yield data
|
yield data
|
||||||
|
|
||||||
fs_cleanup(input_filepath)
|
fs_cleanup(input_filepath)
|
||||||
|
fs_cleanup(converted_output)
|
||||||
|
|
||||||
def get_page(self, output_format=DEFAULT_FILE_FORMAT, as_base64=False):
|
def get_page(self, output_format=DEFAULT_FILE_FORMAT, as_base64=False):
|
||||||
if not self.image:
|
if not self.image:
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import io
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import gnupg
|
import gnupg
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
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 (
|
||||||
DecryptionError, KeyDoesNotExist, KeyFetchingError, VerificationError
|
DecryptionError, KeyDoesNotExist, KeyFetchingError, VerificationError
|
||||||
@@ -22,7 +24,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class KeyManager(models.Manager):
|
class KeyManager(models.Manager):
|
||||||
def decrypt_file(self, file_object, all_keys=False, key_fingerprint=None, key_id=None):
|
def decrypt_file(self, file_object, all_keys=False, key_fingerprint=None, key_id=None):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
|
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ class KeyManager(models.Manager):
|
|||||||
return io.BytesIO(decrypt_result.data)
|
return io.BytesIO(decrypt_result.data)
|
||||||
|
|
||||||
def receive_key(self, key_id):
|
def receive_key(self, key_id):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
|
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ class KeyManager(models.Manager):
|
|||||||
return self.create(key_data=key_data)
|
return self.create(key_data=key_data)
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
|
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
@@ -118,7 +120,7 @@ class KeyManager(models.Manager):
|
|||||||
return self.filter(key_type=KEY_TYPE_SECRET)
|
return self.filter(key_type=KEY_TYPE_SECRET)
|
||||||
|
|
||||||
def verify_file(self, file_object, signature_file=None, all_keys=False, key_fingerprint=None, key_id=None):
|
def verify_file(self, file_object, signature_file=None, all_keys=False, key_fingerprint=None, key_id=None):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
|
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
@@ -156,7 +158,7 @@ class KeyManager(models.Manager):
|
|||||||
if signature_file:
|
if signature_file:
|
||||||
# Save the original data and invert the argument order
|
# Save the original data and invert the argument order
|
||||||
# Signature first, file second
|
# Signature first, file second
|
||||||
temporary_file_object, temporary_filename = tempfile.mkstemp()
|
temporary_file_object, temporary_filename = mkstemp()
|
||||||
os.write(temporary_file_object, file_object.read())
|
os.write(temporary_file_object, file_object.read())
|
||||||
os.close(temporary_file_object)
|
os.close(temporary_file_object)
|
||||||
|
|
||||||
@@ -192,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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
21
mayan/apps/django_gpg/migrations/0007_key_organization.py
Normal file
21
mayan/apps/django_gpg/migrations/0007_key_organization.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -4,7 +4,6 @@ from datetime import date
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import gnupg
|
import gnupg
|
||||||
|
|
||||||
@@ -14,20 +13,24 @@ 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 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 (
|
||||||
ERROR_MSG_NEED_PASSPHRASE, ERROR_MSG_BAD_PASSPHRASE,
|
ERROR_MSG_NEED_PASSPHRASE, ERROR_MSG_BAD_PASSPHRASE,
|
||||||
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__)
|
||||||
|
|
||||||
|
|
||||||
def gpg_command(function):
|
def gpg_command(function):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
gpg = gnupg.GPG(
|
gpg = gnupg.GPG(
|
||||||
@@ -43,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')
|
||||||
@@ -71,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)
|
||||||
@@ -85,14 +95,14 @@ 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):
|
||||||
return reverse('django_gpg:key_detail', args=(self.pk,))
|
return reverse('django_gpg:key_detail', args=(self.pk,))
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
|
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
@@ -122,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.
|
||||||
@@ -133,7 +140,7 @@ class Key(models.Model):
|
|||||||
# file, and appear to be due to random data being inserted in the
|
# file, and appear to be due to random data being inserted in the
|
||||||
# output data stream."
|
# output data stream."
|
||||||
|
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
|
|
||||||
os.chmod(temporary_directory, 0x1C0)
|
os.chmod(temporary_directory, 0x1C0)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import StringIO
|
import StringIO
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import gnupg
|
import gnupg
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from django.test import TestCase
|
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(TestCase):
|
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)
|
||||||
@@ -74,7 +74,7 @@ class KeyTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_cleartext_file_verification(self):
|
def test_cleartext_file_verification(self):
|
||||||
cleartext_file = tempfile.TemporaryFile()
|
cleartext_file = TemporaryFile()
|
||||||
cleartext_file.write('test')
|
cleartext_file.write('test')
|
||||||
cleartext_file.seek(0)
|
cleartext_file.seek(0)
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ class KeyTestCase(TestCase):
|
|||||||
self.assertEqual(result.read(), TEST_SIGNED_FILE_CONTENT)
|
self.assertEqual(result.read(), TEST_SIGNED_FILE_CONTENT)
|
||||||
|
|
||||||
def test_cleartext_file_decryption(self):
|
def test_cleartext_file_decryption(self):
|
||||||
cleartext_file = tempfile.TemporaryFile()
|
cleartext_file = TemporaryFile()
|
||||||
cleartext_file.write('test')
|
cleartext_file.write('test')
|
||||||
cleartext_file.seek(0)
|
cleartext_file.seek(0)
|
||||||
|
|
||||||
|
|||||||
83
mayan/apps/django_gpg/tests/test_organization_views.py
Normal file
83
mayan/apps/django_gpg/tests/test_organization_views.py
Normal 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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from common.utils import mkstemp
|
||||||
from django_gpg.exceptions import DecryptionError
|
from django_gpg.exceptions import DecryptionError
|
||||||
from django_gpg.models import Key
|
from django_gpg.models import Key
|
||||||
from documents.models import DocumentVersion
|
from documents.models import DocumentVersion
|
||||||
@@ -34,7 +34,7 @@ class EmbeddedSignatureManager(models.Manager):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def sign_document_version(self, document_version, key, passphrase=None, user=None):
|
def sign_document_version(self, document_version, key, passphrase=None, user=None):
|
||||||
temporary_file_object, temporary_filename = tempfile.mkstemp()
|
temporary_file_object, temporary_filename = mkstemp()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with document_version.open() as file_object:
|
with document_version.open() as file_object:
|
||||||
|
|||||||
@@ -218,9 +218,10 @@ class SignaturesViewTestCase(GenericDocumentViewTestCase):
|
|||||||
args=(signature.pk,),
|
args=(signature.pk,),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert_download_response(
|
with signature.signature_file as file_object:
|
||||||
self, response=response, content=signature.signature_file.read(),
|
assert_download_response(
|
||||||
)
|
self, response=response, content=file_object.read(),
|
||||||
|
)
|
||||||
|
|
||||||
def test_signature_delete_view_no_permission(self):
|
def test_signature_delete_view_no_permission(self):
|
||||||
with open(TEST_KEY_FILE) as file_object:
|
with open(TEST_KEY_FILE) as file_object:
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import tempfile
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
@@ -16,6 +15,7 @@ from common.generics import (
|
|||||||
ConfirmView, FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
ConfirmView, FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||||
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectListView
|
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectListView
|
||||||
)
|
)
|
||||||
|
from common.utils import TemporaryFile
|
||||||
from django_gpg.exceptions import NeedPassphrase, PassphraseError
|
from django_gpg.exceptions import NeedPassphrase, PassphraseError
|
||||||
from django_gpg.permissions import permission_key_sign
|
from django_gpg.permissions import permission_key_sign
|
||||||
from documents.models import DocumentVersion
|
from documents.models import DocumentVersion
|
||||||
@@ -83,7 +83,7 @@ class DocumentVersionDetachedSignatureCreateView(FormView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
temporary_file_object = tempfile.TemporaryFile()
|
temporary_file_object = TemporaryFile()
|
||||||
temporary_file_object.write(detached_signature.data)
|
temporary_file_object.write(detached_signature.data)
|
||||||
temporary_file_object.seek(0)
|
temporary_file_object.seek(0)
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ class DocumentVersionEmbeddedSignatureCreateView(FormView):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
temporary_file_object = tempfile.TemporaryFile()
|
temporary_file_object = TemporaryFile()
|
||||||
temporary_file_object.write(signature_result.data)
|
temporary_file_object.write(signature_result.data)
|
||||||
temporary_file_object.seek(0)
|
temporary_file_object.seek(0)
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ from organizations.tests.test_organization_views import OrganizationViewTestCase
|
|||||||
|
|
||||||
from ..models import Workflow
|
from ..models import Workflow
|
||||||
|
|
||||||
from .literals import (
|
from .literals import TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED
|
||||||
TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED,
|
|
||||||
TEST_WORKFLOW_INITIAL_STATE_LABEL, TEST_WORKFLOW_INITIAL_STATE_COMPLETION,
|
|
||||||
TEST_WORKFLOW_STATE_LABEL, TEST_WORKFLOW_STATE_COMPLETION,
|
|
||||||
TEST_WORKFLOW_TRANSITION_LABEL
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(OCR_AUTO_OCR=False)
|
@override_settings(OCR_AUTO_OCR=False)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
@@ -182,8 +181,6 @@ class RecentDocumentManager(models.Manager):
|
|||||||
|
|
||||||
class TrashedDocumentManager(models.Manager):
|
class TrashedDocumentManager(models.Manager):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
DocumentType = apps.get_model('documents', 'DocumentType')
|
|
||||||
|
|
||||||
return super(
|
return super(
|
||||||
TrashedDocumentManager, self
|
TrashedDocumentManager, self
|
||||||
).get_queryset().filter(in_trash=True)
|
).get_queryset().filter(in_trash=True)
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ from django.utils.six import BytesIO
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from rest_api.tests import GenericAPITestCase
|
from rest_api.tests import GenericAPITestCase
|
||||||
from user_management.tests.literals import (
|
|
||||||
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,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from actstream.models import Action
|
from actstream.models import Action
|
||||||
|
|
||||||
|
from common.tests import skip_file_descriptor_check
|
||||||
from user_management.tests.literals import (
|
from user_management.tests.literals import (
|
||||||
TEST_USER_PASSWORD, TEST_USER_USERNAME
|
TEST_USER_PASSWORD, TEST_USER_USERNAME
|
||||||
)
|
)
|
||||||
@@ -38,7 +39,10 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
|
|||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertEqual(list(Action.objects.any(obj=self.document)), [])
|
self.assertEqual(list(Action.objects.any(obj=self.document)), [])
|
||||||
|
|
||||||
|
@skip_file_descriptor_check
|
||||||
def test_document_download_event_with_permissions(self):
|
def test_document_download_event_with_permissions(self):
|
||||||
|
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||||
|
# SingleObjectDownloadView CBV
|
||||||
self.login(
|
self.login(
|
||||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.test import TestCase, override_settings
|
from django.test import override_settings
|
||||||
|
|
||||||
from organizations.tests.base import OrganizationTestCase
|
from organizations.tests.base import OrganizationTestCase
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from organizations.tests.test_organization_views import OrganizationViewTestCase
|
from organizations.tests.test_organization_views import OrganizationViewTestCase
|
||||||
|
|
||||||
from ..models import Document,DocumentType
|
from ..models import DocumentType
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_2_LABEL,
|
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_2_LABEL, TEST_SMALL_DOCUMENT_PATH
|
||||||
TEST_DOCUMENT_TYPE_QUICK_LABEL, TEST_SMALL_DOCUMENT_CHECKSUM,
|
|
||||||
TEST_SMALL_DOCUMENT_PATH
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
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 common.tests import skip_file_descriptor_check
|
||||||
from common.tests.test_views import GenericViewTestCase
|
from common.tests.test_views import GenericViewTestCase
|
||||||
from converter.models import Transformation
|
from converter.models import Transformation
|
||||||
from converter.permissions import permission_transformation_delete
|
from converter.permissions import permission_transformation_delete
|
||||||
@@ -221,7 +222,11 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
|||||||
Document.on_organization.first().document_type, document_type
|
Document.on_organization.first().document_type, document_type
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@skip_file_descriptor_check
|
||||||
def test_document_download_user_view(self):
|
def test_document_download_user_view(self):
|
||||||
|
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||||
|
# SingleObjectDownloadView CBV
|
||||||
|
|
||||||
self.login(
|
self.login(
|
||||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||||
)
|
)
|
||||||
@@ -253,7 +258,11 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
del(buf)
|
del(buf)
|
||||||
|
|
||||||
|
@skip_file_descriptor_check
|
||||||
def test_document_multiple_download_user_view(self):
|
def test_document_multiple_download_user_view(self):
|
||||||
|
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||||
|
# SingleObjectDownloadView CBV
|
||||||
|
|
||||||
self.login(
|
self.login(
|
||||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||||
)
|
)
|
||||||
@@ -287,7 +296,11 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
|||||||
|
|
||||||
del(buf)
|
del(buf)
|
||||||
|
|
||||||
|
@skip_file_descriptor_check
|
||||||
def test_document_version_download_user_view(self):
|
def test_document_version_download_user_view(self):
|
||||||
|
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||||
|
# SingleObjectDownloadView CBV
|
||||||
|
|
||||||
self.login(
|
self.login(
|
||||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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')
|
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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',)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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')
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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')
|
|
||||||
@@ -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')
|
|
||||||
@@ -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)
|
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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'),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
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 documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||||
from rest_api.tests import GenericAPITestCase
|
from rest_api.tests import GenericAPITestCase
|
||||||
from user_management.tests.literals import (
|
|
||||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..models import Folder
|
from ..models import Folder
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class FolderOrganizationViewTestCase(OrganizationViewTestCase):
|
|||||||
def test_folder_create_view(self):
|
def test_folder_create_view(self):
|
||||||
# Create a folder for organization A
|
# Create a folder for organization A
|
||||||
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
|
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
|
||||||
response = self.post(
|
self.post(
|
||||||
'folders:folder_create', data={
|
'folders:folder_create', data={
|
||||||
'label': TEST_FOLDER_LABEL
|
'label': TEST_FOLDER_LABEL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
15
mayan/apps/linking/managers.py
Normal file
15
mayan/apps/linking/managers.py
Normal 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(),
|
||||||
|
)
|
||||||
24
mayan/apps/linking/migrations/0006_smartlink_organization.py
Normal file
24
mayan/apps/linking/migrations/0006_smartlink_organization.py
Normal 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'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
94
mayan/apps/linking/tests/test_organization_views.py
Normal file
94
mayan/apps/linking/tests/test_organization_views.py
Normal 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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
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 documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||||
from rest_api.tests import GenericAPITestCase
|
from rest_api.tests import GenericAPITestCase
|
||||||
from user_management.tests.literals import (
|
|
||||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..models import DocumentMetadata, DocumentTypeMetadataType, MetadataType
|
from ..models import DocumentMetadata, DocumentTypeMetadataType, MetadataType
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ from organizations.tests.test_organization_views import OrganizationViewTestCase
|
|||||||
from ..models import MetadataType
|
from ..models import MetadataType
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
TEST_DOCUMENT_METADATA_VALUE_2, TEST_METADATA_TYPE_LABEL,
|
TEST_METADATA_TYPE_LABEL, TEST_METADATA_TYPE_LABEL_2,
|
||||||
TEST_METADATA_TYPE_LABEL_2, TEST_METADATA_TYPE_NAME,
|
TEST_METADATA_TYPE_NAME,
|
||||||
TEST_METADATA_TYPE_NAME_2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,10 @@ from pdfminer.pdfpage import PDFPage
|
|||||||
from pdfminer.converter import TextConverter
|
from pdfminer.converter import TextConverter
|
||||||
from pdfminer.layout import LAParams
|
from pdfminer.layout import LAParams
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.settings import setting_temporary_directory
|
from common.utils import copyfile, fs_cleanup, mkstemp
|
||||||
from common.utils import copyfile
|
|
||||||
|
|
||||||
from .exceptions import ParserError, NoMIMETypeMatch
|
from .exceptions import ParserError, NoMIMETypeMatch
|
||||||
from .models import DocumentPageContent
|
from .models import DocumentPageContent
|
||||||
@@ -137,9 +135,7 @@ class PopplerParser(Parser):
|
|||||||
def execute(self, file_object, page_number):
|
def execute(self, file_object, page_number):
|
||||||
logger.debug('Parsing PDF page: %d', page_number)
|
logger.debug('Parsing PDF page: %d', page_number)
|
||||||
|
|
||||||
destination_descriptor, temp_filepath = tempfile.mkstemp(
|
destination_descriptor, temp_filepath = mkstemp()
|
||||||
dir=setting_temporary_directory.value
|
|
||||||
)
|
|
||||||
copyfile(file_object, temp_filepath)
|
copyfile(file_object, temp_filepath)
|
||||||
|
|
||||||
command = []
|
command = []
|
||||||
@@ -158,9 +154,12 @@ class PopplerParser(Parser):
|
|||||||
return_code = proc.wait()
|
return_code = proc.wait()
|
||||||
if return_code != 0:
|
if return_code != 0:
|
||||||
logger.error(proc.stderr.readline())
|
logger.error(proc.stderr.readline())
|
||||||
|
fs_cleanup(temp_filepath, file_descriptor=destination_descriptor)
|
||||||
|
|
||||||
raise ParserError
|
raise ParserError
|
||||||
|
|
||||||
output = proc.stdout.read()
|
output = proc.stdout.read()
|
||||||
|
fs_cleanup(temp_filepath, file_descriptor=destination_descriptor)
|
||||||
|
|
||||||
if output == b'\x0c':
|
if output == b'\x0c':
|
||||||
logger.debug('Parser didn\'t return any output')
|
logger.debug('Parser didn\'t return any output')
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from ..parsers import PDFMinerParser, PopplerParser
|
|||||||
class ParserTestCase(OrganizationTestCase):
|
class ParserTestCase(OrganizationTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(ParserTestCase, self).setUp()
|
super(ParserTestCase, self).setUp()
|
||||||
|
|
||||||
self.document_type = DocumentType.on_organization.create(
|
self.document_type = DocumentType.on_organization.create(
|
||||||
label=TEST_DOCUMENT_TYPE
|
label=TEST_DOCUMENT_TYPE
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from common.apps import MayanAppConfig
|
from common.apps import MayanAppConfig
|
||||||
|
|
||||||
|
|
||||||
class OrganizationApp(AppConfig):
|
class OrganizationApp(MayanAppConfig):
|
||||||
name = 'organizations'
|
name = 'organizations'
|
||||||
verbose_name = _('Organizations')
|
verbose_name = _('Organizations')
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core import management
|
from django.core import management
|
||||||
from django.utils.crypto import get_random_string
|
|
||||||
|
|
||||||
from ...models import Organization
|
from ...models import Organization
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Command(management.BaseCommand):
|
class Command(management.BaseCommand):
|
||||||
help = 'Creates an organization admin user with a secure random password and all permissions.'
|
help = 'Creates an organization admin user with a secure random password and all permissions.'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations
|
||||||
|
|
||||||
from ..literals import DEFAULT_ORGANIZATION_LABEL
|
from ..literals import DEFAULT_ORGANIZATION_LABEL
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ from ..literals import DEFAULT_ORGANIZATION_LABEL
|
|||||||
def default_data(apps, schema_editor):
|
def default_data(apps, schema_editor):
|
||||||
# We can't import the Organization model directly as it may be a newer
|
# We can't import the Organization model directly as it may be a newer
|
||||||
# version than this migration expects. We use the historical version.
|
# version than this migration expects. We use the historical version.
|
||||||
Organization = apps.get_model("organizations", "Organization")
|
Organization = apps.get_model('organizations', 'Organization')
|
||||||
default_organization = Organization(label=DEFAULT_ORGANIZATION_LABEL)
|
default_organization = Organization(label=DEFAULT_ORGANIZATION_LABEL)
|
||||||
default_organization.save()
|
default_organization.save()
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import string
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
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.exceptions import ImproperlyConfigured, ValidationError
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import pre_save, pre_delete
|
from django.db.models.signals import pre_save, pre_delete
|
||||||
from django.utils.deprecation import RemovedInDjango19Warning
|
|
||||||
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 _
|
||||||
|
|
||||||
@@ -110,7 +107,7 @@ class Organization(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
account = UserModel.objects.get(
|
account = UserModel.objects.get(
|
||||||
**{UserModel.USERNAME_FIELD: username, 'organization': self,}
|
**{UserModel.USERNAME_FIELD: username, 'organization': self}
|
||||||
)
|
)
|
||||||
account.set_password(raw_password=password_value)
|
account.set_password(raw_password=password_value)
|
||||||
account.save()
|
account.save()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from smart_settings import Namespace
|
from smart_settings import Namespace
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ from django.apps import apps
|
|||||||
|
|
||||||
|
|
||||||
def get_current_organization():
|
def get_current_organization():
|
||||||
from .models import Organization
|
Organization = apps.get_model('organizations', 'Organization')
|
||||||
return Organization.objects.get_current().pk
|
return Organization.objects.get_current().pk
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .base import OrganizationTestCase #NOQA
|
from .base import OrganizationTestCase # NOQA
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.test import TestCase
|
from common.tests import BaseTestCase
|
||||||
|
|
||||||
from ..models import Organization
|
from ..models import Organization
|
||||||
from ..utils import create_default_organization
|
from ..utils import create_default_organization
|
||||||
|
|
||||||
|
|
||||||
class OrganizationTestCase(TestCase):
|
class OrganizationTestCase(BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
create_default_organization()
|
create_default_organization()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
TEST_ORGANIZATION_LABEL = 'test organization label'
|
TEST_ORGANIZATION_LABEL = 'test organization label'
|
||||||
TEST_ORGANIZATION_EDITED_LABEL = 'test organization edited label'
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import unittest
|
|||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import connections, router
|
from django.db import connections, router
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.test import TestCase, modify_settings, override_settings
|
from django.test import TestCase, modify_settings, override_settings
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from ..models import Organization
|
from ..models import Organization
|
||||||
|
|
||||||
from .literals import TEST_ORGANIZATION_LABEL, TEST_ORGANIZATION_EDITED_LABEL
|
from .literals import TEST_ORGANIZATION_LABEL
|
||||||
|
|
||||||
|
|
||||||
class OrganizationModelTestCase(TestCase):
|
class OrganizationModelTestCase(TestCase):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.management.color import no_style
|
from django.core.management.color import no_style
|
||||||
from django.db import DEFAULT_DB_ALIAS, connections, router
|
from django.db import DEFAULT_DB_ALIAS, connections, router
|
||||||
from django.db.models import signals
|
|
||||||
|
|
||||||
from .literals import DEFAULT_ORGANIZATION_LABEL
|
from .literals import DEFAULT_ORGANIZATION_LABEL
|
||||||
|
|
||||||
@@ -28,12 +27,14 @@ def create_default_organization(verbosity=2, interactive=True, using=DEFAULT_DB_
|
|||||||
# the next id will be 1, so we coerce it. See #15573 and #16353. This
|
# the next id will be 1, so we coerce it. See #15573 and #16353. This
|
||||||
# can also crop up outside of tests - see #15346.
|
# can also crop up outside of tests - see #15346.
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print("Creating default Organization object")
|
print('Creating default Organization object')
|
||||||
Organization(pk=1, label=DEFAULT_ORGANIZATION_LABEL).save(using=using)
|
Organization(pk=1, label=DEFAULT_ORGANIZATION_LABEL).save(using=using)
|
||||||
|
|
||||||
# We set an explicit pk instead of relying on auto-incrementation,
|
# We set an explicit pk instead of relying on auto-incrementation,
|
||||||
# so we need to reset the database sequence. See #17415.
|
# so we need to reset the database sequence. See #17415.
|
||||||
sequence_sql = connections[using].ops.sequence_reset_sql(no_style(), [Organization])
|
sequence_sql = connections[using].ops.sequence_reset_sql(
|
||||||
|
no_style(), [Organization]
|
||||||
|
)
|
||||||
if sequence_sql:
|
if sequence_sql:
|
||||||
if verbosity >= 2:
|
if verbosity >= 2:
|
||||||
print('Resetting sequence')
|
print('Resetting sequence')
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from user_management.models import MayanGroup
|
|||||||
from user_management.tests import TEST_GROUP, TEST_USER_USERNAME
|
from user_management.tests import TEST_GROUP, TEST_USER_USERNAME
|
||||||
|
|
||||||
from ..classes import Permission
|
from ..classes import Permission
|
||||||
from ..models import Role, StoredPermission
|
from ..models import Role
|
||||||
from ..permissions import permission_role_view
|
from ..permissions import permission_role_view
|
||||||
|
|
||||||
from .literals import TEST_ROLE_LABEL
|
from .literals import TEST_ROLE_LABEL
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
from .base import GenericAPITestCase #NOQA
|
from .base import GenericAPITestCase # NOQA
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.test import override_settings
|
|
||||||
|
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from organizations.models import Organization
|
from organizations.models import Organization
|
||||||
@@ -24,7 +21,6 @@ class GenericAPITestCase(APITestCase):
|
|||||||
username=self.admin_user.username, password=password
|
username=self.admin_user.username, password=password
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(GenericAPITestCase, self).tearDown()
|
super(GenericAPITestCase, self).tearDown()
|
||||||
self.admin_user.delete()
|
self.admin_user.delete()
|
||||||
|
|||||||
@@ -2,18 +2,18 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from documents.tests import TEST_NON_ASCII_DOCUMENT_PATH
|
from documents.tests import TEST_NON_ASCII_DOCUMENT_PATH
|
||||||
|
from common.utils import mkdtemp
|
||||||
|
|
||||||
from ..classes import StagingFile
|
from ..classes import StagingFile
|
||||||
|
|
||||||
|
|
||||||
class StagingFileTestCase(TestCase):
|
class StagingFileTestCase(TestCase):
|
||||||
def test_unicode_staging_file(self):
|
def test_unicode_staging_file(self):
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory)
|
shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory)
|
||||||
|
|
||||||
filename = os.path.basename(TEST_NON_ASCII_DOCUMENT_PATH)
|
filename = os.path.basename(TEST_NON_ASCII_DOCUMENT_PATH)
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.files.base import File
|
from django.core.files.base import File
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.test.client import Client
|
from django.test.client import Client
|
||||||
|
|
||||||
|
from common.utils import mkdtemp
|
||||||
from documents.models import Document, DocumentType
|
from documents.models import Document, DocumentType
|
||||||
from documents.tests import (
|
from documents.tests import (
|
||||||
TEST_COMPRESSED_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
|
TEST_COMPRESSED_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
|
||||||
@@ -55,7 +55,7 @@ class UploadDocumentTestCase(TestCase):
|
|||||||
gh-issue #163 https://github.com/mayan-edms/mayan-edms/issues/163
|
gh-issue #163 https://github.com/mayan-edms/mayan-edms/issues/163
|
||||||
"""
|
"""
|
||||||
|
|
||||||
temporary_directory = tempfile.mkdtemp()
|
temporary_directory = mkdtemp()
|
||||||
shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory)
|
shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory)
|
||||||
|
|
||||||
watch_folder = WatchFolderSource.on_organization.create(
|
watch_folder = WatchFolderSource.on_organization.create(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
|
||||||
|
|
||||||
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
|
||||||
@@ -11,6 +10,7 @@ from django.test import TestCase, override_settings
|
|||||||
|
|
||||||
from acls.models import AccessControlList
|
from acls.models import AccessControlList
|
||||||
from common.tests.test_views import GenericViewTestCase
|
from common.tests.test_views import GenericViewTestCase
|
||||||
|
from common.utils import fs_cleanup, mkdtemp
|
||||||
from documents.models import Document, DocumentType, NewVersionBlock
|
from documents.models import Document, DocumentType, NewVersionBlock
|
||||||
from documents.permissions import permission_document_create
|
from documents.permissions import permission_document_create
|
||||||
from documents.tests import (
|
from documents.tests import (
|
||||||
@@ -230,15 +230,14 @@ class NewDocumentVersionViewTestCase(GenericDocumentViewTestCase):
|
|||||||
class StagingFolderTestCase(GenericViewTestCase):
|
class StagingFolderTestCase(GenericViewTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(StagingFolderTestCase, self).setUp()
|
super(StagingFolderTestCase, self).setUp()
|
||||||
self.temporary_directory = tempfile.mkdtemp()
|
self.temporary_directory = mkdtemp()
|
||||||
# TODO: remove temp directory after test
|
|
||||||
shutil.copy(TEST_SMALL_DOCUMENT_PATH, self.temporary_directory)
|
shutil.copy(TEST_SMALL_DOCUMENT_PATH, self.temporary_directory)
|
||||||
|
|
||||||
self.filename = os.path.basename(TEST_SMALL_DOCUMENT_PATH)
|
self.filename = os.path.basename(TEST_SMALL_DOCUMENT_PATH)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
fs_cleanup(self.temporary_directory)
|
||||||
super(StagingFolderTestCase, self).tearDown()
|
super(StagingFolderTestCase, self).tearDown()
|
||||||
shutil.rmtree(self.temporary_directory)
|
|
||||||
|
|
||||||
def test_staging_folder_delete_no_permission(self):
|
def test_staging_folder_delete_no_permission(self):
|
||||||
self.login(
|
self.login(
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
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 documents.models import DocumentType
|
from documents.models import DocumentType
|
||||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||||
from rest_api.tests.base import GenericAPITestCase
|
from rest_api.tests.base import GenericAPITestCase
|
||||||
from user_management.tests.literals import (
|
|
||||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..models import Tag
|
from ..models import Tag
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.core.files.base import File
|
from django.core.files.base import File
|
||||||
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_TYPE, TEST_SMALL_DOCUMENT_PATH
|
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin, GroupAdmin
|
from django.contrib.auth.admin import UserAdmin, GroupAdmin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from organizations.admin import OrganizationAdminMixin
|
from organizations.admin import OrganizationAdminMixin
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import organizations.shortcuts
|
||||||
|
import user_management.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('user_management', '0003_auto_20160525_0155'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelManagers(
|
||||||
|
name='mayanuser',
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
('on_organization', user_management.models.OrganizationUserManagerHybridClass()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mayanuser',
|
||||||
|
name='organization',
|
||||||
|
field=models.ForeignKey(default=organizations.shortcuts.get_current_organization, blank=True, to='organizations.Organization', null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mayanuser',
|
||||||
|
name='organization_groups',
|
||||||
|
field=models.ManyToManyField(related_query_name='user', related_name='users', to='user_management.MayanGroup', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='Groups'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -18,6 +18,10 @@ class MayanGroup(Group):
|
|||||||
objects = GroupManager()
|
objects = GroupManager()
|
||||||
on_organization = CurrentOrganizationManager()
|
on_organization = CurrentOrganizationManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('Group')
|
||||||
|
verbose_name_plural = _('Groups')
|
||||||
|
|
||||||
|
|
||||||
class OrganizationUserManagerHybridClass(CurrentOrganizationManager, UserManager):
|
class OrganizationUserManagerHybridClass(CurrentOrganizationManager, UserManager):
|
||||||
"""
|
"""
|
||||||
@@ -27,7 +31,7 @@ class OrganizationUserManagerHybridClass(CurrentOrganizationManager, UserManager
|
|||||||
|
|
||||||
class MayanUser(AbstractUser):
|
class MayanUser(AbstractUser):
|
||||||
organization = models.ForeignKey(
|
organization = models.ForeignKey(
|
||||||
Organization, default=get_current_organization
|
Organization, blank=True, default=get_current_organization, null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
organization_groups = models.ManyToManyField(
|
organization_groups = models.ManyToManyField(
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ coveralls==0.5
|
|||||||
django-test-without-migrations==0.2
|
django-test-without-migrations==0.2
|
||||||
mock==2.0.0
|
mock==2.0.0
|
||||||
tox==2.1.1
|
tox==2.1.1
|
||||||
|
psutil==4.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user