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)
|
||||
==================
|
||||
- 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|
|
||||
|
||||
@@ -63,8 +63,6 @@ Contribute
|
||||
:target: https://gitlab.com/mayan-edms/mayan-edms/commits/master
|
||||
.. |Logo| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/mayan_logo.png
|
||||
.. |Animation| image:: https://gitlab.com/mayan-edms/mayan-edms/raw/master/docs/_static/overview.gif
|
||||
.. |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
|
||||
:target: http://badge.fury.io/py/mayan-edms
|
||||
.. |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::
|
||||
:maxdepth: 1
|
||||
|
||||
2.1.3
|
||||
2.1.2
|
||||
2.1.1
|
||||
2.1
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__title__ = 'Mayan EDMS'
|
||||
__version__ = '2.1.2'
|
||||
__build__ = 0x020102
|
||||
__version__ = '2.1.3'
|
||||
__build__ = 0x020103
|
||||
__author__ = 'Roberto Rosario'
|
||||
__author_email__ = 'roberto.rosario@mayan-edms.com'
|
||||
__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.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.permissions import permission_document_view
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
|
||||
@@ -15,6 +14,12 @@ class UsernameModelBackend(ModelBackend):
|
||||
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)
|
||||
# Check for superadmins, they can login from any organization.
|
||||
try:
|
||||
user = UserModel.objects.filter(is_superuser=True).get(username=username)
|
||||
if user.check_password(password):
|
||||
return user
|
||||
except UserModel.DoesNotExist:
|
||||
# Run the default password hasher once to reduce the timing
|
||||
# difference between an existing and a non-existing user (#20760).
|
||||
UserModel().set_password(password)
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
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 organizations.tests.base import OrganizationTestCase
|
||||
|
||||
@@ -4,7 +4,7 @@ import datetime
|
||||
import time
|
||||
|
||||
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 documents.exceptions import NewDocumentVersionNotAllowed
|
||||
|
||||
@@ -12,9 +12,15 @@ class Command(management.BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
management.call_command('createsettings', interactive=False)
|
||||
try:
|
||||
result = management.call_command('migrate', interactive=False)
|
||||
except OperationalError as exception:
|
||||
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.'))
|
||||
management.call_command('migrate', interactive=False)
|
||||
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.'
|
||||
)
|
||||
)
|
||||
raise
|
||||
management.call_command('createautoadmin', 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(),
|
||||
help_text=_(
|
||||
'Temporary directory used site wide to store thumbnails, previews '
|
||||
'and temporary files. If none is specified, one will be created '
|
||||
'using tempfile.mkdtemp().'
|
||||
'and temporary files.'
|
||||
),
|
||||
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.http import HttpResponse
|
||||
from django.template import Context, Template
|
||||
from django.test import TestCase
|
||||
|
||||
from organizations.tests.base import OrganizationTestCase
|
||||
from permissions import Permission
|
||||
@@ -23,6 +22,7 @@ from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
|
||||
class GenericViewTestCase(OrganizationTestCase):
|
||||
def setUp(self):
|
||||
super(GenericViewTestCase, self).setUp()
|
||||
|
||||
self.has_test_view = False
|
||||
self.admin_user = get_user_model().on_organization.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
@@ -46,7 +46,6 @@ class GenericViewTestCase(OrganizationTestCase):
|
||||
self.client.logout()
|
||||
if self.has_test_view:
|
||||
urlpatterns.pop(0)
|
||||
|
||||
super(GenericViewTestCase, self).tearDown()
|
||||
|
||||
def add_test_view(self, test_object):
|
||||
@@ -95,6 +94,12 @@ class GenericViewTestCase(OrganizationTestCase):
|
||||
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):
|
||||
def test_about_view(self):
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
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 urlencode as django_urlencode
|
||||
|
||||
from .settings import setting_temporary_directory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -42,17 +45,23 @@ def encapsulate(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
|
||||
"""
|
||||
if file_descriptor:
|
||||
os.close(file_descriptor)
|
||||
|
||||
try:
|
||||
os.remove(filename)
|
||||
except OSError:
|
||||
if suppress_exceptions:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
try:
|
||||
shutil.rmtree(filename)
|
||||
except OSError:
|
||||
if suppress_exceptions:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def get_descriptor(file_input, read=True):
|
||||
@@ -69,6 +78,21 @@ def get_descriptor(file_input, read=True):
|
||||
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):
|
||||
try:
|
||||
if isinstance(attrib, types.FunctionType):
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
@@ -16,7 +15,7 @@ import sh
|
||||
|
||||
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 ..exceptions import PageCountError
|
||||
@@ -50,7 +49,7 @@ class Python(ConverterBase):
|
||||
|
||||
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)
|
||||
os.write(new_file_object, self.file_object.read())
|
||||
self.file_object.seek(0)
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
@@ -16,7 +15,7 @@ import sh
|
||||
from django.utils.translation import string_concat, ugettext_lazy as _
|
||||
|
||||
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 .exceptions import InvalidOfficeFormat, OfficeConversionError
|
||||
@@ -122,7 +121,7 @@ class ConverterBase(object):
|
||||
) % setting_libreoffice_path.value
|
||||
)
|
||||
|
||||
new_file_object, input_filepath = tempfile.mkstemp()
|
||||
new_file_object, input_filepath = mkstemp()
|
||||
self.file_object.seek(0)
|
||||
os.write(new_file_object, self.file_object.read())
|
||||
self.file_object.seek(0)
|
||||
@@ -168,6 +167,7 @@ class ConverterBase(object):
|
||||
yield data
|
||||
|
||||
fs_cleanup(input_filepath)
|
||||
fs_cleanup(converted_output)
|
||||
|
||||
def get_page(self, output_format=DEFAULT_FILE_FORMAT, as_base64=False):
|
||||
if not self.image:
|
||||
|
||||
@@ -4,12 +4,14 @@ import io
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import gnupg
|
||||
|
||||
from django.db import models
|
||||
|
||||
from common.utils import mkdtemp, mkstemp
|
||||
from organizations.managers import CurrentOrganizationManager
|
||||
|
||||
from .classes import KeyStub, SignatureVerification
|
||||
from .exceptions import (
|
||||
DecryptionError, KeyDoesNotExist, KeyFetchingError, VerificationError
|
||||
@@ -22,7 +24,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class KeyManager(models.Manager):
|
||||
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)
|
||||
|
||||
@@ -71,7 +73,7 @@ class KeyManager(models.Manager):
|
||||
return io.BytesIO(decrypt_result.data)
|
||||
|
||||
def receive_key(self, key_id):
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
|
||||
os.chmod(temporary_directory, 0x1C0)
|
||||
|
||||
@@ -92,7 +94,7 @@ class KeyManager(models.Manager):
|
||||
return self.create(key_data=key_data)
|
||||
|
||||
def search(self, query):
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
|
||||
os.chmod(temporary_directory, 0x1C0)
|
||||
|
||||
@@ -118,7 +120,7 @@ class KeyManager(models.Manager):
|
||||
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):
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
|
||||
os.chmod(temporary_directory, 0x1C0)
|
||||
|
||||
@@ -156,7 +158,7 @@ class KeyManager(models.Manager):
|
||||
if signature_file:
|
||||
# Save the original data and invert the argument order
|
||||
# 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.close(temporary_file_object)
|
||||
|
||||
@@ -192,3 +194,7 @@ class KeyManager(models.Manager):
|
||||
else:
|
||||
logger.debug('file not signed')
|
||||
raise VerificationError('File not signed')
|
||||
|
||||
|
||||
class OrganizationKeyManager(KeyManager, CurrentOrganizationManager):
|
||||
pass
|
||||
|
||||
@@ -14,6 +14,9 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='key',
|
||||
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 os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import gnupg
|
||||
|
||||
@@ -14,20 +13,24 @@ from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
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 .literals import (
|
||||
ERROR_MSG_NEED_PASSPHRASE, ERROR_MSG_BAD_PASSPHRASE,
|
||||
ERROR_MSG_GOOD_PASSPHRASE, KEY_TYPE_CHOICES, KEY_TYPE_SECRET,
|
||||
OUTPUT_MESSAGE_CONTAINS_PRIVATE_KEY
|
||||
)
|
||||
from .managers import KeyManager
|
||||
from .managers import KeyManager, OrganizationKeyManager
|
||||
from .settings import setting_gpg_path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def gpg_command(function):
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
os.chmod(temporary_directory, 0x1C0)
|
||||
|
||||
gpg = gnupg.GPG(
|
||||
@@ -43,6 +46,9 @@ def gpg_command(function):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Key(models.Model):
|
||||
organization = models.ForeignKey(
|
||||
Organization, default=get_current_organization
|
||||
)
|
||||
key_data = models.TextField(
|
||||
help_text=_('ASCII armored version of the key.'),
|
||||
verbose_name=_('Key data')
|
||||
@@ -71,11 +77,15 @@ class Key(models.Model):
|
||||
)
|
||||
|
||||
objects = KeyManager()
|
||||
on_organization = OrganizationKeyManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Key')
|
||||
verbose_name_plural = _('Keys')
|
||||
|
||||
def __str__(self):
|
||||
return '{} - {}'.format(self.key_id, self.user_id)
|
||||
|
||||
def clean(self):
|
||||
def import_key(gpg):
|
||||
return gpg.import_keys(key_data=self.key_data)
|
||||
@@ -85,14 +95,14 @@ class Key(models.Model):
|
||||
if not import_results.count:
|
||||
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.'))
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('django_gpg:key_detail', args=(self.pk,))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
|
||||
os.chmod(temporary_directory, 0x1C0)
|
||||
|
||||
@@ -122,9 +132,6 @@ class Key(models.Model):
|
||||
|
||||
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):
|
||||
# WARNING: using clearsign=True and subsequent decryption corrupts the
|
||||
# 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
|
||||
# output data stream."
|
||||
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
|
||||
os.chmod(temporary_directory, 0x1C0)
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import StringIO
|
||||
import tempfile
|
||||
|
||||
import gnupg
|
||||
import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from common.utils import TemporaryFile
|
||||
from organizations.tests import OrganizationTestCase
|
||||
|
||||
from ..exceptions import (
|
||||
DecryptionError, KeyDoesNotExist, NeedPassphrase, PassphraseError,
|
||||
@@ -43,7 +43,7 @@ def mock_recv_keys(self, keyserver, *keyids):
|
||||
return ImportResult()
|
||||
|
||||
|
||||
class KeyTestCase(TestCase):
|
||||
class KeyTestCase(OrganizationTestCase):
|
||||
def test_key_instance_creation(self):
|
||||
# Creating a Key instance is analogous to importing a key
|
||||
key = Key.objects.create(key_data=TEST_KEY_DATA)
|
||||
@@ -74,7 +74,7 @@ class KeyTestCase(TestCase):
|
||||
)
|
||||
|
||||
def test_cleartext_file_verification(self):
|
||||
cleartext_file = tempfile.TemporaryFile()
|
||||
cleartext_file = TemporaryFile()
|
||||
cleartext_file.write('test')
|
||||
cleartext_file.seek(0)
|
||||
|
||||
@@ -124,7 +124,7 @@ class KeyTestCase(TestCase):
|
||||
self.assertEqual(result.read(), TEST_SIGNED_FILE_CONTENT)
|
||||
|
||||
def test_cleartext_file_decryption(self):
|
||||
cleartext_file = tempfile.TemporaryFile()
|
||||
cleartext_file = TemporaryFile()
|
||||
cleartext_file.write('test')
|
||||
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 common.tests.test_views import GenericViewTestCase
|
||||
from user_management.tests import (
|
||||
TEST_USER_USERNAME, TEST_USER_PASSWORD
|
||||
)
|
||||
from user_management.tests import TEST_USER_USERNAME, TEST_USER_PASSWORD
|
||||
|
||||
from ..models import Key
|
||||
from ..permissions import permission_key_download, permission_key_upload
|
||||
|
||||
@@ -25,7 +25,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KeyDeleteView(SingleObjectDeleteView):
|
||||
model = Key
|
||||
object_permission = permission_key_delete
|
||||
|
||||
def get_post_action_redirect(self):
|
||||
@@ -37,10 +36,12 @@ class KeyDeleteView(SingleObjectDeleteView):
|
||||
def get_extra_context(self):
|
||||
return {'title': _('Delete key: %s') % self.get_object()}
|
||||
|
||||
def get_queryset(self):
|
||||
return Key.on_organization.all()
|
||||
|
||||
|
||||
class KeyDetailView(SingleObjectDetailView):
|
||||
form_class = KeyDetailForm
|
||||
model = Key
|
||||
object_permission = permission_key_view
|
||||
|
||||
def get_extra_context(self):
|
||||
@@ -48,9 +49,11 @@ class KeyDetailView(SingleObjectDetailView):
|
||||
'title': _('Details for key: %s') % self.get_object(),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return Key.on_organization.all()
|
||||
|
||||
|
||||
class KeyDownloadView(SingleObjectDownloadView):
|
||||
model = Key
|
||||
object_permission = permission_key_download
|
||||
|
||||
def get_file(self):
|
||||
@@ -58,6 +61,9 @@ class KeyDownloadView(SingleObjectDownloadView):
|
||||
|
||||
return ContentFile(key.key_data, name=key.key_id)
|
||||
|
||||
def get_queryset(self):
|
||||
return Key.on_organization.all()
|
||||
|
||||
|
||||
class KeyReceive(ConfirmView):
|
||||
post_action_redirect = reverse_lazy('django_gpg:key_public_list')
|
||||
@@ -71,7 +77,7 @@ class KeyReceive(ConfirmView):
|
||||
|
||||
def view_action(self):
|
||||
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:
|
||||
messages.error(
|
||||
self.request,
|
||||
@@ -122,14 +128,13 @@ class KeyQueryResultView(SingleObjectListView):
|
||||
def get_queryset(self):
|
||||
term = self.request.GET.get('term')
|
||||
if term:
|
||||
return Key.objects.search(query=term)
|
||||
return Key.on_organization.search(query=term)
|
||||
else:
|
||||
return ()
|
||||
|
||||
|
||||
class KeyUploadView(SingleObjectCreateView):
|
||||
fields = ('key_data',)
|
||||
model = Key
|
||||
post_action_redirect = reverse_lazy('django_gpg:key_public_list')
|
||||
view_permission = permission_key_upload
|
||||
|
||||
@@ -138,10 +143,12 @@ class KeyUploadView(SingleObjectCreateView):
|
||||
'title': _('Upload new key'),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return Key.on_organization.all()
|
||||
|
||||
|
||||
class PublicKeyListView(SingleObjectListView):
|
||||
object_permission = permission_key_view
|
||||
queryset = Key.objects.public_keys()
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
@@ -149,13 +156,18 @@ class PublicKeyListView(SingleObjectListView):
|
||||
'title': _('Public keys')
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return Key.on_organization.public_keys()
|
||||
|
||||
|
||||
class PrivateKeyListView(SingleObjectListView):
|
||||
object_permission = permission_key_view
|
||||
queryset = Key.objects.private_keys()
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'hide_object': True,
|
||||
'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 os
|
||||
import tempfile
|
||||
|
||||
from django.db import models
|
||||
|
||||
from common.utils import mkstemp
|
||||
from django_gpg.exceptions import DecryptionError
|
||||
from django_gpg.models import Key
|
||||
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):
|
||||
temporary_file_object, temporary_filename = tempfile.mkstemp()
|
||||
temporary_file_object, temporary_filename = mkstemp()
|
||||
|
||||
try:
|
||||
with document_version.open() as file_object:
|
||||
|
||||
@@ -218,9 +218,10 @@ class SignaturesViewTestCase(GenericDocumentViewTestCase):
|
||||
args=(signature.pk,),
|
||||
)
|
||||
|
||||
assert_download_response(
|
||||
self, response=response, content=signature.signature_file.read(),
|
||||
)
|
||||
with signature.signature_file as file_object:
|
||||
assert_download_response(
|
||||
self, response=response, content=file_object.read(),
|
||||
)
|
||||
|
||||
def test_signature_delete_view_no_permission(self):
|
||||
with open(TEST_KEY_FILE) as file_object:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
@@ -16,6 +15,7 @@ from common.generics import (
|
||||
ConfirmView, FormView, SingleObjectCreateView, SingleObjectDeleteView,
|
||||
SingleObjectDetailView, SingleObjectDownloadView, SingleObjectListView
|
||||
)
|
||||
from common.utils import TemporaryFile
|
||||
from django_gpg.exceptions import NeedPassphrase, PassphraseError
|
||||
from django_gpg.permissions import permission_key_sign
|
||||
from documents.models import DocumentVersion
|
||||
@@ -83,7 +83,7 @@ class DocumentVersionDetachedSignatureCreateView(FormView):
|
||||
)
|
||||
)
|
||||
else:
|
||||
temporary_file_object = tempfile.TemporaryFile()
|
||||
temporary_file_object = TemporaryFile()
|
||||
temporary_file_object.write(detached_signature.data)
|
||||
temporary_file_object.seek(0)
|
||||
|
||||
@@ -188,7 +188,7 @@ class DocumentVersionEmbeddedSignatureCreateView(FormView):
|
||||
)
|
||||
)
|
||||
else:
|
||||
temporary_file_object = tempfile.TemporaryFile()
|
||||
temporary_file_object = TemporaryFile()
|
||||
temporary_file_object.write(signature_result.data)
|
||||
temporary_file_object.seek(0)
|
||||
|
||||
|
||||
@@ -6,12 +6,7 @@ from organizations.tests.test_organization_views import OrganizationViewTestCase
|
||||
|
||||
from ..models import Workflow
|
||||
|
||||
from .literals import (
|
||||
TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED,
|
||||
TEST_WORKFLOW_INITIAL_STATE_LABEL, TEST_WORKFLOW_INITIAL_STATE_COMPLETION,
|
||||
TEST_WORKFLOW_STATE_LABEL, TEST_WORKFLOW_STATE_COMPLETION,
|
||||
TEST_WORKFLOW_TRANSITION_LABEL
|
||||
)
|
||||
from .literals import TEST_WORKFLOW_LABEL, TEST_WORKFLOW_LABEL_EDITED
|
||||
|
||||
|
||||
@override_settings(OCR_AUTO_OCR=False)
|
||||
|
||||
@@ -4,7 +4,6 @@ from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
|
||||
@@ -182,8 +181,6 @@ class RecentDocumentManager(models.Manager):
|
||||
|
||||
class TrashedDocumentManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
DocumentType = apps.get_model('documents', 'DocumentType')
|
||||
|
||||
return super(
|
||||
TrashedDocumentManager, self
|
||||
).get_queryset().filter(in_trash=True)
|
||||
|
||||
@@ -11,9 +11,6 @@ from django.utils.six import BytesIO
|
||||
from rest_framework import status
|
||||
|
||||
from rest_api.tests import GenericAPITestCase
|
||||
from user_management.tests.literals import (
|
||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
|
||||
)
|
||||
|
||||
from .literals import (
|
||||
TEST_DOCUMENT_FILENAME, TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE,
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from actstream.models import Action
|
||||
|
||||
from common.tests import skip_file_descriptor_check
|
||||
from user_management.tests.literals import (
|
||||
TEST_USER_PASSWORD, TEST_USER_USERNAME
|
||||
)
|
||||
@@ -38,7 +39,10 @@ class DocumentEventsTestCase(GenericDocumentViewTestCase):
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(list(Action.objects.any(obj=self.document)), [])
|
||||
|
||||
@skip_file_descriptor_check
|
||||
def test_document_download_event_with_permissions(self):
|
||||
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||
# SingleObjectDownloadView CBV
|
||||
self.login(
|
||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
||||
from datetime import timedelta
|
||||
import time
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test import override_settings
|
||||
|
||||
from organizations.tests.base import OrganizationTestCase
|
||||
|
||||
|
||||
@@ -2,12 +2,10 @@ from __future__ import unicode_literals
|
||||
|
||||
from organizations.tests.test_organization_views import OrganizationViewTestCase
|
||||
|
||||
from ..models import Document,DocumentType
|
||||
from ..models import DocumentType
|
||||
|
||||
from .literals import (
|
||||
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_2_LABEL,
|
||||
TEST_DOCUMENT_TYPE_QUICK_LABEL, TEST_SMALL_DOCUMENT_CHECKSUM,
|
||||
TEST_SMALL_DOCUMENT_PATH
|
||||
TEST_DOCUMENT_TYPE, TEST_DOCUMENT_TYPE_2_LABEL, TEST_SMALL_DOCUMENT_PATH
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import override_settings
|
||||
from django.utils.six import BytesIO
|
||||
|
||||
from common.tests import skip_file_descriptor_check
|
||||
from common.tests.test_views import GenericViewTestCase
|
||||
from converter.models import Transformation
|
||||
from converter.permissions import permission_transformation_delete
|
||||
@@ -221,7 +222,11 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
||||
Document.on_organization.first().document_type, document_type
|
||||
)
|
||||
|
||||
@skip_file_descriptor_check
|
||||
def test_document_download_user_view(self):
|
||||
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||
# SingleObjectDownloadView CBV
|
||||
|
||||
self.login(
|
||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||
)
|
||||
@@ -253,7 +258,11 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
||||
|
||||
del(buf)
|
||||
|
||||
@skip_file_descriptor_check
|
||||
def test_document_multiple_download_user_view(self):
|
||||
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||
# SingleObjectDownloadView CBV
|
||||
|
||||
self.login(
|
||||
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
|
||||
)
|
||||
@@ -287,7 +296,11 @@ class DocumentsViewsTestCase(GenericDocumentViewTestCase):
|
||||
|
||||
del(buf)
|
||||
|
||||
@skip_file_descriptor_check
|
||||
def test_document_version_download_user_view(self):
|
||||
# TODO: Skip this test's file descriptor check until it gets migrate
|
||||
# SingleObjectDownloadView CBV
|
||||
|
||||
self.login(
|
||||
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 .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):
|
||||
|
||||
@@ -2,10 +2,10 @@ from __future__ import unicode_literals
|
||||
|
||||
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 .links import link_search, link_search_advanced, link_search_again
|
||||
from .links import link_search, link_search_advanced
|
||||
|
||||
|
||||
class DynamicSearchApp(MayanAppConfig):
|
||||
@@ -26,6 +26,3 @@ class DynamicSearchApp(MayanAppConfig):
|
||||
'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 permissions import Permission
|
||||
|
||||
from .models import RecentSearch
|
||||
from .settings import setting_limit
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -148,7 +147,7 @@ class SearchModel(object):
|
||||
for query in field_query_list:
|
||||
logger.debug('query: %s', query)
|
||||
term_query_result_set = set(
|
||||
model.objects.filter(query).values_list(
|
||||
model.on_organization.filter(query).values_list(
|
||||
data['return_value'], flat=True
|
||||
)
|
||||
)
|
||||
@@ -183,7 +182,7 @@ class SearchModel(object):
|
||||
datetime.datetime.now() - start_time
|
||||
).split(':')[2]
|
||||
|
||||
queryset = self.model.objects.filter(
|
||||
queryset = self.model.on_organization.filter(
|
||||
pk__in=list(result_set)[:setting_limit.value]
|
||||
)
|
||||
|
||||
@@ -195,10 +194,6 @@ class SearchModel(object):
|
||||
self.permission, user, queryset
|
||||
)
|
||||
|
||||
RecentSearch.objects.add_query_for_user(
|
||||
user, query_string, len(result_set)
|
||||
)
|
||||
|
||||
return queryset, result_set, elapsed_time
|
||||
|
||||
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(
|
||||
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(
|
||||
model_name='recentsearch',
|
||||
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,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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 json import loads
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
from user_management.tests import (
|
||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME
|
||||
)
|
||||
from rest_api.tests import GenericAPITestCase
|
||||
|
||||
|
||||
@override_settings(OCR_AUTO_OCR=False)
|
||||
class SearchAPITestCase(APITestCase):
|
||||
class SearchAPITestCase(GenericAPITestCase):
|
||||
"""
|
||||
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):
|
||||
document_type = DocumentType.objects.create(
|
||||
document_type = DocumentType.on_organization.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
@@ -45,6 +28,5 @@ class SearchAPITestCase(APITestCase):
|
||||
'{}?q={}'.format(reverse('rest_api:search-view'), document.label)
|
||||
)
|
||||
|
||||
content = loads(response.content)
|
||||
self.assertEqual(content['results'][0]['label'], document.label)
|
||||
self.assertEqual(content['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['label'], document.label)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.search import document_search
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
from organizations.tests import OrganizationTestCase
|
||||
from user_management.tests import (
|
||||
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_ADMIN_EMAIL
|
||||
)
|
||||
|
||||
|
||||
class DocumentSearchTestCase(TestCase):
|
||||
class DocumentSearchTestCase(OrganizationTestCase):
|
||||
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,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.document_type = DocumentType.objects.create(
|
||||
self.document_type = DocumentType.on_organization.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
@@ -28,6 +30,7 @@ class DocumentSearchTestCase(TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
self.document_type.delete()
|
||||
super(DocumentSearchTestCase, self).tearDown()
|
||||
|
||||
def test_simple_search_after_related_name_change(self):
|
||||
"""
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
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.search import document_search
|
||||
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
|
||||
"""
|
||||
|
||||
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 = 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())
|
||||
super(Issue46TestCase, self).setUp()
|
||||
|
||||
self.login_superuser()
|
||||
|
||||
self.document_count = 4
|
||||
|
||||
self.document_type = DocumentType.objects.create(
|
||||
self.document_type = DocumentType.on_organization.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
@@ -46,9 +33,11 @@ class Issue46TestCase(TestCase):
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
for document_type in DocumentType.objects.all():
|
||||
for document_type in DocumentType.on_organization.all():
|
||||
document_type.delete()
|
||||
|
||||
super(Issue46TestCase, self).tearDown()
|
||||
|
||||
def test_advanced_search_past_first_page(self):
|
||||
# Make sure all documents are returned by the 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 .api_views import (
|
||||
APIRecentSearchListView, APIRecentSearchView, APISearchView
|
||||
)
|
||||
from .api_views import APISearchView
|
||||
from .views import AdvancedSearchView, ResultsView, SearchView
|
||||
|
||||
urlpatterns = patterns(
|
||||
'dynamic_search.views',
|
||||
url(r'^$', SearchView.as_view(), name='search'),
|
||||
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'),
|
||||
)
|
||||
|
||||
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'),
|
||||
)
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.generics import SimpleView, SingleObjectListView
|
||||
@@ -82,12 +79,3 @@ class AdvancedSearchView(SearchView):
|
||||
return AdvancedSearchForm(
|
||||
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 django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
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
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class FolderOrganizationViewTestCase(OrganizationViewTestCase):
|
||||
def test_folder_create_view(self):
|
||||
# Create a folder for organization A
|
||||
with self.settings(ORGANIZATION_ID=self.organization_a.pk):
|
||||
response = self.post(
|
||||
self.post(
|
||||
'folders:folder_create', data={
|
||||
'label': TEST_FOLDER_LABEL
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from organizations.admin import OrganizationAdminMixin
|
||||
|
||||
from .models import SmartLink, SmartLinkCondition
|
||||
|
||||
|
||||
@@ -13,7 +15,7 @@ class SmartLinkConditionInline(admin.StackedInline):
|
||||
|
||||
|
||||
@admin.register(SmartLink)
|
||||
class SmartLinkAdmin(admin.ModelAdmin):
|
||||
class SmartLinkAdmin(OrganizationAdminMixin, admin.ModelAdmin):
|
||||
def document_type_list(self, instance):
|
||||
return ','.join(
|
||||
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 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 (
|
||||
INCLUSION_AND, INCLUSION_CHOICES, INCLUSION_OR, OPERATOR_CHOICES
|
||||
)
|
||||
from .managers import OrganizationSmartLinkConditionManager
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SmartLink(models.Model):
|
||||
organization = models.ForeignKey(
|
||||
Organization, default=get_current_organization
|
||||
)
|
||||
label = models.CharField(max_length=96, verbose_name=_('Label'))
|
||||
dynamic_label = models.CharField(
|
||||
blank=True, max_length=96, help_text=_(
|
||||
@@ -29,6 +36,9 @@ class SmartLink(models.Model):
|
||||
DocumentType, verbose_name=_('Document types')
|
||||
)
|
||||
|
||||
objects = models.Manager()
|
||||
on_organization = CurrentOrganizationManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
@@ -75,9 +85,9 @@ class SmartLink(models.Model):
|
||||
smart_link_query |= condition_query
|
||||
|
||||
if smart_link_query:
|
||||
return Document.objects.filter(smart_link_query)
|
||||
return Document.on_organization.filter(smart_link_query)
|
||||
else:
|
||||
return Document.objects.none()
|
||||
return Document.on_organization.none()
|
||||
|
||||
def resolve_for(self, document):
|
||||
return ResolvedSmartLink(
|
||||
@@ -123,6 +133,9 @@ class SmartLinkCondition(models.Model):
|
||||
)
|
||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||
|
||||
objects = models.Manager()
|
||||
on_organization = OrganizationSmartLinkConditionManager()
|
||||
|
||||
def __str__(self):
|
||||
return '%s foreign %s %s %s %s' % (
|
||||
self.get_inclusion_display(),
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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.tests import TEST_DOCUMENT_PATH, TEST_DOCUMENT_TYPE
|
||||
from organizations.tests import OrganizationTestCase
|
||||
from user_management.tests.literals import (
|
||||
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)
|
||||
class SmartLinkTestCase(TestCase):
|
||||
class SmartLinkTestCase(OrganizationTestCase):
|
||||
def setUp(self):
|
||||
self.document_type = DocumentType.objects.create(
|
||||
super(SmartLinkTestCase, self).setUp()
|
||||
|
||||
self.document_type = DocumentType.on_organization.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
@@ -28,16 +31,17 @@ class SmartLinkTestCase(TestCase):
|
||||
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,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document_type.delete()
|
||||
super(SmartLinkTestCase, self).tearDown()
|
||||
|
||||
def test_dynamic_label(self):
|
||||
smart_link = SmartLink.objects.create(
|
||||
smart_link = SmartLink.on_organization.create(
|
||||
label=TEST_SMART_LINK_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.assertEqual(SmartLink.objects.count(), 0)
|
||||
self.assertEqual(SmartLink.on_organization.count(), 0)
|
||||
|
||||
def test_smart_link_create_view_with_permission(self):
|
||||
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
|
||||
@@ -44,21 +44,23 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
|
||||
}, follow=True
|
||||
)
|
||||
self.assertContains(response, text='created', status_code=200)
|
||||
self.assertEqual(SmartLink.objects.count(), 1)
|
||||
self.assertEqual(SmartLink.on_organization.count(), 1)
|
||||
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):
|
||||
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(
|
||||
'linking:smart_link_delete', args=(smart_link.pk,)
|
||||
)
|
||||
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):
|
||||
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
|
||||
@@ -67,19 +69,23 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
|
||||
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(
|
||||
'linking:smart_link_delete', args=(smart_link.pk,), follow=True
|
||||
)
|
||||
|
||||
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):
|
||||
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(
|
||||
'linking:smart_link_edit', args=(smart_link.pk,), data={
|
||||
@@ -87,7 +93,7 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
|
||||
}
|
||||
)
|
||||
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)
|
||||
|
||||
def test_smart_link_edit_view_with_permission(self):
|
||||
@@ -97,7 +103,9 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
|
||||
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(
|
||||
'linking:smart_link_edit', args=(smart_link.pk,), data={
|
||||
@@ -105,18 +113,18 @@ class SmartLinkViewTestCase(GenericDocumentViewTestCase):
|
||||
}, 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.assertEqual(smart_link.label, TEST_SMART_LINK_EDITED_LABEL)
|
||||
|
||||
def setup_smart_links(self):
|
||||
smart_link = SmartLink.objects.create(
|
||||
smart_link = SmartLink.on_organization.create(
|
||||
label=TEST_SMART_LINK_LABEL,
|
||||
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
|
||||
)
|
||||
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,
|
||||
dynamic_label=TEST_SMART_LINK_DYNAMIC_LABEL
|
||||
)
|
||||
|
||||
@@ -31,10 +31,10 @@ logger = logging.getLogger(__name__)
|
||||
class ResolvedSmartLinkView(DocumentListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
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(
|
||||
SmartLink, pk=self.kwargs['smart_link_pk']
|
||||
SmartLink.on_organization, pk=self.kwargs['smart_link_pk']
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -63,7 +63,7 @@ class ResolvedSmartLinkView(DocumentListView):
|
||||
try:
|
||||
queryset = self.smart_link.get_linked_document_for(self.document)
|
||||
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:
|
||||
messages.error(
|
||||
@@ -110,12 +110,14 @@ class SetupSmartLinkDocumentTypesView(AssignRemoveView):
|
||||
}
|
||||
|
||||
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):
|
||||
# TODO: filter document type list by user ACL
|
||||
return AssignRemoveView.generate_choices(
|
||||
DocumentType.objects.exclude(
|
||||
DocumentType.on_organization.exclude(
|
||||
pk__in=self.get_object().document_types.all()
|
||||
)
|
||||
)
|
||||
@@ -144,12 +146,14 @@ class SmartLinkListView(SingleObjectListView):
|
||||
return super(SmartLinkListView, self).get_queryset()
|
||||
|
||||
def get_smart_link_queryset(self):
|
||||
return SmartLink.objects.all()
|
||||
return SmartLink.on_organization.all()
|
||||
|
||||
|
||||
class DocumentSmartLinkListView(SmartLinkListView):
|
||||
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:
|
||||
Permission.check_permissions(
|
||||
@@ -174,7 +178,7 @@ class DocumentSmartLinkListView(SmartLinkListView):
|
||||
}
|
||||
|
||||
def get_smart_link_queryset(self):
|
||||
return ResolvedSmartLink.objects.filter(
|
||||
return ResolvedSmartLink.on_organization.filter(
|
||||
document_types=self.document.document_type, enabled=True
|
||||
)
|
||||
|
||||
@@ -188,7 +192,6 @@ class SmartLinkCreateView(SingleObjectCreateView):
|
||||
|
||||
class SmartLinkEditView(SingleObjectEditView):
|
||||
form_class = SmartLinkForm
|
||||
model = SmartLink
|
||||
post_action_redirect = reverse_lazy('linking:smart_link_list')
|
||||
view_permission = permission_smart_link_edit
|
||||
|
||||
@@ -198,9 +201,11 @@ class SmartLinkEditView(SingleObjectEditView):
|
||||
'title': _('Edit smart link: %s') % self.get_object()
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return SmartLink.on_organization.all()
|
||||
|
||||
|
||||
class SmartLinkDeleteView(SingleObjectDeleteView):
|
||||
model = SmartLink
|
||||
post_action_redirect = reverse_lazy('linking:smart_link_list')
|
||||
view_permission = permission_smart_link_delete
|
||||
|
||||
@@ -210,6 +215,9 @@ class SmartLinkDeleteView(SingleObjectDeleteView):
|
||||
'title': _('Delete smart link: %s') % self.get_object()
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return SmartLink.on_organization.all()
|
||||
|
||||
|
||||
class SmartLinkConditionListView(SingleObjectListView):
|
||||
view_permission = permission_smart_link_edit
|
||||
@@ -227,7 +235,9 @@ class SmartLinkConditionListView(SingleObjectListView):
|
||||
return self.get_smart_link().conditions.all()
|
||||
|
||||
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):
|
||||
@@ -269,12 +279,13 @@ class SmartLinkConditionCreateView(SingleObjectCreateView):
|
||||
return self.get_smart_link().conditions.all()
|
||||
|
||||
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):
|
||||
form_class = SmartLinkConditionForm
|
||||
model = SmartLinkCondition
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
@@ -306,10 +317,11 @@ class SmartLinkConditionEditView(SingleObjectEditView):
|
||||
)
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
return SmartLinkCondition.on_organization.all()
|
||||
|
||||
|
||||
class SmartLinkConditionDeleteView(SingleObjectDeleteView):
|
||||
model = SmartLinkCondition
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(
|
||||
@@ -341,3 +353,6 @@ class SmartLinkConditionDeleteView(SingleObjectDeleteView):
|
||||
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 django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
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
|
||||
|
||||
|
||||
@@ -5,9 +5,8 @@ from organizations.tests.test_organization_views import OrganizationViewTestCase
|
||||
from ..models import MetadataType
|
||||
|
||||
from .literals import (
|
||||
TEST_DOCUMENT_METADATA_VALUE_2, TEST_METADATA_TYPE_LABEL,
|
||||
TEST_METADATA_TYPE_LABEL_2, TEST_METADATA_TYPE_NAME,
|
||||
TEST_METADATA_TYPE_NAME_2
|
||||
TEST_METADATA_TYPE_LABEL, TEST_METADATA_TYPE_LABEL_2,
|
||||
TEST_METADATA_TYPE_NAME,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -8,12 +8,10 @@ from pdfminer.pdfpage import PDFPage
|
||||
from pdfminer.converter import TextConverter
|
||||
from pdfminer.layout import LAParams
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.settings import setting_temporary_directory
|
||||
from common.utils import copyfile
|
||||
from common.utils import copyfile, fs_cleanup, mkstemp
|
||||
|
||||
from .exceptions import ParserError, NoMIMETypeMatch
|
||||
from .models import DocumentPageContent
|
||||
@@ -137,9 +135,7 @@ class PopplerParser(Parser):
|
||||
def execute(self, file_object, page_number):
|
||||
logger.debug('Parsing PDF page: %d', page_number)
|
||||
|
||||
destination_descriptor, temp_filepath = tempfile.mkstemp(
|
||||
dir=setting_temporary_directory.value
|
||||
)
|
||||
destination_descriptor, temp_filepath = mkstemp()
|
||||
copyfile(file_object, temp_filepath)
|
||||
|
||||
command = []
|
||||
@@ -158,9 +154,12 @@ class PopplerParser(Parser):
|
||||
return_code = proc.wait()
|
||||
if return_code != 0:
|
||||
logger.error(proc.stderr.readline())
|
||||
fs_cleanup(temp_filepath, file_descriptor=destination_descriptor)
|
||||
|
||||
raise ParserError
|
||||
|
||||
output = proc.stdout.read()
|
||||
fs_cleanup(temp_filepath, file_descriptor=destination_descriptor)
|
||||
|
||||
if output == b'\x0c':
|
||||
logger.debug('Parser didn\'t return any output')
|
||||
|
||||
@@ -16,7 +16,6 @@ from ..parsers import PDFMinerParser, PopplerParser
|
||||
class ParserTestCase(OrganizationTestCase):
|
||||
def setUp(self):
|
||||
super(ParserTestCase, self).setUp()
|
||||
|
||||
self.document_type = DocumentType.on_organization.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from common.apps import MayanAppConfig
|
||||
|
||||
|
||||
class OrganizationApp(AppConfig):
|
||||
class OrganizationApp(MayanAppConfig):
|
||||
name = 'organizations'
|
||||
verbose_name = _('Organizations')
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import management
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
from ...models import Organization
|
||||
|
||||
|
||||
|
||||
class Command(management.BaseCommand):
|
||||
help = 'Creates an organization admin user with a secure random password and all permissions.'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.db import migrations
|
||||
|
||||
from ..literals import DEFAULT_ORGANIZATION_LABEL
|
||||
|
||||
@@ -9,7 +9,7 @@ from ..literals import DEFAULT_ORGANIZATION_LABEL
|
||||
def default_data(apps, schema_editor):
|
||||
# We can't import the Organization model directly as it may be a newer
|
||||
# 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.save()
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
import string
|
||||
import warnings
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
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.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.translation import ugettext_lazy as _
|
||||
|
||||
@@ -110,7 +107,7 @@ class Organization(models.Model):
|
||||
)
|
||||
|
||||
account = UserModel.objects.get(
|
||||
**{UserModel.USERNAME_FIELD: username, 'organization': self,}
|
||||
**{UserModel.USERNAME_FIELD: username, 'organization': self}
|
||||
)
|
||||
account.set_password(raw_password=password_value)
|
||||
account.save()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import tempfile
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from smart_settings import Namespace
|
||||
|
||||
@@ -4,5 +4,5 @@ from django.apps import apps
|
||||
|
||||
|
||||
def get_current_organization():
|
||||
from .models import Organization
|
||||
Organization = apps.get_model('organizations', 'Organization')
|
||||
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 django.test import TestCase
|
||||
from common.tests import BaseTestCase
|
||||
|
||||
from ..models import Organization
|
||||
from ..utils import create_default_organization
|
||||
|
||||
|
||||
class OrganizationTestCase(TestCase):
|
||||
class OrganizationTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
create_default_organization()
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
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.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import connections, router
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase, modify_settings, override_settings
|
||||
|
||||
@@ -4,7 +4,7 @@ from django.test import TestCase
|
||||
|
||||
from ..models import Organization
|
||||
|
||||
from .literals import TEST_ORGANIZATION_LABEL, TEST_ORGANIZATION_EDITED_LABEL
|
||||
from .literals import TEST_ORGANIZATION_LABEL
|
||||
|
||||
|
||||
class OrganizationModelTestCase(TestCase):
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
||||
from django.apps import apps
|
||||
from django.core.management.color import no_style
|
||||
from django.db import DEFAULT_DB_ALIAS, connections, router
|
||||
from django.db.models import signals
|
||||
|
||||
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
|
||||
# can also crop up outside of tests - see #15346.
|
||||
if verbosity >= 2:
|
||||
print("Creating default Organization object")
|
||||
print('Creating default Organization object')
|
||||
Organization(pk=1, label=DEFAULT_ORGANIZATION_LABEL).save(using=using)
|
||||
|
||||
# We set an explicit pk instead of relying on auto-incrementation,
|
||||
# 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 verbosity >= 2:
|
||||
print('Resetting sequence')
|
||||
|
||||
@@ -9,7 +9,7 @@ from user_management.models import MayanGroup
|
||||
from user_management.tests import TEST_GROUP, TEST_USER_USERNAME
|
||||
|
||||
from ..classes import Permission
|
||||
from ..models import Role, StoredPermission
|
||||
from ..models import Role
|
||||
from ..permissions import permission_role_view
|
||||
|
||||
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 django.contrib.auth import get_user_model
|
||||
from django.test import override_settings
|
||||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from organizations.models import Organization
|
||||
@@ -24,7 +21,6 @@ class GenericAPITestCase(APITestCase):
|
||||
username=self.admin_user.username, password=password
|
||||
)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
super(GenericAPITestCase, self).tearDown()
|
||||
self.admin_user.delete()
|
||||
|
||||
@@ -2,18 +2,18 @@ from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from documents.tests import TEST_NON_ASCII_DOCUMENT_PATH
|
||||
from common.utils import mkdtemp
|
||||
|
||||
from ..classes import StagingFile
|
||||
|
||||
|
||||
class StagingFileTestCase(TestCase):
|
||||
def test_unicode_staging_file(self):
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory)
|
||||
|
||||
filename = os.path.basename(TEST_NON_ASCII_DOCUMENT_PATH)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.files.base import File
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test.client import Client
|
||||
|
||||
from common.utils import mkdtemp
|
||||
from documents.models import Document, DocumentType
|
||||
from documents.tests import (
|
||||
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
|
||||
"""
|
||||
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
temporary_directory = mkdtemp()
|
||||
shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory)
|
||||
|
||||
watch_folder = WatchFolderSource.on_organization.create(
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -11,6 +10,7 @@ from django.test import TestCase, override_settings
|
||||
|
||||
from acls.models import AccessControlList
|
||||
from common.tests.test_views import GenericViewTestCase
|
||||
from common.utils import fs_cleanup, mkdtemp
|
||||
from documents.models import Document, DocumentType, NewVersionBlock
|
||||
from documents.permissions import permission_document_create
|
||||
from documents.tests import (
|
||||
@@ -230,15 +230,14 @@ class NewDocumentVersionViewTestCase(GenericDocumentViewTestCase):
|
||||
class StagingFolderTestCase(GenericViewTestCase):
|
||||
def setUp(self):
|
||||
super(StagingFolderTestCase, self).setUp()
|
||||
self.temporary_directory = tempfile.mkdtemp()
|
||||
# TODO: remove temp directory after test
|
||||
self.temporary_directory = mkdtemp()
|
||||
shutil.copy(TEST_SMALL_DOCUMENT_PATH, self.temporary_directory)
|
||||
|
||||
self.filename = os.path.basename(TEST_SMALL_DOCUMENT_PATH)
|
||||
|
||||
def tearDown(self):
|
||||
fs_cleanup(self.temporary_directory)
|
||||
super(StagingFolderTestCase, self).tearDown()
|
||||
shutil.rmtree(self.temporary_directory)
|
||||
|
||||
def test_staging_folder_delete_no_permission(self):
|
||||
self.login(
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import override_settings
|
||||
|
||||
from documents.models import DocumentType
|
||||
from documents.tests import TEST_DOCUMENT_TYPE, TEST_SMALL_DOCUMENT_PATH
|
||||
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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
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.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.auth.admin import UserAdmin, GroupAdmin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
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()
|
||||
on_organization = CurrentOrganizationManager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Group')
|
||||
verbose_name_plural = _('Groups')
|
||||
|
||||
|
||||
class OrganizationUserManagerHybridClass(CurrentOrganizationManager, UserManager):
|
||||
"""
|
||||
@@ -27,7 +31,7 @@ class OrganizationUserManagerHybridClass(CurrentOrganizationManager, UserManager
|
||||
|
||||
class MayanUser(AbstractUser):
|
||||
organization = models.ForeignKey(
|
||||
Organization, default=get_current_organization
|
||||
Organization, blank=True, default=get_current_organization, null=True
|
||||
)
|
||||
|
||||
organization_groups = models.ManyToManyField(
|
||||
|
||||
@@ -4,4 +4,4 @@ coveralls==0.5
|
||||
django-test-without-migrations==0.2
|
||||
mock==2.0.0
|
||||
tox==2.1.1
|
||||
|
||||
psutil==4.3.0
|
||||
|
||||
Reference in New Issue
Block a user