diff --git a/mayan/apps/main/templates/main/generic_form_instance.html b/mayan/apps/main/templates/main/generic_form_instance.html index 1e48abe0af..8e88e85fae 100644 --- a/mayan/apps/main/templates/main/generic_form_instance.html +++ b/mayan/apps/main/templates/main/generic_form_instance.html @@ -17,16 +17,26 @@ {% endif %} {% if form_display_mode_table %} +
+ {% for field in form.visible_fields %} + {% for error in field.errors %} +
+

{{ error }}

+
+ {% endfor %} + {% endfor %} +
+ {% for field in form.hidden_fields %} {{ field }} {% endfor %} {% for field in form.visible_fields %} - - {% if field.errors %}
{% endif %} + + {% if field.errors %}
{% endif %} {{ field }} - {% if field.errors %}
{% endif %} - + {% if field.errors %}
{% endif %} + {% endfor %} {% else %} diff --git a/mayan/apps/metadata/admin.py b/mayan/apps/metadata/admin.py index 6f8534f34e..2f2b9f2a72 100644 --- a/mayan/apps/metadata/admin.py +++ b/mayan/apps/metadata/admin.py @@ -6,7 +6,7 @@ from .models import MetadataType class MetadataTypeAdmin(admin.ModelAdmin): - list_display = ('name', 'title', 'default', 'lookup') + list_display = ('name', 'title', 'default', 'lookup', 'validation') admin.site.register(MetadataType, MetadataTypeAdmin) diff --git a/mayan/apps/metadata/forms.py b/mayan/apps/metadata/forms.py index dc0bdbe79f..c358b924b8 100644 --- a/mayan/apps/metadata/forms.py +++ b/mayan/apps/metadata/forms.py @@ -1,6 +1,7 @@ from __future__ import absolute_import from django import forms +from django.core.exceptions import ValidationError from django.forms.formsets import formset_factory from django.utils.translation import ugettext_lazy as _ @@ -11,15 +12,42 @@ from .settings import AVAILABLE_FUNCTIONS, AVAILABLE_MODELS, AVAILABLE_VALIDATOR class MetadataForm(forms.Form): def clean_value(self): - value = self.cleaned_data['value'] - metadata_id = self.cleaned_data['id'] - metadata_type = MetadataType.objects.get(pk=metadata_id) - if metadata_type.lookup and metadata_type.lookup in AVAILABLE_VALIDATORS: - val_func = AVAILABLE_VALIDATORS[metadata_type.lookup] - new_value = val_func(value) - if new_value: - value = new_value - return value + metadata_type = MetadataType.objects.get(pk=self.cleaned_data['id']) + + try: + validation_function = AVAILABLE_VALIDATORS[metadata_type.validation] + except KeyError: + # User entered a validation function name, but was not found + # Return value entered as is + return self.cleaned_data['value'] + else: + try: + # If it is a parsing function we should get a value + # If it is a validation function we get nothing on success + result = validation_function(self.cleaned_data['value']) + except Exception as exception: + # If it is a validation function and an exception is raise + # we wrap that into a new ValidationError exception + # If the function exception is a ValidationError itself the + # error messages will be in a 'messages' property, so we + # contatenate them. + # Otherwise we extract whatever single message the exception + # included. + try: + message = u', '.join(exception.messages) + except AttributeError: + message = unicode(exception) + + raise ValidationError(_('Invalid value: %(message)s'), params={'message': message}, code='invalid') + else: + # Return the result if it was a parsing function + # If it was a validation function and passed correctly we return + # the original input value + return result or self.cleaned_data['value'] + + # If a validation function was never specified we return the original + # value + return self.cleaned_data['value'] def __init__(self, *args, **kwargs): super(MetadataForm, self).__init__(*args, **kwargs) @@ -39,7 +67,7 @@ class MetadataForm(forms.Form): self.fields['name'].initial = '%s%s' % ((self.metadata_type.title if self.metadata_type.title else self.metadata_type.name), required_string) self.fields['id'].initial = self.metadata_type.pk - if self.metadata_type.lookup and self.metadata_type.lookup not in AVAILABLE_VALIDATORS: + if self.metadata_type.lookup: try: choices = eval(self.metadata_type.lookup, AVAILABLE_MODELS) self.fields['value'] = forms.ChoiceField(label=self.fields['value'].label) @@ -68,22 +96,26 @@ MetadataFormSet = formset_factory(MetadataForm, extra=0) class AddMetadataForm(forms.Form): + metadata_type = forms.ModelChoiceField(queryset=MetadataType.objects.all(), label=_(u'Metadata type')) + def __init__(self, *args, **kwargs): document_type = kwargs.pop('document_type') super(AddMetadataForm, self).__init__(*args, **kwargs) self.fields['metadata_type'].queryset = document_type.metadata.all() - metadata_type = forms.ModelChoiceField(queryset=MetadataType.objects.all(), label=_(u'Metadata type')) - class MetadataRemoveForm(MetadataForm): update = forms.BooleanField(initial=False, label=_(u'Remove'), required=False) + def __init__(self, *args, **kwargs): + super(MetadataRemoveForm, self).__init__(*args, **kwargs) + self.fields.pop('value') + MetadataRemoveFormSet = formset_factory(MetadataRemoveForm, extra=0) class MetadataTypeForm(forms.ModelForm): class Meta: - fields = ('name', 'title', 'default', 'lookup') + fields = ('name', 'title', 'default', 'lookup', 'validation') model = MetadataType diff --git a/mayan/apps/metadata/models.py b/mayan/apps/metadata/models.py index a02a7ec743..6b810aed00 100644 --- a/mayan/apps/metadata/models.py +++ b/mayan/apps/metadata/models.py @@ -7,6 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from documents.models import Document, DocumentType from .managers import MetadataTypeManager +from .settings import AVAILABLE_VALIDATORS class MetadataType(models.Model): @@ -24,7 +25,7 @@ class MetadataType(models.Model): lookup = models.TextField(blank=True, null=True, verbose_name=_(u'Lookup'), help_text=_(u'Enter a string to be evaluated that returns an iterable.')) - # TODO: Add datatype choice: Date, Time, String, Number + validation = models.CharField(blank=True, choices=zip(AVAILABLE_VALIDATORS, AVAILABLE_VALIDATORS), max_length=64, verbose_name=_(u'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() diff --git a/mayan/apps/metadata/settings.py b/mayan/apps/metadata/settings.py index ae23297d54..93398c3e61 100644 --- a/mayan/apps/metadata/settings.py +++ b/mayan/apps/metadata/settings.py @@ -1,11 +1,12 @@ """Configuration options for the metadata app""" +from dateutil.parser import parse + from django.contrib.auth.models import User from django.utils.timezone import now from smart_settings.api import register_settings - default_available_functions = { 'current_date': now().date, } @@ -15,6 +16,7 @@ default_available_models = { } default_available_validators = { + 'parse_date': lambda input: parse(input).isoformat() } register_settings( diff --git a/mayan/apps/metadata/south_migrations/0011_auto__add_field_metadatatype_validation.py b/mayan/apps/metadata/south_migrations/0011_auto__add_field_metadatatype_validation.py new file mode 100644 index 0000000000..8bd44a7f0a --- /dev/null +++ b/mayan/apps/metadata/south_migrations/0011_auto__add_field_metadatatype_validation.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'MetadataType.validation' + db.add_column(u'metadata_metadatatype', 'validation', + self.gf('django.db.models.fields.CharField')(default='', max_length=64), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'MetadataType.validation' + db.delete_column(u'metadata_metadatatype', 'validation') + + + models = { + u'documents.document': { + 'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'}, + 'date_added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'documents'", 'to': u"orm['documents.DocumentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'label': ('django.db.models.fields.CharField', [], {'default': "u'Uninitialized document'", 'max_length': '255', 'db_index': 'True'}), + 'language': ('django.db.models.fields.CharField', [], {'default': "u'eng'", 'max_length': '8'}), + 'uuid': ('django.db.models.fields.CharField', [], {'default': "u'8fd13d23-fcd0-438c-a1b3-7421a1d0eed5'", 'max_length': '48'}) + }, + u'documents.documenttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}), + 'ocr': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + u'metadata.documentmetadata': { + 'Meta': {'unique_together': "(('document', 'metadata_type'),)", 'object_name': 'DocumentMetadata'}, + 'document': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'metadata'", 'to': u"orm['documents.Document']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['metadata.MetadataType']"}), + 'value': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'null': 'True', 'blank': 'True'}) + }, + u'metadata.documenttypemetadatatype': { + 'Meta': {'unique_together': "(('document_type', 'metadata_type'),)", 'object_name': 'DocumentTypeMetadataType'}, + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'metadata'", 'to': u"orm['documents.DocumentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['metadata.MetadataType']"}), + 'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + u'metadata.metadatatype': { + 'Meta': {'ordering': "('title',)", 'object_name': 'MetadataType'}, + 'default': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'lookup': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '48'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '48'}), + 'validation': ('django.db.models.fields.CharField', [], {'max_length': '64'}) + } + } + + complete_apps = ['metadata'] \ No newline at end of file diff --git a/mayan/apps/metadata/views.py b/mayan/apps/metadata/views.py index c0a1d7a855..4ad1c591be 100644 --- a/mayan/apps/metadata/views.py +++ b/mayan/apps/metadata/views.py @@ -156,10 +156,13 @@ def metadata_add(request, document_id=None, document_id_list=None): metadata_type = form.cleaned_data['metadata_type'] for document in documents: try: - document_metadata, created = DocumentMetadata.objects.get_or_create(document=document, metadata_type=metadata_type, defaults={'value': u''}) + document_metadata, created = DocumentMetadata.objects.get_or_create(document=document, metadata_type=metadata_type.metadata_type, defaults={'value': u''}) except Exception as exception: - messages.error(request, _(u'Error adding metadata type "%(metadata_type)s" to document: %(document)s; %(exception)s') % { - 'metadata_type': metadata_type, 'document': document, 'exception': ', '.join(exception.messages)}) + if getattr(settings, 'DEBUG', False): + raise + else: + messages.error(request, _(u'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))}) else: if created: messages.success(request, _(u'Metadata type: %(metadata_type)s successfully added to document %(document)s.') % { diff --git a/requirements/common.txt b/requirements/common.txt index 8d141f5139..f57aab2684 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -23,6 +23,7 @@ pdfminer==20110227 psutil==2.1.3 pycountry==1.8 pytz==2014.4 +python-dateutil==2.4.0 python-gnupg==0.3.6 python-hkp==0.1.3 python-magic==0.4.6