diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index 67f0cfe77b..def6a6f508 100644 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -11,6 +11,10 @@ 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 +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_CREATE = 'document_create' PERMISSION_DOCUMENT_PROPERTIES_EDIT = 'document_properties_edit' @@ -72,17 +76,13 @@ 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_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_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') @@ -109,8 +109,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 215923a88e..94ff09a3c1 100644 --- a/apps/documents/admin.py +++ b/apps/documents/admin.py @@ -3,8 +3,8 @@ from django.contrib import admin from metadata.admin import DocumentMetadataInline from documents.models import DocumentType, Document, \ - DocumentTypeFilename, MetadataIndex, DocumentPage, DocumentGroup, \ - DocumentGroupItem, DocumentPageTransformation, RecentDocument + DocumentTypeFilename, MetadataIndex, DocumentPage, \ + DocumentPageTransformation, RecentDocument from filesystem_serving.admin import DocumentMetadataIndexInline @@ -48,18 +48,6 @@ class DocumentAdmin(admin.ModelAdmin): list_display = ('uuid', 'file_filename', 'file_extension') -class DocumentGroupItemInline(admin.StackedInline): - model = DocumentGroupItem - extra = 1 - classes = ('collapse-open',) - allow_add = True - - -class DocumentGroupAdmin(admin.ModelAdmin): - inlines = [DocumentGroupItemInline] - filter_horizontal = ['document_type'] - - class RecentDocumentAdmin(admin.ModelAdmin): model = RecentDocument list_display = ('user', 'document', 'datetime_accessed') @@ -70,7 +58,6 @@ class RecentDocumentAdmin(admin.ModelAdmin): admin.site.register(DocumentType, DocumentTypeAdmin) admin.site.register(Document, DocumentAdmin) -admin.site.register(DocumentGroup, DocumentGroupAdmin) 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 cae9ccc2c2..6c9cdd801e 100644 --- a/apps/documents/conf/settings.py +++ b/apps/documents/conf/settings.py @@ -60,7 +60,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 e5a1d9795d..8703822b49 100644 --- a/apps/documents/forms.py +++ b/apps/documents/forms.py @@ -118,7 +118,7 @@ class ImageWidget(forms.widgets.Widget): for page in value.documentpage_set.all(): output.append( - u'''
+ u'''
%(page_string)s %(page)s
@@ -325,85 +325,6 @@ class DocumentCreateWizard(BoundFormWizard): return HttpResponseRedirect(url) -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'''
-
%(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 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 - } - ) - - class PrintForm(forms.Form): page_size = forms.ChoiceField(choices=PAGE_SIZE_CHOICES, initial=DEFAULT_PAPER_SIZE, label=_(u'Page size'), required=False) custom_page_width = forms.CharField(label=_(u'Custom page width'), required=False) 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/managers.py b/apps/documents/managers.py deleted file mode 100644 index 4888f7cd08..0000000000 --- a/apps/documents/managers.py +++ /dev/null @@ -1,52 +0,0 @@ -from django.db import models - -#from documents.models import DocumentGroup -from metadata.classes import MetadataObject - - -class DocumentGroupManager(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[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 = DocumentGroup.objects.filter((Q(document_type=document.document_type) | Q(document_type=None)) & Q(enabled=True) & Q(pk=group_obj.pk)) - #else: - # groups_qs = DocumentGroup.objects.filter((Q(document_type=document.document_type) | Q(document_type=None)) & Q(enabled=True)) - groups_qs=[] - - 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, eval_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 diff --git a/apps/documents/models.py b/apps/documents/models.py index dbb39d752d..6491736632 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -24,7 +24,6 @@ from documents.conf.settings import STORAGE_BACKEND from documents.conf.settings import AVAILABLE_TRANSFORMATIONS from documents.conf.settings import DEFAULT_TRANSFORMATIONS from documents.conf.settings import RECENT_COUNT -from documents.managers import DocumentGroupManager def get_filename_from_uuid(instance, filename): filename, extension = os.path.splitext(filename) @@ -187,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 DocumentGroup.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: @@ -276,68 +272,7 @@ class DocumentPage(models.Model): return ' '.join(transformation_list), warnings -class DocumentGroup(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.')) - label = models.CharField(max_length=32, verbose_name=_(u'label')) - enabled = models.BooleanField(default=True, verbose_name=_(u'enabled')) - objects = DocumentGroupManager() - - 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)')), -) - -#LOCAL_SOURCE_CHOICES = ( -# (u' - -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_metadata_type = models.ForeignKey(MetadataType, related_name='metadata_type_foreign', verbose_name=_(u'foreign metadata'), help_text=_(u'This represents the metadata of all other documents.')) - operator = models.CharField(max_length=16, choices=OPERATOR_CHOICES) - - #local_metadata_type = 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 %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()]) diff --git a/apps/documents/urls.py b/apps/documents/urls.py index e843201ee6..4b88aace3e 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -34,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'), @@ -61,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 4347707012..979273fb60 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -17,29 +17,31 @@ 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 filesystem_serving.api import document_create_fs_links, document_delete_fs_links +from filesystem_serving.conf.settings import FILESERVING_ENABLE +from filetransfers.api import serve_file +from grouping.models import DocumentGroup +from grouping import document_group_link +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 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 @@ -49,7 +51,7 @@ 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_VIEW, \ PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_DOWNLOAD, \ @@ -60,13 +62,12 @@ from documents.forms import DocumentTypeSelectForm, DocumentCreateWizard, \ DocumentForm, DocumentForm_edit, DocumentForm_view, \ StagingDocumentForm, DocumentPreviewForm, \ DocumentPageForm, DocumentPageTransformationForm, \ - DocumentContentForm, DocumentPageForm_edit, MetaDataGroupForm, \ + DocumentContentForm, DocumentPageForm_edit, \ DocumentPageForm_text, PrintForm, MetadataSelectionForm from documents.models import Document, DocumentType, DocumentPage, \ - DocumentPageTransformation, RecentDocument, DocumentGroup + 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 @@ -314,33 +315,10 @@ def document_view_simple(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 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 document_group_subtemplate: + subtemplates_list.append(document_group_subtemplate) return render_to_response('generic_detail.html', { 'object': document, @@ -413,30 +391,10 @@ def document_view_advanced(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 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 document_group_subtemplate: + subtemplates_list.append(document_group_subtemplate) if FILESERVING_ENABLE: subtemplates_list.append({ @@ -1057,35 +1015,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', u'/')) - - 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/grouping/__init__.py b/apps/grouping/__init__.py new file mode 100644 index 0000000000..b36e41abf3 --- /dev/null +++ b/apps/grouping/__init__.py @@ -0,0 +1,23 @@ +from django.utils.translation import ugettext_lazy as _ +#from django.core.urlresolvers import reverse +#from django.conf import settings + +from navigation.api import register_links, register_menu, \ + register_model_list_columns, register_multi_item_links +from main.api import register_diagnostic, register_tool +from permissions.api import register_permissions +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 +from documents.literals import PERMISSION_DOCUMENT_CREATE, PERMISSION_DOCUMENT_VIEW +#from documents import document_multiple_clear_transformations + +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_multi_item_links(['document_group_view'], [document_multiple_clear_transformations, document_multiple_delete]) + +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..989a88043b --- /dev/null +++ b/apps/grouping/forms.py @@ -0,0 +1,103 @@ +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, \ +# DocumentPage, DocumentPageTransformation + + +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..ec11f53310 --- /dev/null +++ b/apps/grouping/managers.py @@ -0,0 +1,86 @@ +from django.db import models +from django.db.models import Q + +from metadata.classes import MetadataObject +from metadata.models import DocumentMetadata +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 = (Q(metadata_type__name=attribute) & ~value_query) + query = ~value_query + else: + #query = (Q(metadata_type__name=attribute) & value_query) + 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() + print 'total_query', total_query + 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..f92665958c --- /dev/null +++ b/apps/grouping/models.py @@ -0,0 +1,42 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from documents.models import Document + +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..bec63324f7 --- /dev/null +++ b/apps/grouping/views.py @@ -0,0 +1,41 @@ +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 documents.views import document_list +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/metadata/__init__.py b/apps/metadata/__init__.py index f3bfe8c528..8ebc25e07f 100644 --- a/apps/metadata/__init__.py +++ b/apps/metadata/__init__.py @@ -25,4 +25,4 @@ metadata_remove = {'text': _(u'remove metadata'), 'view': 'metadata_remove', 'ar metadata_multiple_remove = {'text': _(u'remove metadata'), 'view': 'metadata_multiple_remove', 'famfam': 'xhtml_delete', 'permissions': {'namespace': 'metadata', 'permissions': [PERMISSION_METADATA_DOCUMENT_REMOVE]}} register_links(Document, [metadata_add, metadata_edit, metadata_remove]) -register_multi_item_links(['metadatagroup_view', 'document_list', 'document_list_recent'], [metadata_multiple_add, metadata_multiple_edit, metadata_multiple_remove]) +register_multi_item_links(['document_datagroup_view', 'document_list', 'document_list_recent'], [metadata_multiple_add, metadata_multiple_edit, metadata_multiple_remove]) diff --git a/settings.py b/settings.py index 1591f996bd..1296bde3f3 100644 --- a/settings.py +++ b/settings.py @@ -146,6 +146,7 @@ INSTALLED_APPS = ( 'document_comments', 'user_management', 'documents', + 'grouping', ) TEMPLATE_CONTEXT_PROCESSORS = ( @@ -212,8 +213,8 @@ TEMPLATE_CONTEXT_PROCESSORS = ( #DOCUMENTS_ZOOM_MIN_LEVEL = 50 #DOCUMENTS_ROTATION_STEP = 90 -# Groups -#DOCUMENTS_GROUP_SHOW_EMPTY = True +#------------- Groups -------------------- +#GROUPING_SHOW_EMPTY_GROUPS = True #------------ Filesystem serving -------------- #FILESYSTEM_FILESERVING_ENABLE = True #FILESYSTEM_FILESERVING_PATH = u'/tmp/mayan/documents' diff --git a/urls.py b/urls.py index afc451f855..fee8c9620d 100644 --- a/urls.py +++ b/urls.py @@ -22,6 +22,7 @@ urlpatterns = patterns('', (r'^user_management/', include('user_management.urls')), (r'^settings/', include('smart_settings.urls')), (r'^metadata/', include('metadata.urls')), + (r'^grouping/', include('grouping.urls')), )