Finished conversion of Mayan to the new document grouping app and paradigm

This commit is contained in:
Roberto Rosario
2011-05-12 04:03:05 -04:00
parent 06bf7e8c50
commit bc252d4ae4
24 changed files with 461 additions and 321 deletions

23
apps/grouping/__init__.py Normal file
View File

@@ -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')

16
apps/grouping/admin.py Normal file
View File

@@ -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)

View File

View File

@@ -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},
]
)

103
apps/grouping/forms.py Normal file
View File

@@ -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'<div class="group navform wat-cf">')
for link in value['links']:
output.append(u'''
<button class="button" type="submit" name="action" value="%(action)s">
<span class="famfam active famfam-%(famfam)s"></span>%(text)s
</button>
''' % {
'famfam': link.get('famfam', u'link'),
'text': capfirst(link['text']),
'action': reverse('document_group_view', args=[value['current_document'].pk, value['group'].pk])
})
output.append(u'</div>')
output.append(u'<div style="white-space:nowrap; overflow: auto;">')
for document in value['group_data']:
tags_template = get_tags_inline_widget(document)
output.append(
u'''<div style="display: inline-block; margin: 0px 10px 10px 10px; %(current)s">
<div class="tc">%(document_name)s</div>
<div class="tc">%(page_string)s: %(document_pages)d</div>
%(tags_template)s
<div class="tc">
<a rel="group_%(group_id)d_documents_gallery" class="fancybox-noscaling" href="%(view_url)s">
<img class="lazy-load" style="border: 1px solid black; margin: 10px;" src="%(media_url)s/images/ajax-loader.gif" data-href="%(img)s" alt="%(string)s" />
<noscript>
<img style="border: 1px solid black; margin: 10px;" src="%(img)s" alt="%(string)s" />
</noscript>
</a>
</div>
<div class="tc">
<a href="%(url)s"><span class="famfam active famfam-page_go"></span>%(details_string)s</a>
</div>
</div>''' % {
'url': reverse('document_view_simple', args=[document.pk]),
'img': reverse('document_preview_multipage', args=[document.pk]),
'current': u'border: 5px solid black; padding: 3px;' if value['current_document'] == document else u'',
'view_url': reverse('document_display', args=[document.pk]),
'document_pages': document.documentpage_set.count(),
'page_string': ugettext(u'Pages'),
'details_string': ugettext(u'Select'),
'group_id': value['group'].pk,
'document_name': document,
'media_url': settings.MEDIA_URL,
'tags_template': tags_template if tags_template else u'',
'string': _(u'group document'),
})
output.append(u'</div>')
output.append(
u'<br /><span class="famfam active famfam-magnifier"></span>%s' %
ugettext(u'Click on the image for full size view of the first page.'))
return mark_safe(u''.join(output))
class 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
}
)

27
apps/grouping/literals.py Normal file
View File

@@ -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)')),
)

86
apps/grouping/managers.py Normal file
View File

@@ -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

42
apps/grouping/models.py Normal file
View File

@@ -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.<attribute>` and `metadata.<metadata_type_name>`.'))
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')

23
apps/grouping/tests.py Normal file
View File

@@ -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
"""}

6
apps/grouping/urls.py Normal file
View File

@@ -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<document_id>\d+)/group/(?P<document_group_id>\d+)/$', 'document_group_view', (), 'document_group_view'),
)

36
apps/grouping/utils.py Normal file
View File

@@ -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

41
apps/grouping/views.py Normal file
View File

@@ -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))