Merge branch 'features/mercs_5_6' into 3_way_merge
Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
@@ -2,42 +2,42 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from rest_framework import generics
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from .classes import Template
|
||||
from .serializers import ContentTypeSerializer, TemplateSerializer
|
||||
|
||||
|
||||
class APIContentTypeList(generics.ListAPIView):
|
||||
class ContentTypeAPIViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
Returns a list of all the available content types.
|
||||
list:
|
||||
Return a list of all the available content types.
|
||||
|
||||
retrieve:
|
||||
Return the given content type details.
|
||||
"""
|
||||
serializer_class = ContentTypeSerializer
|
||||
lookup_url_kwarg = 'content_type_id'
|
||||
queryset = ContentType.objects.order_by('app_label', 'model')
|
||||
serializer_class = ContentTypeSerializer
|
||||
|
||||
|
||||
class APITemplateListView(generics.ListAPIView):
|
||||
class TemplateAPIViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
"""
|
||||
Returns a list of partial templates.
|
||||
get: Returns a list of partial templates.
|
||||
list:
|
||||
Return a list of partial templates.
|
||||
|
||||
retrieve:
|
||||
Return the given partial template details.
|
||||
"""
|
||||
serializer_class = TemplateSerializer
|
||||
lookup_url_kwarg = 'template_name'
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = TemplateSerializer
|
||||
|
||||
def get_object(self):
|
||||
return Template.get(name=self.kwargs['template_name']).render(
|
||||
request=self.request
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
return Template.all(rendered=True, request=self.request)
|
||||
|
||||
|
||||
class APITemplateView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Returns the selected partial template details.
|
||||
get: Retrieve the details of the partial template.
|
||||
"""
|
||||
serializer_class = TemplateSerializer
|
||||
permission_classes = (IsAuthenticated,)
|
||||
|
||||
def get_object(self):
|
||||
return Template.get(name=self.kwargs['name']).render(
|
||||
request=self.request
|
||||
)
|
||||
|
||||
@@ -4,6 +4,8 @@ import logging
|
||||
import os
|
||||
import warnings
|
||||
from datetime import timedelta
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from kombu import Exchange, Queue
|
||||
|
||||
@@ -41,6 +43,7 @@ from .settings import (
|
||||
from .signals import pre_initial_setup, pre_upgrade
|
||||
from .tasks import task_delete_stale_uploads # NOQA - Force task registration
|
||||
from .utils import check_for_sqlite
|
||||
from .warnings import DatabaseWarning
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -74,6 +77,8 @@ class MayanAppConfig(apps.AppConfig):
|
||||
'Import time error when running AppConfig.ready() of app '
|
||||
'"%s".', self.name
|
||||
)
|
||||
exc_info = sys.exc_info()
|
||||
traceback.print_exception(*exc_info)
|
||||
raise exception
|
||||
|
||||
|
||||
@@ -88,7 +93,9 @@ class CommonApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(CommonApp, self).ready()
|
||||
if check_for_sqlite():
|
||||
warnings.warn(force_text(MESSAGE_SQLITE_WARNING))
|
||||
warnings.warn(
|
||||
category=DatabaseWarning, message=force_text(MESSAGE_SQLITE_WARNING)
|
||||
)
|
||||
|
||||
Template(
|
||||
name='menu_main', template_name='appearance/menu_main.html'
|
||||
|
||||
@@ -72,16 +72,6 @@ class ErrorLogNamespace(object):
|
||||
return ErrorLogEntry.objects.filter(namespace=self.name)
|
||||
|
||||
|
||||
class FakeStorageSubclass(object):
|
||||
"""
|
||||
Placeholder class to allow serializing the real storage subclass to
|
||||
support migrations.
|
||||
"""
|
||||
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
|
||||
class MissingItem(object):
|
||||
_registry = []
|
||||
|
||||
@@ -302,7 +292,7 @@ class Template(object):
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
viewname='rest_api:template-detail', kwargs={'template_pk': self.name}
|
||||
viewname='rest_api:template-detail', kwargs={'template_name': self.name}
|
||||
)
|
||||
|
||||
def render(self, request):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,12 @@ from __future__ import absolute_import, unicode_literals
|
||||
from mayan.apps.appearance.classes import Icon
|
||||
|
||||
icon_about = Icon(driver_name='fontawesome', symbol='info')
|
||||
icon_add_all = Icon(
|
||||
driver_name='fontawesome-layers', data=[
|
||||
{'class': 'far fa-circle'},
|
||||
{'class': 'fas fa-plus', 'transform': 'shrink-6'}
|
||||
]
|
||||
)
|
||||
icon_assign_remove_add = Icon(driver_name='fontawesome', symbol='plus')
|
||||
icon_assign_remove_remove = Icon(driver_name='fontawesome', symbol='minus')
|
||||
icon_check_version = Icon(driver_name='fontawesome', symbol='sync')
|
||||
@@ -43,6 +49,12 @@ icon_ok = Icon(
|
||||
icon_packages_licenses = Icon(
|
||||
driver_name='fontawesome', symbol='certificate'
|
||||
)
|
||||
icon_remove_all = Icon(
|
||||
driver_name='fontawesome-layers', data=[
|
||||
{'class': 'far fa-circle'},
|
||||
{'class': 'fas fa-minus', 'transform': 'shrink-6'}
|
||||
]
|
||||
)
|
||||
icon_setup = Icon(
|
||||
driver_name='fontawesome', symbol='cog'
|
||||
)
|
||||
|
||||
@@ -57,12 +57,12 @@ link_documentation = Link(
|
||||
link_object_error_list = Link(
|
||||
icon_class=icon_object_error_list,
|
||||
kwargs=get_kwargs_factory('resolved_object'),
|
||||
permissions=(permission_error_log_view,), text=_('Errors'),
|
||||
permission=permission_error_log_view, text=_('Errors'),
|
||||
view='common:object_error_list',
|
||||
)
|
||||
link_object_error_list_clear = Link(
|
||||
kwargs=get_kwargs_factory('resolved_object'),
|
||||
permissions=(permission_error_log_view,), text=_('Clear all'),
|
||||
permission=permission_error_log_view, text=_('Clear all'),
|
||||
view='common:object_error_list_clear',
|
||||
)
|
||||
link_forum = Link(
|
||||
|
||||
@@ -11,7 +11,7 @@ MESSAGE_SQLITE_WARNING = _(
|
||||
'for development and testing, not for production.'
|
||||
)
|
||||
PYPI_URL = 'https://pypi.python.org/pypi'
|
||||
|
||||
PK_LIST_SEPARATOR = ','
|
||||
TEXT_LIST_AS_ITEMS_PARAMETER = '_list_mode'
|
||||
TEXT_LIST_AS_ITEMS_VARIABLE_NAME = 'list_as_items'
|
||||
TEXT_CHOICE_ITEMS = 'items'
|
||||
|
||||
@@ -11,8 +11,8 @@ from django.core.management.base import CommandError
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from mayan.apps.common.utils import fs_cleanup
|
||||
from mayan.apps.documents.models import DocumentType
|
||||
from mayan.apps.storage.utils import fs_cleanup
|
||||
|
||||
CONVERTDB_FOLDER = 'convertdb'
|
||||
CONVERTDB_OUTPUT_FILENAME = 'migrate.json'
|
||||
|
||||
@@ -8,21 +8,20 @@ from .icons import icon_menu_about, icon_menu_user
|
||||
|
||||
__all__ = (
|
||||
'menu_about', 'menu_facet', 'menu_list_facet', 'menu_main', 'menu_object',
|
||||
'menu_multi_item', 'menu_secondary', 'menu_setup', 'menu_sidebar',
|
||||
'menu_multi_item', 'menu_secondary', 'menu_setup', 'menu_secondary',
|
||||
'menu_tools', 'menu_topbar', 'menu_user'
|
||||
)
|
||||
|
||||
menu_about = Menu(
|
||||
icon_class=icon_menu_about, label=_('System'), name='about'
|
||||
)
|
||||
menu_facet = Menu(name='facet')
|
||||
menu_list_facet = Menu(name='list facet')
|
||||
menu_facet = Menu(label=_('Facet'), name='facet')
|
||||
menu_list_facet = Menu(label=_('Facet'), name='list facet')
|
||||
menu_main = Menu(name='main')
|
||||
menu_multi_item = Menu(name='multi item')
|
||||
menu_object = Menu(name='object')
|
||||
menu_secondary = Menu(name='secondary')
|
||||
menu_object = Menu(label=_('Actions'), name='object')
|
||||
menu_secondary = Menu(label=_('Secondary'), name='secondary')
|
||||
menu_setup = Menu(name='setup')
|
||||
menu_sidebar = Menu(name='sidebar')
|
||||
menu_tools = Menu(name='tools')
|
||||
menu_topbar = Menu(name='topbar')
|
||||
menu_user = Menu(
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.16 on 2018-12-29 07:38
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
import mayan.apps.common.classes
|
||||
import mayan.apps.common.models
|
||||
import mayan.apps.storage.classes
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('common', '0010_auto_20180403_0702_squashed_0011_auto_20180429_0758'),
|
||||
]
|
||||
@@ -17,6 +16,10 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='shareduploadedfile',
|
||||
name='file',
|
||||
field=models.FileField(storage=mayan.apps.common.classes.FakeStorageSubclass(), upload_to=mayan.apps.common.models.upload_to, verbose_name='File'),
|
||||
field=models.FileField(
|
||||
storage=mayan.apps.storage.classes.FakeStorageSubclass(),
|
||||
upload_to=mayan.apps.common.models.upload_to,
|
||||
verbose_name='File'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ from .storages import storage_sharedupload
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# TODO: move outside of models.py or as a static method of SharedUploadedFile
|
||||
def upload_to(instance, filename):
|
||||
return 'shared-file-{}'.format(uuid.uuid4().hex)
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ class PurePaginator(Paginator):
|
||||
self.allow_empty_first_page = allow_empty_first_page
|
||||
self.object_list = object_list
|
||||
self.orphans = orphans
|
||||
self.per_page = per_page
|
||||
self.page_kwarg = page_kwarg
|
||||
self.per_page = per_page
|
||||
self.request = request
|
||||
|
||||
def page(self, number):
|
||||
|
||||
@@ -7,5 +7,5 @@ from mayan.apps.permissions import PermissionNamespace
|
||||
namespace = PermissionNamespace(label=_('Common'), name='common')
|
||||
|
||||
permission_error_log_view = namespace.add_permission(
|
||||
name='error_log_view', label=_('View error log')
|
||||
label=_('View error log'), name='error_log_view'
|
||||
)
|
||||
|
||||
@@ -5,13 +5,13 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from mayan.apps.task_manager.classes import CeleryQueue
|
||||
|
||||
queue_default = CeleryQueue(
|
||||
name='default', label=_('Default'), is_default_queue=True
|
||||
is_default_queue=True, label=_('Default'), name='default'
|
||||
)
|
||||
queue_tools = CeleryQueue(name='tools', label=_('Tools'))
|
||||
queue_tools = CeleryQueue(label=_('Tools'), name='tools')
|
||||
queue_common_periodic = CeleryQueue(
|
||||
name='common_periodic', label=_('Common periodic'), transient=True
|
||||
label=_('Common periodic'), name='common_periodic', transient=True
|
||||
)
|
||||
queue_common_periodic.add_task_type(
|
||||
name='mayan.apps.common.tasks.task_delete_stale_uploads',
|
||||
label=_('Delete stale uploads')
|
||||
label=_('Delete stale uploads'),
|
||||
name='mayan.apps.common.tasks.task_delete_stale_uploads'
|
||||
)
|
||||
|
||||
@@ -5,9 +5,15 @@ from django.contrib.contenttypes.models import ContentType
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class ContentTypeSerializer(serializers.ModelSerializer):
|
||||
class ContentTypeSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
fields = ('app_label', 'id', 'model')
|
||||
extra_kwargs = {
|
||||
'url': {
|
||||
'lookup_url_kwarg': 'content_type_id',
|
||||
'view_name': 'rest_api:content_type-detail'
|
||||
}
|
||||
}
|
||||
fields = ('app_label', 'id', 'model', 'url')
|
||||
model = ContentType
|
||||
|
||||
|
||||
@@ -15,3 +21,7 @@ class TemplateSerializer(serializers.Serializer):
|
||||
hex_hash = serializers.CharField(read_only=True)
|
||||
name = serializers.CharField(read_only=True)
|
||||
html = serializers.CharField(read_only=True)
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
lookup_field='name', lookup_url_kwarg='template_name',
|
||||
view_name='rest_api:template-detail'
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
@@ -11,7 +10,7 @@ from mayan.apps.smart_settings import Namespace
|
||||
|
||||
from .literals import DEFAULT_COMMON_HOME_VIEW
|
||||
|
||||
namespace = Namespace(name='common', label=_('Common'))
|
||||
namespace = Namespace(label=_('Common'), name='common')
|
||||
|
||||
setting_auto_logging = namespace.add_setting(
|
||||
global_name='COMMON_AUTO_LOGGING',
|
||||
@@ -53,8 +52,7 @@ setting_production_error_log_path = namespace.add_setting(
|
||||
global_name='COMMON_PRODUCTION_ERROR_LOG_PATH',
|
||||
default=os.path.join(settings.MEDIA_ROOT, 'error.log'), help_text=_(
|
||||
'Path to the logfile that will track errors during production.'
|
||||
),
|
||||
is_path=True
|
||||
)
|
||||
)
|
||||
setting_project_title = namespace.add_setting(
|
||||
global_name='COMMON_PROJECT_TITLE',
|
||||
@@ -77,16 +75,8 @@ setting_shared_storage_arguments = namespace.add_setting(
|
||||
global_name='COMMON_SHARED_STORAGE_ARGUMENTS',
|
||||
default={'location': os.path.join(settings.MEDIA_ROOT, 'shared_files')}
|
||||
)
|
||||
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.'
|
||||
),
|
||||
is_path=True
|
||||
)
|
||||
|
||||
namespace = Namespace(name='django', label=_('Django'))
|
||||
namespace = Namespace(label=_('Django'), name='django')
|
||||
|
||||
setting_django_allowed_hosts = namespace.add_setting(
|
||||
global_name='ALLOWED_HOSTS', default=settings.ALLOWED_HOSTS,
|
||||
@@ -357,7 +347,20 @@ setting_django_login_redirect_url = namespace.add_setting(
|
||||
'for example. This setting also accepts named URL patterns which '
|
||||
'can be used to reduce configuration duplication since you don\'t '
|
||||
'have to define the URL in two places (settings and URLconf).'
|
||||
),
|
||||
)
|
||||
)
|
||||
setting_django_logout_redirect_url = namespace.add_setting(
|
||||
global_name='LOGOUT_REDIRECT_URL',
|
||||
default=settings.LOGOUT_REDIRECT_URL,
|
||||
help_text=_(
|
||||
'Default: None. The URL where requests are redirected after a user '
|
||||
'logs out using LogoutView (if the view doesn\'t get a next_page '
|
||||
'argument). If None, no redirect will be performed and the logout '
|
||||
'view will be rendered. This setting also accepts named URL '
|
||||
'patterns which can be used to reduce configuration duplication '
|
||||
'since you don\'t have to define the URL in two places (settings '
|
||||
'and URLconf).'
|
||||
)
|
||||
)
|
||||
setting_django_static_url = namespace.add_setting(
|
||||
global_name='STATIC_URL',
|
||||
@@ -402,7 +405,7 @@ setting_django_wsgi_application = namespace.add_setting(
|
||||
),
|
||||
)
|
||||
|
||||
namespace = Namespace(name='celery', label=_('Celery'))
|
||||
namespace = Namespace(label=_('Celery'), name='celery')
|
||||
|
||||
setting_celery_always_eager = namespace.add_setting(
|
||||
global_name='CELERY_TASK_ALWAYS_EAGER',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mayan.apps.storage.utils import get_storage_subclass
|
||||
|
||||
from .settings import setting_shared_storage, setting_shared_storage_arguments
|
||||
from .utils import get_storage_subclass
|
||||
|
||||
storage_sharedupload = get_storage_subclass(
|
||||
dotted_path=setting_shared_storage.value
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.template import Context, Library, VariableDoesNotExist, Variable
|
||||
from django.template.defaultfilters import truncatechars
|
||||
from django.template.loader import get_template
|
||||
@@ -14,6 +16,7 @@ from ..icons import icon_list_mode_items, icon_list_mode_list
|
||||
from ..literals import MESSAGE_SQLITE_WARNING
|
||||
from ..utils import check_for_sqlite, resolve_attribute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
register = Library()
|
||||
|
||||
|
||||
@@ -48,6 +51,14 @@ def common_calculate_title(context):
|
||||
return _('Create')
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def common_get_object_verbose_name(obj):
|
||||
try:
|
||||
return obj._meta.verbose_name
|
||||
except AttributeError:
|
||||
return type(obj)
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def get_collections():
|
||||
return Collection.get_all()
|
||||
|
||||
@@ -1,30 +1,20 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.http import HttpResponse
|
||||
from django.template import Context, Template
|
||||
from django.test import TestCase
|
||||
from django.test.utils import ContextList
|
||||
from django.urls import clear_url_caches, reverse
|
||||
from django_downloadview import assert_download_response
|
||||
|
||||
from mayan.apps.acls.tests.mixins import ACLBaseTestMixin
|
||||
from mayan.apps.acls.tests.mixins import ACLTestCaseMixin
|
||||
from mayan.apps.permissions.classes import Permission
|
||||
from mayan.apps.smart_settings.classes import Namespace
|
||||
from mayan.apps.user_management.tests import (
|
||||
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_USER_PASSWORD,
|
||||
TEST_USER_USERNAME
|
||||
)
|
||||
|
||||
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
|
||||
from .mixins import (
|
||||
ContentTypeCheckMixin, DatabaseConversionMixin, OpenFileCheckMixin,
|
||||
TempfileCheckMixin
|
||||
ClientMethodsTestCaseMixin, ContentTypeCheckMixin, DatabaseConversionMixin,
|
||||
OpenFileCheckTestCaseMixin, RandomPrimaryKeyModelMonkeyPatchMixin,
|
||||
TempfileCheckTestCaseMixin, TestViewTestCaseMixin
|
||||
)
|
||||
|
||||
|
||||
class BaseTestCase(DatabaseConversionMixin, ACLBaseTestMixin, ContentTypeCheckMixin, OpenFileCheckMixin, TempfileCheckMixin, TestCase):
|
||||
class BaseTestCase(RandomPrimaryKeyModelMonkeyPatchMixin, DatabaseConversionMixin, ACLTestCaseMixin, OpenFileCheckTestCaseMixin, TempfileCheckTestCaseMixin, TestCase):
|
||||
"""
|
||||
This is the most basic test case class any test in the project should use.
|
||||
"""
|
||||
@@ -36,81 +26,9 @@ class BaseTestCase(DatabaseConversionMixin, ACLBaseTestMixin, ContentTypeCheckMi
|
||||
Permission.invalidate_cache()
|
||||
|
||||
|
||||
class GenericViewTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super(GenericViewTestCase, self).setUp()
|
||||
self.has_test_view = False
|
||||
|
||||
def tearDown(self):
|
||||
from mayan.urls import urlpatterns
|
||||
|
||||
self.client.logout()
|
||||
if self.has_test_view:
|
||||
urlpatterns.pop(0)
|
||||
super(GenericViewTestCase, self).tearDown()
|
||||
|
||||
def add_test_view(self, test_object):
|
||||
from mayan.urls import urlpatterns
|
||||
|
||||
def test_view(request):
|
||||
template = Template('{{ object }}')
|
||||
context = Context(
|
||||
{'object': test_object, 'resolved_object': test_object}
|
||||
)
|
||||
return HttpResponse(template.render(context=context))
|
||||
|
||||
urlpatterns.insert(0, url(TEST_VIEW_URL, test_view, name=TEST_VIEW_NAME))
|
||||
clear_url_caches()
|
||||
self.has_test_view = True
|
||||
|
||||
def get_test_view(self):
|
||||
response = self.get(TEST_VIEW_NAME)
|
||||
if isinstance(response.context, ContextList):
|
||||
# template widget rendering causes test client response to be
|
||||
# ContextList rather than RequestContext. Typecast to dictionary
|
||||
# before updating.
|
||||
result = dict(response.context).copy()
|
||||
result.update({'request': response.wsgi_request})
|
||||
return Context(result)
|
||||
else:
|
||||
response.context.update({'request': response.wsgi_request})
|
||||
return Context(response.context)
|
||||
|
||||
def get(self, viewname=None, path=None, *args, **kwargs):
|
||||
data = kwargs.pop('data', {})
|
||||
follow = kwargs.pop('follow', False)
|
||||
|
||||
if viewname:
|
||||
path = reverse(viewname=viewname, *args, **kwargs)
|
||||
|
||||
return self.client.get(
|
||||
path=path, data=data, follow=follow
|
||||
)
|
||||
|
||||
def login(self, username, password):
|
||||
logged_in = self.client.login(username=username, password=password)
|
||||
|
||||
user = get_user_model().objects.get(username=username)
|
||||
|
||||
self.assertTrue(logged_in)
|
||||
self.assertTrue(user.is_authenticated)
|
||||
|
||||
def login_user(self):
|
||||
self.login(username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD)
|
||||
|
||||
def login_admin_user(self):
|
||||
self.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
|
||||
|
||||
def logout(self):
|
||||
self.client.logout()
|
||||
|
||||
def post(self, viewname=None, path=None, *args, **kwargs):
|
||||
data = kwargs.pop('data', {})
|
||||
follow = kwargs.pop('follow', False)
|
||||
|
||||
if viewname:
|
||||
path = reverse(viewname=viewname, *args, **kwargs)
|
||||
|
||||
return self.client.post(
|
||||
path=path, data=data, follow=follow
|
||||
)
|
||||
class GenericViewTestCase(ClientMethodsTestCaseMixin, ContentTypeCheckMixin, TestViewTestCaseMixin, BaseTestCase):
|
||||
"""
|
||||
A generic view test case built on top of the base test case providing
|
||||
a single, user customizable view to test object resolution and shorthand
|
||||
HTTP method functions.
|
||||
"""
|
||||
|
||||
@@ -2,20 +2,26 @@ from __future__ import unicode_literals
|
||||
|
||||
import glob
|
||||
import os
|
||||
import random
|
||||
|
||||
from furl import furl
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.conf.urls import url
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import management
|
||||
from django.db import connection
|
||||
from django.db import models
|
||||
from django.http import HttpResponse
|
||||
from django.template import Context, Template
|
||||
from django.test.utils import ContextList
|
||||
from django.urls import clear_url_caches, reverse
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
from mayan.apps.user_management.tests import (
|
||||
TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME,
|
||||
TEST_GROUP_NAME, TEST_USER_EMAIL, TEST_USER_PASSWORD,
|
||||
TEST_USER_USERNAME
|
||||
)
|
||||
|
||||
from ..settings import setting_temporary_directory
|
||||
from mayan.apps.storage.settings import setting_temporary_directory
|
||||
|
||||
from .literals import TEST_VIEW_NAME, TEST_VIEW_URL
|
||||
from .utils import mute_stdout
|
||||
|
||||
|
||||
@@ -23,6 +29,56 @@ if getattr(settings, 'COMMON_TEST_FILE_HANDLES', False):
|
||||
import psutil
|
||||
|
||||
|
||||
class ClientMethodsTestCaseMixin(object):
|
||||
def _build_verb_kwargs(self, viewname=None, path=None, *args, **kwargs):
|
||||
data = kwargs.pop('data', {})
|
||||
follow = kwargs.pop('follow', False)
|
||||
query = kwargs.pop('query', {})
|
||||
|
||||
if viewname:
|
||||
path = reverse(viewname=viewname, *args, **kwargs)
|
||||
|
||||
path = furl(url=path)
|
||||
path.args.update(query)
|
||||
|
||||
return {'follow': follow, 'data': data, 'path': path.tostr()}
|
||||
|
||||
def delete(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.delete(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def get(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.get(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def patch(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.patch(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def post(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.post(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
def put(self, viewname=None, path=None, *args, **kwargs):
|
||||
return self.client.put(
|
||||
**self._build_verb_kwargs(
|
||||
path=path, viewname=viewname, *args, **kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ContentTypeCheckMixin(object):
|
||||
expected_content_type = 'text/html; charset=utf-8'
|
||||
|
||||
@@ -34,13 +90,14 @@ class ContentTypeCheckMixin(object):
|
||||
def request(self, *args, **kwargs):
|
||||
response = super(CustomClient, self).request(*args, **kwargs)
|
||||
|
||||
content_type = response._headers['content-type'][1]
|
||||
test_instance.assertEqual(
|
||||
content_type, test_instance.expected_content_type,
|
||||
msg='Unexpected response content type: {}, expected: {}.'.format(
|
||||
content_type, test_instance.expected_content_type
|
||||
content_type = response._headers.get('content-type', [None, ''])[1]
|
||||
if test_instance.expected_content_type:
|
||||
test_instance.assertEqual(
|
||||
content_type, test_instance.expected_content_type,
|
||||
msg='Unexpected response content type: {}, expected: {}.'.format(
|
||||
content_type, test_instance.expected_content_type
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -55,7 +112,7 @@ class DatabaseConversionMixin(object):
|
||||
)
|
||||
|
||||
|
||||
class OpenFileCheckMixin(object):
|
||||
class OpenFileCheckTestCaseMixin(object):
|
||||
def _get_descriptor_count(self):
|
||||
process = psutil.Process()
|
||||
return process.num_fds()
|
||||
@@ -65,7 +122,7 @@ class OpenFileCheckMixin(object):
|
||||
return process.open_files()
|
||||
|
||||
def setUp(self):
|
||||
super(OpenFileCheckMixin, self).setUp()
|
||||
super(OpenFileCheckTestCaseMixin, self).setUp()
|
||||
if getattr(settings, 'COMMON_TEST_FILE_HANDLES', False):
|
||||
self._open_files = self._get_open_files()
|
||||
|
||||
@@ -80,10 +137,61 @@ class OpenFileCheckMixin(object):
|
||||
|
||||
self._skip_file_descriptor_test = False
|
||||
|
||||
super(OpenFileCheckMixin, self).tearDown()
|
||||
super(OpenFileCheckTestCaseMixin, self).tearDown()
|
||||
|
||||
|
||||
class TempfileCheckMixin(object):
|
||||
class RandomPrimaryKeyModelMonkeyPatchMixin(object):
|
||||
random_primary_key_random_floor = 100
|
||||
random_primary_key_random_ceiling = 10000
|
||||
random_primary_key_maximum_attempts = 100
|
||||
|
||||
@staticmethod
|
||||
def get_unique_primary_key(model):
|
||||
pk_list = model._meta.default_manager.values_list('pk', flat=True)
|
||||
|
||||
attempts = 0
|
||||
while True:
|
||||
primary_key = random.randint(
|
||||
RandomPrimaryKeyModelMonkeyPatchMixin.random_primary_key_random_floor,
|
||||
RandomPrimaryKeyModelMonkeyPatchMixin.random_primary_key_random_ceiling
|
||||
)
|
||||
|
||||
if primary_key not in pk_list:
|
||||
break
|
||||
|
||||
attempts = attempts + 1
|
||||
|
||||
if attempts > RandomPrimaryKeyModelMonkeyPatchMixin.random_primary_key_maximum_attempts:
|
||||
raise Exception(
|
||||
'Maximum number of retries for an unique random primary '
|
||||
'key reached.'
|
||||
)
|
||||
|
||||
return primary_key
|
||||
|
||||
def setUp(self):
|
||||
self.method_save_original = models.Model.save
|
||||
|
||||
def method_save_new(instance, *args, **kwargs):
|
||||
if instance.pk:
|
||||
return self.method_save_original(instance, *args, **kwargs)
|
||||
else:
|
||||
instance.pk = RandomPrimaryKeyModelMonkeyPatchMixin.get_unique_primary_key(
|
||||
model=instance._meta.model
|
||||
)
|
||||
instance.id = instance.pk
|
||||
|
||||
return instance.save_base(force_insert=True)
|
||||
|
||||
setattr(models.Model, 'save', method_save_new)
|
||||
super(RandomPrimaryKeyModelMonkeyPatchMixin, self).setUp()
|
||||
|
||||
def tearDown(self):
|
||||
models.Model.save = self.method_save_original
|
||||
super(RandomPrimaryKeyModelMonkeyPatchMixin, self).tearDown()
|
||||
|
||||
|
||||
class TempfileCheckTestCaseMixin(object):
|
||||
# Ignore the jvmstat instrumentation and GitLab's CI .config files
|
||||
# Ignore LibreOffice fontconfig cache dir
|
||||
ignore_globs = ('hsperfdata_*', '.config', '.cache')
|
||||
@@ -108,7 +216,7 @@ class TempfileCheckMixin(object):
|
||||
) - set(ignored_result)
|
||||
|
||||
def setUp(self):
|
||||
super(TempfileCheckMixin, self).setUp()
|
||||
super(TempfileCheckTestCaseMixin, self).setUp()
|
||||
if getattr(settings, 'COMMON_TEST_TEMP_FILES', False):
|
||||
self._temporary_items = self._get_temporary_entries()
|
||||
|
||||
@@ -123,4 +231,101 @@ class TempfileCheckMixin(object):
|
||||
','.join(final_temporary_items - self._temporary_items)
|
||||
)
|
||||
)
|
||||
super(TempfileCheckMixin, self).tearDown()
|
||||
super(TempfileCheckTestCaseMixin, self).tearDown()
|
||||
|
||||
|
||||
class TestModelTestMixin(object):
|
||||
def _create_test_model(self, fields=None, model_name='TestModel', options=None):
|
||||
# Obtain the app_config and app_label from the test's module path
|
||||
app_config = apps.get_containing_app_config(
|
||||
object_name=self.__class__.__module__
|
||||
)
|
||||
app_label = app_config.label
|
||||
|
||||
class Meta:
|
||||
pass
|
||||
|
||||
setattr(Meta, 'app_label', app_label)
|
||||
|
||||
if options is not None:
|
||||
for key, value in options.items():
|
||||
setattr(Meta, key, value)
|
||||
|
||||
def save(instance, *args, **kwargs):
|
||||
# Custom .save() method to use random primary key values.
|
||||
if instance.pk:
|
||||
return models.Model.self(instance, *args, **kwargs)
|
||||
else:
|
||||
instance.pk = RandomPrimaryKeyModelMonkeyPatchMixin.get_unique_primary_key(
|
||||
model=instance._meta.model
|
||||
)
|
||||
instance.id = instance.pk
|
||||
|
||||
return instance.save_base(force_insert=True)
|
||||
|
||||
attrs = {
|
||||
'__module__': self.__class__.__module__, 'save': save, 'Meta': Meta
|
||||
}
|
||||
|
||||
if fields:
|
||||
attrs.update(fields)
|
||||
|
||||
# Clear previous model registration before re-registering it again to
|
||||
# avoid conflict with test models with the same name, in the same app
|
||||
# but from another test module.
|
||||
apps.all_models[app_label].pop(model_name.lower(), None)
|
||||
|
||||
TestModel = type(
|
||||
force_bytes(model_name), (models.Model,), attrs
|
||||
)
|
||||
|
||||
setattr(self, model_name, TestModel)
|
||||
|
||||
with connection.schema_editor() as schema_editor:
|
||||
schema_editor.create_model(model=TestModel)
|
||||
|
||||
ContentType.objects.clear_cache()
|
||||
|
||||
def _create_test_object(self, model_name='TestModel', **kwargs):
|
||||
TestModel = getattr(self, model_name)
|
||||
|
||||
self.test_object = TestModel.objects.create(**kwargs)
|
||||
|
||||
|
||||
class TestViewTestCaseMixin(object):
|
||||
has_test_view = False
|
||||
|
||||
def tearDown(self):
|
||||
from mayan.urls import urlpatterns
|
||||
|
||||
self.client.logout()
|
||||
if self.has_test_view:
|
||||
urlpatterns.pop(0)
|
||||
super(TestViewTestCaseMixin, self).tearDown()
|
||||
|
||||
def add_test_view(self, test_object):
|
||||
from mayan.urls import urlpatterns
|
||||
|
||||
def test_view(request):
|
||||
template = Template('{{ object }}')
|
||||
context = Context(
|
||||
{'object': test_object, 'resolved_object': test_object}
|
||||
)
|
||||
return HttpResponse(template.render(context=context))
|
||||
|
||||
urlpatterns.insert(0, url(TEST_VIEW_URL, test_view, name=TEST_VIEW_NAME))
|
||||
clear_url_caches()
|
||||
self.has_test_view = True
|
||||
|
||||
def get_test_view(self):
|
||||
response = self.get(TEST_VIEW_NAME)
|
||||
if isinstance(response.context, ContextList):
|
||||
# template widget rendering causes test client response to be
|
||||
# ContextList rather than RequestContext. Typecast to dictionary
|
||||
# before updating.
|
||||
result = dict(response.context).copy()
|
||||
result.update({'request': response.wsgi_request})
|
||||
return Context(result)
|
||||
else:
|
||||
response.context.update({'request': response.wsgi_request})
|
||||
return Context(response.context)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from mayan.apps.rest_api.tests import BaseAPITestCase
|
||||
|
||||
@@ -11,23 +10,40 @@ TEST_TEMPLATE_RESULT = '<div'
|
||||
|
||||
|
||||
class CommonAPITestCase(BaseAPITestCase):
|
||||
auto_login_user = False
|
||||
|
||||
def test_content_type_list_view(self):
|
||||
response = self.client.get(reverse('rest_api:content-type-list'))
|
||||
response = self.get(viewname='rest_api:content_type-list')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@override_settings(LANGUAGE_CODE='de')
|
||||
def _request_template_detail_view(self):
|
||||
return self.get(path=self.test_template.get_absolute_url())
|
||||
|
||||
def test_template_detail_view(self):
|
||||
self.login_user()
|
||||
template_main_menu = Template.get(name='menu_main')
|
||||
response = self.client.get(template_main_menu.get_absolute_url())
|
||||
|
||||
self.test_template = Template.get(name='menu_main')
|
||||
|
||||
response = self._request_template_detail_view()
|
||||
self.assertContains(
|
||||
response=response, text=TEST_TEMPLATE_RESULT, status_code=200
|
||||
)
|
||||
|
||||
@override_settings(LANGUAGE_CODE='de')
|
||||
def test_template_detail_german_view(self):
|
||||
self.login_user()
|
||||
|
||||
self.test_template = Template.get(name='menu_main')
|
||||
|
||||
response = self._request_template_detail_view()
|
||||
self.assertContains(
|
||||
response=response, text=TEST_TEMPLATE_RESULT, status_code=200
|
||||
)
|
||||
|
||||
def test_template_detail_anonymous_view(self):
|
||||
template_main_menu = Template.get(name='menu_main')
|
||||
response = self.client.get(template_main_menu.get_absolute_url())
|
||||
self.test_template = Template.get(name='menu_main')
|
||||
|
||||
response = self._request_template_detail_view()
|
||||
self.assertNotContains(
|
||||
response=response, text=TEST_TEMPLATE_RESULT, status_code=403
|
||||
)
|
||||
|
||||
@@ -6,5 +6,5 @@ from mayan.apps.user_management.tests.mixins import UserTestMixin
|
||||
|
||||
class UserLocaleProfileTestCase(UserTestMixin, BaseTestCase):
|
||||
def test_natural_keys(self):
|
||||
self._create_user()
|
||||
self._create_test_user()
|
||||
self._test_database_conversion('auth', 'common')
|
||||
|
||||
@@ -14,8 +14,6 @@ from .literals import TEST_ERROR_LOG_ENTRY_RESULT
|
||||
|
||||
class CommonViewTestCase(GenericViewTestCase):
|
||||
def test_about_view(self):
|
||||
self.login_user()
|
||||
|
||||
response = self.get('common:about_view')
|
||||
self.assertContains(response, text='About', status_code=200)
|
||||
|
||||
@@ -25,27 +23,36 @@ class CommonViewTestCase(GenericViewTestCase):
|
||||
)
|
||||
ErrorLogEntry.objects.register(model=get_user_model())
|
||||
|
||||
self.error_log_entry = self.user.error_logs.create(
|
||||
self.error_log_entry = self._test_case_user.error_logs.create(
|
||||
result=TEST_ERROR_LOG_ENTRY_RESULT
|
||||
)
|
||||
|
||||
def _request_object_error_log_list(self):
|
||||
content_type = ContentType.objects.get_for_model(model=self.user)
|
||||
content_type = ContentType.objects.get_for_model(model=self._test_case_user)
|
||||
|
||||
return self.get(
|
||||
'common:object_error_list', kwargs={
|
||||
'app_label': content_type.app_label,
|
||||
'model': content_type.model,
|
||||
'object_id': self.user.pk
|
||||
'object_id': self._test_case_user.pk
|
||||
}, follow=True
|
||||
)
|
||||
|
||||
def test_object_error_list_view_with_permissions(self):
|
||||
def test_object_error_list_view_no_permissions(self):
|
||||
self._create_error_log_entry()
|
||||
|
||||
response = self._request_object_error_log_list()
|
||||
|
||||
self.assertNotContains(
|
||||
response=response, text=TEST_ERROR_LOG_ENTRY_RESULT,
|
||||
status_code=403
|
||||
)
|
||||
|
||||
def test_object_error_list_view_with_access(self):
|
||||
self._create_error_log_entry()
|
||||
|
||||
self.login_user()
|
||||
self.grant_access(
|
||||
obj=self.user, permission=permission_error_log_view
|
||||
obj=self._test_case_user, permission=permission_error_log_view
|
||||
)
|
||||
|
||||
response = self._request_object_error_log_list()
|
||||
@@ -54,15 +61,3 @@ class CommonViewTestCase(GenericViewTestCase):
|
||||
response=response, text=TEST_ERROR_LOG_ENTRY_RESULT,
|
||||
status_code=200
|
||||
)
|
||||
|
||||
def test_object_error_list_view_no_permissions(self):
|
||||
self._create_error_log_entry()
|
||||
|
||||
self.login_user()
|
||||
|
||||
response = self._request_object_error_log_list()
|
||||
|
||||
self.assertNotContains(
|
||||
response=response, text=TEST_ERROR_LOG_ENTRY_RESULT,
|
||||
status_code=403
|
||||
)
|
||||
|
||||
@@ -3,9 +3,7 @@ from __future__ import unicode_literals
|
||||
from django.conf.urls import url
|
||||
from django.views.i18n import javascript_catalog, set_language
|
||||
|
||||
from .api_views import (
|
||||
APIContentTypeList, APITemplateListView, APITemplateView
|
||||
)
|
||||
from .api_views import ContentTypeAPIViewSet, TemplateAPIViewSet
|
||||
from .views import (
|
||||
AboutView, CheckVersionView, CurrentUserLocaleProfileDetailsView,
|
||||
CurrentUserLocaleProfileEditView, FaviconRedirectView, HomeView,
|
||||
@@ -15,67 +13,65 @@ from .views import (
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', RootView.as_view(), name='root'),
|
||||
url(r'^home/$', HomeView.as_view(), name='home'),
|
||||
url(r'^about/$', AboutView.as_view(), name='about_view'),
|
||||
url(regex=r'^$', name='root', view=RootView.as_view()),
|
||||
url(regex=r'^home/$', name='home', view=HomeView.as_view()),
|
||||
url(regex=r'^about/$', name='about_view', view=AboutView.as_view()),
|
||||
url(
|
||||
r'^check_version/$', CheckVersionView.as_view(),
|
||||
name='check_version_view'
|
||||
regex=r'^check_version/$', name='check_version_view',
|
||||
view=CheckVersionView.as_view()
|
||||
),
|
||||
url(r'^license/$', LicenseView.as_view(), name='license_view'),
|
||||
url(regex=r'^license/$', name='license_view', view=LicenseView.as_view()),
|
||||
url(
|
||||
r'^packages/licenses/$', PackagesLicensesView.as_view(),
|
||||
name='packages_licenses_view'
|
||||
regex=r'^packages/licenses/$', name='packages_licenses_view',
|
||||
view=PackagesLicensesView.as_view()
|
||||
),
|
||||
url(
|
||||
r'^object/multiple/action/$', multi_object_action_view,
|
||||
name='multi_object_action_view'
|
||||
regex=r'^objects/multiple/action/$', name='multi_object_action_view',
|
||||
view=multi_object_action_view
|
||||
),
|
||||
url(r'^setup/$', SetupListView.as_view(), name='setup_list'),
|
||||
url(r'^tools/$', ToolsListView.as_view(), name='tools_list'),
|
||||
url(regex=r'^setup/$', name='setup_list', view=SetupListView.as_view()),
|
||||
url(regex=r'^tools/$', name='tools_list', view=ToolsListView.as_view()),
|
||||
url(
|
||||
r'^user/locale/$', CurrentUserLocaleProfileDetailsView.as_view(),
|
||||
name='current_user_locale_profile_details'
|
||||
regex=r'^users/current/locale/$',
|
||||
name='current_user_locale_profile_details',
|
||||
view=CurrentUserLocaleProfileDetailsView.as_view()
|
||||
),
|
||||
url(
|
||||
r'^user/locale/edit/$', CurrentUserLocaleProfileEditView.as_view(),
|
||||
name='current_user_locale_profile_edit'
|
||||
regex=r'^users/current/locale/edit/$',
|
||||
name='current_user_locale_profile_edit',
|
||||
view=CurrentUserLocaleProfileEditView.as_view()
|
||||
),
|
||||
url(
|
||||
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/errors/$',
|
||||
ObjectErrorLogEntryListView.as_view(), name='object_error_list'
|
||||
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/errors/$',
|
||||
name='object_error_list', view=ObjectErrorLogEntryListView.as_view()
|
||||
),
|
||||
url(
|
||||
r'^object/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/errors/clear/$',
|
||||
ObjectErrorLogEntryListClearView.as_view(),
|
||||
name='object_error_list_clear'
|
||||
regex=r'^objects/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/errors/clear/$',
|
||||
name='object_error_list_clear',
|
||||
view=ObjectErrorLogEntryListClearView.as_view()
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
url(
|
||||
r'^favicon\.ico$', FaviconRedirectView.as_view()
|
||||
regex=r'^favicon\.ico$', view=FaviconRedirectView.as_view()
|
||||
),
|
||||
url(
|
||||
r'^jsi18n/(?P<packages>\S+?)/$', javascript_catalog,
|
||||
name='javascript_catalog'
|
||||
regex=r'^jsi18n/(?P<packages>\S+?)/$', name='javascript_catalog',
|
||||
view=javascript_catalog
|
||||
),
|
||||
url(
|
||||
r'^set_language/$', set_language, name='set_language'
|
||||
regex=r'^set_language/$', name='set_language', view=set_language
|
||||
),
|
||||
]
|
||||
|
||||
api_urls = [
|
||||
url(
|
||||
r'^content_types/$', APIContentTypeList.as_view(),
|
||||
name='content-type-list'
|
||||
),
|
||||
url(
|
||||
r'^templates/$', APITemplateListView.as_view(),
|
||||
name='template-list'
|
||||
),
|
||||
url(
|
||||
r'^templates/(?P<name>[-\w]+)/$', APITemplateView.as_view(),
|
||||
name='template-detail'
|
||||
),
|
||||
]
|
||||
api_router_entries = (
|
||||
{
|
||||
'prefix': r'content_types', 'viewset': ContentTypeAPIViewSet,
|
||||
'basename': 'content_type'
|
||||
},
|
||||
{
|
||||
'prefix': r'templates', 'viewset': TemplateAPIViewSet,
|
||||
'basename': 'template'
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import FieldDoesNotExist
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.urls import resolve as django_resolve
|
||||
from django.urls.base import get_script_prefix
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.http import urlencode as django_urlencode
|
||||
from django.utils.http import urlquote as django_urlquote
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.six.moves import reduce as reduce_function
|
||||
from django.utils.six.moves import xmlrpc_client
|
||||
|
||||
@@ -20,7 +17,6 @@ import mayan
|
||||
|
||||
from .exceptions import NotLatestVersion, UnknownLatestVersion
|
||||
from .literals import DJANGO_SQLITE_BACKEND, MAYAN_PYPI_NAME, PYPI_URL
|
||||
from .settings import setting_temporary_directory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -39,27 +35,6 @@ def check_version():
|
||||
raise NotLatestVersion(upstream_version=versions[0])
|
||||
|
||||
|
||||
# http://stackoverflow.com/questions/123198/how-do-i-copy-a-file-in-python
|
||||
def copyfile(source, destination, buffer_size=1024 * 1024):
|
||||
"""
|
||||
Copy a file from source to dest. source and dest
|
||||
can either be strings or any object with a read or
|
||||
write method, like StringIO for example.
|
||||
"""
|
||||
source_descriptor = get_descriptor(source)
|
||||
destination_descriptor = get_descriptor(destination, read=False)
|
||||
|
||||
while True:
|
||||
copy_buffer = source_descriptor.read(buffer_size)
|
||||
if copy_buffer:
|
||||
destination_descriptor.write(copy_buffer)
|
||||
else:
|
||||
break
|
||||
|
||||
source_descriptor.close()
|
||||
destination_descriptor.close()
|
||||
|
||||
|
||||
def encapsulate(function):
|
||||
# Workaround Django ticket 15791
|
||||
# Changeset 16045
|
||||
@@ -68,74 +43,49 @@ def encapsulate(function):
|
||||
return lambda: function
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def get_related_field(model, related_field_name):
|
||||
try:
|
||||
os.remove(filename)
|
||||
except OSError:
|
||||
local_field_name, remaining_field_path = related_field_name.split(
|
||||
LOOKUP_SEP, 1
|
||||
)
|
||||
except ValueError:
|
||||
local_field_name = related_field_name
|
||||
remaining_field_path = None
|
||||
|
||||
related_field = model._meta.get_field(local_field_name)
|
||||
|
||||
if remaining_field_path:
|
||||
return get_related_field(
|
||||
model=related_field.related_model,
|
||||
related_field_name=remaining_field_path
|
||||
)
|
||||
|
||||
return related_field
|
||||
|
||||
|
||||
def introspect_attribute(attribute_name, obj):
|
||||
try:
|
||||
# Try as a related field
|
||||
obj._meta.get_field(field_name=attribute_name)
|
||||
except (AttributeError, FieldDoesNotExist):
|
||||
attribute_name = attribute_name.replace('__', '.')
|
||||
|
||||
try:
|
||||
shutil.rmtree(filename)
|
||||
except OSError:
|
||||
if suppress_exceptions:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def get_descriptor(file_input, read=True):
|
||||
try:
|
||||
# Is it a file like object?
|
||||
file_input.seek(0)
|
||||
except AttributeError:
|
||||
# If not, try open it.
|
||||
if read:
|
||||
return open(file_input, mode='rb')
|
||||
# If there are separators in the attribute name, traverse them
|
||||
# to the final attribute
|
||||
attribute_part, attribute_remaining = attribute_name.split(
|
||||
'.', 1
|
||||
)
|
||||
except ValueError:
|
||||
return attribute_name, obj
|
||||
else:
|
||||
return open(file_input, mode='wb')
|
||||
related_field = obj._meta.get_field(field_name=attribute_part)
|
||||
return introspect_attribute(
|
||||
attribute_name=attribute_part,
|
||||
obj=related_field.related_model,
|
||||
)
|
||||
else:
|
||||
return file_input
|
||||
|
||||
|
||||
def get_storage_subclass(dotted_path):
|
||||
"""
|
||||
Import a storage class and return a subclass that will always return eq
|
||||
True to avoid creating a new migration when for runtime storage class
|
||||
changes.
|
||||
"""
|
||||
imported_storage_class = import_string(dotted_path=dotted_path)
|
||||
|
||||
class StorageSubclass(imported_storage_class):
|
||||
def __init__(self, *args, **kwargs):
|
||||
return super(StorageSubclass, self).__init__(*args, **kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
def deconstruct(self):
|
||||
return ('mayan.apps.common.classes.FakeStorageSubclass', (), {})
|
||||
|
||||
|
||||
return StorageSubclass
|
||||
|
||||
|
||||
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)
|
||||
return attribute_name, obj
|
||||
|
||||
|
||||
def resolve(path, urlconf=None):
|
||||
@@ -230,24 +180,3 @@ def urlquote(link=None, get=None):
|
||||
return '%s%s' % (link, django_urlencode(get, doseq=True))
|
||||
else:
|
||||
return django_urlquote(link)
|
||||
|
||||
|
||||
def validate_path(path):
|
||||
if not os.path.exists(path):
|
||||
# If doesn't exist try to create it
|
||||
try:
|
||||
os.mkdir(path)
|
||||
except Exception as exception:
|
||||
logger.debug('unhandled exception: %s', exception)
|
||||
return False
|
||||
|
||||
# Check if it is writable
|
||||
try:
|
||||
fd, test_filepath = tempfile.mkstemp(dir=path)
|
||||
os.close(fd)
|
||||
os.unlink(test_filepath)
|
||||
except Exception as exception:
|
||||
logger.debug('unhandled exception: %s', exception)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -6,27 +6,25 @@ from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, resolve_url
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import RequestContext
|
||||
from django.urls import reverse_lazy
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone, translation
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import RedirectView, TemplateView
|
||||
|
||||
from mayan.apps.acls.models import AccessControlList
|
||||
from mayan.apps.common.mixins import (
|
||||
ContentTypeViewMixin, ExternalObjectMixin
|
||||
)
|
||||
|
||||
from .exceptions import NotLatestVersion, UnknownLatestVersion
|
||||
from .forms import (
|
||||
LicenseForm, LocaleProfileForm, LocaleProfileForm_view,
|
||||
PackagesLicensesForm
|
||||
)
|
||||
from .generics import ( # NOQA
|
||||
AssignRemoveView, ConfirmView, FormView, MultiFormView,
|
||||
MultipleObjectConfirmActionView, MultipleObjectFormActionView, SimpleView,
|
||||
SingleObjectCreateView, SingleObjectDeleteView, SingleObjectDetailView,
|
||||
SingleObjectDownloadView, SingleObjectDynamicFormCreateView,
|
||||
SingleObjectDynamicFormEditView, SingleObjectEditView, SingleObjectListView
|
||||
from .generics import (
|
||||
ConfirmView, SimpleView, SingleObjectEditView, SingleObjectListView
|
||||
)
|
||||
from .icons import icon_object_error_list, icon_setup
|
||||
from .menus import menu_setup, menu_tools
|
||||
@@ -171,7 +169,9 @@ class ObjectErrorLogEntryListClearView(ConfirmView):
|
||||
)
|
||||
|
||||
|
||||
class ObjectErrorLogEntryListView(SingleObjectListView):
|
||||
class ObjectErrorLogEntryListView(ContentTypeViewMixin, ExternalObjectMixin, SingleObjectListView):
|
||||
#TODO: Update for MERC 6. Return 404.
|
||||
"""
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
AccessControlList.objects.check_access(
|
||||
obj=self.get_object(), permissions=permission_error_log_view,
|
||||
@@ -181,6 +181,7 @@ class ObjectErrorLogEntryListView(SingleObjectListView):
|
||||
return super(ObjectErrorLogEntryListView, self).dispatch(
|
||||
request, *args, **kwargs
|
||||
)
|
||||
"""
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
@@ -202,6 +203,7 @@ class ObjectErrorLogEntryListView(SingleObjectListView):
|
||||
'title': _('Error log entries for: %s' % self.get_object()),
|
||||
}
|
||||
|
||||
"""
|
||||
def get_object(self):
|
||||
content_type = get_object_or_404(
|
||||
klass=ContentType, app_label=self.kwargs['app_label'],
|
||||
@@ -211,9 +213,9 @@ class ObjectErrorLogEntryListView(SingleObjectListView):
|
||||
return get_object_or_404(
|
||||
klass=content_type.model_class(), pk=self.kwargs['object_id']
|
||||
)
|
||||
|
||||
"""
|
||||
def get_object_list(self):
|
||||
return self.get_object().error_logs.all()
|
||||
return self.get_external_object().error_logs.all()
|
||||
|
||||
|
||||
class PackagesLicensesView(SimpleView):
|
||||
@@ -234,41 +236,41 @@ class RootView(SimpleView):
|
||||
template_name = 'appearance/root.html'
|
||||
|
||||
|
||||
class SetupListView(TemplateView):
|
||||
class SetupListView(SimpleView):
|
||||
template_name = 'appearance/generic_list_horizontal.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(SetupListView, self).get_context_data(**kwargs)
|
||||
def get_extra_context(self):
|
||||
context = RequestContext(self.request)
|
||||
context['request'] = self.request
|
||||
data.update(
|
||||
{
|
||||
'no_results_icon': icon_setup,
|
||||
'no_results_label': _('No setup options available.'),
|
||||
'no_results_text': _(
|
||||
'No results here means that don\'t have the required '
|
||||
'permissions to perform administrative task.'
|
||||
),
|
||||
'resolved_links': menu_setup.resolve(context=context),
|
||||
'title': _('Setup items'),
|
||||
}
|
||||
)
|
||||
return data
|
||||
return {
|
||||
'no_results_icon': icon_setup,
|
||||
'no_results_label': _('No setup options available.'),
|
||||
'no_results_text': _(
|
||||
'No results here means that don\'t have the required '
|
||||
'permissions to perform administrative task.'
|
||||
),
|
||||
'resolved_links': menu_setup.resolve(context=context),
|
||||
'title': _('Setup'),
|
||||
'subtitle': _(
|
||||
'Here you can configure all aspects of the system.'
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class ToolsListView(SimpleView):
|
||||
template_name = 'appearance/generic_list_horizontal.html'
|
||||
|
||||
def get_menu_links(self):
|
||||
def get_extra_context(self):
|
||||
context = RequestContext(self.request)
|
||||
context['request'] = self.request
|
||||
|
||||
return menu_tools.resolve(context=context)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'resolved_links': self.get_menu_links(),
|
||||
'resolved_links': menu_tools.resolve(context=context),
|
||||
'title': _('Tools'),
|
||||
'subtitle': _(
|
||||
'These are programs are modules used to do maintenance in '
|
||||
'the system.'
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -280,7 +282,7 @@ def multi_object_action_view(request):
|
||||
next = request.POST.get(
|
||||
'next', request.GET.get(
|
||||
'next', request.META.get(
|
||||
'HTTP_REFERER', resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||
'HTTP_REFERER', reverse(setting_home_view.value)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -297,7 +299,7 @@ def multi_object_action_view(request):
|
||||
messages.error(request, _('No action selected.'))
|
||||
return HttpResponseRedirect(
|
||||
request.META.get(
|
||||
'HTTP_REFERER', resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||
'HTTP_REFERER', reverse(setting_home_view.value)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -305,7 +307,7 @@ def multi_object_action_view(request):
|
||||
messages.error(request, _('Must select at least one item.'))
|
||||
return HttpResponseRedirect(
|
||||
request.META.get(
|
||||
'HTTP_REFERER', resolve_url(settings.LOGIN_REDIRECT_URL)
|
||||
'HTTP_REFERER', reverse(setting_home_view.value)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
13
mayan/apps/common/warnings.py
Normal file
13
mayan/apps/common/warnings.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
class DatabaseWarning(UserWarning):
|
||||
"""
|
||||
Warning when using unsupported database backends
|
||||
"""
|
||||
|
||||
|
||||
class InterfaceWarning(UserWarning):
|
||||
"""
|
||||
Warning when using obsolete internal interfaces
|
||||
"""
|
||||
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.template import Context, Template
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from .icons import icon_fail as default_icon_fail
|
||||
|
||||
Reference in New Issue
Block a user