Compare commits
3 Commits
features/f
...
features/q
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9340afd196 | ||
|
|
f97ccb693b | ||
|
|
c3b539ba19 |
@@ -233,6 +233,12 @@ and will exhaust the available Postgres connections available if a number
|
|||||||
other than 0 is used. Reference: https://serverfault.com/questions/635100/django-conn-max-age-persists-connections-but-doesnt-reuse-them-with-postgresq
|
other than 0 is used. Reference: https://serverfault.com/questions/635100/django-conn-max-age-persists-connections-but-doesnt-reuse-them-with-postgresq
|
||||||
and https://github.com/benoitc/gunicorn/issues/996
|
and https://github.com/benoitc/gunicorn/issues/996
|
||||||
|
|
||||||
|
``MAYAN_GUNICORN_TIMEOUT``
|
||||||
|
|
||||||
|
Optional. Changes the amount of time the frontend worker will wait for a
|
||||||
|
request to finish before raising a timeout error. The default is 120
|
||||||
|
seconds.
|
||||||
|
|
||||||
``MAYAN_GUNICORN_WORKERS``
|
``MAYAN_GUNICORN_WORKERS``
|
||||||
|
|
||||||
Optional. This environment variable controls the number of frontend workers
|
Optional. This environment variable controls the number of frontend workers
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
QUnit.test('partialNavigation.filterLocation', function (assert) {
|
||||||
|
var testPartialNavigation = new PartialNavigation({
|
||||||
|
initialURL: '/testInitialURL',
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For an empty newLocation we expect the fragment of the URL minus the
|
||||||
|
* query
|
||||||
|
*/
|
||||||
|
var expected = new URI(new URI(location).fragment()).path().toString();
|
||||||
|
assert.strictEqual(
|
||||||
|
testPartialNavigation.filterLocation(''), expected, 'newLocation === ""');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For an empty root value we expect initialURL passed to the
|
||||||
|
* partialNavigation instance when initialized.
|
||||||
|
*/
|
||||||
|
assert.strictEqual(
|
||||||
|
testPartialNavigation.filterLocation('/'), testPartialNavigation.initialURL, 'newLocation === "/"'
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For an empty root value we expect initialURL passed to the
|
||||||
|
* partialNavigation instance when initialized.
|
||||||
|
*/
|
||||||
|
assert.strictEqual(
|
||||||
|
testPartialNavigation.filterLocation('random'), 'random', 'newLocation === "random"'
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
<script src="{% static 'appearance/node_modules/jquery/dist/jquery.min.js' %}" type="text/javascript"></script>
|
<script src="{% static 'appearance/node_modules/jquery/dist/jquery.min.js' %}" type="text/javascript"></script>
|
||||||
<script src="{% static 'appearance/node_modules/bootstrap/dist/js/bootstrap.min.js' %}" type="text/javascript"></script>
|
<script src="{% static 'appearance/node_modules/bootstrap/dist/js/bootstrap.min.js' %}" type="text/javascript"></script>
|
||||||
<script src="{% static 'appearance/node_modules/@fortawesome/fontawesome-free/js/all.min.js' %}" data-auto-replace-svg="nest" type="text/javascript"></script>
|
<script src="{% static 'appearance/node_modules/@fortawesome/fontawesome-free/js/all.min.js' %}" data-auto-replace-svg="nest" type="text/javascript"></script>
|
||||||
|
{% block javascript %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{% endspaceless %}
|
{% endspaceless %}
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from mayan.apps.dependencies.classes import (
|
from mayan.apps.dependencies.classes import (
|
||||||
environment_build, environment_development, environment_testing,
|
environment_build, environment_development, environment_testing,
|
||||||
PythonDependency
|
JavaScriptDependency, PythonDependency
|
||||||
|
)
|
||||||
|
|
||||||
|
JavaScriptDependency(
|
||||||
|
environment=environment_testing, label=_('QUnit'), module=__name__,
|
||||||
|
name='qunit', version_string='=2.9.2'
|
||||||
)
|
)
|
||||||
|
|
||||||
PythonDependency(
|
PythonDependency(
|
||||||
|
|||||||
22
mayan/apps/common/templates/common/qunit.html
Normal file
22
mayan/apps/common/templates/common/qunit.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{% extends 'appearance/base.html' %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block base_title %}{% trans 'Qunit' %}{% endblock %}
|
||||||
|
|
||||||
|
{% block project_name %}{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link href="{% static 'common/node_modules/qunit/qunit/qunit.css' %}" media="screen" rel="stylesheet" type="text/css" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascript %}
|
||||||
|
<script src="{% static 'common/node_modules/qunit/qunit/qunit.js' %}" type="text/javascript"></script>
|
||||||
|
<script src="{% static 'appearance/js/test/unit/partial_navigation.js' %}" type="text/javascript"></script>
|
||||||
|
{% endblock %}
|
||||||
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
TEST FILE SPECIAL CHARACTERS FILENAME
|
||||||
@@ -11,6 +11,7 @@ TEST_VIEW_NAME = 'test view name'
|
|||||||
TEST_VIEW_URL = 'test-view-url'
|
TEST_VIEW_URL = 'test-view-url'
|
||||||
|
|
||||||
# Filenames
|
# Filenames
|
||||||
|
TEST_ARCHIVE_ZIP_SPECIAL_CHARACTERS_FILENAME_MEMBER = 'test_archvive_with_special_characters_filename_member.zip'
|
||||||
TEST_FILENAME1 = 'test_file1.txt'
|
TEST_FILENAME1 = 'test_file1.txt'
|
||||||
TEST_FILENAME2 = 'test_file2.txt'
|
TEST_FILENAME2 = 'test_file2.txt'
|
||||||
TEST_FILENAME3 = 'test_file3.txt'
|
TEST_FILENAME3 = 'test_file3.txt'
|
||||||
@@ -23,6 +24,10 @@ TEST_ZIP_FILE = 'test_file.zip'
|
|||||||
TEST_COMPRESSED_FILE_CONTENTS = [TEST_FILENAME1, TEST_FILENAME2]
|
TEST_COMPRESSED_FILE_CONTENTS = [TEST_FILENAME1, TEST_FILENAME2]
|
||||||
|
|
||||||
# File paths
|
# File paths
|
||||||
|
TEST_ARCHIVE_ZIP_SPECIAL_CHARACTERS_FILENAME_MEMBER_PATH = os.path.join(
|
||||||
|
settings.BASE_DIR, 'apps', 'common', 'tests', 'contrib',
|
||||||
|
TEST_ARCHIVE_ZIP_SPECIAL_CHARACTERS_FILENAME_MEMBER
|
||||||
|
)
|
||||||
TEST_FILE3_PATH = os.path.join(
|
TEST_FILE3_PATH = os.path.join(
|
||||||
settings.BASE_DIR, 'apps', 'common', 'tests', 'contrib', TEST_FILENAME3
|
settings.BASE_DIR, 'apps', 'common', 'tests', 'contrib', TEST_FILENAME3
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from mayan.apps.common.tests import BaseTestCase
|
|||||||
from ..compressed_files import Archive, TarArchive, ZipArchive
|
from ..compressed_files import Archive, TarArchive, ZipArchive
|
||||||
|
|
||||||
from .literals import (
|
from .literals import (
|
||||||
|
TEST_ARCHIVE_ZIP_SPECIAL_CHARACTERS_FILENAME_MEMBER_PATH,
|
||||||
TEST_COMPRESSED_FILE_CONTENTS, TEST_FILE_CONTENTS_1, TEST_FILE3_PATH,
|
TEST_COMPRESSED_FILE_CONTENTS, TEST_FILE_CONTENTS_1, TEST_FILE3_PATH,
|
||||||
TEST_FILENAME1, TEST_FILENAME3, TEST_TAR_BZ2_FILE_PATH,
|
TEST_FILENAME1, TEST_FILENAME3, TEST_TAR_BZ2_FILE_PATH,
|
||||||
TEST_TAR_FILE_PATH, TEST_TAR_GZ_FILE_PATH, TEST_ZIP_FILE_PATH
|
TEST_TAR_FILE_PATH, TEST_TAR_GZ_FILE_PATH, TEST_ZIP_FILE_PATH
|
||||||
@@ -58,6 +59,11 @@ class ZipArchiveClassTestCase(TarArchiveClassTestCase):
|
|||||||
archive_path = TEST_ZIP_FILE_PATH
|
archive_path = TEST_ZIP_FILE_PATH
|
||||||
cls = ZipArchive
|
cls = ZipArchive
|
||||||
|
|
||||||
|
def test_open_member_with_special_characters_filename(self):
|
||||||
|
with open(TEST_ARCHIVE_ZIP_SPECIAL_CHARACTERS_FILENAME_MEMBER_PATH, mode='rb') as file_object:
|
||||||
|
archive = Archive.open(file_object=file_object)
|
||||||
|
list(archive.get_members())
|
||||||
|
|
||||||
|
|
||||||
class TarGzArchiveClassTestCase(TarArchiveClassTestCase):
|
class TarGzArchiveClassTestCase(TarArchiveClassTestCase):
|
||||||
archive_path = TEST_TAR_GZ_FILE_PATH
|
archive_path = TEST_TAR_GZ_FILE_PATH
|
||||||
@@ -67,3 +73,5 @@ class TarGzArchiveClassTestCase(TarArchiveClassTestCase):
|
|||||||
class TarBz2ArchiveClassTestCase(TarArchiveClassTestCase):
|
class TarBz2ArchiveClassTestCase(TarArchiveClassTestCase):
|
||||||
archive_path = TEST_TAR_BZ2_FILE_PATH
|
archive_path = TEST_TAR_BZ2_FILE_PATH
|
||||||
cls = TarArchive
|
cls = TarArchive
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from .views import (
|
|||||||
AboutView, CurrentUserLocaleProfileDetailsView,
|
AboutView, CurrentUserLocaleProfileDetailsView,
|
||||||
CurrentUserLocaleProfileEditView, FaviconRedirectView, HomeView,
|
CurrentUserLocaleProfileEditView, FaviconRedirectView, HomeView,
|
||||||
LicenseView, ObjectErrorLogEntryListClearView, ObjectErrorLogEntryListView,
|
LicenseView, ObjectErrorLogEntryListClearView, ObjectErrorLogEntryListView,
|
||||||
RootView, SetupListView, ToolsListView, multi_object_action_view
|
RootView, SetupListView, ToolsListView, QUnitView,
|
||||||
|
multi_object_action_view
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -43,6 +44,9 @@ urlpatterns = [
|
|||||||
view=ObjectErrorLogEntryListClearView.as_view(),
|
view=ObjectErrorLogEntryListClearView.as_view(),
|
||||||
name='object_error_list_clear'
|
name='object_error_list_clear'
|
||||||
),
|
),
|
||||||
|
url(
|
||||||
|
regex=r'^qunit/', view=QUnitView.as_view(), name='qunit'
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
|
|||||||
@@ -276,3 +276,8 @@ def multi_object_action_view(request):
|
|||||||
action, urlencode({'id_list': id_list, 'next': next})
|
action, urlencode({'id_list': id_list, 'next': next})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QUnitView(SimpleView):
|
||||||
|
extra_context = {'title': _('QUnit tests')}
|
||||||
|
template_name = 'common/qunit.html'
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDocumentFilenameGenerator(object):
|
|
||||||
_registry = {}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, name):
|
|
||||||
return cls._registry[name]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_choices(cls):
|
|
||||||
return sorted(
|
|
||||||
[
|
|
||||||
(name, klass.label) for name, klass in cls._registry.items()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def register(cls, klass):
|
|
||||||
cls._registry[klass.name] = klass
|
|
||||||
|
|
||||||
def upload_to(self, instance, filename):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class UUIDDocumentFilenameGenerator(BaseDocumentFilenameGenerator):
|
|
||||||
name = 'uuid'
|
|
||||||
label = _('UUID')
|
|
||||||
|
|
||||||
def upload_to(self, instance, filename):
|
|
||||||
return force_text(uuid.uuid4())
|
|
||||||
|
|
||||||
|
|
||||||
BaseDocumentFilenameGenerator.register(klass=UUIDDocumentFilenameGenerator)
|
|
||||||
@@ -5,20 +5,11 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from mayan.apps.acls.models import AccessControlList
|
from mayan.apps.acls.models import AccessControlList
|
||||||
|
|
||||||
from ..classes import BaseDocumentFilenameGenerator
|
|
||||||
from ..models import DocumentType, DocumentTypeFilename
|
from ..models import DocumentType, DocumentTypeFilename
|
||||||
|
|
||||||
__all__ = ('DocumentTypeFilteredSelectForm', 'DocumentTypeFilenameForm_create')
|
__all__ = ('DocumentTypeFilteredSelectForm', 'DocumentTypeFilenameForm_create')
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeForm(forms.ModelForm):
|
|
||||||
#filename_generator = forms.
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
fields = ('label', 'filename_generator')
|
|
||||||
model = DocumentType
|
|
||||||
|
|
||||||
|
|
||||||
class DocumentTypeFilteredSelectForm(forms.Form):
|
class DocumentTypeFilteredSelectForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Form to select the document type of a document to be created. This form
|
Form to select the document type of a document to be created. This form
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from mayan.apps.acls.models import AccessControlList
|
from mayan.apps.acls.models import AccessControlList
|
||||||
from mayan.apps.common.literals import TIME_DELTA_UNIT_CHOICES
|
from mayan.apps.common.literals import TIME_DELTA_UNIT_CHOICES
|
||||||
|
|
||||||
from ..classes import BaseDocumentFilenameGenerator
|
|
||||||
from ..events import event_document_type_created, event_document_type_edited
|
from ..events import event_document_type_created, event_document_type_edited
|
||||||
from ..literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
|
from ..literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
|
||||||
from ..managers import DocumentTypeManager
|
from ..managers import DocumentTypeManager
|
||||||
@@ -53,12 +52,6 @@ class DocumentType(models.Model):
|
|||||||
default=DEFAULT_DELETE_TIME_UNIT, max_length=8, null=True,
|
default=DEFAULT_DELETE_TIME_UNIT, max_length=8, null=True,
|
||||||
verbose_name=_('Delete time unit')
|
verbose_name=_('Delete time unit')
|
||||||
)
|
)
|
||||||
filename_generator = models.CharField(
|
|
||||||
help_text=_(
|
|
||||||
'The class responsible for producing the actual filename used '
|
|
||||||
'to store the uploaded documents.'
|
|
||||||
), max_length=128, verbose_name=_('Filename generator')
|
|
||||||
)
|
|
||||||
|
|
||||||
objects = DocumentTypeManager()
|
objects = DocumentTypeManager()
|
||||||
|
|
||||||
@@ -101,10 +94,6 @@ class DocumentType(models.Model):
|
|||||||
|
|
||||||
return queryset.count()
|
return queryset.count()
|
||||||
|
|
||||||
def get_upload_filename(self, instance, filename):
|
|
||||||
klass = BaseDocumentFilenameGenerator.get(name=self.filename_generator)
|
|
||||||
return klass.upload_to(instance=instance, filename=filename)
|
|
||||||
|
|
||||||
def natural_key(self):
|
def natural_key(self):
|
||||||
return (self.label,)
|
return (self.label,)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import hashlib
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import uuid
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
@@ -36,10 +37,8 @@ def hash_function():
|
|||||||
return hashlib.sha256()
|
return hashlib.sha256()
|
||||||
|
|
||||||
|
|
||||||
def upload_to(instance, filename):
|
def UUID_FUNCTION(*args, **kwargs):
|
||||||
return instance.document.document_type.get_upload_filename(
|
return force_text(uuid.uuid4())
|
||||||
instance=instance, filename=filename
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
@@ -87,7 +86,7 @@ class DocumentVersion(models.Model):
|
|||||||
|
|
||||||
# File related fields
|
# File related fields
|
||||||
file = models.FileField(
|
file = models.FileField(
|
||||||
storage=storage_documentversion, upload_to=upload_to,
|
storage=storage_documentversion, upload_to=UUID_FUNCTION,
|
||||||
verbose_name=_('File')
|
verbose_name=_('File')
|
||||||
)
|
)
|
||||||
mimetype = models.CharField(
|
mimetype = models.CharField(
|
||||||
|
|||||||
@@ -82,9 +82,7 @@ class DocumentTypeListView(SingleObjectListView):
|
|||||||
class DocumentTypeCreateView(SingleObjectCreateView):
|
class DocumentTypeCreateView(SingleObjectCreateView):
|
||||||
fields = ('label',)
|
fields = ('label',)
|
||||||
model = DocumentType
|
model = DocumentType
|
||||||
post_action_redirect = reverse_lazy(
|
post_action_redirect = reverse_lazy(viewname='documents:document_type_list')
|
||||||
viewname='documents:document_type_list'
|
|
||||||
)
|
|
||||||
view_permission = permission_document_type_create
|
view_permission = permission_document_type_create
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user