Merge branch 'new_metadata'

Conflicts:
	requirements/development.txt
	requirements/production.txt
	settings.py
This commit is contained in:
Roberto Rosario
2011-05-21 00:13:38 -04:00
186 changed files with 2614 additions and 21837 deletions

View File

@@ -11,22 +11,16 @@ from tags.widgets import get_tags_inline_widget_simple
from documents.models import Document, DocumentPage, DocumentPageTransformation
from documents.staging import StagingFile
from documents.conf.settings import ENABLE_SINGLE_DOCUMENT_UPLOAD
PERMISSION_DOCUMENT_CREATE = 'document_create'
PERMISSION_DOCUMENT_PROPERTIES_EDIT = 'document_properties_edit'
PERMISSION_DOCUMENT_EDIT = 'document_edit'
PERMISSION_DOCUMENT_METADATA_EDIT = 'document_metadata_edit'
PERMISSION_DOCUMENT_VIEW = 'document_view'
PERMISSION_DOCUMENT_DELETE = 'document_delete'
PERMISSION_DOCUMENT_DOWNLOAD = 'document_download'
PERMISSION_DOCUMENT_TRANSFORM = 'document_transform'
PERMISSION_DOCUMENT_TOOLS = 'document_tools'
from documents.literals import PERMISSION_DOCUMENT_CREATE, \
PERMISSION_DOCUMENT_PROPERTIES_EDIT, PERMISSION_DOCUMENT_VIEW, \
PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_DOWNLOAD, \
PERMISSION_DOCUMENT_TRANSFORM, PERMISSION_DOCUMENT_TOOLS, \
PERMISSION_DOCUMENT_EDIT
register_permissions('documents', [
{'name': PERMISSION_DOCUMENT_CREATE, 'label': _(u'Create document')},
{'name': PERMISSION_DOCUMENT_PROPERTIES_EDIT, 'label': _(u'Edit document properties')},
{'name': PERMISSION_DOCUMENT_EDIT, 'label': _(u'Edit document')},
{'name': PERMISSION_DOCUMENT_METADATA_EDIT, 'label': _(u'Edit document metadata')},
{'name': PERMISSION_DOCUMENT_VIEW, 'label': _(u'View document')},
{'name': PERMISSION_DOCUMENT_DELETE, 'label': _(u'Delete document')},
{'name': PERMISSION_DOCUMENT_DOWNLOAD, 'label': _(u'Download document')},
@@ -39,13 +33,11 @@ document_list_recent = {'text': _(u'recent documents list'), 'view': 'document_l
document_create = {'text': _(u'upload a new document'), 'view': 'document_create', 'famfam': 'page_add', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_CREATE]}}
document_create_multiple = {'text': _(u'upload multiple new documents'), 'view': 'document_create_multiple', 'famfam': 'page_add', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_CREATE]}}
document_create_sibling = {'text': _(u'upload new document using same metadata'), 'view': 'document_create_sibling', 'args': 'object.id', 'famfam': 'page_copy', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_CREATE]}}
document_view = {'text': _(u'details (advanced)'), 'view': 'document_view', 'args': 'object.id', 'famfam': 'page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_view_simple = {'text': _(u'details (simple)'), 'view': 'document_view_simple', 'args': 'object.id', 'famfam': 'page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_view_advanced = {'text': _(u'details (advanced)'), 'view': 'document_view_advanced', 'args': 'object.id', 'famfam': 'page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_delete = {'text': _(u'delete'), 'view': 'document_delete', 'args': 'object.id', 'famfam': 'page_delete', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_DELETE]}}
document_multiple_delete = {'text': _(u'delete'), 'view': 'document_multiple_delete', 'famfam': 'page_delete', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_DELETE]}}
document_edit = {'text': _(u'edit'), 'view': 'document_edit', 'args': 'object.id', 'famfam': 'page_edit', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_PROPERTIES_EDIT]}}
document_edit_metadata = {'text': _(u'edit metadata'), 'view': 'document_edit_metadata', 'args': 'object.id', 'famfam': 'page_edit', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_METADATA_EDIT]}}
document_multiple_edit_metadata = {'text': _(u'edit metadata'), 'view': 'document_multiple_edit_metadata', 'famfam': 'page_edit', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_METADATA_EDIT]}}
document_preview = {'text': _(u'preview'), 'class': 'fancybox', 'view': 'document_preview', 'args': 'object.id', 'famfam': 'magnifier', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_download = {'text': _(u'download'), 'view': 'document_download', 'args': 'object.id', 'famfam': 'page_save', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_DOWNLOAD]}}
document_find_duplicates = {'text': _(u'find duplicates'), 'view': 'document_find_duplicates', 'args': 'object.id', 'famfam': 'page_refresh', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
@@ -62,9 +54,9 @@ document_page_transformation_page_view = {'text': _(u'page details'), 'class': '
document_page_transformation_page_edit = {'text': _(u'edit page'), 'class': 'no-parent-history', 'view': 'document_page_edit', 'args': 'object.document_page.id', 'famfam': 'page_white', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_EDIT]}}
document_page_transformation_page_transformation_list = {'text': _(u'page transformations'), 'class': 'no-parent-history', 'view': 'document_page_transformation_list', 'args': 'object.document_page.id', 'famfam': 'pencil_go', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_TRANSFORM]}}
document_page_view = {'text': _(u'page image'), 'class': 'no-parent-history', 'view': 'document_page_view', 'args': 'object.id', 'famfam': 'page_white', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_page_text = {'text': _(u'page text'), 'class': 'no-parent-history', 'view': 'document_page_text', 'args': 'object.id', 'famfam': 'page_white', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_page_edit = {'text': _(u'edit page text'), 'class': 'no-parent-history', 'view': 'document_page_edit', 'args': 'object.id', 'famfam': 'page_white', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_EDIT]}}
document_page_view = {'text': _(u'page image'), 'class': 'no-parent-history', 'view': 'document_page_view', 'args': 'object.id', 'famfam': 'page_white_picture', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_page_text = {'text': _(u'page text'), 'class': 'no-parent-history', 'view': 'document_page_text', 'args': 'object.id', 'famfam': 'page_white_text', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_page_edit = {'text': _(u'edit page text'), 'class': 'no-parent-history', 'view': 'document_page_edit', 'args': 'object.id', 'famfam': 'page_white_edit', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_EDIT]}}
document_page_navigation_next = {'text': _(u'next page'), 'class': 'no-parent-history', 'view': 'document_page_navigation_next', 'args': 'object.id', 'famfam': 'resultset_next', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_page_navigation_previous = {'text': _(u'previous page'), 'class': 'no-parent-history', 'view': 'document_page_navigation_previous', 'args': 'object.id', 'famfam': 'resultset_previous', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
document_page_navigation_first = {'text': _(u'first page'), 'class': 'no-parent-history', 'view': 'document_page_navigation_first', 'args': 'object.id', 'famfam': 'resultset_first', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
@@ -76,22 +68,18 @@ document_page_rotate_left = {'text': _(u'rotate left'), 'class': 'no-parent-hist
document_missing_list = {'text': _(u'Find missing document files'), 'view': 'document_missing_list', 'famfam': 'folder_page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
metadata_group_link = {'text': _(u'group actions'), 'view': 'metadatagroup_view', 'famfam': 'page_go', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
metadata_group_back_to_document = {'text': _(u'return to document'), 'view': 'document_view_simple', 'args': 'ref_object.id', 'famfam': 'page', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}}
metadata_group_create_sibling = {'text': _(u'upload new document using same metadata'), 'view': 'document_create_sibling', 'args': 'ref_object.id', 'famfam': 'page_copy', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_CREATE]}}
staging_file_preview = {'text': _(u'preview'), 'class': 'fancybox-noscaling', 'view': 'staging_file_preview', 'args': 'object.id', 'famfam': 'drive_magnify'}
staging_file_delete = {'text': _(u'delete'), 'view': 'staging_file_delete', 'args': 'object.id', 'famfam': 'drive_delete'}
register_links(Document, [document_view_simple, document_view, document_edit, document_edit_metadata, document_print, document_delete, document_download, document_find_duplicates, document_clear_transformations])
register_links(Document, [document_view_simple, document_view_advanced, document_edit, document_print, document_delete, document_download, document_find_duplicates, document_clear_transformations])
register_links(Document, [document_create_sibling], menu_name='sidebar')
register_multi_item_links(['metadatagroup_view', 'document_list', 'document_list_recent'], [document_multiple_clear_transformations, document_multiple_edit_metadata, document_multiple_delete])
register_multi_item_links(['document_group_view', 'document_list', 'document_list_recent'], [document_multiple_clear_transformations, document_multiple_delete])
if ENABLE_SINGLE_DOCUMENT_UPLOAD:
register_links(['document_list_recent', 'document_list', 'document_create', 'document_create_multiple', 'upload_document_with_type', 'upload_multiple_documents_with_type'], [document_list_recent, document_list, document_create, document_create_multiple], menu_name='sidebar')
register_links(['document_list_recent', 'document_list', 'document_create', 'document_create_multiple', 'upload_document', 'upload_document_multiple'], [document_list_recent, document_list, document_create, document_create_multiple], menu_name='sidebar')
else:
register_links(['document_list_recent', 'document_list', 'document_create', 'document_create_multiple', 'upload_document_with_type', 'upload_multiple_documents_with_type'], [document_list_recent, document_list, document_create_multiple], menu_name='sidebar')
register_links(['document_list_recent', 'document_list', 'document_create', 'document_create_multiple', 'upload_document', 'upload_document_multiple'], [document_list_recent, document_list, document_create_multiple], menu_name='sidebar')
register_links(DocumentPage, [
document_page_transformation_list, document_page_view,
@@ -113,8 +101,6 @@ register_links(['document_page_transformation_edit', 'document_page_transformati
register_links(StagingFile, [staging_file_preview, staging_file_delete])
register_links(['metadatagroup_view'], [metadata_group_back_to_document, metadata_group_create_sibling], menu_name='sidebar')
register_diagnostic('documents', _(u'Documents'), document_missing_list)
register_tool(document_find_all_duplicates, namespace='documents', title=_(u'documents'))

View File

@@ -1,29 +1,10 @@
from django.contrib import admin
from documents.models import MetadataType, DocumentType, Document, \
DocumentTypeMetadataType, DocumentMetadata, DocumentTypeFilename, \
MetadataIndex, DocumentPage, MetadataGroup, \
MetadataGroupItem, DocumentPageTransformation, RecentDocument
from metadata.admin import DocumentMetadataInline
from filesystem_serving.admin import DocumentMetadataIndexInline
class MetadataTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'title', 'default', 'lookup')
class MetadataIndexInline(admin.StackedInline):
model = MetadataIndex
extra = 1
classes = ('collapse-open',)
allow_add = True
class DocumentTypeMetadataTypeInline(admin.StackedInline):
model = DocumentTypeMetadataType
extra = 1
classes = ('collapse-open',)
allow_add = True
from documents.models import DocumentType, Document, \
DocumentTypeFilename, DocumentPage, \
DocumentPageTransformation, RecentDocument
class DocumentTypeFilenameInline(admin.StackedInline):
@@ -35,18 +16,10 @@ class DocumentTypeFilenameInline(admin.StackedInline):
class DocumentTypeAdmin(admin.ModelAdmin):
inlines = [
DocumentTypeFilenameInline, DocumentTypeMetadataTypeInline,
MetadataIndexInline
DocumentTypeFilenameInline
]
class DocumentMetadataInline(admin.StackedInline):
model = DocumentMetadata
extra = 0
classes = ('collapse-open',)
allow_add = False
class DocumentPageTransformationAdmin(admin.ModelAdmin):
model = DocumentPageTransformation
@@ -60,24 +33,11 @@ class DocumentPageInline(admin.StackedInline):
class DocumentAdmin(admin.ModelAdmin):
inlines = [
DocumentMetadataInline, DocumentMetadataIndexInline,
DocumentPageInline
DocumentMetadataInline, DocumentPageInline
]
list_display = ('uuid', 'file_filename', 'file_extension')
class MetadataGroupItemInline(admin.StackedInline):
model = MetadataGroupItem
extra = 1
classes = ('collapse-open',)
allow_add = True
class MetadataGroupAdmin(admin.ModelAdmin):
inlines = [MetadataGroupItemInline]
filter_horizontal = ['document_type']
class RecentDocumentAdmin(admin.ModelAdmin):
model = RecentDocument
list_display = ('user', 'document', 'datetime_accessed')
@@ -86,10 +46,8 @@ class RecentDocumentAdmin(admin.ModelAdmin):
date_hierarchy = 'datetime_accessed'
admin.site.register(MetadataType, MetadataTypeAdmin)
admin.site.register(DocumentType, DocumentTypeAdmin)
admin.site.register(Document, DocumentAdmin)
admin.site.register(MetadataGroup, MetadataGroupAdmin)
admin.site.register(DocumentPageTransformation,
DocumentPageTransformationAdmin)
admin.site.register(RecentDocument, RecentDocumentAdmin)

View File

@@ -1,13 +1,10 @@
"""Configuration options for the documents app"""
import datetime
import hashlib
import uuid
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from common.utils import proper_name
from storage.backends.filebasedstorage import FileBasedStorage
from smart_settings.api import register_settings
@@ -21,30 +18,14 @@ def default_uuid():
"""unicode(uuid.uuid4())"""
return unicode(uuid.uuid4())
default_available_functions = {
'current_date': datetime.datetime.now().date,
}
default_available_models = {
'User': User
}
available_transformations = {
'rotate': {'label': _(u'Rotate [degrees]'), 'arguments': [{'name': 'degrees'}]}
}
available_indexing_functions = {
'proper_name': proper_name
}
register_settings(
namespace=u'documents',
module=u'documents.conf.settings',
settings=[
# Definition
{'name': u'AVAILABLE_FUNCTIONS', 'global_name': u'DOCUMENTS_METADATA_AVAILABLE_FUNCTIONS', 'default': default_available_functions},
{'name': u'AVAILABLE_MODELS', 'global_name': u'DOCUMENTS_METADATA_AVAILABLE_MODELS', 'default': default_available_models},
{'name': u'AVAILABLE_INDEXING_FUNCTIONS', 'global_name': u'DOCUMENTS_INDEXING_AVAILABLE_FUNCTIONS', 'default': available_indexing_functions},
# Upload
{'name': u'USE_STAGING_DIRECTORY', 'global_name': u'DOCUMENTS_USE_STAGING_DIRECTORY', 'default': False},
{'name': u'STAGING_DIRECTORY', 'global_name': u'DOCUMENTS_STAGING_DIRECTORY', 'default': u'/tmp/mayan/staging', 'exists': True},
@@ -72,7 +53,5 @@ register_settings(
{'name': u'ZOOM_MAX_LEVEL', 'global_name': u'DOCUMENTS_ZOOM_MAX_LEVEL', 'default': 200, 'description': _(u'Maximum amount in percent (%) to allow user to zoom in a document page interactively.')},
{'name': u'ZOOM_MIN_LEVEL', 'global_name': u'DOCUMENTS_ZOOM_MIN_LEVEL', 'default': 50, 'description': _(u'Minimum amount in percent (%) to allow user to zoom out a document page interactively.')},
{'name': u'ROTATION_STEP', 'global_name': u'DOCUMENTS_ROTATION_STEP', 'default': 90, 'description': _(u'Amount in degrees to rotate a document page per user interaction.')},
#Groups
{'name': u'GROUP_SHOW_EMPTY', 'global_name': u'DOCUMENTS_GROUP_SHOW_EMPTY', 'default': True},
]
)

View File

@@ -2,25 +2,22 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.http import HttpResponseRedirect
from django.utils.http import urlencode
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
from django.forms.formsets import formset_factory
from django.template.defaultfilters import capfirst
from django.conf import settings
from tags.widgets import get_tags_inline_widget
from common.wizard import BoundFormWizard
from common.forms import DetailForm
from common.literals import PAGE_SIZE_CHOICES, PAGE_ORIENTATION_CHOICES
from common.conf.settings import DEFAULT_PAPER_SIZE
from common.conf.settings import DEFAULT_PAGE_ORIENTATION
from common.utils import urlquote
from metadata.models import MetadataSet, MetadataType
from metadata.forms import MetadataFormSet
from documents.staging import StagingFile
from documents.models import Document, DocumentType, DocumentTypeMetadataType, \
from documents.models import Document, DocumentType, \
DocumentPage, DocumentPageTransformation
from documents.conf.settings import AVAILABLE_FUNCTIONS
from documents.conf.settings import AVAILABLE_MODELS
class DocumentPageTransformationForm(forms.ModelForm):
@@ -117,7 +114,7 @@ class ImageWidget(forms.widgets.Widget):
for page in value.documentpage_set.all():
output.append(
u'''<div style="display: inline-block; margin: 5px 10px 0px 10px;">
u'''<div style="display: inline-block; margin: 5px 10px 10px 10px;">
<div class="tc">%(page_string)s %(page)s</div>
<div class="tc" style="border: 1px solid black; margin: 5px 0px 5px 0px;">
<a rel="page_gallery" class="fancybox-noscaling" href="%(view_url)s?page=%(page)d">
@@ -154,7 +151,8 @@ class DocumentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DocumentForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
if 'document_type' in kwargs['initial']:
document_type = kwargs['initial'].get('document_type', None)
if document_type:
if 'document_type' in self.fields:
#To allow merging with DocumentForm_edit
self.fields['document_type'].widget = forms.HiddenInput()
@@ -167,7 +165,7 @@ class DocumentForm(forms.ModelForm):
class Meta:
model = Document
exclude = ('description', 'tags')
exclude = ('description', 'tags', 'document_type')
new_filename = forms.CharField(
label=_('New document filename'), required=False
@@ -228,7 +226,8 @@ class StagingDocumentForm(forms.Form):
pass
if 'initial' in kwargs:
if 'document_type' in kwargs['initial']:
document_type = kwargs['initial'].get('document_type', None)
if document_type:
filenames_qs = kwargs['initial']['document_type'].documenttypefilename_set.filter(enabled=True)
if filenames_qs.count() > 0:
self.fields['document_type_available_filenames'] = forms.ModelChoiceField(
@@ -243,71 +242,34 @@ class StagingDocumentForm(forms.Form):
class DocumentTypeSelectForm(forms.Form):
document_type = forms.ModelChoiceField(queryset=DocumentType.objects.all())
class MetadataForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MetadataForm, self).__init__(*args, **kwargs)
#Set form fields initial values
if 'initial' in kwargs:
self.metadata_type = kwargs['initial'].pop('metadata_type', None)
self.document_type = kwargs['initial'].pop('document_type', None)
required = self.document_type.documenttypemetadatatype_set.get(metadata_type=self.metadata_type).required
required_string = u''
if required:
self.fields['value'].required = True
required_string = ' (%s)' % ugettext(u'required')
else:
#TODO: FIXME: not working correctly
self.fields['value'].required = False
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.id
if self.metadata_type.default:
try:
self.fields['value'].initial = eval(self.metadata_type.default, AVAILABLE_FUNCTIONS)
except Exception, err:
self.fields['value'].initial = err
if self.metadata_type.lookup:
try:
choices = eval(self.metadata_type.lookup, AVAILABLE_MODELS)
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, err:
self.fields['value'].initial = err
self.fields['value'].widget = forms.TextInput(attrs={'readonly': 'readonly'})
id = forms.CharField(label=_(u'id'), widget=forms.HiddenInput)
name = forms.CharField(label=_(u'Name'),
required=False, widget=forms.TextInput(attrs={'readonly': 'readonly'}))
value = forms.CharField(label=_(u'Value'), required=False)
MetadataFormSet = formset_factory(MetadataForm, extra=0)
document_type = forms.ModelChoiceField(queryset=DocumentType.objects.all(), label=(u'Document type'), required=False)
class DocumentCreateWizard(BoundFormWizard):
def generate_metadata_initial_values(self):
initial = []
for item in DocumentTypeMetadataType.objects.filter(document_type=self.document_type):
for metadata_type in self.metadata_types:
initial.append({
'metadata_type': item.metadata_type,
'document_type': self.document_type,
'metadata_type': metadata_type,
})
for metadata_set in self.metadata_sets:
for metadata_set_item in metadata_set.metadatasetitem_set.all():
data = {
'metadata_type': metadata_set_item.metadata_type,
}
if data not in initial:
initial.append(data)
return initial
def __init__(self, *args, **kwargs):
self.urldata = []
self.query_dict = {}
self.multiple = kwargs.pop('multiple', True)
self.step_titles = kwargs.pop('step_titles', [
_(u'step 1 of 2: Document type'),
_(u'step 2 of 2: Document metadata'),
_(u'step 1 of 3: Document type'),
_(u'step 2 of 3: Metadata selection'),
_(u'step 3 of 3: Document metadata'),
])
self.document_type = kwargs.pop('document_type', None)
@@ -328,104 +290,35 @@ class DocumentCreateWizard(BoundFormWizard):
def process_step(self, request, form, step):
if isinstance(form, DocumentTypeSelectForm):
self.document_type = form.cleaned_data['document_type']
self.initial = {1: self.generate_metadata_initial_values()}
if isinstance(form, MetadataSelectionForm):
self.metadata_sets = form.cleaned_data['metadata_sets']
self.metadata_types = form.cleaned_data['metadata_types']
initial_data = self.generate_metadata_initial_values()
self.initial = {2: initial_data}
if not initial_data:
# If there is no metadata selected end wizard
self.form_list = [DocumentTypeSelectForm, MetadataSelectionForm]
if isinstance(form, MetadataFormSet):
for identifier, metadata in enumerate(form.cleaned_data):
if metadata['value']:
self.urldata.append(('metadata%s_id' % identifier, metadata['id']))
self.urldata.append(('metadata%s_value' % identifier, metadata['value']))
self.query_dict['metadata%s_id' % identifier] = metadata['id']
self.query_dict['metadata%s_value' % identifier] = metadata['value']
def get_template(self, step):
return 'generic_wizard.html'
def done(self, request, form_list):
if self.multiple:
view = 'upload_multiple_documents_with_type'
view = 'upload_document_multiple'
else:
view = 'upload_document_with_type'
view = 'upload_document'
url = reverse(view, args=[self.document_type.id])
return HttpResponseRedirect('%s?%s' % (url, urlencode(self.urldata)))
if self.document_type:
self.query_dict['document_type_id'] = self.document_type.pk
class MetaDataImageWidget(forms.widgets.Widget):
def render(self, name, value, attrs=None):
output = []
if value['links']:
output.append(u'<div class="group navform wat-cf">')
for link in value['links']:
output.append(u'''
<button class="button" type="submit" name="action" value="%(action)s">
<span class="famfam active famfam-%(famfam)s"></span>%(text)s
</button>
''' % {
'famfam': link.get('famfam', u'link'),
'text': capfirst(link['text']),
'action': reverse('metadatagroup_view', args=[value['current_document'].pk, value['group'].pk])
})
output.append(u'</div>')
output.append(u'<div style="white-space:nowrap; overflow: auto;">')
for document in value['group_data']:
tags_template = get_tags_inline_widget(document)
output.append(
u'''<div style="display: inline-block; margin: 10px; %(current)s">
<div class="tc">%(document_name)s</div>
<div class="tc">%(page_string)s: %(document_pages)d</div>
%(tags_template)s
<div class="tc">
<a rel="group_%(group_id)d_documents_gallery" class="fancybox-noscaling" href="%(view_url)s">
<img class="lazy-load" style="border: 1px solid black; margin: 10px;" src="%(media_url)s/images/ajax-loader.gif" data-href="%(img)s" alt="%(string)s" />
<noscript>
<img style="border: 1px solid black; margin: 10px;" src="%(img)s" alt="%(string)s" />
</noscript>
</a>
</div>
<div class="tc">
<a href="%(url)s"><span class="famfam active famfam-page_go"></span>%(details_string)s</a>
</div>
</div>''' % {
'url': reverse('document_view_simple', args=[document.pk]),
'img': reverse('document_preview_multipage', args=[document.pk]),
'current': u'border: 5px solid black; padding: 3px;' if value['current_document'] == document else u'',
'view_url': reverse('document_display', args=[document.pk]),
'document_pages': document.documentpage_set.count(),
'page_string': ugettext(u'Pages'),
'details_string': ugettext(u'Select'),
'group_id': value['group'].pk,
'document_name': document,
'media_url': settings.MEDIA_URL,
'tags_template': tags_template if tags_template else u'',
'string': _(u'group document'),
})
output.append(u'</div>')
output.append(
u'<br /><span class="famfam active famfam-magnifier"></span>%s' %
ugettext(u'Click on the image for full size view of the first page.'))
return mark_safe(u''.join(output))
class MetaDataGroupForm(forms.Form):
def __init__(self, *args, **kwargs):
groups = kwargs.pop('groups', None)
links = kwargs.pop('links', None)
current_document = kwargs.pop('current_document', None)
super(MetaDataGroupForm, self).__init__(*args, **kwargs)
for group, data in groups.items():
self.fields['preview-%s' % group] = forms.CharField(
widget=MetaDataImageWidget(),
label=u'%s (%d)' % (unicode(group), len(data)),
required=False,
initial={
'group': group,
'group_data': data,
'current_document': current_document,
'links': links
}
)
url = urlquote(reverse(view), self.query_dict)
return HttpResponseRedirect(url)
class PrintForm(forms.Form):
@@ -434,3 +327,19 @@ class PrintForm(forms.Form):
custom_page_height = forms.CharField(label=_(u'Custom page height'), required=False)
page_orientation = forms.ChoiceField(choices=PAGE_ORIENTATION_CHOICES, initial=DEFAULT_PAGE_ORIENTATION, label=_(u'Page orientation'), required=True)
page_range = forms.CharField(label=_(u'Page range'), required=False)
class MetadataSelectionForm(forms.Form):
metadata_sets = forms.ModelMultipleChoiceField(
queryset=MetadataSet.objects.all(),
label=_(u'Metadata sets'),
required=False,
widget=forms.widgets.SelectMultiple(attrs={'size': 10, 'class': 'choice_form'})
)
metadata_types = forms.ModelMultipleChoiceField(
queryset=MetadataType.objects.all(),
label=_(u'Metadata'),
required=False,
widget=forms.widgets.SelectMultiple(attrs={'size': 10, 'class': 'choice_form'})
)

View File

@@ -2,3 +2,12 @@ PICTURE_ERROR_SMALL = u'picture_error.png'
PICTURE_ERROR_MEDIUM = u'1297211435_error.png'
PICTURE_UNKNOWN_SMALL = u'1299549572_unknown2.png'
PICTURE_UNKNOWN_MEDIUM = u'1299549805_unknown.png'
PERMISSION_DOCUMENT_CREATE = 'document_create'
PERMISSION_DOCUMENT_PROPERTIES_EDIT = 'document_properties_edit'
PERMISSION_DOCUMENT_EDIT = 'document_edit'
PERMISSION_DOCUMENT_VIEW = 'document_view'
PERMISSION_DOCUMENT_DELETE = 'document_delete'
PERMISSION_DOCUMENT_DOWNLOAD = 'document_download'
PERMISSION_DOCUMENT_TRANSFORM = 'document_transform'
PERMISSION_DOCUMENT_TOOLS = 'document_tools'

View File

@@ -1,94 +0,0 @@
'''Metadata handling commonalities
'''
from urllib import unquote_plus
from django.shortcuts import get_object_or_404
from django.core.exceptions import ObjectDoesNotExist
from documents.models import DocumentMetadata, MetadataType
def decode_metadata_from_url(url_dict):
'''Parses a URL query string to a list of metadata
'''
metadata_dict = {
'id': {},
'value': {}
}
metadata_list = []
#Match out of order metadata_type ids with metadata values from request
for key, value in url_dict.items():
if 'metadata' in key:
index, element = key[8:].split('_')
metadata_dict[element][index] = value
#Convert the nested dictionary into a list of id+values dictionaries
for order, identifier in metadata_dict['id'].items():
if order in metadata_dict['value'].keys():
metadata_list.append({
'id': identifier,
'value': metadata_dict['value'][order]
})
return metadata_list
def save_metadata_list(metadata_list, document):
'''Takes a list of metadata values and associates a document to it
'''
for item in metadata_list:
if item['value']:
save_metadata(item, document)
else:
#If there is no metadata value, delete the metadata entry
#completely from the document
try:
metadata_type = MetadataType.objects.get(id=item['id'])
document_metadata = DocumentMetadata.objects.get(
document=document,
metadata_type=metadata_type
)
document_metadata.delete()
except ObjectDoesNotExist:
pass
def save_metadata(metadata_dict, document):
'''save metadata_dict
'''
#Use matched metadata now to create document metadata
document_metadata, created = DocumentMetadata.objects.get_or_create(
document=document,
metadata_type=get_object_or_404(
MetadataType,
pk=metadata_dict['id']
),
)
#Handle 'plus sign as space' in the url
#unquote_plus handles utf-8?!?
#http://stackoverflow.com/questions/4382875/handling-iri-in-django
#.decode('utf-8')
document_metadata.value = unquote_plus(metadata_dict['value'])
document_metadata.save()
def metadata_repr(metadata_list):
'''Return a printable representation of a metadata list
'''
return ', '.join(metadata_repr_as_list(metadata_list))
def metadata_repr_as_list(metadata_list):
'''Turn a list of metadata into a list of printable representations
'''
output = []
for metadata_dict in metadata_list:
try:
output.append(u'%s - %s' % (MetadataType.objects.get(
pk=metadata_dict['id']), metadata_dict.get('value', '')))
except:
pass
return output

View File

@@ -4,7 +4,6 @@ import tempfile
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
from django.contrib.auth.models import User
from django.contrib.contenttypes import generic
from django.contrib.comments.models import Comment
@@ -16,9 +15,6 @@ from dynamic_search.api import register
from converter.api import get_page_count
from converter import TRANFORMATION_CHOICES
from documents.conf.settings import AVAILABLE_INDEXING_FUNCTIONS
from documents.conf.settings import AVAILABLE_FUNCTIONS
from documents.conf.settings import AVAILABLE_MODELS
from documents.conf.settings import CHECKSUM_FUNCTION
from documents.conf.settings import UUID_FUNCTION
from documents.conf.settings import STORAGE_BACKEND
@@ -26,6 +22,8 @@ from documents.conf.settings import AVAILABLE_TRANSFORMATIONS
from documents.conf.settings import DEFAULT_TRANSFORMATIONS
from documents.conf.settings import RECENT_COUNT
available_transformations = ([(name, data['label']) for name, data in AVAILABLE_TRANSFORMATIONS.items()])
def get_filename_from_uuid(instance, filename):
filename, extension = os.path.splitext(filename)
@@ -52,14 +50,14 @@ class Document(models.Model):
"""
Defines a single document with it's fields and properties
"""
document_type = models.ForeignKey(DocumentType, verbose_name=_(u'document type'))
document_type = models.ForeignKey(DocumentType, verbose_name=_(u'document type'), null=True, blank=True)
file = models.FileField(upload_to=get_filename_from_uuid, storage=STORAGE_BACKEND(), verbose_name=_(u'file'))
uuid = models.CharField(max_length=48, default=UUID_FUNCTION(), blank=True, editable=False)
file_mimetype = models.CharField(max_length=64, default='', editable=False)
file_mime_encoding = models.CharField(max_length=64, default='', editable=False)
#FAT filename can be up to 255 using LFN
file_filename = models.CharField(max_length=255, default='', editable=False, db_index=True)
file_extension = models.CharField(max_length=16, default='', editable=False, db_index=True)
file_filename = models.CharField(max_length=255, default=u'', editable=False, db_index=True)
file_extension = models.CharField(max_length=16, default=u'', editable=False, db_index=True)
date_added = models.DateTimeField(verbose_name=_(u'added'), auto_now_add=True, db_index=True)
date_updated = models.DateTimeField(verbose_name=_(u'updated'), auto_now=True)
checksum = models.TextField(blank=True, null=True, verbose_name=_(u'checksum'), editable=False)
@@ -188,9 +186,6 @@ class Document(models.Model):
"""
return u', '.join([u'%s - %s' % (metadata.metadata_type, metadata.value) for metadata in self.documentmetadata_set.select_related('metadata_type', 'document').defer('document__document_type', 'document__file', 'document__description', 'document__file_filename', 'document__uuid', 'document__date_added', 'document__date_updated', 'document__file_mimetype', 'document__file_mime_encoding')])
def get_metadata_groups(self, group_obj=None):
return MetadataGroup.objects.get_groups_for(self, group_obj)
def apply_default_transformations(self):
#Only apply default transformations on new documents
if DEFAULT_TRANSFORMATIONS and reduce(lambda x, y: x + y, [page.documentpagetransformation_set.count() for page in self.documentpage_set.all()]) == 0:
@@ -206,80 +201,6 @@ class Document(models.Model):
page_transformation.save()
available_functions_string = (_(u' Available functions: %s') % u','.join([u'%s()' % name for name, function in AVAILABLE_FUNCTIONS.items()])) if AVAILABLE_FUNCTIONS else u''
available_models_string = (_(u' Available models: %s') % u','.join([name for name, model in AVAILABLE_MODELS.items()])) if AVAILABLE_MODELS else u''
class MetadataType(models.Model):
name = models.CharField(max_length=48, verbose_name=_(u'name'), help_text=_(u'Do not use python reserved words.'))
title = models.CharField(max_length=48, verbose_name=_(u'title'), blank=True, null=True)
default = models.CharField(max_length=128, blank=True, null=True,
verbose_name=_(u'default'),
help_text=_(u'Enter a string to be evaluated.%s') % available_functions_string)
lookup = models.CharField(max_length=128, blank=True, null=True,
verbose_name=_(u'lookup'),
help_text=_(u'Enter a string to be evaluated. Example: [user.get_full_name() for user in User.objects.all()].%s') % available_models_string)
#TODO: datatype?
def __unicode__(self):
return self.title if self.title else self.name
class Meta:
verbose_name = _(u'metadata type')
verbose_name_plural = _(u'metadata types')
class DocumentTypeMetadataType(models.Model):
"""
Define the set of metadata that relates to a single document type
"""
document_type = models.ForeignKey(DocumentType, verbose_name=_(u'document type'))
metadata_type = models.ForeignKey(MetadataType, verbose_name=_(u'metadata type'))
required = models.BooleanField(default=True, verbose_name=_(u'required'))
#TODO: override default for this document type
def __unicode__(self):
return unicode(self.metadata_type)
class Meta:
verbose_name = _(u'document type metadata type connector')
verbose_name_plural = _(u'document type metadata type connectors')
available_indexing_functions_string = (_(u' Available functions: %s') % u','.join([u'%s()' % name for name, function in AVAILABLE_INDEXING_FUNCTIONS.items()])) if AVAILABLE_INDEXING_FUNCTIONS else u''
class MetadataIndex(models.Model):
document_type = models.ForeignKey(DocumentType, verbose_name=_(u'document type'))
expression = models.CharField(max_length=128,
verbose_name=_(u'indexing expression'),
help_text=_(u'Enter a python string expression to be evaluated. The slash caracter "/" acts as a directory delimiter.%s') % available_indexing_functions_string)
enabled = models.BooleanField(default=True, verbose_name=_(u'enabled'))
def __unicode__(self):
return unicode(self.expression)
class Meta:
verbose_name = _(u'metadata index')
verbose_name_plural = _(u'metadata indexes')
class DocumentMetadata(models.Model):
"""
Link a document to a specific instance of a metadata type with it's
current value
"""
document = models.ForeignKey(Document, verbose_name=_(u'document'))
metadata_type = models.ForeignKey(MetadataType, verbose_name=_(u'metadata type'))
value = models.TextField(blank=True, null=True, verbose_name=_(u'metadata value'), db_index=True)
def __unicode__(self):
return unicode(self.metadata_type)
class Meta:
verbose_name = _(u'document metadata')
verbose_name_plural = _(u'document metadata')
class DocumentTypeFilename(models.Model):
"""
@@ -335,115 +256,6 @@ class DocumentPage(models.Model):
return ' '.join(transformation_list), warnings
class MetadataGroupManager(models.Manager):
def get_groups_for(self, document, group_obj=None):
errors = []
metadata_groups = {}
metadata_dict = {}
for document_metadata in document.documentmetadata_set.all():
metadata_dict['metadata_%s' % document_metadata.metadata_type.name] = document_metadata.value
if group_obj:
groups_qs = MetadataGroup.objects.filter((Q(document_type=document.document_type) | Q(document_type=None)) & Q(enabled=True) & Q(pk=group_obj.pk))
else:
groups_qs = MetadataGroup.objects.filter((Q(document_type=document.document_type) | Q(document_type=None)) & Q(enabled=True))
for group in groups_qs:
total_query = Q()
for item in group.metadatagroupitem_set.filter(enabled=True):
try:
value_query = Q(**{'value__%s' % item.operator: eval(item.expression, metadata_dict)})
if item.negated:
query = (Q(metadata_type__id=item.metadata_type_id) & ~value_query)
else:
query = (Q(metadata_type__id=item.metadata_type_id) & value_query)
if item.inclusion == INCLUSION_AND:
total_query &= query
elif item.inclusion == INCLUSION_OR:
total_query |= query
except Exception, e:
errors.append(e)
value_query = Q()
query = Q()
if total_query:
document_id_list = DocumentMetadata.objects.filter(total_query).values_list('document', flat=True)
metadata_groups[group] = Document.objects.filter(Q(id__in=document_id_list)).order_by('file_filename') or []
else:
metadata_groups[group] = []
if group_obj:
return metadata_groups[group_obj], errors
return metadata_groups, errors
class MetadataGroup(models.Model):
document_type = models.ManyToManyField(DocumentType, null=True, blank=True,
verbose_name=_(u'document type'), help_text=_(u'If left blank, all document types will be matched.'))
name = models.CharField(max_length=32, verbose_name=_(u'name'))
label = models.CharField(max_length=32, verbose_name=_(u'label'))
enabled = models.BooleanField(default=True, verbose_name=_(u'enabled'))
objects = MetadataGroupManager()
def __unicode__(self):
return self.label if self.label else self.name
class Meta:
verbose_name = _(u'document group')
verbose_name_plural = _(u'document groups')
INCLUSION_AND = u'&'
INCLUSION_OR = u'|'
INCLUSION_CHOICES = (
(INCLUSION_AND, _(u'and')),
(INCLUSION_OR, _(u'or')),
)
OPERATOR_CHOICES = (
(u'exact', _(u'is equal')),
(u'iexact', _(u'is equal (case insensitive)')),
(u'contains', _(u'contains')),
(u'icontains', _(u'contains (case insensitive)')),
(u'in', _(u'is in')),
(u'gt', _(u'is greater than')),
(u'gte', _(u'is greater than or equal')),
(u'lt', _(u'is less than')),
(u'lte', _(u'is less than or equal')),
(u'startswith', _(u'starts with')),
(u'istartswith', _(u'starts with (case insensitive)')),
(u'endswith', _(u'ends with')),
(u'iendswith', _(u'ends with (case insensitive)')),
(u'regex', _(u'is in regular expression')),
(u'iregex', _(u'is in regular expression (case insensitive)')),
)
class MetadataGroupItem(models.Model):
metadata_group = models.ForeignKey(MetadataGroup, verbose_name=_(u'metadata group'))
inclusion = models.CharField(default=INCLUSION_AND, max_length=16, choices=INCLUSION_CHOICES, help_text=_(u'The inclusion is ignored for the first item.'))
metadata_type = models.ForeignKey(MetadataType, verbose_name=_(u'metadata type'), help_text=_(u'This represents the metadata of all other documents.'))
operator = models.CharField(max_length=16, choices=OPERATOR_CHOICES)
expression = models.CharField(max_length=128,
verbose_name=_(u'expression'), help_text=_(u'This expression will be evaluated against the current selected document. The document metadata is available as variables of the same name but with the "metadata_" prefix added their name.'))
negated = models.BooleanField(default=False, verbose_name=_(u'negated'), help_text=_(u'Inverts the logic of the operator.'))
enabled = models.BooleanField(default=True, verbose_name=_(u'enabled'))
def __unicode__(self):
return u'[%s] %s %s %s %s %s' % (u'x' if self.enabled else u' ', self.get_inclusion_display(), self.metadata_type, _(u'not') if self.negated else u'', self.get_operator_display(), self.expression)
class Meta:
verbose_name = _(u'group item')
verbose_name_plural = _(u'group items')
available_transformations = ([(name, data['label']) for name, data in AVAILABLE_TRANSFORMATIONS.items()]) if AVAILABLE_MODELS else []
class DocumentPageTransformation(models.Model):
document_page = models.ForeignKey(DocumentPage, verbose_name=_(u'document page'))
order = models.PositiveIntegerField(default=0, blank=True, null=True, verbose_name=_(u'order'), db_index=True)

View File

@@ -4,6 +4,7 @@ from common.utils import pretty_size, pretty_size_10
from documents.conf.settings import STORAGE_BACKEND
from documents.models import Document, DocumentType, DocumentPage
from django.db.models import Avg, Count, Min, Max
def get_used_size(path, file_list):
@@ -50,9 +51,15 @@ def get_statistics():
except NotImplementedError:
pass
paragraphs.append(
_(u'Document pages in database: %d') % DocumentPage.objects.only('pk',).count(),
paragraphs.extend(
[
_(u'Document pages in database: %d') % DocumentPage.objects.only('pk',).count(),
_(u'Minimum amount of pages per document: %(page_count__min)d') % Document.objects.annotate(page_count=Count('documentpage')).aggregate(Min('page_count')),
_(u'Maximum amount of pages per document: %(page_count__max)d') % Document.objects.annotate(page_count=Count('documentpage')).aggregate(Max('page_count')),
_(u'Average amount of pages per document: %(page_count__avg)f') % Document.objects.annotate(page_count=Count('documentpage')).aggregate(Avg('page_count')),
]
)
#[(day_count['date_added'].strftime('%Y-%m-%d'), day_count['id__count']) for day_count in Document.objects.values('date_added').annotate(Count("id"))]
return {
'title': _(u'Document statistics'),

View File

@@ -14,15 +14,13 @@ urlpatterns = patterns('documents.views',
url(r'^document/list/recent/$', 'document_list_recent', (), 'document_list_recent'),
url(r'^document/create/from/local/single/$', 'document_create', {'multiple': False}, 'document_create'),
url(r'^document/create/from/local/multiple/$', 'document_create', {'multiple': True}, 'document_create_multiple'),
url(r'^document/type/(?P<document_type_id>\d+)/upload/single/$', 'upload_document_with_type', {'multiple': False}, 'upload_document_with_type'),
url(r'^document/type/(?P<document_type_id>\d+)/upload/multiple/$', 'upload_document_with_type', {'multiple': True}, 'upload_multiple_documents_with_type'),
url(r'^document/(?P<document_id>\d+)/$', 'document_view', (), 'document_view'),
url(r'^document/type/upload/single/$', 'upload_document_with_type', {'multiple': False}, 'upload_document'),
url(r'^document/type/upload/multiple/$', 'upload_document_with_type', {'multiple': True}, 'upload_document_multiple'),
url(r'^document/(?P<document_id>\d+)/$', 'document_view_advanced', (), 'document_view_advanced'),
url(r'^document/(?P<document_id>\d+)/simple/$', 'document_view_simple', (), 'document_view_simple'),
url(r'^document/(?P<document_id>\d+)/delete/$', 'document_delete', (), 'document_delete'),
url(r'^document/multiple/delete/$', 'document_multiple_delete', (), 'document_multiple_delete'),
url(r'^document/(?P<document_id>\d+)/edit/$', 'document_edit', (), 'document_edit'),
url(r'^document/(?P<document_id>\d+)/edit/metadata/$', 'document_edit_metadata', (), 'document_edit_metadata'),
url(r'^document/multiple/edit/metadata/$', 'document_multiple_edit_metadata', (), 'document_multiple_edit_metadata'),
url(r'^document/(?P<document_id>\d+)/print/$', 'document_print', (), 'document_print'),
url(r'^document/(?P<document_id>\d+)/hard_copy/$', 'document_hard_copy', (), 'document_hard_copy'),
@@ -36,7 +34,6 @@ urlpatterns = patterns('documents.views',
url(r'^document/(?P<document_id>\d+)/create/siblings/$', 'document_create_sibling', {'multiple': True if ENABLE_SINGLE_DOCUMENT_UPLOAD == False else False}, 'document_create_sibling'),
url(r'^document/(?P<document_id>\d+)/find_duplicates/$', 'document_find_duplicates', (), 'document_find_duplicates'),
url(r'^document/(?P<document_id>\d+)/clear_transformations/$', 'document_clear_transformations', (), 'document_clear_transformations'),
url(r'^document/(?P<document_id>\d+)/group/(?P<metadata_group_id>\d+)/$', 'metadatagroup_view', (), 'metadatagroup_view'),
url(r'^document/multiple/clear_transformations/$', 'document_multiple_clear_transformations', (), 'document_multiple_clear_transformations'),
url(r'^duplicates/$', 'document_find_all_duplicates', (), 'document_find_all_duplicates'),
@@ -63,6 +60,4 @@ urlpatterns = patterns('documents.views',
url(r'^document/page/transformation/(?P<document_page_transformation_id>\d+)/delete/$', 'document_page_transformation_delete', (), 'document_page_transformation_delete'),
url(r'^document/missing/list/$', 'document_missing_list', (), 'document_missing_list'),
url(r'^metadatagroup_action/action/$', 'metadatagroup_action', (), 'metadatagroup_action'),
)

File diff suppressed because it is too large Load Diff