Smart settings refactor

This commit is contained in:
Roberto Rosario
2015-06-22 21:04:06 -04:00
parent 6dcb984aca
commit 78198f3398
55 changed files with 370 additions and 243 deletions

View File

@@ -3,7 +3,7 @@ python:
- 2.7
env:
global:
- TEST_APPS="document_indexing documents django_gpg dynamic_search lock_manager document_signatures folders ocr sources tags"
- TEST_APPS="authentication document_indexing documents django_gpg dynamic_search lock_manager document_signatures folders ocr sources tags"
matrix:
- DB=mysql
- DB=postgres

View File

@@ -6,7 +6,7 @@ from django.http import HttpResponseRedirect
from django.conf import settings
from django.core.urlresolvers import reverse
from ..settings import ALLOW_ANONYMOUS_ACCESS
from ..settings import setting_allow_anonymous_access
EXEMPT_URLS = [re.compile(reverse(settings.LOGIN_URL).lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
@@ -25,7 +25,7 @@ class LoginRequiredMiddleware:
"""
def process_request(self, request):
if not ALLOW_ANONYMOUS_ACCESS:
if not setting_allow_anonymous_access.value:
assert hasattr(request, 'user'), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\

View File

@@ -1,24 +1,9 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_setting
from smart_settings import Namespace
register_setting(
namespace='authentication',
module='authentication.settings',
name='LOGIN_METHOD',
global_name='COMMON_LOGIN_METHOD',
default='username',
description=_('Controls the mechanism used to authenticated user. Options are: username, email'),
)
register_setting(
namespace='authentication',
module='authentication.settings',
name='ALLOW_ANONYMOUS_ACCESS',
global_name='COMMON_ALLOW_ANONYMOUS_ACCESS',
default=False,
description=_('Allow non authenticated users, access to all views'),
)
namespace = Namespace(name='authentication', label=_('Authentication'))
setting_login_method = namespace.add_setting(global_name='AUTHENTICATION_LOGIN_METHOD', default='username', help_text=_('Controls the mechanism used to authenticated user. Options are: username, email'))
setting_allow_anonymous_access = namespace.add_setting(global_name='AUTHENTICATION_ALLOW_ANONYMOUS_ACCESS', default=False, help_text=_('Allow non authenticated users, access to all views'))

View File

@@ -6,8 +6,7 @@ from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.client import Client
from authentication import settings as auth_authentication
import authentication
from .settings import setting_login_method
TEST_ADMIN_EMAIL = 'admin@admin.com'
TEST_ADMIN_PASSWORD = 'test_admin_password'
@@ -24,12 +23,14 @@ class UserLoginTestCase(TestCase):
self.client = Client()
def test_normal_behaviour(self):
setattr(authentication.settings, 'LOGIN_METHOD', 'username')
# TODO set setting_login_method to 'username'
# setattr(authentication.settings, 'LOGIN_METHOD', 'username')
response = self.client.get(reverse('documents:document_list'))
self.assertRedirects(response, 'http://testserver/authentication/login/')
def test_username_login(self):
setattr(authentication.settings, 'LOGIN_METHOD', 'username')
# TODO set setting_login_method to 'username'
# setattr(authentication.settings, 'LOGIN_METHOD', 'username')
logged_in = self.client.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
self.assertTrue(logged_in)
response = self.client.get(reverse('documents:document_list'))
@@ -38,7 +39,8 @@ class UserLoginTestCase(TestCase):
def test_email_login(self):
with self.settings(COMMON_LOGIN_METHOD='email', AUTHENTICATION_BACKENDS=('authentication.auth.email_auth_backend.EmailAuthBackend',)):
setattr(authentication.settings, 'LOGIN_METHOD', 'email')
# TODO set setting_login_method to 'email'
#setattr(authentication.settings, 'LOGIN_METHOD', 'email')
logged_in = self.client.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
self.assertFalse(logged_in)
@@ -50,7 +52,8 @@ class UserLoginTestCase(TestCase):
self.assertEqual(response.status_code, 200)
def test_username_login_via_views(self):
setattr(authentication.settings, 'LOGIN_METHOD', 'username')
# TODO set setting_login_method to 'username'
# setattr(authentication.settings, 'LOGIN_METHOD', 'username')
response = self.client.get(reverse('documents:document_list'))
self.assertRedirects(response, 'http://testserver/authentication/login/')
@@ -61,7 +64,8 @@ class UserLoginTestCase(TestCase):
def test_email_login_via_views(self):
with self.settings(COMMON_LOGIN_METHOD='email', AUTHENTICATION_BACKENDS=('authentication.auth.email_auth_backend.EmailAuthBackend',)):
setattr(authentication.settings, 'LOGIN_METHOD', 'email')
# TODO set setting_login_method to 'email'
#setattr(authentication.settings, 'LOGIN_METHOD', 'email')
response = self.client.get(reverse('documents:document_list'))
self.assertRedirects(response, 'http://testserver/authentication/login/')

View File

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
from django.conf import settings
from django.conf.urls import patterns, url
urlpatterns = patterns('authentication.views',
url(r'^login/$', 'login_view', (), name='login_view'),
url(r'^password/change/done/$', 'password_change_done', (), name='password_change_done'),

View File

@@ -22,7 +22,7 @@ from dynamic_search.classes import SearchModel
from permissions.models import Permission
from .forms import EmailAuthenticationForm
from .settings import LOGIN_METHOD
from .settings import setting_login_method
def login_view(request):
@@ -32,7 +32,7 @@ def login_view(request):
"""
kwargs = {'template_name': 'appearance/login.html'}
if LOGIN_METHOD == 'email':
if setting_login_method.value == 'email':
kwargs['authentication_form'] = EmailAuthenticationForm
if not request.user.is_authenticated():

View File

@@ -10,8 +10,6 @@ from django.contrib.auth.signals import user_logged_in
from django.db.models.signals import post_migrate, post_save
from django.utils.translation import ugettext_lazy as _
from common import settings as common_settings
from .handlers import (
user_locale_profile_session_config, user_locale_profile_create
)
@@ -25,7 +23,7 @@ from .menus import (
menu_facet, menu_main, menu_secondary, menu_setup, menu_tools
)
from .models import AnonymousUserSingleton
from .settings import TEMPORARY_DIRECTORY
from .settings import setting_temporary_directory
from .utils import validate_path
logger = logging.getLogger(__name__)
@@ -80,5 +78,6 @@ class CommonApp(MayanAppConfig):
user_logged_in.connect(user_locale_profile_session_config, dispatch_uid='user_locale_profile_session_config', sender=settings.AUTH_USER_MODEL)
post_save.connect(user_locale_profile_create, dispatch_uid='user_locale_profile_create', sender=settings.AUTH_USER_MODEL)
if (not validate_path(TEMPORARY_DIRECTORY)) or (not TEMPORARY_DIRECTORY):
setattr(common_settings, 'TEMPORARY_DIRECTORY', tempfile.mkdtemp())
# TODO: Create temp directory and update setting if /tmp not found/writable or value == None
#if (not validate_path(setting_temporary_directory.value)) or (not setting_temporary_directory.value):
# setattr(common_settings, 'setting_temporary_directory.value', tempfile.mkdtemp())

View File

@@ -1,5 +1,5 @@
from django.utils.module_loading import import_string
from .settings import SHARED_STORAGE
from .settings import setting_shared_storage
shared_storage_backend = import_string(SHARED_STORAGE)()
shared_storage_backend = import_string(setting_shared_storage.value)()

View File

@@ -2,23 +2,8 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_setting
from smart_settings import Namespace
TEMPORARY_DIRECTORY = register_setting(
namespace='common',
module='common.settings',
name='TEMPORARY_DIRECTORY',
global_name='COMMON_TEMPORARY_DIRECTORY',
default='/tmp',
description=_('Temporary directory used site wide to store thumbnails, previews and temporary files. If none is specified, one will be created using tempfile.mkdtemp()'),
exists=True
)
register_setting(
namespace='common',
module='common.settings',
name='SHARED_STORAGE',
global_name='COMMON_SHARED_STORAGE',
default='storage.backends.filebasedstorage.FileBasedStorage',
description=_('A storage backend that all workers can use to share files.'),
)
namespace = Namespace(name='common', label=_('Common'))
setting_temporary_directory = namespace.add_setting(global_name='COMMON_setting_temporary_directory', default='/tmp', 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()'), is_path=True) # TODO: get os default temp directory
setting_shared_storage = namespace.add_setting(global_name='COMMON_SHARED_STORAGE', default='storage.backends.filebasedstorage.FileBasedStorage', help_text=_('A storage backend that all workers can use to share files.'))

View File

@@ -27,6 +27,9 @@ def get_model_list_columns(obj):
except IndexError:
# It a list and it's empty
pass
except KeyError:
# It a list and it's empty
pass
for key, value in model_list_columns.items():
if key == obj or isinstance(obj, key):

View File

@@ -465,6 +465,10 @@ class SetupListView(TemplateView):
return data
class SimpleView(ViewPermissionCheckMixin, ExtraContextMixin, TemplateView):
pass
class ToolsListView(TemplateView):
template_name = 'appearance/generic_list_horizontal.html'

View File

@@ -17,10 +17,10 @@ import sh
from common.utils import fs_cleanup
from ..classes import ConverterBase
from ..settings import PDFTOPPM_PATH
from ..settings import setting_pdftoppm_path
try:
pdftoppm = sh.Command(PDFTOPPM_PATH)
pdftoppm = sh.Command(setting_pdftoppm_path.value)
except sh.CommandNotFound:
pdftoppm = None
else:

View File

@@ -15,13 +15,13 @@ from PIL import Image
from django.utils.translation import ugettext_lazy as _
from common.settings import TEMPORARY_DIRECTORY
from common.settings import setting_temporary_directory
from common.utils import fs_cleanup
from mimetype.api import get_mimetype
from .exceptions import OfficeConversionError, UnknownFileFormat
from .literals import DEFAULT_PAGE_NUMBER, DEFAULT_FILE_FORMAT
from .settings import LIBREOFFICE_PATH
from .settings import setting_libreoffice_path
CONVERTER_OFFICE_FILE_MIMETYPES = [
'application/msword',
@@ -86,18 +86,18 @@ class ConverterBase(object):
new_file_object.close()
command = []
command.append(LIBREOFFICE_PATH)
command.append(setting_libreoffice_path.value)
command.append('--headless')
command.append('--convert-to')
command.append('pdf')
command.append(input_filepath)
command.append('--outdir')
command.append(TEMPORARY_DIRECTORY)
command.append(setting_temporary_directory.value)
logger.debug('command: %s', command)
os.environ['HOME'] = TEMPORARY_DIRECTORY
os.environ['HOME'] = setting_temporary_directory.value
proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
return_code = proc.wait()
logger.debug('return_code: %s', return_code)
@@ -111,7 +111,7 @@ class ConverterBase(object):
logger.debug('filename: %s', filename)
logger.debug('extension: %s', extension)
converted_output = os.path.join(TEMPORARY_DIRECTORY, os.path.extsep.join([filename, 'pdf']))
converted_output = os.path.join(setting_temporary_directory.value, os.path.extsep.join([filename, 'pdf']))
logger.debug('converted_output: %s', converted_output)
return converted_output
@@ -151,7 +151,7 @@ class ConverterBase(object):
self.mime_type = 'application/pdf'
if self.mime_type in CONVERTER_OFFICE_FILE_MIMETYPES:
if os.path.exists(LIBREOFFICE_PATH):
if os.path.exists(setting_libreoffice_path.value):
if not self.soffice_file_object:
converted_output = ConverterBase.soffice(self.file_object)
self.file_object.seek(0)

View File

@@ -4,7 +4,7 @@ import logging
from django.utils.module_loading import import_string
from .settings import GRAPHICS_BACKEND
from .settings import setting_graphics_backend
logger = logging.getLogger(__name__)
backend = converter_class = import_string(GRAPHICS_BACKEND)
backend = converter_class = import_string(setting_graphics_backend.value)

View File

@@ -2,18 +2,21 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_settings
from smart_settings import Namespace
register_settings(
namespace='converter',
module='converter.settings',
settings=[
{'name': 'IM_CONVERT_PATH', 'global_name': 'CONVERTER_IM_CONVERT_PATH', 'default': '/usr/bin/convert', 'description': _('File path to imagemagick\'s convert program.'), 'exists': True},
{'name': 'IM_IDENTIFY_PATH', 'global_name': 'CONVERTER_IM_IDENTIFY_PATH', 'default': '/usr/bin/identify', 'description': _('File path to imagemagick\'s identify program.'), 'exists': True},
{'name': 'GM_PATH', 'global_name': 'CONVERTER_GM_PATH', 'default': '/usr/bin/gm', 'description': _('File path to graphicsmagick\'s program.'), 'exists': True},
{'name': 'GM_SETTINGS', 'global_name': 'CONVERTER_GM_SETTINGS', 'default': ''},
{'name': 'GRAPHICS_BACKEND', 'global_name': 'CONVERTER_GRAPHICS_BACKEND', 'default': 'converter.backends.python.Python', 'description': _('Graphics conversion backend to use. Options are: converter.backends.imagemagick.ImageMagick, converter.backends.graphicsmagick.GraphicsMagick and converter.backends.python.Python')},
{'name': 'LIBREOFFICE_PATH', 'global_name': 'CONVERTER_LIBREOFFICE_PATH', 'default': '/usr/bin/libreoffice', 'exists': True, 'description': _('Path to the libreoffice program.')},
{'name': 'PDFTOPPM_PATH', 'global_name': 'CONVERTER_PDFTOPPM_PATH', 'default': '/usr/bin/pdftoppm', 'exists': True, 'description': _('Path to the Popple program pdftoppm.')},
]
)
namespace = Namespace(name='converter', label=_('Converter'))
setting_graphics_backend = namespace.add_setting(global_name='CONVERTER_GRAPHICS_BACKEND', default='converter.backends.python.Python', help_text=_('Graphics conversion backend to use.'))
setting_libreoffice_path = namespace.add_setting(global_name='CONVERTER_LIBREOFFICE_PATH', default='/usr/bin/libreoffice', help_text=_('Path to the libreoffice program.'), is_path=True)
setting_pdftoppm_path = namespace.add_setting(global_name='CONVERTER_PDFTOPPM_PATH', default='/usr/bin/pdftoppm', help_text=_('Path to the Popple program pdftoppm.'), is_path=True)
# TODO: remove unconverted backends from repository
#register_settings(
# namespace='converter',
# module='converter.settings',
# settings=[
# {'name': 'IM_CONVERT_PATH', 'global_name': 'CONVERTER_IM_CONVERT_PATH', 'default': '/usr/bin/convert', 'description': _('File path to imagemagick\'s convert program.'), 'exists': True},
# {'name': 'IM_IDENTIFY_PATH', 'global_name': 'CONVERTER_IM_IDENTIFY_PATH', 'default': '/usr/bin/identify', 'description': _('File path to imagemagick\'s identify program.'), 'exists': True},
# {'name': 'GM_PATH', 'global_name': 'CONVERTER_GM_PATH', 'default': '/usr/bin/gm', 'description': _('File path to graphicsmagick\'s program.'), 'exists': True},
# {'name': 'GM_SETTINGS', 'global_name': 'CONVERTER_GM_SETTINGS', 'default': ''},
# ]
#)

View File

@@ -1,4 +1,4 @@
from .api import GPG
from .settings import GPG_HOME, GPG_PATH, KEYSERVERS
from .settings import setting_gpg_home, setting_gpg_path, setting_keyservers
gpg = GPG(binary_path=GPG_PATH, home=GPG_HOME, keyservers=KEYSERVERS)
gpg = GPG(binary_path=setting_gpg_path.value, home=setting_gpg_home.value, keyservers=setting_keyservers.value)

View File

@@ -5,14 +5,9 @@ import os
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_settings
from smart_settings import Namespace
register_settings(
namespace='django_gpg',
module='django_gpg.settings',
settings=[
{'name': 'KEYSERVERS', 'global_name': 'SIGNATURES_KEYSERVERS', 'default': ['pool.sks-keyservers.net'], 'description': _('List of keyservers to be queried for unknown keys.')},
{'name': 'GPG_HOME', 'global_name': 'SIGNATURES_GPG_HOME', 'default': os.path.join(settings.MEDIA_ROOT, 'gpg_home'), 'description': _('Home directory used to store keys as well as configuration files.')},
{'name': 'GPG_PATH', 'global_name': 'SIGNATURES_GPG_PATH', 'default': '/usr/bin/gpg', 'exists': True, 'description': _('Path to the GPG binary.')},
]
)
namespace = Namespace(name='django_gpg', label=_('Signatures'))
setting_keyservers = namespace.add_setting(global_name='SIGNATURES_KEYSERVERS', default=['pool.sks-keyservers.net'], help_text=_('List of keyservers to be queried for unknown keys.'))
setting_gpg_home = namespace.add_setting(global_name='SIGNATURES_GPG_HOME', default=os.path.join(settings.MEDIA_ROOT, 'gpg_home'), help_text=_('Home directory used to store keys as well as configuration files.'), is_path=True)
setting_gpg_path = namespace.add_setting(global_name='SIGNATURES_GPG_PATH', default='/usr/bin/gpg', help_text=_('Path to the GPG binary.'), is_path=True)

View File

@@ -35,7 +35,9 @@ from .serializers import (
DocumentTypeSerializer, DocumentVersionSerializer, NewDocumentSerializer,
RecentDocumentSerializer
)
from .settings import DISPLAY_SIZE, ZOOM_MAX_LEVEL, ZOOM_MIN_LEVEL
from .settings import (
setting_display_size, setting_zoom_max_level, setting_zoom_min_level
)
from .tasks import task_get_document_page_image, task_new_document
@@ -186,7 +188,7 @@ class APIDocumentImageView(generics.GenericAPIView):
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_DOCUMENT_VIEW, request.user, document)
size = request.GET.get('size', DISPLAY_SIZE)
size = request.GET.get('size', setting_display_size.value)
page = int(request.GET.get('page', DEFAULT_PAGE_NUMBER))
@@ -194,11 +196,11 @@ class APIDocumentImageView(generics.GenericAPIView):
version = int(request.GET.get('version', document.latest_version.pk))
if zoom < ZOOM_MIN_LEVEL:
zoom = ZOOM_MIN_LEVEL
if zoom < setting_zoom_min_level.value:
zoom = setting_zoom_min_level.value
if zoom > ZOOM_MAX_LEVEL:
zoom = ZOOM_MAX_LEVEL
if zoom > setting_zoom_max_level.value:
zoom = setting_zoom_max_level.value
rotation = int(request.GET.get('rotation', DEFAULT_ROTATION)) % 360

View File

@@ -64,7 +64,7 @@ from .permissions import (
PERMISSION_DOCUMENT_PRINT, PERMISSION_DOCUMENT_PROPERTIES_EDIT,
PERMISSION_DOCUMENT_VERSION_REVERT, PERMISSION_DOCUMENT_VIEW
)
from .settings import THUMBNAIL_SIZE
from .settings import setting_thumbnail_size
from .statistics import DocumentStatistics, DocumentUsageStatistics
from .widgets import document_thumbnail
@@ -76,8 +76,9 @@ class DocumentsApp(MayanAppConfig):
def ready(self):
super(DocumentsApp, self).ready()
if (not validate_path(document_settings.CACHE_PATH)) or (not document_settings.CACHE_PATH):
setattr(document_settings, 'CACHE_PATH', tempfile.mkdtemp())
# TODO: validate cache_path or create new
#if (not validate_path(document_settings.CACHE_PATH)) or (not document_settings.CACHE_PATH):
# setattr(document_settings, 'CACHE_PATH', tempfile.mkdtemp())
APIEndPoint('documents')
@@ -155,7 +156,7 @@ class DocumentsApp(MayanAppConfig):
register_model_list_columns(Document, [
{
'name': _('Thumbnail'), 'attribute':
encapsulate(lambda x: document_thumbnail(x, gallery_name='documents:document_list', title=getattr(x, 'label', None), size=THUMBNAIL_SIZE))
encapsulate(lambda x: document_thumbnail(x, gallery_name='documents:document_list', title=getattr(x, 'label', None), size=setting_thumbnail_size.value))
},
{
'name': _('Type'), 'attribute': 'document_type'

View File

@@ -17,7 +17,7 @@ from .permissions import (
PERMISSION_DOCUMENT_TYPE_CREATE, PERMISSION_DOCUMENT_TYPE_DELETE,
PERMISSION_DOCUMENT_TYPE_EDIT, PERMISSION_DOCUMENT_TYPE_VIEW
)
from .settings import ZOOM_MAX_LEVEL, ZOOM_MIN_LEVEL
from .settings import setting_zoom_max_level, setting_zoom_min_level
def is_not_current_version(context):
@@ -33,11 +33,11 @@ def is_last_page(context):
def is_max_zoom(context):
return context['zoom'] >= ZOOM_MAX_LEVEL
return context['zoom'] >= setting_zoom_max_level.value
def is_min_zoom(context):
return context['zoom'] <= ZOOM_MIN_LEVEL
return context['zoom'] <= setting_zoom_min_level.value
# Facet

View File

@@ -6,7 +6,7 @@ from django.db import models, transaction
from common.compressed_files import CompressedFile, NotACompressedFile
from .settings import RECENT_COUNT, LANGUAGE
from .settings import setting_recent_count, setting_language
logger = logging.getLogger(__name__)
@@ -19,7 +19,7 @@ class RecentDocumentManager(models.Manager):
# document already in the recent list, just save to force
# accessed date and time update
new_recent.save()
for recent_to_delete in self.model.objects.filter(user=user)[RECENT_COUNT:]:
for recent_to_delete in self.model.objects.filter(user=user)[setting_recent_count.value:]:
recent_to_delete.delete()
def get_for_user(self, user):
@@ -52,7 +52,7 @@ class DocumentManager(models.Manager):
for compressed_file_child in compressed_file.children():
if command_line:
print 'Uploading file #%d: %s' % (count, compressed_file_child)
versions_created.append(self.upload_single_document(document_type=document_type, file_object=compressed_file_child, description=description, label=unicode(compressed_file_child), language=language or LANGUAGE, user=user))
versions_created.append(self.upload_single_document(document_type=document_type, file_object=compressed_file_child, description=description, label=unicode(compressed_file_child), language=language or setting_language.value, user=user))
compressed_file_child.close()
count += 1
@@ -60,9 +60,9 @@ class DocumentManager(models.Manager):
logging.debug('Exception: NotACompressedFile')
if command_line:
raise
versions_created.append(self.upload_single_document(document_type=document_type, file_object=file_object, description=description, label=label, language=language or LANGUAGE, user=user))
versions_created.append(self.upload_single_document(document_type=document_type, file_object=file_object, description=description, label=label, language=language or setting_language.value, user=user))
else:
versions_created.append(self.upload_single_document(document_type=document_type, file_object=file_object, description=description, label=label, language=language or LANGUAGE, user=user))
versions_created.append(self.upload_single_document(document_type=document_type, file_object=file_object, description=description, label=label, language=language or setting_language.value, user=user))
return versions_created

View File

@@ -16,7 +16,7 @@ from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from acls.utils import apply_default_acls
from common.settings import TEMPORARY_DIRECTORY
from common.settings import setting_temporary_directory
from common.utils import fs_cleanup
from converter import (
converter_class, TransformationResize, TransformationRotate, TransformationZoom
@@ -37,8 +37,8 @@ from .managers import (
)
from .runtime import storage_backend
from .settings import (
CACHE_PATH, DISPLAY_SIZE, LANGUAGE, LANGUAGE_CHOICES, ZOOM_MAX_LEVEL,
ZOOM_MIN_LEVEL
setting_cache_path, setting_display_size, setting_language,
setting_language_choices, setting_zoom_max_level, setting_zoom_min_level
)
from .signals import post_version_upload, post_document_type_change
@@ -86,7 +86,7 @@ class Document(models.Model):
label = models.CharField(max_length=255, default=_('Uninitialized document'), db_index=True, help_text=_('The name of the document'), verbose_name=_('Label'))
description = models.TextField(blank=True, null=True, verbose_name=_('Description'))
date_added = models.DateTimeField(verbose_name=_('Added'), auto_now_add=True)
language = models.CharField(choices=LANGUAGE_CHOICES, default=LANGUAGE, max_length=8, verbose_name=_('Language'))
language = models.CharField(choices=setting_language_choices.value, default=setting_language.value, max_length=8, verbose_name=_('Language'))
objects = DocumentManager()
@@ -215,7 +215,7 @@ class Document(models.Model):
return self.versions.order_by('timestamp').last()
def document_save_to_temp_dir(self, filename, buffer_size=1024 * 1024):
temporary_path = os.path.join(TEMPORARY_DIRECTORY, filename)
temporary_path = os.path.join(setting_temporary_directory.value, filename)
return self.save_to_file(temporary_path, buffer_size)
@@ -466,20 +466,20 @@ class DocumentPage(models.Model):
return 'page-cache-{}-{}-{}'.format(self.document.uuid, self.document_version.pk, self.pk)
def get_cache_filename(self):
return os.path.join(CACHE_PATH, self.get_uuid())
return os.path.join(setting_cache_path.value, self.get_uuid())
def get_image(self, *args, **kwargs):
as_base64 = kwargs.pop('as_base64', False)
transformations = kwargs.pop('transformations', [])
size = kwargs.pop('size', DISPLAY_SIZE)
size = kwargs.pop('size', setting_display_size.value)
rotation = kwargs.pop('rotation', DEFAULT_ROTATION)
zoom_level = kwargs.pop('zoom', DEFAULT_ZOOM_LEVEL)
if zoom_level < ZOOM_MIN_LEVEL:
zoom_level = ZOOM_MIN_LEVEL
if zoom_level < setting_zoom_min_level.value:
zoom_level = setting_zoom_min_level.value
if zoom_level > ZOOM_MAX_LEVEL:
zoom_level = ZOOM_MAX_LEVEL
if zoom_level > setting_zoom_max_level.value:
zoom_level = setting_zoom_max_level.value
rotation = rotation % 360

View File

@@ -1,5 +1,5 @@
from django.utils.module_loading import import_string
from .settings import STORAGE_BACKEND
from .settings import setting_storage_backend
storage_backend = import_string(STORAGE_BACKEND)()
storage_backend = import_string(setting_storage_backend.value)()

View File

@@ -6,30 +6,24 @@ import pycountry
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_settings
from smart_settings import Namespace
LANGUAGE_CHOICES = [(i.bibliographic, _(i.name)) for i in list(pycountry.languages)]
# TODO: Findout method to make languages names' translatable.
# YAML fails to serialize ugettext_lazy and ugettext is not allowed at this level
LANGUAGE_CHOICES = [(i.bibliographic, i.name) for i in list(pycountry.languages)]
register_settings(
namespace='documents',
module='documents.settings',
settings=[
# Storage
{'name': 'STORAGE_BACKEND', 'global_name': 'DOCUMENTS_STORAGE_BACKEND', 'default': 'storage.backends.filebasedstorage.FileBasedStorage'},
# Usage
{'name': 'PREVIEW_SIZE', 'global_name': 'DOCUMENTS_PREVIEW_SIZE', 'default': '640x480'},
{'name': 'PRINT_SIZE', 'global_name': 'DOCUMENTS_PRINT_SIZE', 'default': '3600'},
{'name': 'MULTIPAGE_PREVIEW_SIZE', 'global_name': 'DOCUMENTS_MULTIPAGE_PREVIEW_SIZE', 'default': '160x120'},
{'name': 'THUMBNAIL_SIZE', 'global_name': 'DOCUMENTS_THUMBNAIL_SIZE', 'default': '50x50'},
{'name': 'DISPLAY_SIZE', 'global_name': 'DOCUMENTS_DISPLAY_SIZE', 'default': '3600'},
{'name': 'RECENT_COUNT', 'global_name': 'DOCUMENTS_RECENT_COUNT', 'default': 40, 'description': _('Maximum number of recent (created, edited, viewed) documents to remember per user.')},
{'name': 'ZOOM_PERCENT_STEP', 'global_name': 'DOCUMENTS_ZOOM_PERCENT_STEP', 'default': 25, 'description': _('Amount in percent zoom in or out a document page per user interaction.')},
{'name': 'ZOOM_MAX_LEVEL', 'global_name': 'DOCUMENTS_ZOOM_MAX_LEVEL', 'default': 300, 'description': _('Maximum amount in percent (%) to allow user to zoom in a document page interactively.')},
{'name': 'ZOOM_MIN_LEVEL', 'global_name': 'DOCUMENTS_ZOOM_MIN_LEVEL', 'default': 25, 'description': _('Minimum amount in percent (%) to allow user to zoom out a document page interactively.')},
{'name': 'ROTATION_STEP', 'global_name': 'DOCUMENTS_ROTATION_STEP', 'default': 90, 'description': _('Amount in degrees to rotate a document page per user interaction.')},
#
{'name': 'CACHE_PATH', 'global_name': 'DOCUMENTS_CACHE_PATH', 'default': os.path.join(settings.MEDIA_ROOT, 'image_cache'), 'exists': True},
{'name': 'LANGUAGE', 'global_name': 'DOCUMENTS_LANGUAGE', 'default': 'eng', 'description': _('Default documents language (in ISO639-2 format).')},
{'name': 'LANGUAGE_CHOICES', 'global_name': 'DOCUMENTS_LANGUAGE_CHOICES', 'default': LANGUAGE_CHOICES, 'description': _('List of supported document languages.')},
]
)
namespace = Namespace(name='documents', label=_('Documents'))
setting_storage_backend = namespace.add_setting(global_name='DOCUMENTS_STORAGE_BACKEND', default='storage.backends.filebasedstorage.FileBasedStorage')
setting_preview_size = namespace.add_setting(global_name='DOCUMENTS_PREVIEW_SIZE', default='640x480')
setting_print_size = namespace.add_setting(global_name='DOCUMENTS_PRINT_SIZE', default='3600')
setting_multipage_preview_size = namespace.add_setting(global_name='DOCUMENTS_MULTIPAGE_PREVIEW_SIZE', default='160x120')
setting_thumbnail_size = namespace.add_setting(global_name='DOCUMENTS_THUMBNAIL_SIZE', default='50x50')
setting_display_size = namespace.add_setting(global_name='DOCUMENTS_DISPLAY_SIZE', default='3600')
setting_recent_count = namespace.add_setting(global_name='DOCUMENTS_RECENT_COUNT', default=40, help_text=_('Maximum number of recent (created, edited, viewed) documents to remember per user.'))
setting_zoom_percent_step = namespace.add_setting(global_name='DOCUMENTS_ZOOM_PERCENT_STEP', default=25, help_text=_('Amount in percent zoom in or out a document page per user interaction.'))
setting_zoom_max_level = namespace.add_setting(global_name='DOCUMENTS_ZOOM_MAX_LEVEL', default=300, help_text=_('Maximum amount in percent (%) to allow user to zoom in a document page interactively.'))
setting_zoom_min_level = namespace.add_setting(global_name='DOCUMENTS_ZOOM_MIN_LEVEL', default=25, help_text=_('Minimum amount in percent (%) to allow user to zoom out a document page interactively.'))
setting_rotation_step = namespace.add_setting(global_name='DOCUMENTS_ROTATION_STEP', default=90, help_text=_('Amount in degrees to rotate a document page per user interaction.'))
setting_cache_path = namespace.add_setting(global_name='DOCUMENTS_CACHE_PATH', default=os.path.join(settings.MEDIA_ROOT, 'image_cache'), is_path=True)
setting_language = namespace.add_setting(global_name='DOCUMENTS_LANGUAGE', default='eng', help_text=_('Default documents language (in ISO639-2 format).'))
setting_language_choices = namespace.add_setting(global_name='DOCUMENTS_LANGUAGE_CHOICES', default=LANGUAGE_CHOICES, help_text=_('List of supported document languages.'))

View File

@@ -9,7 +9,7 @@ from .api_views import (
APIDocumentVersionCreateView, APIDocumentVersionView,
APIRecentDocumentListView
)
from .settings import PRINT_SIZE, DISPLAY_SIZE
from .settings import setting_print_size, setting_display_size
from .views import (
DocumentListView, DocumentPageListView, RecentDocumentListView
)
@@ -32,8 +32,8 @@ urlpatterns = patterns(
url(r'^(?P<document_id>\d+)/acls/$', 'document_acl_list', name='document_acl_list'),
url(r'^(?P<document_id>\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE}, 'document_display'),
url(r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE}, 'document_display_print'),
url(r'^(?P<document_id>\d+)/display/$', 'get_document_image', {'size': setting_display_size.value}, 'document_display'),
url(r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {'size': setting_print_size.value}, 'document_display_print'),
url(r'^(?P<document_id>\d+)/download/$', 'document_download', name='document_download'),
url(r'^multiple/download/$', 'document_multiple_download', name='document_multiple_download'),

View File

@@ -51,8 +51,8 @@ from .permissions import (
PERMISSION_DOCUMENT_VERSION_REVERT, PERMISSION_DOCUMENT_VIEW,
)
from .settings import (
PREVIEW_SIZE, RECENT_COUNT, ROTATION_STEP, ZOOM_PERCENT_STEP,
ZOOM_MAX_LEVEL, ZOOM_MIN_LEVEL
setting_preview_size, setting_recent_count, setting_rotation_step,
setting_zoom_percent_step, setting_zoom_max_level, setting_zoom_min_level
)
from .tasks import (
task_clear_image_cache, task_get_document_page_image,
@@ -94,7 +94,7 @@ class DocumentPageListView(ParentChildListView):
class RecentDocumentListView(DocumentListView):
extra_context = {
'hide_links': True,
'recent_count': RECENT_COUNT,
'recent_count': setting_recent_count.value, # TODO: used for something?
'title': _('Recent documents'),
}
@@ -338,7 +338,7 @@ def document_multiple_document_type_edit(request):
# TODO: Get rid of this view and convert widget to use API and base64 only images
def get_document_image(request, document_id, size=PREVIEW_SIZE):
def get_document_image(request, document_id, size=setting_preview_size.value):
document = get_object_or_404(Document, pk=document_id)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW])
@@ -351,11 +351,11 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE):
version = int(request.GET.get('version', document.latest_version.pk))
if zoom < ZOOM_MIN_LEVEL:
zoom = ZOOM_MIN_LEVEL
if zoom < setting_zoom_min_level.value:
zoom = setting_zoom_min_level.value
if zoom > ZOOM_MAX_LEVEL:
zoom = ZOOM_MAX_LEVEL
if zoom > setting_zoom_max_level.value:
zoom = setting_zoom_max_level.value
rotation = int(request.GET.get('rotation', DEFAULT_ROTATION)) % 360
@@ -713,7 +713,7 @@ def document_page_zoom_in(request, document_page_id):
return transform_page(
request,
document_page_id,
zoom_function=lambda x: ZOOM_MAX_LEVEL if x + ZOOM_PERCENT_STEP > ZOOM_MAX_LEVEL else x + ZOOM_PERCENT_STEP
zoom_function=lambda x: setting_zoom_max_level.value if x + setting_zoom_percent_step.value > setting_zoom_max_level.value else x + setting_zoom_percent_step.value
)
@@ -721,7 +721,7 @@ def document_page_zoom_out(request, document_page_id):
return transform_page(
request,
document_page_id,
zoom_function=lambda x: ZOOM_MIN_LEVEL if x - ZOOM_PERCENT_STEP < ZOOM_MIN_LEVEL else x - ZOOM_PERCENT_STEP
zoom_function=lambda x: setting_zoom_min_level.value if x - setting_zoom_percent_step.value < setting_zoom_min_level.value else x - setting_zoom_percent_step.value
)
@@ -729,7 +729,7 @@ def document_page_rotate_right(request, document_page_id):
return transform_page(
request,
document_page_id,
rotation_function=lambda x: (x + ROTATION_STEP) % 360
rotation_function=lambda x: (x + setting_rotation_step.value) % 360
)
@@ -737,7 +737,7 @@ def document_page_rotate_left(request, document_page_id):
return transform_page(
request,
document_page_id,
rotation_function=lambda x: (x - ROTATION_STEP) % 360
rotation_function=lambda x: (x - setting_rotation_step.value) % 360
)

View File

@@ -13,7 +13,7 @@ from converter.literals import (
DEFAULT_PAGE_NUMBER, DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
)
from .settings import DISPLAY_SIZE, THUMBNAIL_SIZE
from .settings import setting_display_size, setting_thumbnail_size
class DocumentPageImageWidget(forms.widgets.Widget):
@@ -24,7 +24,7 @@ class DocumentPageImageWidget(forms.widgets.Widget):
if value:
output = []
output.append('<div class="full-height scrollable mayan-page-wrapper-interactive" data-height-difference=230>')
output.append(document_html_widget(value, zoom=zoom, rotation=rotation, image_class='lazy-load', nolazyload=False, size=DISPLAY_SIZE))
output.append(document_html_widget(value, zoom=zoom, rotation=rotation, image_class='lazy-load', nolazyload=False, size=setting_display_size.value))
output.append('</div>')
return mark_safe(''.join(output))
else:
@@ -55,7 +55,7 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
click_view_arguments=[page.pk],
fancybox_class='',
image_class='lazy-load-carousel',
size=DISPLAY_SIZE,
size=setting_display_size.value,
post_load_class='lazy-load-carousel-loaded',
)
)
@@ -75,7 +75,7 @@ def document_link(document):
return mark_safe('<a href="%s">%s</a>' % (document.get_absolute_url(), document))
def document_html_widget(document_page, click_view=None, click_view_arguments=None, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox', image_class='lazy-load', title=None, size=THUMBNAIL_SIZE, nolazyload=False, post_load_class=None):
def document_html_widget(document_page, click_view=None, click_view_arguments=None, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False, post_load_class=None):
result = []
alt_text = _('Document page image')

View File

@@ -120,7 +120,7 @@ def perform_search(query_string, field_list=None):
model_result_ids = []
result_count += len(model_result_ids)
results = model.objects.in_bulk(list(model_result_ids)[: LIMIT]).values()
results = model.objects.in_bulk(list(model_result_ids)[: LIMIT.value]).values()
shown_result_count += len(results)
if results:
model_list[title] = results

View File

@@ -163,7 +163,7 @@ class SearchModel(object):
elapsed_time = unicode(datetime.datetime.now() - start_time).split(':')[2]
queryset = self.model.objects.in_bulk(list(result_set)[: LIMIT]).values()
queryset = self.model.objects.in_bulk(list(result_set)[: LIMIT.value]).values()
if self.permission:
try:

View File

@@ -31,5 +31,5 @@ class RecentSearchManager(models.Manager):
new_recent.hits = hits
new_recent.save()
for recent_to_delete in self.model.objects.filter(user=user)[RECENT_COUNT:]:
for recent_to_delete in self.model.objects.filter(user=user)[RECENT_COUNT.value:]:
recent_to_delete.delete()

View File

@@ -2,14 +2,10 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_settings
from smart_settings import Namespace
register_settings(
namespace='dynamic_search',
module='dynamic_search.settings',
settings=[
{'name': 'SHOW_OBJECT_TYPE', 'global_name': 'SEARCH_SHOW_OBJECT_TYPE', 'default': True},
{'name': 'LIMIT', 'global_name': 'SEARCH_LIMIT', 'default': 100, 'description': _('Maximum amount search hits to fetch and display.')},
{'name': 'RECENT_COUNT', 'global_name': 'SEARCH_RECENT_COUNT', 'default': 5, 'description': _('Maximum number of search queries to remember per user.')},
]
)
namespace = Namespace(name='dynamic_search', label=_('Search'))
SHOW_OBJECT_TYPE = namespace.add_setting(global_name='SEARCH_SHOW_OBJECT_TYPE', default=True)
LIMIT = namespace.add_setting(global_name='SEARCH_LIMIT', default=100, help_text=_('Maximum amount search hits to fetch and display.'))
RECENT_COUNT = namespace.add_setting(global_name='SEARCH_RECENT_COUNT', default=5, help_text=_('Maximum number of search queries to remember per user.'))

View File

@@ -23,7 +23,7 @@ def results(request, extra_context=None):
context = {
'query_string': request.GET,
'hide_links': True,
'search_results_limit': LIMIT,
'search_results_limit': LIMIT.value,
}
if request.GET:
@@ -42,7 +42,7 @@ def results(request, extra_context=None):
if extra_context:
context.update(extra_context)
if SHOW_OBJECT_TYPE:
if SHOW_OBJECT_TYPE.value:
context.update({
'extra_columns': [{'name': _('Type'), 'attribute': lambda x: x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]}]
})
@@ -63,7 +63,7 @@ def search(request, advanced=False):
'title': _('Advanced search'),
'form_action': reverse('search:results'),
'submit_method': 'GET',
'search_results_limit': LIMIT,
'search_results_limit': LIMIT.value,
'submit_label': _('Search'),
'submit_icon': 'fa fa-search',
}, context_instance=RequestContext(request)

View File

@@ -6,7 +6,7 @@ from django.forms.formsets import formset_factory
from django.utils.translation import ugettext_lazy as _
from .models import MetadataType
from .settings import AVAILABLE_FUNCTIONS, AVAILABLE_MODELS, AVAILABLE_VALIDATORS
from .settings import AVAILABLE_FUNCTIONS, AVAILABLE_MODELS, setting_available_validators
class MetadataForm(forms.Form):
@@ -15,7 +15,7 @@ class MetadataForm(forms.Form):
metadata_type = MetadataType.objects.get(pk=self.cleaned_data['id'])
try:
validation_function = AVAILABLE_VALIDATORS[metadata_type.validation]
validation_function = setting_available_validators.value[metadata_type.validation]
except KeyError:
# User entered a validation function name, but was not found
# Return value entered as is

View File

@@ -8,7 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from documents.models import Document, DocumentType
from .managers import MetadataTypeManager
from .settings import AVAILABLE_VALIDATORS
from .settings import setting_available_validators
@python_2_unicode_compatible
@@ -27,7 +27,7 @@ class MetadataType(models.Model):
lookup = models.TextField(blank=True, null=True,
verbose_name=_('Lookup'),
help_text=_('Enter a string to be evaluated that returns an iterable.'))
validation = models.CharField(blank=True, choices=zip(AVAILABLE_VALIDATORS, AVAILABLE_VALIDATORS), max_length=64, verbose_name=_('Validation function name'))
validation = models.CharField(blank=True, choices=zip(setting_available_validators.value, setting_available_validators.value), max_length=64, verbose_name=_('Validation function name'))
# TODO: Find a different way to let users know what models and functions are
# available now that we removed these from the help_text
objects = MetadataTypeManager()

View File

@@ -0,0 +1,48 @@
from __future__ import unicode_literals
from dateutil.parser import parse
from django.utils.translation import ugettext_lazy as _
class MetadataParser(object):
_registry = []
@classmethod
def register(cls, parser):
cls._registry.append(parser)
@classmethod
def get_all(cls):
return cls._registry
@classmethod
def get_import_path(cls):
return cls.__module__ + '.' + cls.__name__
@classmethod
def get_import_paths(cls):
return [parser.get_import_path() for parser in cls.get_all()]
def parse(self, input_data):
raise NotImplementedError
class DateAndTimeParser(MetadataParser):
def parse(self, input_data):
return parse(input_data).isoformat()
class DateParser(MetadataParser):
def parse(self, input_data):
return parse(input_data).date().isoformat()
class TimeParser(MetadataParser):
def parse(self, input_data):
return parse(input_data).time().isoformat()
MetadataParser.register(DateAndTimeParser)
MetadataParser.register(DateParser)
MetadataParser.register(TimeParser)

View File

@@ -1,12 +1,16 @@
from __future__ import unicode_literals
from dateutil.parser import parse
import yaml
from django.contrib.auth.models import User
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from smart_settings import Namespace
from smart_settings.api import register_settings
from .parsers import MetadataParser
default_available_functions = {
'current_date': now().date,
}
@@ -15,12 +19,6 @@ default_available_models = {
'User': User
}
default_available_validators = {
'Parse date and time': lambda input: parse(input).isoformat(),
'Parse date': lambda input: parse(input).date().isoformat(),
'Parse time': lambda input: parse(input).time().isoformat()
}
register_settings(
namespace='metadata',
module='metadata.settings',
@@ -28,8 +26,10 @@ register_settings(
# Definition
{'name': 'AVAILABLE_FUNCTIONS', 'global_name': 'METADATA_AVAILABLE_FUNCTIONS', 'default': default_available_functions},
{'name': 'AVAILABLE_MODELS', 'global_name': 'METADATA_AVAILABLE_MODELS', 'default': default_available_models},
{'name': 'AVAILABLE_VALIDATORS', 'global_name': 'METADATA_AVAILABLE_VALIDATORS', 'default': default_available_validators},
]
)
# TODO: remove classes, import by string, all settings must be simple serializable types
namespace = Namespace(name='metadata', label=_('Metadata'))
setting_available_validators = namespace.add_setting(global_name='METADATA_AVAILABLE_VALIDATORS', default=MetadataParser.get_import_paths())

View File

@@ -9,7 +9,7 @@ import sh
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from common.settings import TEMPORARY_DIRECTORY
from common.settings import setting_temporary_directory
from common.utils import fs_cleanup
from converter import converter_class
from documents.models import DocumentPage
@@ -47,7 +47,7 @@ def execute_unpaper(input_filepath, output_filepath=None):
"""
if UNPAPER:
if not output_filepath:
fd, output_filepath = tempfile.mkstemp(dir=TEMPORARY_DIRECTORY)
fd, output_filepath = tempfile.mkstemp(dir=setting_temporary_directory.value)
try:
UNPAPER(input_filepath, output_filepath)

View File

@@ -29,7 +29,7 @@ from .links import (
)
from .models import DocumentVersionOCRError
from .permissions import PERMISSION_OCR_DOCUMENT, PERMISSION_OCR_CONTENT_VIEW
from .settings import PDFTOTEXT_PATH, TESSERACT_PATH, UNPAPER_PATH
from .settings import setting_pdftotext_path, setting_tesseract_path, setting_unpaper_path
from .tasks import task_do_ocr
logger = logging.getLogger(__name__)
@@ -74,7 +74,7 @@ class OCRApp(MayanAppConfig):
namespace = PropertyNamespace('ocr', _('OCR'))
try:
pdftotext = sh.Command(PDFTOTEXT_PATH)
pdftotext = sh.Command(setting_pdftotext_path.value)
except sh.CommandNotFound:
namespace.add_property('pdftotext', _('pdftotext version'), _('not found'), report=True)
except Exception:
@@ -83,7 +83,7 @@ class OCRApp(MayanAppConfig):
namespace.add_property('pdftotext', _('pdftotext version'), pdftotext('-v').stderr, report=True)
try:
tesseract = sh.Command(TESSERACT_PATH)
tesseract = sh.Command(setting_tesseract_path.value)
except sh.CommandNotFound:
namespace.add_property('tesseract', _('tesseract version'), _('not found'), report=True)
except Exception:
@@ -92,7 +92,7 @@ class OCRApp(MayanAppConfig):
namespace.add_property('tesseract', _('tesseract version'), tesseract('-v').stderr, report=True)
try:
unpaper = sh.Command(UNPAPER_PATH)
unpaper = sh.Command(setting_unpaper_path.value)
except sh.CommandNotFound:
namespace.add_property('unpaper', _('unpaper version'), _('not found'), report=True)
except Exception:

View File

@@ -18,7 +18,7 @@ from common.utils import fs_cleanup
from ..classes import OCRBackendBase
from ..exceptions import OCRError
from ..settings import TESSERACT_PATH
from ..settings import setting_tesseract_path
logger = logging.getLogger(__name__)

View File

@@ -7,7 +7,6 @@ import tempfile
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
from common.settings import TEMPORARY_DIRECTORY
from common.utils import fs_cleanup
from converter import converter_class
from documents.models import DocumentPage
@@ -19,7 +18,6 @@ from .literals import (
from .models import DocumentPageContent
from .parsers import parse_document_page
from .parsers.exceptions import ParserError, ParserUnknownFile
from .settings import UNPAPER_PATH
logger = logging.getLogger(__name__)

View File

@@ -8,14 +8,14 @@ import tempfile
from django.utils.translation import ugettext_lazy as _
from common.settings import TEMPORARY_DIRECTORY
from common.settings import setting_temporary_directory
from common.utils import copyfile
from converter.exceptions import OfficeConversionError
from converter.classes import (
CONVERTER_OFFICE_FILE_MIMETYPES
)
from ..settings import PDFTOTEXT_PATH
from ..settings import setting_pdftotext_path
from .exceptions import ParserError, ParserUnknownFile
@@ -128,7 +128,7 @@ class PopplerParser(Parser):
PDF parser using the pdftotext execute from the poppler package
"""
def __init__(self):
self.pdftotext_path = PDFTOTEXT_PATH if PDFTOTEXT_PATH else '/usr/bin/pdftotext'
self.pdftotext_path = setting_pdftotext_path.value if setting_pdftotext_path.value else '/usr/bin/pdftotext'
if not os.path.exists(self.pdftotext_path):
raise ParserError('cannot find pdftotext executable')
logger.debug('self.pdftotext_path: %s', self.pdftotext_path)
@@ -138,7 +138,7 @@ class PopplerParser(Parser):
pagenum = str(document_page.page_number)
if descriptor:
destination_descriptor, temp_filepath = tempfile.mkstemp(dir=TEMPORARY_DIRECTORY)
destination_descriptor, temp_filepath = tempfile.mkstemp(dir=setting_temporary_directory.value)
copyfile(descriptor, temp_filepath)
document_file = temp_filepath
else:

View File

@@ -1,5 +1,5 @@
from django.utils.module_loading import import_string
from .settings import BACKEND
from .settings import setting_ocr_backend
ocr_backend_class = import_string(BACKEND)
ocr_backend_class = import_string(setting_ocr_backend.value)

View File

@@ -2,15 +2,10 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_settings
from smart_settings import Namespace
register_settings(
namespace='ocr',
module='ocr.settings',
settings=[
{'name': 'TESSERACT_PATH', 'global_name': 'OCR_TESSERACT_PATH', 'default': '/usr/bin/tesseract', 'exists': True},
{'name': 'UNPAPER_PATH', 'global_name': 'OCR_UNPAPER_PATH', 'default': '/usr/bin/unpaper', 'description': _('File path to unpaper program.'), 'exists': True},
{'name': 'PDFTOTEXT_PATH', 'global_name': 'OCR_PDFTOTEXT_PATH', 'default': '/usr/bin/pdftotext', 'description': _('File path to poppler\'s pdftotext program used to extract text from PDF files.'), 'exists': True},
{'name': 'BACKEND', 'global_name': 'OCR_BACKEND', 'default': 'ocr.backends.tesseract.Tesseract', 'description': _('Full path to the backend to be used to do OCR.')},
]
)
namespace = Namespace(name='ocr', label=_('OCR'))
setting_tesseract_path = namespace.add_setting(global_name='OCR_TESSERACT_PATH', default='/usr/bin/tesseract', help_text=_('File path to tesseract program.'), is_path=True)
setting_unpaper_path = namespace.add_setting(global_name='OCR_UNPAPER_PATH', default='/usr/bin/unpaper', help_text=_('File path to unpaper program.'), is_path=True)
setting_pdftotext_path = namespace.add_setting(global_name='OCR_PDFTOTEXT_PATH', default='/usr/bin/pdftotext', help_text=_('File path to poppler\'s pdftotext program used to extract text from PDF files.'), is_path=True)
setting_ocr_backend = namespace.add_setting(global_name='OCR_BACKEND', default='ocr.backends.tesseract.Tesseract', help_text=_('Full path to the backend to be used to do OCR.'))

View File

@@ -0,0 +1 @@
from .classes import Namespace, Setting # NOQA

View File

@@ -2,9 +2,14 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from common import MayanAppConfig, menu_setup
from common import MayanAppConfig, menu_setup, menu_object
from common.utils import encapsulate
from common.widgets import exists_widget
from navigation.api import register_model_list_columns
from .links import link_check_settings
from .classes import Namespace, Setting
from .links import link_check_settings, link_namespace_detail
from .widgets import setting_widget
class SmartSettingsApp(MayanAppConfig):
@@ -16,4 +21,18 @@ class SmartSettingsApp(MayanAppConfig):
def ready(self):
super(SmartSettingsApp, self).ready()
menu_object.bind_links(links=[link_namespace_detail], sources=[Namespace])
menu_setup.bind_links(links=[link_check_settings])
register_model_list_columns(Setting, [
{
'name': _('Name'),
'attribute': encapsulate(lambda instance: setting_widget(instance))
},
{
'name': _('Value'), 'attribute': 'value'
},
{
'name': _('Found in path'), 'attribute': encapsulate(lambda instance: exists_widget(instance.value) if instance.is_path else _('n/a'))
},
])

View File

@@ -0,0 +1,46 @@
from __future__ import unicode_literals
import yaml
from django.conf import settings
class Namespace(object):
_registry = {}
@classmethod
def get_all(cls):
return cls._registry.values()
@classmethod
def get(cls, name):
return cls._registry[name]
def __unicode__(self):
return unicode(self.label)
def __init__(self, name, label):
if name in self.__class__._registry:
raise Exception('Namespace names must be unique; "%s" already exists.' % name)
self.name = name
self.label = label
self.__class__._registry[name] = self
self.settings = []
def add_setting(self, **kwargs):
return Setting(namespace=self, **kwargs)
class Setting(object):
def __init__(self, namespace, global_name, default, help_text=None, is_path=False):
self.global_name = global_name
self.default = default
self.help_text = help_text
self.is_path = is_path
namespace.settings.append(self)
def __unicode__(self):
return unicode(self.global_name)
@property
def value(self):
return yaml.safe_load(getattr(settings, yaml.safe_dump(self.global_name), yaml.safe_dump(self.default)))

View File

@@ -10,3 +10,4 @@ def is_superuser(context):
link_check_settings = Link(condition=is_superuser, icon='fa fa-sliders', text=_('Settings'), view='settings:setting_list')
link_namespace_detail = Link(condition=is_superuser, text=_('Settings'), view='settings:namespace_detail', args='resolved_object.name')

View File

@@ -2,7 +2,11 @@ from __future__ import unicode_literals
from django.conf.urls import patterns, url
from .views import NamespaceDetailView, NamespaceListView
urlpatterns = patterns(
'smart_settings.views',
url(r'^list/$', 'setting_list', name='setting_list'),
url(r'^namespace/all/$', NamespaceListView.as_view(), name='namespace_list'),
url(r'^namespace/(?P<namespace_name>\w+)/$', NamespaceDetailView.as_view(), name='namespace_detail'),
)

View File

@@ -7,11 +7,49 @@ from django.utils.safestring import mark_safe
from common.utils import encapsulate
from common.widgets import exists_widget
from common.views import SimpleView
from .api import settings
from .classes import Namespace
from .utils import return_type # TODO: remove return_type, all settings must be simple types
class NamespaceListView(SimpleView):
template_name = 'appearance/generic_list.html'
def get_context_data(self, **kwargs):
context = super(NamespaceListView, self).get_context_data(**kwargs)
context.update(
{
'hide_link': True,
'object_list': Namespace.get_all(),
'title': _('Setting namespaces'),
}
)
return context
class NamespaceDetailView(SimpleView):
template_name = 'appearance/generic_list.html'
def get_context_data(self, **kwargs):
context = super(NamespaceDetailView, self).get_context_data(**kwargs)
namespace = Namespace.get(self.kwargs['namespace_name'])
context.update(
{
'hide_object': True,
'object_list': namespace.settings,
'title': _('Settings in namespace: %s') % namespace,
}
)
return context
def setting_list(request):
new_settings = []
for namespace, sub_settings in settings.items():

View File

@@ -0,0 +1,12 @@
from __future__ import unicode_literals
from django.utils.safestring import mark_safe
def setting_widget(instance):
return mark_safe(
'''
<strong>{}</strong>
<p class="small">{}</p>
'''.format(instance, instance.help_text or '')
)

View File

@@ -10,9 +10,7 @@ from converter.models import Transformation
from rest_framework import generics
from rest_framework.response import Response
from documents.settings import (
DISPLAY_SIZE, ZOOM_MAX_LEVEL, ZOOM_MIN_LEVEL
)
from documents.settings import setting_display_size
from .models import StagingFolderSource
from .serializers import (
@@ -63,7 +61,7 @@ class APIStagingSourceFileImageView(generics.GenericAPIView):
staging_folder = get_object_or_404(StagingFolderSource, pk=staging_folder_pk)
staging_file = staging_folder.get_file(encoded_filename=encoded_filename)
size = request.GET.get('size', DISPLAY_SIZE)
size = request.GET.get('size', setting_display_size.value)
try:
return Response({

View File

@@ -10,14 +10,14 @@ from django.utils.translation import ugettext_lazy as _
from converter.literals import (
DEFAULT_PAGE_NUMBER, DEFAULT_ROTATION, DEFAULT_ZOOM_LEVEL
)
from documents.settings import PREVIEW_SIZE, THUMBNAIL_SIZE
from documents.settings import setting_preview_size, setting_thumbnail_size
def staging_file_thumbnail(staging_file, **kwargs):
return staging_file_html_widget(staging_file, click_view='stagingfolderfile-image-view', **kwargs)
def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox-staging', image_class='lazy-load', title=None, size=THUMBNAIL_SIZE, nolazyload=False):
def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox-staging', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False):
result = []
alt_text = _('Staging file page image')
@@ -50,7 +50,7 @@ def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NU
if click_view:
# TODO: fix this hack
query_dict['size'] = PREVIEW_SIZE
query_dict['size'] = setting_preview_size.value
query_string = urlencode(query_dict)
result.append('<a %s class="%s" href="%s" %s>' % (gallery_template, fancybox_class, '%s?%s' % (reverse(click_view, args=[staging_file.staging_folder.pk, staging_file.encoded_filename]), query_string), title_template))

View File

@@ -4,7 +4,7 @@ import os
from django.core.files.storage import FileSystemStorage
from ..settings import FILESTORAGE_LOCATION
from ..settings import setting_filestorage_location
class FileBasedStorage(FileSystemStorage):
@@ -14,4 +14,4 @@ class FileBasedStorage(FileSystemStorage):
def __init__(self, *args, **kwargs):
super(FileBasedStorage, self).__init__(*args, **kwargs)
self.location = FILESTORAGE_LOCATION
self.location = setting_filestorage_location.value

View File

@@ -3,13 +3,9 @@ from __future__ import unicode_literals
import os
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_settings
from smart_settings import Namespace
register_settings(
namespace='storage',
module='storage.settings',
settings=[
{'name': 'FILESTORAGE_LOCATION', 'global_name': 'STORAGE_FILESTORAGE_LOCATION', 'default': os.path.join(settings.MEDIA_ROOT, 'document_storage'), 'exists': True},
]
)
namespace = Namespace(name='storage', label=_('Storage'))
setting_filestorage_location = namespace.add_setting(global_name='STORAGE_FILESTORAGE_LOCATION', default=os.path.join(settings.MEDIA_ROOT, 'document_storage'), is_path=True)

View File

@@ -1,6 +1,7 @@
Django==1.7.8
Pillow==2.8.2
PyYAML==3.11
celery==3.1.18
cssmin==0.2.0