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 %}
+
+ {% 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