Get rid of eval in metadata type default and lookup fields. gh-issue #151.
This commit is contained in:
@@ -71,6 +71,7 @@ What's new in Mayan EDMS v2.0
|
||||
* Document image and intermediate file caching now has it's own storage backend.
|
||||
* RGB tags
|
||||
* ``performupgrade`` management command.
|
||||
* Removal of eval from metadata type defaults and lookup fields. Django's own template language is now used instead.
|
||||
|
||||
Upgrading from a previous version
|
||||
=================================
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from .classes import MetadataLookup # NOQA
|
||||
|
||||
@@ -104,7 +104,9 @@ def get_metadata_string(document):
|
||||
"""
|
||||
Return a formated representation of a document's metadata values
|
||||
"""
|
||||
return ', '.join(['%s - %s' % (document_metadata.metadata_type, document_metadata.value) for document_metadata in document.metadata.all()])
|
||||
return ', '.join(
|
||||
['%s - %s' % (document_metadata.metadata_type, document_metadata.value) for document_metadata in document.metadata.all()]
|
||||
)
|
||||
|
||||
|
||||
def convert_dict_to_dict_list(dictionary):
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .models import MetadataType
|
||||
|
||||
|
||||
class DocumentMetadataHelper(object):
|
||||
@staticmethod
|
||||
@@ -17,7 +16,35 @@ class DocumentMetadataHelper(object):
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.instance.metadata.get(metadata_type__name=name).value
|
||||
except MetadataType.DoesNotExist:
|
||||
except ObjectDoesNotExist:
|
||||
raise AttributeError(
|
||||
_('\'metadata\' object has no attribute \'%s\'') % name
|
||||
)
|
||||
|
||||
|
||||
class MetadataLookup(object):
|
||||
_registry = []
|
||||
|
||||
@classmethod
|
||||
def get_as_context(cls):
|
||||
result = {}
|
||||
for entry in cls._registry:
|
||||
result[entry.name] = entry.value
|
||||
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def get_as_help_text(cls):
|
||||
result = []
|
||||
for entry in cls._registry:
|
||||
result.append(
|
||||
'{{{{ {0} }}}} = "{1}"'.format(entry.name, entry.description)
|
||||
)
|
||||
|
||||
return ' '.join(result)
|
||||
|
||||
def __init__(self, description, name, value):
|
||||
self.description = description
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.__class__._registry.append(self)
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import shlex
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms.formsets import formset_factory
|
||||
from django.template import Context, Template
|
||||
from django.utils.module_loading import import_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import string_concat, ugettext_lazy as _
|
||||
|
||||
from .classes import MetadataLookup
|
||||
from .models import MetadataType
|
||||
|
||||
|
||||
class MetadataForm(forms.Form):
|
||||
@staticmethod
|
||||
def comma_splitter(string):
|
||||
splitter = shlex.shlex(string.encode('utf-8'), posix=True)
|
||||
splitter.whitespace = ','.encode('utf-8')
|
||||
splitter.whitespace_split = True
|
||||
splitter.commenters = ''.encode('utf-8')
|
||||
return list(splitter)
|
||||
|
||||
def clean_value(self):
|
||||
metadata_type = MetadataType.objects.get(pk=self.cleaned_data['id'])
|
||||
@@ -66,23 +76,32 @@ class MetadataForm(forms.Form):
|
||||
self.fields['value'].required = False
|
||||
|
||||
self.fields['name'].initial = '%s%s' % (
|
||||
(self.metadata_type.label if self.metadata_type.label else self.metadata_type.name),
|
||||
(
|
||||
self.metadata_type.label if self.metadata_type.label else self.metadata_type.name
|
||||
),
|
||||
required_string
|
||||
)
|
||||
self.fields['id'].initial = self.metadata_type.pk
|
||||
|
||||
if self.metadata_type.lookup:
|
||||
try:
|
||||
#choices = eval(self.metadata_type.lookup, setting_available_models.value) #####
|
||||
choices = []
|
||||
self.fields['value'] = forms.ChoiceField(label=self.fields['value'].label)
|
||||
template = Template(self.metadata_type.lookup)
|
||||
context = Context(MetadataLookup.get_as_context())
|
||||
choices = MetadataForm.comma_splitter(
|
||||
template.render(context=context)
|
||||
)
|
||||
self.fields['value'] = forms.ChoiceField(
|
||||
label=self.fields['value'].label
|
||||
)
|
||||
choices = zip(choices, choices)
|
||||
if not required:
|
||||
choices.insert(0, ('', '------'))
|
||||
self.fields['value'].choices = choices
|
||||
self.fields['value'].required = required
|
||||
except Exception as exception:
|
||||
self.fields['value'].initial = exception
|
||||
self.fields['value'].initial = _(
|
||||
'Lookup value error: %s'
|
||||
) % exception
|
||||
self.fields['value'].widget = forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}
|
||||
)
|
||||
@@ -95,8 +114,12 @@ class MetadataForm(forms.Form):
|
||||
self.fields['value'].initial = result
|
||||
except Exception as exception:
|
||||
self.fields['value'].initial = _(
|
||||
'Error: %s'
|
||||
'Default value error: %s'
|
||||
) % exception
|
||||
self.fields['value'].widget = forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}
|
||||
)
|
||||
|
||||
|
||||
id = forms.CharField(label=_('ID'), widget=forms.HiddenInput)
|
||||
|
||||
@@ -123,6 +146,20 @@ class AddMetadataForm(forms.Form):
|
||||
self.fields['metadata_type'].queryset = document_type.metadata.all()
|
||||
|
||||
|
||||
class MetadataTypeForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ('name', 'label', 'default', 'lookup', 'validation')
|
||||
model = MetadataType
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MetadataTypeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['lookup'].help_text = string_concat(
|
||||
self.fields['lookup'].help_text,
|
||||
_(' Available template context variables: '),
|
||||
MetadataLookup.get_as_help_text()
|
||||
)
|
||||
|
||||
|
||||
class MetadataRemoveForm(MetadataForm):
|
||||
update = forms.BooleanField(
|
||||
initial=False, label=_('Remove'), required=False
|
||||
|
||||
@@ -14,7 +14,24 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='metadatatype',
|
||||
name='validation',
|
||||
field=models.CharField(blank=True, max_length=64, verbose_name='Validation function name', choices=[(b'metadata.validators.DateAndTimeValidator', b'metadata.validators.DateAndTimeValidator'), (b'metadata.validators.DateValidator', b'metadata.validators.DateValidator'), (b'metadata.validators.TimeValidator', b'metadata.validators.TimeValidator')]),
|
||||
field=models.CharField(
|
||||
blank=True, max_length=64,
|
||||
verbose_name='Validation function name',
|
||||
choices=[
|
||||
(
|
||||
b'metadata.validators.DateAndTimeValidator',
|
||||
b'metadata.validators.DateAndTimeValidator'
|
||||
),
|
||||
(
|
||||
b'metadata.validators.DateValidator',
|
||||
b'metadata.validators.DateValidator'
|
||||
),
|
||||
(
|
||||
b'metadata.validators.TimeValidator',
|
||||
b'metadata.validators.TimeValidator'
|
||||
)
|
||||
]
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -25,28 +25,36 @@ class MetadataType(models.Model):
|
||||
"""
|
||||
name = models.CharField(
|
||||
max_length=48,
|
||||
help_text=_('Name used by other apps to reference this value. Do not use python reserved words, or spaces.'),
|
||||
help_text=_(
|
||||
'Name used by other apps to reference this value. '
|
||||
'Do not use python reserved words, or spaces.'
|
||||
),
|
||||
unique=True, verbose_name=_('Name')
|
||||
)
|
||||
label = models.CharField(max_length=48, verbose_name=_('Label'))
|
||||
default = models.CharField(
|
||||
blank=True, max_length=128, null=True,
|
||||
help_text=_('Enter a template to render. Use Django\'s default templating language (https://docs.djangoproject.com/en/1.7/ref/templates/builtins/)'),
|
||||
help_text=_(
|
||||
'Enter a template to render. '
|
||||
'Use Django\'s default templating language '
|
||||
'(https://docs.djangoproject.com/en/1.7/ref/templates/builtins/)'
|
||||
),
|
||||
verbose_name=_('Default')
|
||||
)
|
||||
# TODO: Add enable_lookup boolean to allow users to switch the lookup on and
|
||||
# off without losing the lookup expression
|
||||
lookup = models.TextField(
|
||||
blank=True, null=True,
|
||||
help_text=_('Enter a string to be evaluated that returns an iterable.'),
|
||||
help_text=_(
|
||||
'Enter a template to render. '
|
||||
'Must result in a command delimited string. '
|
||||
'Use Django\'s default templating language '
|
||||
'(https://docs.djangoproject.com/en/1.7/ref/templates/builtins/).'
|
||||
),
|
||||
verbose_name=_('Lookup')
|
||||
)
|
||||
validation = models.CharField(
|
||||
blank=True, choices=validation_choices(), 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()
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -24,7 +24,9 @@ from documents.views import DocumentListView
|
||||
from permissions import Permission
|
||||
|
||||
from .api import save_metadata_list
|
||||
from .forms import AddMetadataForm, MetadataFormSet, MetadataRemoveFormSet
|
||||
from .forms import (
|
||||
AddMetadataForm, MetadataFormSet, MetadataRemoveFormSet, MetadataTypeForm
|
||||
)
|
||||
from .models import DocumentMetadata, MetadataType
|
||||
from .permissions import (
|
||||
permission_metadata_document_add, permission_metadata_document_edit,
|
||||
@@ -148,7 +150,8 @@ def metadata_edit(request, document_id=None, document_id_list=None):
|
||||
else:
|
||||
messages.error(
|
||||
request, _(
|
||||
'Error editing metadata for document %(document)s; %(exception)s.'
|
||||
'Error editing metadata for document: '
|
||||
'%(document)s; %(exception)s.'
|
||||
) % {
|
||||
'document': document,
|
||||
'exception': ', '.join(exception.messages)
|
||||
@@ -157,7 +160,9 @@ def metadata_edit(request, document_id=None, document_id_list=None):
|
||||
else:
|
||||
messages.success(
|
||||
request,
|
||||
_('Metadata for document %s edited successfully.') % document
|
||||
_(
|
||||
'Metadata for document %s edited successfully.'
|
||||
) % document
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(next)
|
||||
@@ -192,7 +197,10 @@ def metadata_add(request, document_id=None, document_id_list=None):
|
||||
documents = [get_object_or_404(Document, pk=document_id)]
|
||||
elif document_id_list:
|
||||
documents = [
|
||||
get_object_or_404(Document.objects.select_related('document_type'), pk=document_id) for document_id in document_id_list.split(',')
|
||||
get_object_or_404(
|
||||
Document.objects.select_related('document_type'),
|
||||
pk=document_id
|
||||
) for document_id in document_id_list.split(',')
|
||||
]
|
||||
if len(set([document.document_type.pk for document in documents])) > 1:
|
||||
messages.error(
|
||||
@@ -253,11 +261,15 @@ def metadata_add(request, document_id=None, document_id_list=None):
|
||||
messages.error(
|
||||
request,
|
||||
_(
|
||||
'Error adding metadata type "%(metadata_type)s" to document: %(document)s; %(exception)s'
|
||||
'Error adding metadata type '
|
||||
'"%(metadata_type)s" to document: '
|
||||
'%(document)s; %(exception)s'
|
||||
) % {
|
||||
'metadata_type': metadata_type,
|
||||
'document': document,
|
||||
'exception': ', '.join(getattr(exception, 'messages', exception))
|
||||
'exception': ', '.join(
|
||||
getattr(exception, 'messages', exception)
|
||||
)
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -265,17 +277,21 @@ def metadata_add(request, document_id=None, document_id_list=None):
|
||||
messages.success(
|
||||
request,
|
||||
_(
|
||||
'Metadata type: %(metadata_type)s successfully added to document %(document)s.'
|
||||
'Metadata type: %(metadata_type)s '
|
||||
'successfully added to document %(document)s.'
|
||||
) % {
|
||||
'metadata_type': metadata_type, 'document': document
|
||||
'metadata_type': metadata_type,
|
||||
'document': document
|
||||
}
|
||||
)
|
||||
else:
|
||||
messages.warning(
|
||||
request, _(
|
||||
'Metadata type: %(metadata_type)s already present in document %(document)s.'
|
||||
'Metadata type: %(metadata_type)s already '
|
||||
'present in document %(document)s.'
|
||||
) % {
|
||||
'metadata_type': metadata_type, 'document': document
|
||||
'metadata_type': metadata_type,
|
||||
'document': document
|
||||
}
|
||||
)
|
||||
|
||||
@@ -368,7 +384,11 @@ def metadata_remove(request, document_id=None, document_id_list=None):
|
||||
|
||||
post_action_redirect = reverse('documents:document_list_recent')
|
||||
|
||||
next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', post_action_redirect)))
|
||||
next = request.POST.get(
|
||||
'next', request.GET.get(
|
||||
'next', request.META.get('HTTP_REFERER', post_action_redirect)
|
||||
)
|
||||
)
|
||||
|
||||
metadata = {}
|
||||
for document in documents:
|
||||
@@ -397,15 +417,34 @@ def metadata_remove(request, document_id=None, document_id_list=None):
|
||||
|
||||
for form in formset.forms:
|
||||
if form.cleaned_data['update']:
|
||||
metadata_type = get_object_or_404(MetadataType, pk=form.cleaned_data['id'])
|
||||
metadata_type = get_object_or_404(
|
||||
MetadataType, pk=form.cleaned_data['id']
|
||||
)
|
||||
try:
|
||||
document_metadata = DocumentMetadata.objects.get(document=document, metadata_type=metadata_type)
|
||||
document_metadata = DocumentMetadata.objects.get(
|
||||
document=document, metadata_type=metadata_type
|
||||
)
|
||||
document_metadata.delete()
|
||||
messages.success(request, _('Successfully remove metadata type "%(metadata_type)s" from document: %(document)s.') % {
|
||||
'metadata_type': metadata_type, 'document': document})
|
||||
messages.success(
|
||||
request,
|
||||
_(
|
||||
'Successfully remove metadata type "%(metadata_type)s" from document: %(document)s.'
|
||||
) % {
|
||||
'metadata_type': metadata_type,
|
||||
'document': document
|
||||
}
|
||||
)
|
||||
except Exception as exception:
|
||||
messages.error(request, _('Error removing metadata type "%(metadata_type)s" from document: %(document)s; %(exception)s') % {
|
||||
'metadata_type': metadata_type, 'document': document, 'exception': ', '.join(exception.messages)})
|
||||
messages.error(
|
||||
request,
|
||||
_(
|
||||
'Error removing metadata type "%(metadata_type)s" from document: %(document)s; %(exception)s'
|
||||
) % {
|
||||
'metadata_type': metadata_type,
|
||||
'document': document,
|
||||
'exception': ', '.join(exception.messages)
|
||||
}
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
@@ -429,23 +468,32 @@ def metadata_remove(request, document_id=None, document_id_list=None):
|
||||
|
||||
|
||||
def metadata_multiple_remove(request):
|
||||
return metadata_remove(request, document_id_list=request.GET.get('id_list', []))
|
||||
return metadata_remove(
|
||||
request, document_id_list=request.GET.get('id_list', [])
|
||||
)
|
||||
|
||||
|
||||
def metadata_view(request, document_id):
|
||||
document = get_object_or_404(Document, pk=document_id)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_metadata_document_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_metadata_document_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_metadata_document_view, request.user, document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_metadata_document_view, request.user, document
|
||||
)
|
||||
|
||||
return render_to_response('appearance/generic_list.html', {
|
||||
'title': _('Metadata for document: %s') % document,
|
||||
'object_list': document.metadata.all(),
|
||||
'extra_columns': [
|
||||
{'name': _('Value'), 'attribute': 'value'},
|
||||
{'name': _('Required'), 'attribute': encapsulate(lambda x: x.metadata_type in document.document_type.metadata.filter(required=True))}
|
||||
{
|
||||
'name': _('Required'),
|
||||
'attribute': encapsulate(lambda x: x.metadata_type in document.document_type.metadata.filter(required=True))
|
||||
}
|
||||
],
|
||||
'hide_link': True,
|
||||
'object': document,
|
||||
@@ -455,6 +503,7 @@ def metadata_view(request, document_id):
|
||||
# Setup views
|
||||
class MetadataTypeCreateView(SingleObjectCreateView):
|
||||
extra_context = {'title': _('Create metadata type')}
|
||||
form_class = MetadataTypeForm
|
||||
model = MetadataType
|
||||
post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
|
||||
view_permission = permission_metadata_type_create
|
||||
@@ -474,6 +523,7 @@ class MetadataTypeDeleteView(SingleObjectDeleteView):
|
||||
|
||||
|
||||
class MetadataTypeEditView(SingleObjectEditView):
|
||||
form_class = MetadataTypeForm
|
||||
model = MetadataType
|
||||
post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
|
||||
view_permission = permission_metadata_type_edit
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -7,6 +8,7 @@ from actstream import registry
|
||||
|
||||
from common import menu_multi_item, menu_object, menu_secondary, menu_setup
|
||||
from common.apps import MayanAppConfig
|
||||
from metadata import MetadataLookup
|
||||
from rest_api.classes import APIEndPoint
|
||||
|
||||
from .links import (
|
||||
@@ -28,6 +30,9 @@ class UserManagementApp(MayanAppConfig):
|
||||
|
||||
APIEndPoint('users', app_name='user_management')
|
||||
|
||||
MetadataLookup(description=_('All the groups.'), name='group', value=Group.objects.all())
|
||||
MetadataLookup(description=_('All the users.'), name='users', value=get_user_model().objects.all())
|
||||
|
||||
menu_multi_item.bind_links(
|
||||
links=[link_group_multiple_delete],
|
||||
sources=['user_management:group_list']
|
||||
|
||||
Reference in New Issue
Block a user