diff --git a/apps/common/forms.py b/apps/common/forms.py index 4615a1ee9f..c0d410b210 100644 --- a/apps/common/forms.py +++ b/apps/common/forms.py @@ -1,55 +1,9 @@ from django import forms -from django.utils.safestring import mark_safe -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from django.db import models from common.utils import return_attrib - - -class DetailSelectMultiple(forms.widgets.SelectMultiple): - def __init__(self, queryset=None, *args, **kwargs): - self.queryset = queryset - super(DetailSelectMultiple, self).__init__(*args, **kwargs) - - def render(self, name, value, attrs=None, choices=()): - if value is None: - value = '' - final_attrs = self.build_attrs(attrs, name=name) - css_class = final_attrs.get('class', 'list') - output = u'\n') - - -class PlainWidget(forms.widgets.Widget): - def render(self, name, value, attrs=None): - return mark_safe(u'%s' % value) +from common.widgets import DetailSelectMultiple, PlainWidget class DetailForm(forms.ModelForm): @@ -122,7 +76,7 @@ class FilterForm(forms.Form): self.fields[list_filter['name']] = forms.ModelChoiceField( queryset=list_filter['queryset'], label=label[0].upper() + label[1:], required=False) - + class ChoiceForm(forms.Form): """ diff --git a/apps/common/templates/generic_confirm.html b/apps/common/templates/generic_confirm.html index 9ce1fc0781..6b88ca7f0a 100644 --- a/apps/common/templates/generic_confirm.html +++ b/apps/common/templates/generic_confirm.html @@ -23,7 +23,11 @@ {% if next %} {% endif %} - + +
+ {% trans 'form icon' %} +
+
{% if title %}

{{ title }}

{% else %} @@ -47,6 +51,10 @@ {% endif %}
+ + +
+ diff --git a/apps/common/templates/generic_detail.html b/apps/common/templates/generic_detail.html index e201229699..c5cc9789e8 100644 --- a/apps/common/templates/generic_detail.html +++ b/apps/common/templates/generic_detail.html @@ -10,28 +10,26 @@ {% include subtemplate %} {% endfor %} - - {% for subtemplate in sidebar_subtemplates_dict %} - {% with subtemplate.title as title %} - {% with subtemplate.object_list as object_list %} - {% with subtemplate.extra_columns as extra_columns %} - {% with subtemplate.hide_object as hide_object %} - {% with subtemplate.main_object as main_object %} - {% with subtemplate.hide_link as hide_link %} - {% with subtemplate.hide_header as hide_header %} - {% with subtemplate.hide_columns as hide_columns %} - {% with "true" as side_bar %} - {% include subtemplate.name %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} + + {% for subtemplate in sidebar_subtemplates_list %} + {% with "true" as side_bar %} + {% if subtemplate.form %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} + {% with "true" as read_only %} +
+ {{ rendered_subtemplate }} +
+ {% endwith %} + {% else %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} + {{ rendered_subtemplate }} + {% endif %} + + {% if subtemplate.grid_clear or not subtemplate.grid %} +
+ {% endif %} + {% endwith %} + {% endfor %} {% endblock %} {% block stylesheets %} @@ -45,84 +43,34 @@ {% endblock %} {% block content %} -
- {% if form %} {% with "true" as read_only %} -
+
{% include "generic_form_subtemplate.html" %}
{% if grid_clear or not grid %} -
+
{% endif %} {% endwith %} {% endif %} - - {% for subtemplate in subtemplates %} - {% include subtemplate %} - {% endfor %} - - {% for form in form_list %} - {% with form.submit_method as submit_method %} - {% with form.striptags as striptags %} - {% with form.title as title %} - {% with form.object as object %} - {% with form.object_name as object_name %} - {% with form.form_action as form_action %} - {% with "true" as read_only %} -
- {% with form.form as form %} -
- {% include "generic_form_subtemplate.html" %} + +
+ {% for subtemplate in subtemplates_list %} +
+ {% with "true" as read_only %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} +
+ {{ rendered_subtemplate }} +
+ {% endwith %}
- {% endwith %} -
- {% if form.grid_clear or not form.grid %} + {% if subtemplate.grid_clear or not subtemplate.grid %}
{% endif %} - - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} - - {% for subtemplate in subtemplates_list %} - -
- - {% if subtemplate.form %} - {% with subtemplate.submit_method as submit_method %} - {% with subtemplate.striptags as striptags %} - {% with subtemplate.object as object %} - {% with subtemplate.object_name as object_name %} - {% with subtemplate.form_action as form_action %} - {% with "true" as read_only %} - {% with subtemplate.form as form %} -
- {% include subtemplate.name %} -
- {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% else %} - {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} - {{ rendered_subtemplate }} - {% endif %} -
- {% if subtemplate.grid_clear or not subtemplate.grid %} -
- {% endif %} - {% endfor %} -
+ {% endfor %} +
+ {% endblock %} diff --git a/apps/common/templates/generic_detail_subtemplate.html b/apps/common/templates/generic_detail_subtemplate.html index aae27f1983..7d8129f0bc 100644 --- a/apps/common/templates/generic_detail_subtemplate.html +++ b/apps/common/templates/generic_detail_subtemplate.html @@ -19,49 +19,5 @@
{% endwith %} {% endif %} - - {% for subtemplate in subtemplates %} - {% include subtemplate %} - {% endfor %} - - {% for form in form_list %} - {% with form.submit_method as submit_method %} - {% with form.striptags as striptags %} - {% with form.title as title %} - {% with form.object as object %} - {% with form.object_name as object_name %} - {% with form.form_action as form_action %} - {% with "true" as read_only %} - {% with form.form as form %} - {% include "generic_form_subtemplate.html" %} - {% endwith %} - - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} - - - {% for subtemplate in subtemplates_dict %} - {% with subtemplate.title as title %} - {% with subtemplate.object_list as object_list %} - {% with subtemplate.extra_columns as extra_columns %} - {% with subtemplate.hide_object as hide_object %} - {% with subtemplate.main_object as main_object %} - {% with subtemplate.hide_link as hide_link %} - {% with subtemplate.hide_header as hide_header %} - {% include subtemplate.name %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} {% endblock %} diff --git a/apps/common/templates/generic_form.html b/apps/common/templates/generic_form.html index ba6afd3efb..f78216e987 100644 --- a/apps/common/templates/generic_form.html +++ b/apps/common/templates/generic_form.html @@ -1,116 +1,47 @@ {% extends "base.html" %} +{% load subtemplates_tags %} + {% block title %} :: {% with "true" as striptags %}{% include "calculate_form_title.html" %}{% endwith %}{% endblock %} {% block sidebar %} - {% for subtemplate in sidebar_subtemplates %} -
- {% include subtemplate %} -
- {% endfor %} - {% for subtemplate in sidebar_subtemplates_list %} - {% with subtemplate.title as title %} - {% with subtemplate.object_list as object_list %} - {% with subtemplate.extra_columns as extra_columns %} - {% with subtemplate.hide_object as hide_object %} - {% with subtemplate.main_object as main_object %} - {% with subtemplate.hide_link as hide_link %} - {% with subtemplate.hide_header as hide_header %} - {% with subtemplate.hide_columns as hide_columns %} - {% with "true" as side_bar %} - - {% with subtemplate.submit_method as submit_method %} - {% with subtemplate.striptags as striptags %} - {% with subtemplate.title as title %} - {% with subtemplate.object as object %} - {% with subtemplate.object_name as object_name %} - {% with subtemplate.form_action as form_action %} - {% with subtemplate.submit_label as submit_label %} - {% with subtemplate.form as form %} - - {% with subtemplate.content as content %} - {% with subtemplate.paragraphs as paragraphs %} - - {% include subtemplate.name %} - - {% endwith %} - {% endwith %} - - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - - - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} + {% if subtemplate.form %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} +
+ {{ rendered_subtemplate }} +
+ {% else %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} + {{ rendered_subtemplate }} + {% endif %} + {% if subtemplate.grid_clear or not subtemplate.grid %} + {% endif %} + {% endfor %} {% endblock %} {% block content %} -
{% if form %} -
+
{% include "generic_form_subtemplate.html" %}
- {% if form.grid_clear or not form.grid %} + {% endif %} + +
+ {% for subtemplate in subtemplates_list %} +
+ {% if subtemplate.form %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} +
+ {{ rendered_subtemplate }} +
+ {% else %} + {% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %} + {{ rendered_subtemplate }} + {% endif %} +
+ {% if subtemplate.grid_clear or not subtemplate.grid %}
{% endif %} - {% endif %} - - {% for form in form_list %} - {% with form.submit_method as submit_method %} - {% with form.striptags as striptags %} - {% with form.title as title %} - {% with form.object as object %} - {% with form.object_name as object_name %} - {% with form.submit_label as submit_label %} - {% with form.form_action as form_action %} -
- {% with form.form as form %} - {% include "generic_form_subtemplate.html" %} - {% endwith %} -
- {% if form.grid_clear or not form.grid %} -
- {% endif %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} + {% endfor %}
- - {% for subtemplate in subtemplates_dict %} - {% with subtemplate.title as title %} - {% with subtemplate.object_list as object_list %} - {% with subtemplate.extra_columns as extra_columns %} - {% with subtemplate.hide_object as hide_object %} - {% with subtemplate.main_object as main_object %} - {% with subtemplate.hide_link as hide_link %} - {% with subtemplate.hide_header as hide_header %} - {% include subtemplate.name %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} - {% endblock %} diff --git a/apps/common/templates/generic_list.html b/apps/common/templates/generic_list.html index 8f0c35f702..064a9752ed 100644 --- a/apps/common/templates/generic_list.html +++ b/apps/common/templates/generic_list.html @@ -1,87 +1,19 @@ {% extends "base.html" %} {% load i18n %} {% load navigation_tags %} -{% block title %} :: {% blocktrans %}List of {{ title }}{% endblocktrans %}{% endblock %} +{% block title %} :: {% blocktrans with title|striptags as stripped_title %}List of {{ stripped_title }}{% endblocktrans %}{% endblock %} {#{% block secondary_links %}{{ secondary_links|safe }}{% endblock %}#} - {% block sidebar %} {% for subtemplate in sidebar_subtemplates %}
{% include subtemplate %}
{% endfor %} - - {% for subtemplate in sidebar_subtemplates_list %} - {% with subtemplate.title as title %} - {% with subtemplate.object_list as object_list %} - {% with subtemplate.extra_columns as extra_columns %} - {% with subtemplate.hide_object as hide_object %} - {% with subtemplate.main_object as main_object %} - {% with subtemplate.hide_link as hide_link %} - {% with subtemplate.hide_header as hide_header %} - {% with subtemplate.hide_columns as hide_columns %} - {% with "true" as side_bar %} - - {% with subtemplate.submit_method as submit_method %} - {% with subtemplate.striptags as striptags %} - {% with subtemplate.title as title %} - {% with subtemplate.object as object %} - {% with subtemplate.object_name as object_name %} - {% with subtemplate.form_action as form_action %} - {% with subtemplate.form as form %} - - {% with subtemplate.content as content %} - {% with subtemplate.paragraphs as paragraphs %} - - {% include subtemplate.name %} - - {% endwith %} - {% endwith %} - - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - - - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} {% endblock %} {% block content %} {% include "generic_list_subtemplate.html" %} - {% for subtemplate in subtemplates_dict %} - {% with subtemplate.title as title %} - {% with subtemplate.object_list as object_list %} - {% with subtemplate.extra_columns as extra_columns %} - {% with subtemplate.hide_object as hide_object %} - {% with subtemplate.main_object as main_object %} - {% with subtemplate.hide_link as hide_link %} - {% with subtemplate.hide_header as hide_header %} - {% with subtemplate.multi_select as multi_select %} - {% include subtemplate.name %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endfor %} - {% endblock %} diff --git a/apps/common/templates/generic_list_subtemplate.html b/apps/common/templates/generic_list_subtemplate.html index d87b6d0067..b92eb13b04 100644 --- a/apps/common/templates/generic_list_subtemplate.html +++ b/apps/common/templates/generic_list_subtemplate.html @@ -85,7 +85,7 @@ {% endif %} {% empty %} - {% blocktrans %}There are no {{ title }}{% endblocktrans %} + {% blocktrans with title|striptags as stripped_title %}There are no {{ stripped_title }}{% endblocktrans %} {% endfor %} diff --git a/apps/common/utils.py b/apps/common/utils.py index 32db044fc0..1881fe8cf7 100644 --- a/apps/common/utils.py +++ b/apps/common/utils.py @@ -317,7 +317,7 @@ def parse_range(astr): x = part.split(u'-') result.update(range(int(x[0]), int(x[-1]) + 1)) return sorted(result) - + def generate_choices_w_labels(choices, display_object_type=True): results = [] diff --git a/apps/common/views.py b/apps/common/views.py index 2c6c32c7b0..c6b236665e 100644 --- a/apps/common/views.py +++ b/apps/common/views.py @@ -1,7 +1,12 @@ from django.shortcuts import redirect from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.contrib import messages +from django.contrib.contenttypes.models import ContentType + +from common.forms import ChoiceForm def password_change_done(request): @@ -15,8 +20,8 @@ def password_change_done(request): def multi_object_action_view(request): """ - Proxy view called first when usuing a multi object action, which - then redirects to the appropiate specialized view + Proxy view called first when usuing a multi object action, which + then redirects to the appropiate specialized view """ action = request.GET.get('action', None) @@ -31,3 +36,96 @@ def multi_object_action_view(request): return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) return HttpResponseRedirect('%s?id_list=%s' % (action, id_list)) + + +def get_obj_from_content_type_string(string): + model, pk = string.split(u',') + ct = ContentType.objects.get(model=model) + return ct.get_object_for_this_type(pk=pk) + + +def assign_remove(request, left_list, right_list, add_method, remove_method, left_list_title, right_list_title, obj=None, object_name=None, decode_content_type=False): + left_list_name = u'left_list' + right_list_name = u'right_list' + + if request.method == 'POST': + if u'%s-submit' % left_list_name in request.POST.keys(): + unselected_list = ChoiceForm(request.POST, + prefix=left_list_name, + choices=left_list()) + if unselected_list.is_valid(): + for selection in unselected_list.cleaned_data['selection']: + label = dict(left_list())[selection] + if decode_content_type: + selection_obj = get_obj_from_content_type_string(selection) + else: + selection_obj = selection + try: + add_method(selection_obj) + messages.success(request, _(u'%(selection)s added successfully added to %(right_list_title)s.') % { + 'selection': label, 'right_list_title': right_list_title}) + except: + messages.error(request, _(u'Unable to add %(selection)s to %(right_list_title)s.') % { + 'selection': label, 'right_list_title': right_list_title}) + + elif u'%s-submit' % right_list_name in request.POST.keys(): + selected_list = ChoiceForm(request.POST, + prefix=right_list_name, + choices=right_list()) + if selected_list.is_valid(): + for selection in selected_list.cleaned_data['selection']: + label = dict(right_list())[selection] + if decode_content_type: + selection = get_obj_from_content_type_string(selection) + try: + remove_method(selection) + messages.success(request, _(u'%(selection)s added successfully removed from %(right_list_title)s.') % { + 'selection': label, 'right_list_title': right_list_title}) + except: + messages.error(request, _(u'Unable to add %(selection)s to %(right_list_title)s.') % { + 'selection': label, 'right_list_title': right_list_title}) + unselected_list = ChoiceForm(prefix=left_list_name, + choices=left_list()) + selected_list = ChoiceForm(prefix=right_list_name, + choices=right_list()) + + context = { + 'subtemplates_list': [ + { + 'name':'generic_form_subtemplate.html', + 'grid': 6, + 'context': { + 'form': unselected_list, + 'title': left_list_title, + 'submit_label': _(u'Add'), + } + }, + { + 'name':'generic_form_subtemplate.html', + 'grid': 6, + 'grid_clear': True, + 'context': { + 'form': selected_list, + 'title': right_list_title, + 'submit_label': _(u'Remove'), + } + }, + + ], + } + if obj: + context.update( + { + 'object': obj + } + ) + + if object_name: + context.update( + { + 'object_name': object_name, + } + ) + + return render_to_response('generic_form.html', context, + context_instance=RequestContext(request)) diff --git a/apps/common/widgets.py b/apps/common/widgets.py new file mode 100644 index 0000000000..2d07facbdf --- /dev/null +++ b/apps/common/widgets.py @@ -0,0 +1,49 @@ +from django.utils.translation import ugettext_lazy as _ +from django.utils.safestring import mark_safe +from django import forms + + +class PlainWidget(forms.widgets.Widget): + def render(self, name, value, attrs=None): + return mark_safe(u'%s' % value) + + +class DetailSelectMultiple(forms.widgets.SelectMultiple): + def __init__(self, queryset=None, *args, **kwargs): + self.queryset = queryset + super(DetailSelectMultiple, self).__init__(*args, **kwargs) + + def render(self, name, value, attrs=None, choices=(), *args, **kwargs): + if value is None: + value = '' + final_attrs = self.build_attrs(attrs, name=name) + css_class = final_attrs.get('class', 'list') + output = u'
    ' % css_class + options = None + if value: + if getattr(value, '__iter__', None): + options = [(index, string) for index, string in \ + self.choices if index in value] + else: + options = [(index, string) for index, string in \ + self.choices if index == value] + else: + if self.choices: + if self.choices[0] != (u'', u'---------') and value != []: + options = [(index, string) for index, string in \ + self.choices] + + if options: + for index, string in options: + if self.queryset: + try: + output += u'
  • %s
  • ' % ( + self.queryset.get(pk=index).get_absolute_url(), + string) + except AttributeError: + output += u'
  • %s
  • ' % (string) + else: + output += u'
  • %s
  • ' % string + else: + output += u'
  • %s
  • ' % _(u"None") + return mark_safe(output + u'
\n') diff --git a/apps/document_comments/views.py b/apps/document_comments/views.py index f56a0c353b..2b3d38b432 100644 --- a/apps/document_comments/views.py +++ b/apps/document_comments/views.py @@ -46,6 +46,7 @@ def comment_delete(request, comment_id=None, comment_id_list=None): 'delete_view': True, 'previous': previous, 'next': next, + 'form_icon': u'comment_delete.png', } if len(comments) == 1: context['object'] = comments[0].content_object diff --git a/apps/document_indexing/__init__.py b/apps/document_indexing/__init__.py new file mode 100644 index 0000000000..65d1dd3dab --- /dev/null +++ b/apps/document_indexing/__init__.py @@ -0,0 +1,23 @@ +from django.utils.translation import ugettext_lazy as _ + +from navigation.api import register_menu +from permissions.api import register_permissions +from main.api import register_tool + +PERMISSION_DOCUMENT_INDEXING_VIEW = 'document_index_view' +PERMISSION_DOCUMENT_INDEXING_REBUILD_INDEXES = 'document_rebuild_indexes' + +register_permissions('document_indexing', [ + {'name': PERMISSION_DOCUMENT_INDEXING_VIEW, 'label': _(u'View document indexes')}, + {'name': PERMISSION_DOCUMENT_INDEXING_REBUILD_INDEXES, 'label': _(u'Rebuild document indexes')}, +]) + +index_list = {'text': _(u'index list'), 'view': 'index_instance_list', 'famfam': 'folder_link', 'permissions': {'namespace': 'document_indexing', 'permissions': [PERMISSION_DOCUMENT_INDEXING_VIEW]}} + +register_menu([ + {'text': _('indexes'), 'view': 'index_instance_list', 'links': [ + ], 'famfam': 'folder_link', 'position': 2, 'permissions': {'namespace': 'document_indexing', 'permissions': [PERMISSION_DOCUMENT_INDEXING_VIEW]}}]) + +rebuild_index_instances = {'text': _('rebuild indexes'), 'view': 'rebuild_index_instances', 'famfam': 'folder_link', 'permissions': {'namespace': 'document_indexing', 'permissions': [PERMISSION_DOCUMENT_INDEXING_REBUILD_INDEXES]}, 'description': _(u'Deletes and creates from scratch all the document indexes.')} + +register_tool(rebuild_index_instances, namespace='document_indexing', title=_(u'Indexes')) diff --git a/apps/document_indexing/admin.py b/apps/document_indexing/admin.py new file mode 100644 index 0000000000..cfe2cd6f99 --- /dev/null +++ b/apps/document_indexing/admin.py @@ -0,0 +1,27 @@ +from django.contrib import admin + +from mptt.admin import MPTTModelAdmin + +from document_indexing.models import Index, IndexInstance, \ + DocumentRenameCount + + +class IndexInstanceInline(admin.StackedInline): + model = IndexInstance + extra = 1 + classes = ('collapse-open',) + allow_add = True + + +class IndexAdmin(MPTTModelAdmin): + list_display = ('expression', 'enabled', 'link_documents') + + +class IndexInstanceAdmin(MPTTModelAdmin): + model = IndexInstance + list_display = ('value', 'index', 'get_document_list_display') + + +admin.site.register(Index, IndexAdmin) +admin.site.register(IndexInstance, IndexInstanceAdmin) +admin.site.register(DocumentRenameCount) diff --git a/apps/document_indexing/api.py b/apps/document_indexing/api.py new file mode 100644 index 0000000000..b2e166f347 --- /dev/null +++ b/apps/document_indexing/api.py @@ -0,0 +1,211 @@ +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext +from django.core.urlresolvers import reverse +from django.utils.safestring import mark_safe +from django.template.defaultfilters import slugify + +from documents.models import Document +from metadata.classes import MetadataObject + +from document_indexing.models import Index, IndexInstance, \ + DocumentRenameCount +from document_indexing.conf.settings import AVAILABLE_INDEXING_FUNCTIONS +from document_indexing.conf.settings import MAX_SUFFIX_COUNT +from document_indexing.filesystem import fs_create_index_directory, \ + fs_create_document_link, fs_delete_document_link, \ + fs_delete_index_directory, fs_delete_directory_recusive +from document_indexing.conf.settings import SLUGIFY_PATHS +from document_indexing.os_agnostic import assemble_document_filename + +if SLUGIFY_PATHS == False: + # Do not slugify path or filenames and extensions + SLUGIFY_FUNCTION = lambda x: x +else: + SLUGIFY_FUNCTION = slugify + + +class MaxSuffixCountReached(Exception): + pass + + +# External functions +def update_indexes(document): + """ + Update or create all the index instances related to a document + """ + warnings = [] + + eval_dict = {} + document_metadata_dict = dict([(metadata.metadata_type.name, metadata.value) for metadata in document.documentmetadata_set.all() if metadata.value]) + eval_dict['document'] = document + eval_dict['metadata'] = MetadataObject(document_metadata_dict) + + for root in Index.objects.filter(parent=None): + index_warnings = _evaluate_index(eval_dict, document, root) + warnings.extend(index_warnings) + + return warnings + + +def delete_indexes(document): + """ + Delete all the index instances related to a document + """ + warnings = [] + + for index_instance in document.indexinstance_set.all(): + index_warnings = _remove_document_from_index_instance(document, index_instance) + warnings.extend(index_warnings) + + return warnings + + +def get_instance_link(index_instance=None, text=None, simple=False): + """ + Return an HTML anchor to an index instance + """ + + if simple: + # Just display the instance's value or overrided text, no + # HTML anchor + template = u'%(value)s' + else: + template = u'%(value)s' + if index_instance: + return template % { + 'url': index_instance.get_absolute_url(), + 'value': text if text else index_instance + } + else: + # Root node + return template % { + 'url': reverse('index_instance_list'), + 'value': ugettext(u'root') + } + + +def get_breadcrumbs(index_instance, simple=False, single_link=False, include_count=False): + """ + Return a joined string of HTML anchors to every index instance's + parent from the root of the tree to the index instance + """ + result = [] + if single_link: + # Return the entire breadcrumb path as a single HTML anchor + simple = True + + result.append(get_instance_link(simple=simple)) + + for instance in index_instance.get_ancestors(): + result.append(get_instance_link(instance, simple=simple)) + + result.append(get_instance_link(index_instance, simple=simple)) + + output = [] + + if include_count: + output.append(u'(%d)' % index_instance.documents.count()) + + if single_link: + # Return the entire breadcrumb path as a single HTML anchor + output.insert(0, get_instance_link(index_instance=index_instance, text=(u' / '.join(result)))) + return mark_safe(u' '.join(output)) + else: + output.insert(0, u' / '.join(result)) + return mark_safe(u' '.join(output)) + + +def do_rebuild_all_indexes(): + fs_delete_directory_recusive() + IndexInstance.objects.all().delete() + DocumentRenameCount.objects.all().delete() + for document in Document.objects.all(): + update_indexes(document) + + return [] # Warnings - None + + +# Internal functions +def find_lowest_available_suffix(index_instance, document): + index_instance_documents = DocumentRenameCount.objects.filter(index_instance=index_instance).filter(document__file_extension=document.file_extension) + files_list = [] + for index_instance_document in index_instance_documents: + files_list.append(assemble_document_filename(index_instance_document.document, index_instance_document.suffix)) + + for suffix in xrange(MAX_SUFFIX_COUNT): + if assemble_document_filename(document, suffix) not in files_list: + return suffix + + raise MaxSuffixCountReached(ugettext(u'Maximum suffix (%s) count reached.') % MAX_SUFFIX_COUNT) + + +def _evaluate_index(eval_dict, document, node, parent_index_instance=None): + """ + Evaluate an enabled index expression and update or create all the + related index instances also recursively calling itself to evaluate + all the index's children + """ + warnings = [] + if node.enabled: + try: + result = eval(node.expression, eval_dict, AVAILABLE_INDEXING_FUNCTIONS) + if result: + index_instance, created = IndexInstance.objects.get_or_create(index=node, value=result, parent=parent_index_instance) + #if created: + fs_create_index_directory(index_instance) + if node.link_documents: + suffix = find_lowest_available_suffix(index_instance, document) + document_count = DocumentRenameCount( + index_instance=index_instance, + document=document, + suffix=suffix + ) + document_count.save() + + fs_create_document_link(index_instance, document, suffix) + index_instance.documents.add(document) + + for children in node.get_children(): + children_warnings = _evaluate_index( + eval_dict, document, children, index_instance + ) + warnings.extend(children_warnings) + + except (NameError, AttributeError), exc: + warnings.append(_(u'Error in document indexing update expression: %(expression)s; %(exception)s') % { + 'expression': node.expression, 'exception': exc}) + except Exception, exc: + warnings.append(_(u'Error updating document index, expression: %(expression)s; %(exception)s') % { + 'expression': node.expression, 'exception': exc}) + + return warnings + + +def _remove_document_from_index_instance(document, index_instance): + """ + Delete a documents reference from an index instance and call itself + recusively deleting documents and empty index instances up to the + root of the tree + """ + warnings = [] + try: + document_rename_count = DocumentRenameCount.objects.get(index_instance=index_instance, document=document) + fs_delete_document_link(index_instance, document, document_rename_count.suffix) + document_rename_count.delete() + index_instance.documents.remove(document) + if index_instance.documents.count() == 0 and index_instance.get_children().count() == 0: + # if there are no more documents and no children, delete + # node and check parent for the same conditions + parent = index_instance.parent + fs_delete_index_directory(index_instance) + index_instance.delete() + parent_warnings = _remove_document_from_index_instance( + document, parent + ) + warnings.extend(parent_warnings) + except DocumentRenameCount.DoesNotExist: + return warnings + except Exception, exc: + warnings.append(_(u'Unable to delete document indexing node; %s') % exc) + + return warnings diff --git a/apps/filesystem_serving/conf/__init__.py b/apps/document_indexing/conf/__init__.py similarity index 100% rename from apps/filesystem_serving/conf/__init__.py rename to apps/document_indexing/conf/__init__.py diff --git a/apps/document_indexing/conf/settings.py b/apps/document_indexing/conf/settings.py new file mode 100644 index 0000000000..9d471c19a1 --- /dev/null +++ b/apps/document_indexing/conf/settings.py @@ -0,0 +1,25 @@ +"""Configuration options for the document_indexing app""" + +from django.utils.translation import ugettext_lazy as _ + +from common.utils import proper_name +from smart_settings.api import register_settings + +available_indexing_functions = { + 'proper_name': proper_name +} + +register_settings( + namespace=u'document_indexing', + module=u'document_indexing.conf.settings', + settings=[ + # Definition + {'name': u'AVAILABLE_INDEXING_FUNCTIONS', 'global_name': u'DOCUMENT_INDEXING_AVAILABLE_INDEXING_FUNCTIONS', 'default': available_indexing_functions}, + {'name': u'SUFFIX_SEPARATOR', 'global_name': u'DOCUMENT_INDEXING_SUFFIX_SEPARATOR', 'default': u'_'}, + # Filesystem serving + {'name': u'SLUGIFY_PATHS', 'global_name': u'DOCUMENT_INDEXING_FILESYSTEM_SLUGIFY_PATHS', 'default': False}, + {'name': u'MAX_SUFFIX_COUNT', 'global_name': u'DOCUMENT_INDEXING_FILESYSTEM_MAX_SUFFIX_COUNT', 'default': 1000}, + {'name': u'FILESERVING_PATH', 'global_name': u'DOCUMENT_INDEXING_FILESYSTEM_FILESERVING_PATH', 'default': u'/tmp/mayan/documents', 'exists': True}, + {'name': u'FILESERVING_ENABLE', 'global_name': u'DOCUMENT_INDEXING_FILESYSTEM_FILESERVING_ENABLE', 'default': True} + ] +) diff --git a/apps/document_indexing/filesystem.py b/apps/document_indexing/filesystem.py new file mode 100644 index 0000000000..a405ca4f8d --- /dev/null +++ b/apps/document_indexing/filesystem.py @@ -0,0 +1,92 @@ +import errno +import os + +from django.utils.translation import ugettext_lazy as _ + +from document_indexing.os_agnostic import assemble_document_filename +from document_indexing.conf.settings import FILESERVING_ENABLE +from document_indexing.conf.settings import FILESERVING_PATH + + +def get_instance_path(index_instance): + """ + Return a platform formated filesytem path corresponding to an + index instance + """ + names = [] + for ancestor in index_instance.get_ancestors(): + names.append(ancestor.value) + + names.append(index_instance.value) + + return os.sep.join(names) + + +def fs_create_index_directory(index_instance): + if FILESERVING_ENABLE: + target_directory = os.path.join(FILESERVING_PATH, get_instance_path(index_instance)) + try: + os.makedirs(target_directory) + except OSError, exc: + if exc.errno == errno.EEXIST: + pass + else: + raise OSError(_(u'Unable to create indexing directory; %s') % exc) + + +def fs_create_document_link(index_instance, document, suffix=0): + if FILESERVING_ENABLE: + name_part = assemble_document_filename(document, suffix) + filename = os.extsep.join([name_part, document.file_extension]) + filepath = os.path.join(FILESERVING_PATH, get_instance_path(index_instance), filename) + try: + os.symlink(document.file.path, filepath) + except OSError, exc: + if exc.errno == errno.EEXIST: + # This link should not exist, try to delete it + try: + os.unlink(filepath) + # Try again + os.symlink(document.file.path, filepath) + except Exception, exc: + raise Exception(_(u'Unable to create symbolic link, file exists and could not be deleted: %(filepath)s; %(exc)s') % {'filepath': filepath, 'exc': exc}) + else: + raise OSError(_(u'Unable to create symbolic link: %(filepath)s; %(exc)s') % {'filepath': filepath, 'exc': exc}) + + +def fs_delete_document_link(index_instance, document, suffix=0): + if FILESERVING_ENABLE: + name_part = document.file_filename + if suffix: + name_part = u'_'.join([name_part, unicode(suffix)]) + + filename = os.extsep.join([name_part, document.file_extension]) + filepath = os.path.join(FILESERVING_PATH, get_instance_path(index_instance), filename) + + try: + os.unlink(filepath) + except OSError, exc: + if exc.errno != errno.ENOENT: + # Raise when any error other than doesn't exits + raise OSError(_(u'Unable to delete document symbolic link; %s') % exc) + + +def fs_delete_index_directory(index_instance): + if FILESERVING_ENABLE: + target_directory = os.path.join(FILESERVING_PATH, get_instance_path(index_instance)) + try: + os.removedirs(target_directory) + except OSError, exc: + if exc.errno == errno.EEXIST: + pass + else: + raise OSError(_(u'Unable to delete indexing directory; %s') % exc) + + +def fs_delete_directory_recusive(path=FILESERVING_PATH): + if FILESERVING_ENABLE: + for dirpath, dirnames, filenames in os.walk(path, topdown=False): + for filename in filenames: + os.unlink(os.path.join(dirpath, filename)) + for dirname in dirnames: + os.rmdir(os.path.join(dirpath, dirname)) diff --git a/apps/document_indexing/models.py b/apps/document_indexing/models.py new file mode 100644 index 0000000000..a8a1003131 --- /dev/null +++ b/apps/document_indexing/models.py @@ -0,0 +1,60 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from mptt.models import MPTTModel +from mptt.fields import TreeForeignKey + +from documents.models import Document + +from document_indexing.conf.settings import AVAILABLE_INDEXING_FUNCTIONS + +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 Index(MPTTModel): + parent = TreeForeignKey('self', null=True, blank=True, related_name='index_meta_class') + expression = models.CharField(max_length=128, verbose_name=_(u'indexing expression'), help_text=_(u'Enter a python string expression to be evaluated.')) + # % available_indexing_functions_string) + enabled = models.BooleanField(default=True, verbose_name=_(u'enabled')) + link_documents = models.BooleanField(default=False, verbose_name=_(u'link documents')) + + def __unicode__(self): + return self.expression if not self.link_documents else u'%s/[document]' % self.expression + + class Meta: + verbose_name = _(u'index') + verbose_name_plural = _(u'indexes') + + +class IndexInstance(MPTTModel): + parent = TreeForeignKey('self', null=True, blank=True, related_name='index_meta_instance') + index = models.ForeignKey(Index, verbose_name=_(u'index')) + value = models.CharField(max_length=128, blank=True, verbose_name=_(u'value')) + documents = models.ManyToManyField(Document, verbose_name=_(u'documents')) + + def __unicode__(self): + return self.value + + @models.permalink + def get_absolute_url(self): + return ('index_instance_list', [self.pk]) + + def get_document_list_display(self): + return u', '.join([d.file_filename for d in self.documents.all()]) + + class Meta: + verbose_name = _(u'index instance') + verbose_name_plural = _(u'indexes instances') + + +class DocumentRenameCount(models.Model): + index_instance = models.ForeignKey(IndexInstance, verbose_name=_(u'index instance')) + document = models.ForeignKey(Document, verbose_name=_(u'document')) + suffix = models.PositiveIntegerField(blank=True, verbose_name=(u'suffix')) + + def __unicode__(self): + return u'%s - %s - %s' % (self.index_instance, self.document, self.suffix or u'0') + + class Meta: + verbose_name = _(u'document rename count') + verbose_name_plural = _(u'documents rename count') diff --git a/apps/document_indexing/os_agnostic.py b/apps/document_indexing/os_agnostic.py new file mode 100644 index 0000000000..ac8ef648ec --- /dev/null +++ b/apps/document_indexing/os_agnostic.py @@ -0,0 +1,8 @@ +from document_indexing.conf.settings import SUFFIX_SEPARATOR + + +def assemble_document_filename(document, suffix=0): + if suffix: + return SUFFIX_SEPARATOR.join([document.file_filename, unicode(suffix)]) + else: + return document.file_filename diff --git a/apps/filesystem_serving/tests.py b/apps/document_indexing/tests.py similarity index 100% rename from apps/filesystem_serving/tests.py rename to apps/document_indexing/tests.py diff --git a/apps/document_indexing/urls.py b/apps/document_indexing/urls.py new file mode 100644 index 0000000000..5aa4fcf3a5 --- /dev/null +++ b/apps/document_indexing/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('document_indexing.views', + url(r'^(?P\d+)/list/$', 'index_instance_list', (), 'index_instance_list'), + url(r'^list/$', 'index_instance_list', (), 'index_instance_list'), + url(r'^rebuild/all/$', 'rebuild_index_instances', (), 'rebuild_index_instances'), +) diff --git a/apps/document_indexing/utils.py b/apps/document_indexing/utils.py new file mode 100644 index 0000000000..24f32d7f12 --- /dev/null +++ b/apps/document_indexing/utils.py @@ -0,0 +1,23 @@ +from django.utils.translation import ugettext_lazy as _ + +from document_indexing.api import get_breadcrumbs + + +def get_document_indexing_subtemplate(document): + """ + Return all the settings to render a subtemplate containing a + list of index instances where a document may be found + """ + object_list = [] + + for index_instance in document.indexinstance_set.all(): + object_list.append(get_breadcrumbs(index_instance, single_link=True, include_count=True)) + + return { + 'name': 'generic_list_subtemplate.html', + 'context': { + 'title': _(u'document indexes'), + 'object_list': object_list, + 'hide_link': True + } + } diff --git a/apps/document_indexing/views.py b/apps/document_indexing/views.py new file mode 100644 index 0000000000..bb341e3222 --- /dev/null +++ b/apps/document_indexing/views.py @@ -0,0 +1,64 @@ +from django.utils.translation import ugettext_lazy as _ +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.contrib import messages +from django.utils.safestring import mark_safe + +from permissions.api import check_permissions + +from document_indexing import PERMISSION_DOCUMENT_INDEXING_VIEW, \ + PERMISSION_DOCUMENT_INDEXING_REBUILD_INDEXES + +from document_indexing.models import IndexInstance +from document_indexing.api import get_breadcrumbs, get_instance_link, \ + do_rebuild_all_indexes + + +def index_instance_list(request, index_id=None): + check_permissions(request.user, 'document_indexing', [PERMISSION_DOCUMENT_INDEXING_VIEW]) + + if index_id: + index_instance = get_object_or_404(IndexInstance, pk=index_id) + index_instance_list = [index for index in index_instance.get_children().order_by('value')] + breadcrumbs = get_breadcrumbs(index_instance) + if index_instance.documents.count(): + for document in index_instance.documents.all().order_by('file_filename'): + index_instance_list.append(document) + else: + index_instance_list = IndexInstance.objects.filter(parent=None) + breadcrumbs = get_instance_link() + + title = mark_safe(_(u'contents for index: %s') % breadcrumbs) + + return render_to_response('generic_list.html', { + 'object_list': index_instance_list, + 'title': title, + 'hide_links': True, + }, context_instance=RequestContext(request)) + + +def rebuild_index_instances(request): + check_permissions(request.user, 'document_indexing', [PERMISSION_DOCUMENT_INDEXING_REBUILD_INDEXES]) + + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', None))) + next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', None))) + + if request.method != 'POST': + return render_to_response('generic_confirm.html', { + 'previous': previous, + 'next': next, + 'message': _(u'On large databases this operation may take some time to execute.'), + 'form_icon': u'folder_link.png', + }, context_instance=RequestContext(request)) + else: + try: + warnings = do_rebuild_all_indexes() + messages.success(request, _(u'Index rebuild completed successfully.')) + for warning in warnings: + messages.warning(request, warning) + + except Exception, e: + messages.error(request, _(u'Index rebuild error: %s') % e) + + return HttpResponseRedirect(next) diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index d6d1da3810..a6bb737d5f 100644 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -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')) diff --git a/apps/documents/admin.py b/apps/documents/admin.py index bc1a0c7c15..ff4aa33cb0 100644 --- a/apps/documents/admin.py +++ b/apps/documents/admin.py @@ -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) diff --git a/apps/documents/conf/settings.py b/apps/documents/conf/settings.py index d504fef996..60f229fb5e 100644 --- a/apps/documents/conf/settings.py +++ b/apps/documents/conf/settings.py @@ -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}, ] ) diff --git a/apps/documents/forms.py b/apps/documents/forms.py index 257a147f64..a81336be5a 100644 --- a/apps/documents/forms.py +++ b/apps/documents/forms.py @@ -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'''
+ u'''
%(page_string)s %(page)s
@@ -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'') - - output.append(u'
') - for document in value['group_data']: - tags_template = get_tags_inline_widget(document) - - output.append( - u'''''' % { - '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'
') - output.append( - u'
%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'}) + ) diff --git a/apps/documents/literals.py b/apps/documents/literals.py index 5ab7c80556..975661d9e8 100644 --- a/apps/documents/literals.py +++ b/apps/documents/literals.py @@ -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' diff --git a/apps/documents/metadata.py b/apps/documents/metadata.py deleted file mode 100644 index f6fb1f4007..0000000000 --- a/apps/documents/metadata.py +++ /dev/null @@ -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 diff --git a/apps/documents/models.py b/apps/documents/models.py index 6f069f5f78..2de24cb20e 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -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) diff --git a/apps/documents/statistics.py b/apps/documents/statistics.py index 1f465b8abb..786f902651 100644 --- a/apps/documents/statistics.py +++ b/apps/documents/statistics.py @@ -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'), diff --git a/apps/documents/urls.py b/apps/documents/urls.py index ea53a547d2..4b88aace3e 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -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\d+)/upload/single/$', 'upload_document_with_type', {'multiple': False}, 'upload_document_with_type'), - url(r'^document/type/(?P\d+)/upload/multiple/$', 'upload_document_with_type', {'multiple': True}, 'upload_multiple_documents_with_type'), - url(r'^document/(?P\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\d+)/$', 'document_view_advanced', (), 'document_view_advanced'), url(r'^document/(?P\d+)/simple/$', 'document_view_simple', (), 'document_view_simple'), url(r'^document/(?P\d+)/delete/$', 'document_delete', (), 'document_delete'), url(r'^document/multiple/delete/$', 'document_multiple_delete', (), 'document_multiple_delete'), url(r'^document/(?P\d+)/edit/$', 'document_edit', (), 'document_edit'), - url(r'^document/(?P\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\d+)/print/$', 'document_print', (), 'document_print'), url(r'^document/(?P\d+)/hard_copy/$', 'document_hard_copy', (), 'document_hard_copy'), @@ -36,7 +34,6 @@ urlpatterns = patterns('documents.views', url(r'^document/(?P\d+)/create/siblings/$', 'document_create_sibling', {'multiple': True if ENABLE_SINGLE_DOCUMENT_UPLOAD == False else False}, 'document_create_sibling'), url(r'^document/(?P\d+)/find_duplicates/$', 'document_find_duplicates', (), 'document_find_duplicates'), url(r'^document/(?P\d+)/clear_transformations/$', 'document_clear_transformations', (), 'document_clear_transformations'), - url(r'^document/(?P\d+)/group/(?P\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\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'), ) diff --git a/apps/documents/views.py b/apps/documents/views.py index 6076abfda1..4a7c43be74 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -1,7 +1,6 @@ import os import zipfile import urlparse -import urllib from django.utils.translation import ugettext_lazy as _ from django.http import HttpResponseRedirect @@ -18,26 +17,29 @@ from django.contrib.comments.models import Comment import sendfile from common.utils import pretty_size, parse_range, urlquote -from converter.api import convert_document, QUALITY_DEFAULT -from converter.exceptions import UnkownConvertError, UnknownFormat -from filetransfers.api import serve_file -from filesystem_serving.api import document_create_fs_links, document_delete_fs_links -from filesystem_serving.conf.settings import FILESERVING_ENABLE -from permissions.api import check_permissions -from navigation.utils import resolve_to_name -from tags.utils import get_tags_subtemplate -from document_comments.utils import get_comments_subtemplate -from converter.api import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \ - DEFAULT_FILE_FORMAT, QUALITY_PRINT from common.literals import PAGE_SIZE_DIMENSIONS, \ PAGE_ORIENTATION_PORTRAIT, PAGE_ORIENTATION_LANDSCAPE from common.conf.settings import DEFAULT_PAPER_SIZE +from converter.api import convert_document, QUALITY_DEFAULT +from converter.exceptions import UnkownConvertError, UnknownFormat +from converter.api import DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, \ + DEFAULT_FILE_FORMAT, QUALITY_PRINT +from document_comments.utils import get_comments_subtemplate +from filetransfers.api import serve_file +from grouping.utils import get_document_group_subtemplate +from metadata.api import save_metadata_list, \ + decode_metadata_from_url, metadata_repr_as_list +from metadata.forms import MetadataFormSet +from navigation.utils import resolve_to_name +from permissions.api import check_permissions +from tags.utils import get_tags_subtemplate +from document_indexing.utils import get_document_indexing_subtemplate +from document_indexing.api import update_indexes, delete_indexes from documents.conf.settings import DELETE_STAGING_FILE_AFTER_UPLOAD from documents.conf.settings import USE_STAGING_DIRECTORY from documents.conf.settings import PREVIEW_SIZE from documents.conf.settings import THUMBNAIL_SIZE -from documents.conf.settings import GROUP_SHOW_EMPTY from documents.conf.settings import UNCOMPRESS_COMPRESSED_LOCAL_FILES from documents.conf.settings import UNCOMPRESS_COMPRESSED_STAGING_FILES from documents.conf.settings import STORAGE_BACKEND @@ -47,26 +49,23 @@ from documents.conf.settings import ZOOM_MIN_LEVEL from documents.conf.settings import ROTATION_STEP from documents.conf.settings import PRINT_SIZE -from documents import PERMISSION_DOCUMENT_CREATE, \ +from documents.literals import PERMISSION_DOCUMENT_CREATE, \ PERMISSION_DOCUMENT_PROPERTIES_EDIT, \ - PERMISSION_DOCUMENT_METADATA_EDIT, PERMISSION_DOCUMENT_VIEW, \ + PERMISSION_DOCUMENT_VIEW, \ PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_DOWNLOAD, \ PERMISSION_DOCUMENT_TRANSFORM, \ PERMISSION_DOCUMENT_EDIT from documents.forms import DocumentTypeSelectForm, DocumentCreateWizard, \ DocumentForm, DocumentForm_edit, DocumentForm_view, \ - StagingDocumentForm, DocumentTypeMetadataType, DocumentPreviewForm, \ - MetadataFormSet, DocumentPageForm, DocumentPageTransformationForm, \ - DocumentContentForm, DocumentPageForm_edit, MetaDataGroupForm, \ - DocumentPageForm_text, PrintForm + StagingDocumentForm, DocumentPreviewForm, \ + DocumentPageForm, DocumentPageTransformationForm, \ + DocumentContentForm, DocumentPageForm_edit, \ + DocumentPageForm_text, PrintForm, MetadataSelectionForm -from documents.metadata import save_metadata_list, \ - decode_metadata_from_url, metadata_repr_as_list from documents.models import Document, DocumentType, DocumentPage, \ - DocumentPageTransformation, RecentDocument, MetadataGroup + DocumentPageTransformation, RecentDocument from documents.staging import StagingFile -from documents import metadata_group_link from documents.literals import PICTURE_ERROR_SMALL, PICTURE_ERROR_MEDIUM, \ PICTURE_UNKNOWN_SMALL, PICTURE_UNKNOWN_MEDIUM @@ -85,15 +84,7 @@ def document_list(request, object_list=None, title=None): def document_create(request, multiple=True): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_CREATE]) - if DocumentType.objects.all().count() == 1: - wizard = DocumentCreateWizard( - document_type=DocumentType.objects.all()[0], - form_list=[MetadataFormSet], multiple=multiple, - step_titles=[ - _(u'document metadata'), - ]) - else: - wizard = DocumentCreateWizard(form_list=[DocumentTypeSelectForm, MetadataFormSet], multiple=multiple) + wizard = DocumentCreateWizard(form_list=[DocumentTypeSelectForm, MetadataSelectionForm, MetadataFormSet], multiple=multiple) return wizard(request) @@ -102,19 +93,21 @@ def document_create_sibling(request, document_id, multiple=True): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_CREATE]) document = get_object_or_404(Document, pk=document_id) - urldata = [] + query_dict = {} for pk, metadata in enumerate(document.documentmetadata_set.all()): - if hasattr(metadata, 'value'): - urldata.append(('metadata%s_id' % pk, metadata.metadata_type_id)) - urldata.append(('metadata%s_value' % pk, metadata.value)) + query_dict['metadata%s_id' % pk] = metadata.metadata_type_id + query_dict['metadata%s_value' % pk] = metadata.value if multiple: - view = 'upload_multiple_documents_with_type' + view = 'upload_document_multiple' else: - view = 'upload_document_with_type' + view = 'upload_document' - url = reverse(view, args=[document.document_type_id]) - return HttpResponseRedirect('%s?%s' % (url, urlencode(urldata))) + if document.document_type_id: + query_dict['document_type_id'] = document.document_type_id + + url = reverse(view) + return HttpResponseRedirect('%s?%s' % (url, urlencode(query_dict))) def _handle_save_document(request, document, form=None): @@ -128,14 +121,12 @@ def _handle_save_document(request, document, form=None): document.file_filename = form.cleaned_data['document_type_available_filenames'].filename document.save() - save_metadata_list(decode_metadata_from_url(request.GET), document) - try: - warnings = document_create_fs_links(document) - if request.user.is_staff or request.user.is_superuser: - for warning in warnings: - messages.warning(request, warning) - except Exception, e: - messages.error(request, e) + save_metadata_list(decode_metadata_from_url(request.GET), document, create=True) + + warnings = update_indexes(document) + if request.user.is_staff or request.user.is_superuser: + for warning in warnings: + messages.warning(request, warning) def _handle_zip_file(request, uploaded_file, document_type): @@ -157,10 +148,15 @@ def _handle_zip_file(request, uploaded_file, document_type): return False -def upload_document_with_type(request, document_type_id, multiple=True): +def upload_document_with_type(request, multiple=True): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_CREATE]) - document_type = get_object_or_404(DocumentType, pk=document_type_id) + document_type_id = request.GET.get('document_type_id', None) + if document_type_id: + document_type = get_object_or_404(DocumentType, pk=document_type_id) + else: + document_type = None + local_form = DocumentForm(prefix='local', initial={'document_type': document_type}) if USE_STAGING_DIRECTORY: staging_form = StagingDocumentForm(prefix='staging', @@ -206,16 +202,13 @@ def upload_document_with_type(request, document_type_id, multiple=True): else: return HttpResponseRedirect(reverse('document_list')) - context = { - 'document_type_id': document_type_id, - 'form_list': [ - { - 'form': local_form, - 'title': _(u'upload a local document'), - 'grid': 6, - 'grid_clear': False if USE_STAGING_DIRECTORY else True, - }, - ], + subtemplates_list = [] + local_upload_form = { + 'name': 'generic_form_subtemplate.html', + 'context': { + 'form': local_form, + 'title': _(u'upload a local document'), + }, } if USE_STAGING_DIRECTORY: @@ -225,74 +218,81 @@ def upload_document_with_type(request, document_type_id, multiple=True): messages.error(request, e) filelist = [] finally: - context.update({ - 'subtemplates_dict': [ - { - 'name': 'generic_list_subtemplate.html', + local_upload_form.update({'grid': 6}) + subtemplates_list.append(local_upload_form) + subtemplates_list.append( + { + 'name': 'generic_form_subtemplate.html', + 'grid': 6, + 'grid_clear': True, + 'context': { + 'form': staging_form, + 'title': _(u'upload a document from staging'), + } + }, + ) + subtemplates_list.append( + { + 'name': 'generic_list_subtemplate.html', + 'context': { 'title': _(u'files in staging'), 'object_list': filelist, 'hide_link': True, - }, - ], - }) - context['form_list'].append( - { - 'form': staging_form, - 'title': _(u'upload a document from staging'), - 'grid': 6, - 'grid_clear': True, + } }, ) + else: + subtemplates_list.append(local_upload_form) - context.update({ + context = { + 'document_type_id': document_type_id, + 'subtemplates_list': subtemplates_list, 'sidebar_subtemplates_list': [ { - 'title': _(u'Current metadata'), 'name': 'generic_subtemplate.html', - #'content': metadata_repr(decode_metadata_from_url(request.GET)), - 'paragraphs': metadata_repr_as_list(decode_metadata_from_url(request.GET)) + 'context': { + 'title': _(u'Current metadata'), + #'content': metadata_repr(decode_metadata_from_url(request.GET)), + 'paragraphs': metadata_repr_as_list(decode_metadata_from_url(request.GET)) + } }] - }) + } return render_to_response('generic_form.html', context, context_instance=RequestContext(request)) -def document_view(request, document_id): +def document_view_simple(request, document_id): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) document = get_object_or_404(Document.objects.select_related(), pk=document_id) RecentDocument.objects.add_document_for_user(request.user, document) - form = DocumentForm_view(instance=document, extra_fields=[ - {'label': _(u'Filename'), 'field': 'file_filename'}, - {'label': _(u'File extension'), 'field': 'file_extension'}, - {'label': _(u'File mimetype'), 'field': 'file_mimetype'}, - {'label': _(u'File mime encoding'), 'field': 'file_mime_encoding'}, - {'label': _(u'File size'), 'field':lambda x: pretty_size(x.file.storage.size(x.file.path)) if x.exists() else '-'}, - {'label': _(u'Exists in storage'), 'field': 'exists'}, - {'label': _(u'File path in storage'), 'field': 'file'}, - {'label': _(u'Date added'), 'field':lambda x: x.date_added.date()}, - {'label': _(u'Time added'), 'field':lambda x: unicode(x.date_added.time()).split('.')[0]}, - {'label': _(u'Checksum'), 'field': 'checksum'}, - {'label': _(u'UUID'), 'field': 'uuid'}, - {'label': _(u'Pages'), 'field': lambda x: x.documentpage_set.count()}, - ]) + subtemplates_list = [] + + content_form = DocumentContentForm(document=document) preview_form = DocumentPreviewForm(document=document) - form_list = [ + subtemplates_list.append( { - 'form': preview_form, - 'object': document, + 'name': 'generic_form_subtemplate.html', + 'context': { + 'form': preview_form, + 'object': document, + } }, + ) + subtemplates_list.append( { - 'title': _(u'document properties'), - 'form': form, - 'object': document, - }, - ] + 'name': 'generic_form_subtemplate.html', + 'context': { + 'title': _(u'document properties'), + 'form': content_form, + 'object': document, + }, + } + ) - subtemplates_list = [] if document.tags.count(): subtemplates_list.append(get_tags_subtemplate(document)) @@ -311,43 +311,94 @@ def document_view(request, document_id): }, ) - metadata_groups, errors = document.get_metadata_groups() - if (request.user.is_staff or request.user.is_superuser) and errors: - for error in errors: - messages.warning(request, _(u'Document group query error: %s' % error)) + document_group_subtemplate = get_document_group_subtemplate(request, document) - if not GROUP_SHOW_EMPTY: - #If GROUP_SHOW_EMPTY is False, remove empty groups from - #dictionary - metadata_groups = dict([(group, data) for group, data in metadata_groups.items() if data]) + if document_group_subtemplate: + subtemplates_list.append(document_group_subtemplate) - if metadata_groups: - subtemplates_list.append( - { - 'name': 'generic_form_subtemplate.html', - 'context': { - 'title': _(u'document groups (%s)') % len(metadata_groups.keys()), - 'form': MetaDataGroupForm(groups=metadata_groups, current_document=document, links=[ - metadata_group_link - ]), - 'form_action': reverse('metadatagroup_action'), - 'submit_method': 'GET', - } - } - ) - - if FILESERVING_ENABLE: - subtemplates_list.append({ - 'name': 'generic_list_subtemplate.html', - 'context': { - 'title': _(u'index links'), - 'object_list': document.documentmetadataindex_set.all(), - 'hide_link': True - } - }) + if document.indexinstance_set.count(): + subtemplates_list.append(get_document_indexing_subtemplate(document)) + + return render_to_response('generic_detail.html', { + 'object': document, + 'document': document, + 'subtemplates_list': subtemplates_list, + }, context_instance=RequestContext(request)) + + +def document_view_advanced(request, document_id): + check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) + + document = get_object_or_404(Document.objects.select_related(), pk=document_id) + + RecentDocument.objects.add_document_for_user(request.user, document) + + subtemplates_list = [] + + form = DocumentForm_view(instance=document, extra_fields=[ + {'label': _(u'Filename'), 'field': 'file_filename'}, + {'label': _(u'File extension'), 'field': 'file_extension'}, + {'label': _(u'File mimetype'), 'field': 'file_mimetype'}, + {'label': _(u'File mime encoding'), 'field': 'file_mime_encoding'}, + {'label': _(u'File size'), 'field':lambda x: pretty_size(x.file.storage.size(x.file.path)) if x.exists() else '-'}, + {'label': _(u'Exists in storage'), 'field': 'exists'}, + {'label': _(u'File path in storage'), 'field': 'file'}, + {'label': _(u'Date added'), 'field':lambda x: x.date_added.date()}, + {'label': _(u'Time added'), 'field':lambda x: unicode(x.date_added.time()).split('.')[0]}, + {'label': _(u'Checksum'), 'field': 'checksum'}, + {'label': _(u'UUID'), 'field': 'uuid'}, + {'label': _(u'Pages'), 'field': lambda x: x.documentpage_set.count()}, + ]) + + preview_form = DocumentPreviewForm(document=document) + + subtemplates_list.append( + { + 'name': 'generic_form_subtemplate.html', + 'context': { + 'form': preview_form, + 'object': document, + } + }, + ) + subtemplates_list.append( + { + 'name': 'generic_form_subtemplate.html', + 'context': { + 'form': form, + 'title': _(u'document properties'), + 'object': document, + } + }, + ) + + if document.tags.count(): + subtemplates_list.append(get_tags_subtemplate(document)) + + if Comment.objects.for_model(document).count(): + subtemplates_list.append(get_comments_subtemplate(document)) + + subtemplates_list.append( + { + 'name': 'generic_list_subtemplate.html', + 'context': { + 'title': _(u'metadata'), + 'object_list': document.documentmetadata_set.all(), + 'extra_columns': [{'name': _(u'value'), 'attribute': 'value'}], + 'hide_link': True, + } + }, + ) + + document_group_subtemplate = get_document_group_subtemplate(request, document) + + if document_group_subtemplate: + subtemplates_list.append(document_group_subtemplate) + + if document.indexinstance_set.count(): + subtemplates_list.append(get_document_indexing_subtemplate(document)) return render_to_response('generic_detail.html', { - 'form_list': form_list, 'object': document, 'document': document, 'subtemplates_list': subtemplates_list, @@ -373,7 +424,11 @@ def document_delete(request, document_id=None, document_id_list=None): if request.method == 'POST': for document in documents: try: - document_delete_fs_links(document) + warnings = delete_indexes(document) + if request.user.is_staff or request.user.is_superuser: + for warning in warnings: + messages.warning(request, warning) + document.delete() messages.success(request, _(u'Document: %s deleted successfully.') % document) except Exception, e: @@ -388,6 +443,7 @@ def document_delete(request, document_id=None, document_id_list=None): 'delete_view': True, 'previous': previous, 'next': next, + 'form_icon': u'page_delete.png', } if len(documents) == 1: context['object'] = documents[0] @@ -417,11 +473,10 @@ def document_edit(request, document_id): if request.method == 'POST': form = DocumentForm_edit(request.POST, initial={'document_type': document.document_type}) if form.is_valid(): - try: - document_delete_fs_links(document) - except Exception, e: - messages.error(request, e) - return HttpResponseRedirect(reverse('document_list')) + warnings = delete_indexes(document) + if request.user.is_staff or request.user.is_superuser: + for warning in warnings: + messages.warning(request, warning) document.file_filename = form.cleaned_data['new_filename'] document.description = form.cleaned_data['description'] @@ -434,17 +489,10 @@ def document_edit(request, document_id): messages.success(request, _(u'Document %s edited successfully.') % document) - try: - warnings = document_create_fs_links(document) - - if request.user.is_staff or request.user.is_superuser: - for warning in warnings: - messages.warning(request, warning) - - messages.success(request, _(u'Document filesystem links updated successfully.')) - except Exception, e: - messages.error(request, e) - return HttpResponseRedirect(document.get_absolute_url()) + warnings = update_indexes(document) + if request.user.is_staff or request.user.is_superuser: + for warning in warnings: + messages.warning(request, warning) return HttpResponseRedirect(document.get_absolute_url()) else: @@ -457,89 +505,6 @@ def document_edit(request, document_id): }, context_instance=RequestContext(request)) -def document_edit_metadata(request, document_id=None, document_id_list=None): - check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_METADATA_EDIT]) - - if document_id: - documents = [get_object_or_404(Document, pk=document_id)] - elif document_id_list: - documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')] - if len(set([document.document_type for document in documents])) > 1: - messages.error(request, _(u'All documents must be from the same type.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) - else: - messages.error(request, _(u'Must provide at least one document.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) - - metadata = {} - for document in documents: - RecentDocument.objects.add_document_for_user(request.user, document) - - for item in DocumentTypeMetadataType.objects.filter(document_type=document.document_type): - value = document.documentmetadata_set.get(metadata_type=item.metadata_type).value if document.documentmetadata_set.filter(metadata_type=item.metadata_type) else u'' - if item.metadata_type in metadata: - if value not in metadata[item.metadata_type]: - metadata[item.metadata_type].append(value) - else: - metadata[item.metadata_type] = [value] - - initial = [] - for key, value in metadata.items(): - initial.append({ - 'metadata_type': key, - 'document_type': document.document_type, - 'value': u', '.join(value) - }) - - formset = MetadataFormSet(initial=initial) - if request.method == 'POST': - formset = MetadataFormSet(request.POST) - if formset.is_valid(): - for document in documents: - save_metadata_list(formset.cleaned_data, document) - try: - document_delete_fs_links(document) - except Exception, e: - messages.error(request, _(u'Error deleting filesystem links for document: %(document)s; %(error)s') % { - 'document': document, 'error': e}) - - messages.success(request, _(u'Metadata for document %s edited successfully.') % document) - - try: - warnings = document_create_fs_links(document) - - if request.user.is_staff or request.user.is_superuser: - for warning in warnings: - messages.warning(request, warning) - - messages.success(request, _(u'Filesystem links updated successfully for document: %s.') % document) - except Exception, e: - messages.error(request, _('Error creating filesystem links for document: %(document)s; %(error)s') % { - 'document': document, 'error': e}) - - if len(documents) == 1: - return HttpResponseRedirect(document.get_absolute_url()) - elif len(documents) > 1: - return HttpResponseRedirect(reverse('document_list')) - - context = { - 'form_display_mode_table': True, - 'form': formset, - } - if len(documents) == 1: - context['object'] = documents[0] - context['title'] = _(u'Edit metadata for document: %s') % ', '.join([unicode(d) for d in documents]) - elif len(documents) > 1: - context['title'] = _(u'Edit metadata for documents: %s') % ', '.join([unicode(d) for d in documents]) - - return render_to_response('generic_form.html', context, - context_instance=RequestContext(request)) - - -def document_multiple_edit_metadata(request): - return document_edit_metadata(request, document_id_list=request.GET.get('id_list', [])) - - def calculate_converter_arguments(document, *args, **kwargs): size = kwargs.pop('size', PREVIEW_SIZE) quality = kwargs.pop('quality', QUALITY_DEFAULT) @@ -762,6 +727,7 @@ def document_page_transformation_delete(request, document_page_transformation_id 'document_page': document_page_transformation.document_page}, 'previous': previous, 'web_theme_hide_menus': True, + 'form_icon': u'pencil_delete.png', }) @@ -779,6 +745,7 @@ def _find_duplicate_list(request, source_document_list=Document.objects.all(), i return render_to_response('generic_confirm.html', { 'previous': previous, 'message': _(u'On large databases this operation may take some time to execute.'), + 'form_icon': u'page_refresh.png', }, context_instance=RequestContext(request)) else: duplicated = [] @@ -807,13 +774,13 @@ def document_clear_transformations(request, document_id=None, document_id_list=N if document_id: documents = [get_object_or_404(Document.objects, pk=document_id)] - post_redirect = reverse('document_view', args=[documents[0].pk]) + post_redirect = reverse('document_view_simple', args=[documents[0].pk]) elif document_id_list: documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')] post_redirect = None else: messages.error(request, _(u'Must provide at least one document.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) + return HttpResponseRedirect(request.META.get('HTTP_REFERER', u'/')) previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', post_redirect or reverse('document_list')))) next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', post_redirect or reverse('document_list')))) @@ -836,6 +803,7 @@ def document_clear_transformations(request, document_id=None, document_id_list=N 'delete_view': True, 'previous': previous, 'next': next, + 'form_icon': u'page_paintbrush.png', } if len(documents) == 1: @@ -852,83 +820,6 @@ def document_multiple_clear_transformations(request): return document_clear_transformations(request, document_id_list=request.GET.get('id_list', [])) -def document_view_simple(request, document_id): - check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - - document = get_object_or_404(Document.objects.select_related(), pk=document_id) - - RecentDocument.objects.add_document_for_user(request.user, document) - - content_form = DocumentContentForm(document=document) - - preview_form = DocumentPreviewForm(document=document) - form_list = [ - { - 'form': preview_form, - 'object': document, - }, - { - 'title':_(u'document properties'), - 'form': content_form, - 'object': document, - }, - ] - - subtemplates_list = [] - if document.tags.count(): - subtemplates_list.append(get_tags_subtemplate(document)) - - if Comment.objects.for_model(document).count(): - subtemplates_list.append(get_comments_subtemplate(document)) - - subtemplates_list.append( - { - 'name': 'generic_list_subtemplate.html', - 'context': { - 'title': _(u'metadata'), - 'object_list': document.documentmetadata_set.all(), - 'extra_columns': [{'name': _(u'value'), 'attribute': 'value'}], - 'hide_link': True, - } - }, - ) - - metadata_groups, errors = document.get_metadata_groups() - if (request.user.is_staff or request.user.is_superuser) and errors: - for error in errors: - messages.warning(request, _(u'Document group query error: %s' % error)) - - if not GROUP_SHOW_EMPTY: - #If GROUP_SHOW_EMPTY is False, remove empty groups from - #dictionary - metadata_groups = dict([(group, data) for group, data in metadata_groups.items() if data]) - - if metadata_groups: - subtemplates_list.append( - { - 'name': 'generic_form_subtemplate.html', - 'context': { - 'title': _(u'document groups (%s)') % len(metadata_groups.keys()), - 'form': MetaDataGroupForm( - groups=metadata_groups, current_document=document, - links=[ - metadata_group_link - ] - ), - 'form_action': reverse('metadatagroup_action'), - 'submit_method': 'GET', - } - } - ) - - return render_to_response('generic_detail.html', { - 'form_list': form_list, - 'object': document, - 'document': document, - 'subtemplates_list': subtemplates_list, - }, context_instance=RequestContext(request)) - - def document_missing_list(request): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) @@ -960,16 +851,11 @@ def document_page_view(request, document_page_id): rotation = int(request.GET.get('rotation', 0)) document_page_form = DocumentPageForm(instance=document_page, zoom=zoom, rotation=rotation) - form_list = [ - { - 'form': document_page_form, - 'title': _(u'details for: %s') % document_page, - }, - ] return render_to_response('generic_detail.html', { - 'form_list': form_list, 'object': document_page, 'web_theme_hide_menus': True, + 'form': document_page_form, + 'title': _(u'details for: %s') % document_page, }, context_instance=RequestContext(request)) @@ -979,16 +865,11 @@ def document_page_text(request, document_page_id): document_page = get_object_or_404(DocumentPage, pk=document_page_id) document_page_form = DocumentPageForm_text(instance=document_page) - form_list = [ - { - 'form': document_page_form, - 'title': _(u'details for: %s') % document_page, - }, - ] return render_to_response('generic_detail.html', { - 'form_list': form_list, 'object': document_page, 'web_theme_hide_menus': True, + 'form': document_page_form, + 'title': _(u'details for: %s') % document_page, }, context_instance=RequestContext(request)) @@ -1018,12 +899,12 @@ def document_page_edit(request, document_page_id): def document_page_navigation_next(request, document_page_id): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', '/')).path) + view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', u'/')).path) document_page = get_object_or_404(DocumentPage, pk=document_page_id) if document_page.page_number >= document_page.document.documentpage_set.count(): messages.warning(request, _(u'There are no more pages in this document')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) + return HttpResponseRedirect(request.META.get('HTTP_REFERER', u'/')) else: document_page = get_object_or_404(DocumentPage, document=document_page.document, page_number=document_page.page_number + 1) return HttpResponseRedirect(reverse(view, args=[document_page.pk])) @@ -1031,12 +912,12 @@ def document_page_navigation_next(request, document_page_id): def document_page_navigation_previous(request, document_page_id): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', '/')).path) + view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', u'/')).path) document_page = get_object_or_404(DocumentPage, pk=document_page_id) if document_page.page_number <= 1: messages.warning(request, _(u'You are already at the first page of this document')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) + return HttpResponseRedirect(request.META.get('HTTP_REFERER', u'/')) else: document_page = get_object_or_404(DocumentPage, document=document_page.document, page_number=document_page.page_number - 1) return HttpResponseRedirect(reverse(view, args=[document_page.pk])) @@ -1044,7 +925,7 @@ def document_page_navigation_previous(request, document_page_id): def document_page_navigation_first(request, document_page_id): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', '/')).path) + view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', u'/')).path) document_page = get_object_or_404(DocumentPage, pk=document_page_id) document_page = get_object_or_404(DocumentPage, document=document_page.document, page_number=1) @@ -1053,7 +934,7 @@ def document_page_navigation_first(request, document_page_id): def document_page_navigation_last(request, document_page_id): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', '/')).path) + view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', u'/')).path) document_page = get_object_or_404(DocumentPage, pk=document_page_id) document_page = get_object_or_404(DocumentPage, document=document_page.document, page_number=document_page.document.documentpage_set.count()) @@ -1070,11 +951,11 @@ def document_list_recent(request): def transform_page(request, document_page_id, zoom_function=None, rotation_function=None): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', '/')).path) + view = resolve_to_name(urlparse.urlparse(request.META.get('HTTP_REFERER', u'/')).path) document_page = get_object_or_404(DocumentPage, pk=document_page_id) # Get the query string from the referer url - query = urlparse.urlparse(request.META.get('HTTP_REFERER', '/')).query + query = urlparse.urlparse(request.META.get('HTTP_REFERER', u'/')).query # Parse the query string and get the zoom value # parse_qs return a dictionary whose values are lists zoom = int(urlparse.parse_qs(query).get('zoom', ['100'])[0]) @@ -1089,7 +970,7 @@ def transform_page(request, document_page_id, zoom_function=None, rotation_funct return HttpResponseRedirect( u'?'.join([ reverse(view, args=[document_page.pk]), - urllib.urlencode({'zoom': zoom, 'rotation': rotation}) + urlencode({'zoom': zoom, 'rotation': rotation}) ]) ) @@ -1126,35 +1007,6 @@ def document_page_rotate_left(request, document_page_id): ) -def metadatagroup_action(request): - action = request.GET.get('action', None) - - if not action: - messages.error(request, _(u'No action selected.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/')) - - return HttpResponseRedirect(action) - - -def metadatagroup_view(request, document_id, metadata_group_id): - check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) - - document = get_object_or_404(Document, pk=document_id) - metadata_group = get_object_or_404(MetadataGroup, pk=metadata_group_id) - - object_list, errors = document.get_metadata_groups(metadata_group) - - return render_to_response('generic_list.html', { - 'object_list': object_list, - 'title': _(u'documents in group: %(group)s, for document: %(document)s') % { - 'group': metadata_group, 'document': document - }, - 'multi_select_as_buttons': True, - 'hide_links': True, - 'ref_object': document - }, context_instance=RequestContext(request)) - - def document_print(request, document_id): check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) diff --git a/apps/dynamic_search/templates/search_results.html b/apps/dynamic_search/templates/search_results.html index e0c0740912..79ded014eb 100644 --- a/apps/dynamic_search/templates/search_results.html +++ b/apps/dynamic_search/templates/search_results.html @@ -6,8 +6,12 @@ {% if form %} {% include "search_results_subtemplate.html" %} {% endif %} - - {% include "generic_list_subtemplate.html" %} + {% if query_string %} + {% include "generic_list_subtemplate.html" %} + {% endif %} + {% if not form and not query_string %} + {% include "generic_list_subtemplate.html" %} + {% endif %} {% endblock %} {% block footer %} diff --git a/apps/filesystem_serving/__init__.py b/apps/filesystem_serving/__init__.py deleted file mode 100644 index 05eb6a38a5..0000000000 --- a/apps/filesystem_serving/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ - - -from permissions.api import register_permissions -from main.api import register_tool - - -FILESYSTEM_SERVING_RECREATE_LINKS = 'recreate_links' - -register_permissions('filesystem_serving', [ - {'name': FILESYSTEM_SERVING_RECREATE_LINKS, 'label':_(u'Recreate filesystem links.')}, -]) - -filesystem_serving_recreate_all_links = {'text': _('recreate index links'), 'view': 'recreate_all_links', 'famfam': 'page_link', 'permissions': {'namespace': 'filesystem_serving', 'permissions': [FILESYSTEM_SERVING_RECREATE_LINKS]}, 'description': _(u'Deletes and creates from scratch all the file system indexing links.')} - -register_tool(filesystem_serving_recreate_all_links, namespace='filesystem_serving', title=_(u'Filesystem')) diff --git a/apps/filesystem_serving/admin.py b/apps/filesystem_serving/admin.py deleted file mode 100644 index a8ee833907..0000000000 --- a/apps/filesystem_serving/admin.py +++ /dev/null @@ -1,11 +0,0 @@ -from django.contrib import admin - -from filesystem_serving.models import DocumentMetadataIndex - - -class DocumentMetadataIndexInline(admin.StackedInline): - model = DocumentMetadataIndex - extra = 1 - classes = ('collapse-open',) - allow_add = True - readonly_fields = ('suffix', 'metadata_index', 'filename') diff --git a/apps/filesystem_serving/api.py b/apps/filesystem_serving/api.py deleted file mode 100644 index 06c0a9227c..0000000000 --- a/apps/filesystem_serving/api.py +++ /dev/null @@ -1,160 +0,0 @@ -import errno -import os - -from django.template.defaultfilters import slugify -from django.utils.translation import ugettext_lazy as _ - -from documents.conf.settings import AVAILABLE_INDEXING_FUNCTIONS - -from filesystem_serving.conf.settings import FILESERVING_ENABLE -from filesystem_serving.conf.settings import FILESERVING_PATH -from filesystem_serving.conf.settings import SLUGIFY_PATHS -from filesystem_serving.conf.settings import MAX_RENAME_COUNT - -from filesystem_serving.models import DocumentMetadataIndex, Document - -if SLUGIFY_PATHS == False: - #Do not slugify path or filenames and extensions - SLUGIFY_FUNCTION = lambda x: x -else: - SLUGIFY_FUNCTION = slugify - - -def document_create_fs_links(document): - warnings = [] - if FILESERVING_ENABLE: - if not document.exists(): - raise Exception(_(u'Not creating metadata indexing, document not found in document storage')) - metadata_dict = {'document': document} - metadata_dict.update(dict([(metadata.metadata_type.name, SLUGIFY_FUNCTION(metadata.value)) for metadata in document.documentmetadata_set.all()])) - - for metadata_index in document.document_type.metadataindex_set.all(): - if metadata_index.enabled: - try: - fabricated_directory = eval(metadata_index.expression, metadata_dict, AVAILABLE_INDEXING_FUNCTIONS) - target_directory = os.path.join(FILESERVING_PATH, fabricated_directory) - try: - os.makedirs(target_directory) - except OSError, exc: - if exc.errno == errno.EEXIST: - pass - else: - raise OSError(_(u'Unable to create metadata indexing directory: %s') % exc) - - next_available_filename(document, metadata_index, target_directory, SLUGIFY_FUNCTION(document.file_filename), SLUGIFY_FUNCTION(document.file_extension)) - except NameError, exc: - warnings.append(_(u'Error in metadata indexing expression: %s') % exc) - #raise NameError() - #This should be a warning not an error - #pass - except Exception, exc: - raise Exception(_(u'Unable to create metadata indexing directory: %s') % exc) - - return warnings - - -def document_delete_fs_links(document): - if FILESERVING_ENABLE: - for document_metadata_index in document.documentmetadataindex_set.all(): - try: - os.unlink(document_metadata_index.filename) - document_metadata_index.delete() - except OSError, exc: - if exc.errno == errno.ENOENT: - #No longer exits, so delete db entry anyway - document_metadata_index.delete() - else: - raise OSError(_(u'Unable to delete metadata indexing symbolic link: %s') % exc) - - path, filename = os.path.split(document_metadata_index.filename) - - #Cleanup directory of dead stuff - #Delete siblings that are dead links - try: - for f in os.listdir(path): - filepath = os.path.join(path, f) - if os.path.islink(filepath): - #Get link's source - source = os.readlink(filepath) - if os.path.isabs(source): - if not os.path.exists(source): - #link's source is absolute and doesn't exit - os.unlink(filepath) - else: - os.unlink(os.path.join(path, filepath)) - elif os.path.isdir(filepath): - #is a directory, try to delete it - try: - os.removedirs(path) - except: - pass - except OSError, exc: - pass - - #Remove the directory if it is empty - try: - os.removedirs(path) - except: - pass - - -def next_available_filename(document, metadata_index, path, filename, extension, suffix=0): - target = filename - if suffix: - target = '_'.join([filename, unicode(suffix)]) - filepath = os.path.join(path, os.extsep.join([target, extension])) - matches = DocumentMetadataIndex.objects.filter(filename=filepath) - if matches.count() == 0: - document_metadata_index = DocumentMetadataIndex( - document=document, metadata_index=metadata_index, - filename=filepath) - try: - os.symlink(document.file.path, filepath) - document_metadata_index.save() - except OSError, exc: - if exc.errno == errno.EEXIST: - #This link should not exist, try to delete it - try: - os.unlink(filepath) - #Try again with same suffix - return next_available_filename(document, metadata_index, path, filename, extension, suffix) - except Exception, exc: - raise Exception(_(u'Unable to create symbolic link, filename clash: %(filepath)s; %(exc)s') % {'filepath': filepath, 'exc': exc}) - else: - raise OSError(_(u'Unable to create symbolic link: %(filepath)s; %(exc)s') % {'filepath': filepath, 'exc': exc}) - - return filepath - else: - if suffix > MAX_RENAME_COUNT: - raise Exception(_(u'Maximum rename count reached, not creating symbolic link')) - return next_available_filename(document, metadata_index, path, filename, extension, suffix + 1) - - -#TODO: diferentiate between evaluation error and filesystem errors -def do_recreate_all_links(raise_exception=True): - errors = [] - warnings = [] - - for document in Document.objects.all(): - try: - document_delete_fs_links(document) - except NameError, e: - warnings.append('%s: %s' % (document, e)) - except Exception, e: - if raise_exception: - raise Exception(e) - else: - errors.append('%s: %s' % (document, e)) - - for document in Document.objects.all(): - try: - create_warnings = document_create_fs_links(document) - except Exception, e: - if raise_exception: - raise Exception(e) - else: - errors.append('%s: %s' % (document, e)) - - for warning in create_warnings: - warnings.append('%s: %s' % (document, warning)) - return errors, warnings diff --git a/apps/filesystem_serving/conf/settings.py b/apps/filesystem_serving/conf/settings.py deleted file mode 100644 index e014493a6d..0000000000 --- a/apps/filesystem_serving/conf/settings.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Configuration options for the filesystem_serving app""" - -from smart_settings.api import register_settings - -register_settings( - namespace=u'filesystem_serving', - module=u'filesystem_serving.conf.settings', - settings=[ - {'name': u'SLUGIFY_PATHS', 'global_name': u'FILESYSTEM_SLUGIFY_PATHS', 'default': False}, - {'name': u'MAX_RENAME_COUNT', 'global_name': u'FILESYSTEM_MAX_RENAME_COUNT', 'default': 200}, - {'name': u'FILESERVING_PATH', 'global_name': u'FILESYSTEM_FILESERVING_PATH', 'default': u'/tmp/mayan/documents', 'exists': True}, - {'name': u'FILESERVING_ENABLE', 'global_name': u'FILESYSTEM_FILESERVING_ENABLE', 'default': True} - ] -) diff --git a/apps/filesystem_serving/locale/es/LC_MESSAGES/django.mo b/apps/filesystem_serving/locale/es/LC_MESSAGES/django.mo deleted file mode 100644 index 1193f50abc..0000000000 Binary files a/apps/filesystem_serving/locale/es/LC_MESSAGES/django.mo and /dev/null differ diff --git a/apps/filesystem_serving/locale/es/LC_MESSAGES/django.po b/apps/filesystem_serving/locale/es/LC_MESSAGES/django.po deleted file mode 100644 index e4ec8aaa35..0000000000 --- a/apps/filesystem_serving/locale/es/LC_MESSAGES/django.po +++ /dev/null @@ -1,118 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2011-05-06 13:29-0400\n" -"PO-Revision-Date: 2011-05-06 13:30\n" -"Last-Translator: Roberto Rosario \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Language: \n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Translated-Using: django-rosetta 0.6.0\n" - -#: __init__.py:11 -msgid "Recreate filesystem links." -msgstr "Recrear vínculos de sistema de archivos." - -#: __init__.py:14 -msgid "recreate index links" -msgstr "recrear enlaces índice" - -#: __init__.py:14 -msgid "Deletes and creates from scratch all the file system indexing links." -msgstr "" -"Borra y crea de la nada todos los enlaces de indexación del sistema de" -" archivos." - -#: __init__.py:16 -msgid "Filesystem" -msgstr "Sistema de archivos" - -#: api.py:27 -msgid "Not creating metadata indexing, document not found in document storage" -msgstr "" -"No de creara indexación de metadatos, el documento no se encuentran en" -" almacenamiento de documentos" - -#: api.py:42 api.py:51 -#, python-format -msgid "Unable to create metadata indexing directory: %s" -msgstr "No se puedo crear el directorio de indexación de metadatos: %s" - -#: api.py:46 -#, python-format -msgid "Error in metadata indexing expression: %s" -msgstr "Error en la expresión de indexación de metadatos: %s" - -#: api.py:67 -#, python-format -msgid "Unable to delete metadata indexing symbolic link: %s" -msgstr "" -"No se puede eliminar el enlace simbólico de indexación de metadatos: " -"%s" - -#: api.py:122 -#, python-format -msgid "Unable to create symbolic link, filename clash: %(filepath)s; %(exc)s" -msgstr "" -"No se puede crear el enlace simbólico, coque de nombre de archivo: " -"%(filepath)s; %(exc)s " - -#: api.py:124 -#, python-format -msgid "Unable to create symbolic link: %(filepath)s; %(exc)s" -msgstr "No se puedo crear enlace simbólico: %(filepath)s; %(exc)s " - -#: api.py:129 -msgid "Maximum rename count reached, not creating symbolic link" -msgstr "" -"Conteo máxima de cambio de nombre alcanzado, no se creará el enlaces " -"simbólico" - -#: models.py:8 -msgid "document" -msgstr "documento" - -#: models.py:9 -msgid "metadata index" -msgstr "índice de metadatos" - -#: models.py:10 -msgid "filename" -msgstr "nombre de archivo" - -#: models.py:11 -msgid "suffix" -msgstr "sufijo" - -#: models.py:17 -msgid "document metadata index" -msgstr "índice de metadatos de document" - -#: models.py:18 -msgid "document metadata indexes" -msgstr "índices de metadatos de documentos" - -#: views.py:23 -msgid "On large databases this operation may take some time to execute." -msgstr "" -"En bases de datos de gran tamaño esta operación puede tardar algún " -"tiempo en ejecutarse." - -#: views.py:28 -msgid "Filesystem links re-creation completed successfully." -msgstr "Re creación de enlaces de sistema de archivos completó correctamente." - -#: views.py:33 -#, python-format -msgid "Filesystem links re-creation error: %s" -msgstr "Error de re creación de enlaces de sistema de archivos: %s" diff --git a/apps/filesystem_serving/models.py b/apps/filesystem_serving/models.py deleted file mode 100644 index 3e3294d5e3..0000000000 --- a/apps/filesystem_serving/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.db import models -from django.utils.translation import ugettext_lazy as _ - -from documents.models import Document, MetadataIndex - - -class DocumentMetadataIndex(models.Model): - document = models.ForeignKey(Document, verbose_name=_(u'document')) - metadata_index = models.ForeignKey(MetadataIndex, verbose_name=_(u'metadata index')) - filename = models.CharField(max_length=255, verbose_name=_(u'filename')) - suffix = models.PositiveIntegerField(default=0, verbose_name=_(u'suffix')) - - def __unicode__(self): - return unicode(self.filename) - - class Meta: - verbose_name = _(u'document metadata index') - verbose_name_plural = _(u'document metadata indexes') diff --git a/apps/filesystem_serving/urls.py b/apps/filesystem_serving/urls.py deleted file mode 100644 index ec434df234..0000000000 --- a/apps/filesystem_serving/urls.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.conf.urls.defaults import patterns, url - -urlpatterns = patterns('filesystem_serving.views', - url(r'^recreate_all_links/$', 'recreate_all_links', (), 'recreate_all_links'), -) diff --git a/apps/filesystem_serving/views.py b/apps/filesystem_serving/views.py deleted file mode 100644 index 9f9c83d906..0000000000 --- a/apps/filesystem_serving/views.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.utils.translation import ugettext_lazy as _ -from django.http import HttpResponseRedirect -from django.shortcuts import render_to_response -from django.template import RequestContext -from django.contrib import messages - -from permissions.api import check_permissions - -from filesystem_serving import FILESYSTEM_SERVING_RECREATE_LINKS -from filesystem_serving.api import do_recreate_all_links - - -def recreate_all_links(request): - check_permissions(request.user, 'filesystem_serving', [FILESYSTEM_SERVING_RECREATE_LINKS]) - - previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', None))) - next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', None))) - - if request.method != 'POST': - return render_to_response('generic_confirm.html', { - 'previous': previous, - 'next': next, - 'message': _(u'On large databases this operation may take some time to execute.'), - }, context_instance=RequestContext(request)) - else: - try: - errors, warnings = do_recreate_all_links() - messages.success(request, _(u'Filesystem links re-creation completed successfully.')) - for warning in warnings: - messages.warning(request, warning) - - except Exception, e: - messages.error(request, _(u'Filesystem links re-creation error: %s') % e) - - return HttpResponseRedirect(next) diff --git a/apps/folders/__init__.py b/apps/folders/__init__.py index d97f18fa0c..bf6aaba5da 100644 --- a/apps/folders/__init__.py +++ b/apps/folders/__init__.py @@ -6,7 +6,7 @@ from navigation.api import register_sidebar_template from folders.models import Folder -folder_list = {'text': _(u'folder list'), 'view': 'folder_list', 'famfam': 'folder'} +folder_list = {'text': _(u'folder list'), 'view': 'folder_list', 'famfam': 'folder_user'} folder_create = {'text': _('create folder'), 'view': 'folder_create', 'famfam': 'folder_add'} folder_edit = {'text': _('edit'), 'view': 'folder_edit', 'args': 'object.id', 'famfam': 'folder_edit'} folder_delete = {'text': _('delete'), 'view': 'folder_delete', 'args': 'object.id', 'famfam': 'folder_delete'} @@ -21,6 +21,6 @@ register_links(['folder_edit', 'folder_delete', 'folder_list', 'folder_create'], register_menu([ {'text': _('folders'), 'view': 'folder_list', 'links': [ folder_list, folder_create - ], 'famfam': 'folder', 'position': 2}]) + ], 'famfam': 'folder_user', 'position': 2}]) -register_sidebar_template(['document_view', 'document_view_simple'], 'folders_sidebar_template.html') +register_sidebar_template(['document_view_advanced', 'document_view_simple'], 'folders_sidebar_template.html') diff --git a/apps/folders/forms.py b/apps/folders/forms.py index 4354ba9bbe..a199b14d9e 100644 --- a/apps/folders/forms.py +++ b/apps/folders/forms.py @@ -1,5 +1,5 @@ from django import forms -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from folders.models import Folder diff --git a/apps/folders/views.py b/apps/folders/views.py index 487205e60f..7b8fb9560f 100644 --- a/apps/folders/views.py +++ b/apps/folders/views.py @@ -1,4 +1,4 @@ -from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy as _ from django.http import HttpResponseRedirect from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext @@ -105,7 +105,8 @@ def folder_delete(request, folder_id): 'previous': previous, 'next': next, 'object': folder, - 'title': _(u'Are you sure you with to delete the folder: %s?') % folder + 'title': _(u'Are you sure you with to delete the folder: %s?') % folder, + 'form_icon': u'folder_delete.png', } return render_to_response('generic_confirm.html', context, diff --git a/apps/grouping/__init__.py b/apps/grouping/__init__.py new file mode 100644 index 0000000000..0fcd10bc36 --- /dev/null +++ b/apps/grouping/__init__.py @@ -0,0 +1,11 @@ +from django.utils.translation import ugettext_lazy as _ + +from navigation.api import register_links + +from documents.literals import PERMISSION_DOCUMENT_CREATE, PERMISSION_DOCUMENT_VIEW + +document_group_link = {'text': _(u'group actions'), 'view': 'document_group_view', 'famfam': 'page_go', 'permissions': {'namespace': 'documents', 'permissions': [PERMISSION_DOCUMENT_VIEW]}} +document_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]}} +document_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]}} + +register_links(['document_group_view'], [document_group_back_to_document, document_group_create_sibling], menu_name='sidebar') diff --git a/apps/grouping/admin.py b/apps/grouping/admin.py new file mode 100644 index 0000000000..092dd7a420 --- /dev/null +++ b/apps/grouping/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin + +from grouping.models import DocumentGroup, DocumentGroupItem + + +class DocumentGroupItemInline(admin.StackedInline): + model = DocumentGroupItem + extra = 1 + classes = ('collapse-open',) + allow_add = True + + +class DocumentGroupAdmin(admin.ModelAdmin): + inlines = [DocumentGroupItemInline] + +admin.site.register(DocumentGroup, DocumentGroupAdmin) diff --git a/apps/grouping/conf/__init__.py b/apps/grouping/conf/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/grouping/conf/settings.py b/apps/grouping/conf/settings.py new file mode 100644 index 0000000000..be352e71a1 --- /dev/null +++ b/apps/grouping/conf/settings.py @@ -0,0 +1,13 @@ +"""Configuration options for the grouping app""" + +from django.utils.translation import ugettext_lazy as _ + +from smart_settings.api import register_settings + +register_settings( + namespace=u'grouping', + module=u'grouping.conf.settings', + settings=[ + {'name': u'SHOW_EMPTY_GROUPS', 'global_name': u'GROUPING_SHOW_EMPTY_GROUPS', 'default': True}, + ] +) diff --git a/apps/grouping/forms.py b/apps/grouping/forms.py new file mode 100644 index 0000000000..f3599c39c3 --- /dev/null +++ b/apps/grouping/forms.py @@ -0,0 +1,88 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext +from django.core.urlresolvers import reverse +from django.utils.safestring import mark_safe +from django.template.defaultfilters import capfirst +from django.conf import settings + +from tags.widgets import get_tags_inline_widget + + +class DocumentGroupImageWidget(forms.widgets.Widget): + def render(self, name, value, attrs=None): + output = [] + if value['links']: + output.append(u'') + + output.append(u'
') + for document in value['group_data']: + tags_template = get_tags_inline_widget(document) + + output.append( + u'''
+
%(document_name)s
+
%(page_string)s: %(document_pages)d
+ %(tags_template)s + + +
''' % { + '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'
') + output.append( + u'
%s' % + ugettext(u'Click on the image for full size view of the first page.')) + + return mark_safe(u''.join(output)) + + +class DocumentDataGroupForm(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(DocumentDataGroupForm, self).__init__(*args, **kwargs) + for group, data in groups.items(): + self.fields['preview-%s' % group] = forms.CharField( + widget=DocumentGroupImageWidget(), + label=u'%s (%d)' % (unicode(data['title']), len(data['documents'])), + required=False, + initial={ + 'group': group, + 'group_data': data['documents'], + 'current_document': current_document, + 'links': links + } + ) diff --git a/apps/grouping/literals.py b/apps/grouping/literals.py new file mode 100644 index 0000000000..d40935b621 --- /dev/null +++ b/apps/grouping/literals.py @@ -0,0 +1,27 @@ +from django.utils.translation import ugettext_lazy as _ + +INCLUSION_AND = u'&' +INCLUSION_OR = u'|' + +INCLUSION_CHOICES = ( + (INCLUSION_AND, _(u'and')), + (INCLUSION_OR, _(u'or')), +) + +OPERATOR_CHOICES = ( + (u'exact', _(u'is equal to')), + (u'iexact', _(u'is equal to (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 to')), + (u'lt', _(u'is less than')), + (u'lte', _(u'is less than or equal to')), + (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)')), +) diff --git a/apps/grouping/managers.py b/apps/grouping/managers.py new file mode 100644 index 0000000000..4ed9a174d4 --- /dev/null +++ b/apps/grouping/managers.py @@ -0,0 +1,82 @@ +from django.db import models +from django.db.models import Q + +from metadata.classes import MetadataObject +from documents.models import Document + +from grouping.literals import INCLUSION_AND, INCLUSION_OR + + +class DocumentGroupManager(models.Manager): + def get_groups_for(self, document, group_obj=None): + errors = [] + document_groups = {} + metadata_dict = {} + for document_metadata in document.documentmetadata_set.all(): + metadata_dict[document_metadata.metadata_type.name] = document_metadata.value + eval_dict = {} + eval_dict['document'] = document + eval_dict['metadata'] = MetadataObject(metadata_dict) + + if group_obj: + groups_qs = self.model.objects.filter(Q(enabled=True) & Q(pk=group_obj.pk)) + else: + groups_qs = self.model.objects.filter(enabled=True) + + for group in groups_qs: + total_query = Q() + for item in group.documentgroupitem_set.filter(enabled=True): + cls, attribute = item.foreign_document_data.lower().split(u'.') + try: + if cls == u'metadata': + value_query = Q(**{'documentmetadata__value__%s' % item.operator: eval(item.expression, eval_dict)}) + if item.negated: + query = (Q(documentmetadata__metadata_type__name=attribute) & ~value_query) + else: + query = (Q(documentmetadata__metadata_type__name=attribute) & value_query) + if item.inclusion == INCLUSION_AND: + total_query &= query + elif item.inclusion == INCLUSION_OR: + total_query |= query + + elif cls == u'document': + value_query = Q(**{ + '%s__%s' % (attribute, item.operator): eval(item.expression, eval_dict) + }) + if item.negated: + query = ~value_query + else: + query = 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: + try: + document_qs = Document.objects.filter(total_query) + document_groups[group] = {'documents': document_qs.order_by('file_filename') or []} + except Exception, e: + document_groups[group] = {'documents': []} + errors.append(e) + else: + document_groups[group] = {'documents': []} + + if group.dynamic_title: + try: + document_groups[group]['title'] = eval(group.dynamic_title, eval_dict) + except Exception, e: + document_groups[group]['title'] = 'Error; %s' % e + else: + document_groups[group]['title'] = group.title + + if group_obj: + # Return a single group if documents even if there were + # many matches + return document_groups[group_obj], errors + + return document_groups, errors diff --git a/apps/grouping/models.py b/apps/grouping/models.py new file mode 100644 index 0000000000..8111d74586 --- /dev/null +++ b/apps/grouping/models.py @@ -0,0 +1,40 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from grouping.managers import DocumentGroupManager +from grouping.literals import OPERATOR_CHOICES, INCLUSION_AND, \ + INCLUSION_CHOICES + + +class DocumentGroup(models.Model): + title = models.CharField(max_length=96, verbose_name=_(u'title')) + dynamic_title = models.CharField(blank=True, max_length=96, verbose_name=_(u'dynamic title')) + enabled = models.BooleanField(default=True, verbose_name=_(u'enabled')) + + objects = DocumentGroupManager() + + def __unicode__(self): + return self.title + + class Meta: + verbose_name = _(u'document group') + verbose_name_plural = _(u'document groups') + + +class DocumentGroupItem(models.Model): + metadata_group = models.ForeignKey(DocumentGroup, 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.')) + foreign_document_data = models.CharField(max_length=32, verbose_name=_(u'foreign document data'), help_text=_(u'This represents the metadata of all other documents. Available objects: `document.` and `metadata.`.')) + operator = models.CharField(max_length=16, choices=OPERATOR_CHOICES) + + #local_document_data = models.ForeignKey(MetadataType, related_name='metadata_type_local', verbose_name=_(u'local metadata'), help_text=_(u'This represents the metadata of the current document.')) + expression = models.TextField(verbose_name=_(u'expression'), help_text=_(u'This expression will be evaluated against the current selected document. The document metadata is available as variables `metadata` and document properties under the variable `document`.')) + 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 foreign %s %s %s %s' % (u'x' if self.enabled else u' ', self.get_inclusion_display(), self.foreign_document_data, _(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') diff --git a/apps/grouping/tests.py b/apps/grouping/tests.py new file mode 100644 index 0000000000..2247054b35 --- /dev/null +++ b/apps/grouping/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/apps/grouping/urls.py b/apps/grouping/urls.py new file mode 100644 index 0000000000..1e54277143 --- /dev/null +++ b/apps/grouping/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('grouping.views', + url(r'^action/$', 'document_group_action', (), 'document_group_action'), + url(r'^document/(?P\d+)/group/(?P\d+)/$', 'document_group_view', (), 'document_group_view'), +) diff --git a/apps/grouping/utils.py b/apps/grouping/utils.py new file mode 100644 index 0000000000..00a81c6ab0 --- /dev/null +++ b/apps/grouping/utils.py @@ -0,0 +1,36 @@ +from django.utils.translation import ugettext_lazy as _ +from django.contrib import messages +from django.core.urlresolvers import reverse + +from grouping.models import DocumentGroup +from grouping.conf.settings import SHOW_EMPTY_GROUPS +from grouping.forms import DocumentDataGroupForm +from grouping import document_group_link + + +def get_document_group_subtemplate(request, document): + document_groups, errors = DocumentGroup.objects.get_groups_for(document) + if (request.user.is_staff or request.user.is_superuser) and errors: + for error in errors: + messages.warning(request, _(u'Document group query error: %s' % error)) + + if not SHOW_EMPTY_GROUPS: + #If GROUP_SHOW_EMPTY is False, remove empty groups from + #dictionary + document_groups = dict([(group, data) for group, data in document_groups.items() if data['documents']]) + + if document_groups: + return { + 'name': 'generic_form_subtemplate.html', + 'context': { + 'title': _(u'document groups (%s)') % len(document_groups.keys()), + 'form': DocumentDataGroupForm( + groups=document_groups, current_document=document, + links=[document_group_link] + ), + 'form_action': reverse('document_group_action'), + 'submit_method': 'GET', + } + } + else: + return None diff --git a/apps/grouping/views.py b/apps/grouping/views.py new file mode 100644 index 0000000000..48179b89d4 --- /dev/null +++ b/apps/grouping/views.py @@ -0,0 +1,40 @@ +from django.utils.translation import ugettext_lazy as _ +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext + +from documents.literals import PERMISSION_DOCUMENT_VIEW +from documents.models import Document +from permissions.api import check_permissions + +from grouping.models import DocumentGroup + + +def document_group_action(request): + action = request.GET.get('action', None) + + if not action: + messages.error(request, _(u'No action selected.')) + return HttpResponseRedirect(request.META.get('HTTP_REFERER', u'/')) + + return HttpResponseRedirect(action) + + +def document_group_view(request, document_id, document_group_id): + check_permissions(request.user, 'documents', [PERMISSION_DOCUMENT_VIEW]) + + document = get_object_or_404(Document, pk=document_id) + document_group = get_object_or_404(DocumentGroup, pk=document_group_id) + object_list, errors = DocumentGroup.objects.get_groups_for(document, document_group) + #object_list, errors = document.get_metadata_groups(document_group) + + return render_to_response('generic_list.html', { + 'object_list': object_list['documents'], + 'title': _(u'documents in group: %(group)s') % { + 'group': object_list['title'] + }, + 'multi_select_as_buttons': True, + 'hide_links': True, + 'ref_object': document + }, context_instance=RequestContext(request)) diff --git a/apps/main/__init__.py b/apps/main/__init__.py index e97fa7321b..5c0e3b53f0 100644 --- a/apps/main/__init__.py +++ b/apps/main/__init__.py @@ -20,7 +20,7 @@ sentry = {'text': _(u'sentry'), 'url': '/sentry', 'famfam': 'bug', 'condition': __version_info__ = { 'major': 0, - 'minor': 6, + 'minor': 7, 'micro': 0, 'releaselevel': 'final', 'serial': 0 diff --git a/apps/main/templates/about.html b/apps/main/templates/about.html index 0e129a8e8b..76e3e70830 100644 --- a/apps/main/templates/about.html +++ b/apps/main/templates/about.html @@ -5,14 +5,23 @@ {% block title %} :: {% trans "About this program" %}{% endblock %} {% block content %} -
-

- {% project_name %} ({% trans "Version" %} {% app_version "main" %})

- {% trans 'Open source, Django based document manager with custom metadata indexing, file serving integration and OCR capabilities' %}

- http://www.github.com/rosarior/mayan/

-
{% trans "Released under the GPL V3 License" %} -

-
+
+

{% project_name %} ({% trans "Version" %} {% app_version "main" %})

+

+ {% trans "Open source, Django based electronic document manager with custom metadata, indexing, tagging, file serving integration and OCR capabilities" %} +

+

+ http://bit.ly/mayan-edms +

+

+ http://www.github.com/rosarior/mayan/ +

+

+ {% trans "Released under the GPL V3 License" %} +

+
+ +
{% endblock %} {% block footer %} diff --git a/apps/main/templates/base.html b/apps/main/templates/base.html index 28c13164ca..34045f0731 100644 --- a/apps/main/templates/base.html +++ b/apps/main/templates/base.html @@ -26,11 +26,10 @@ {% block web_theme_project_name %}{% project_name %}{% if debug %} {% trans "(DEBUG Mode)" %} {% endif %}{% endblock %} {% block web_theme_stylesheets %} - - - {##} + +