Merge branch 'new_metadata'

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

View File

@@ -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'<ul class="%s">' % 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'<li><a href="%s">%s</a></li>' % (
self.queryset.get(pk=index).get_absolute_url(),
string)
except AttributeError:
output += u'<li>%s</li>' % (string)
else:
output += u'<li>%s</li>' % string
else:
output += u'<li>%s</li>' % _(u"None")
return mark_safe(output + u'</ul>\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):
"""

View File

@@ -23,7 +23,11 @@
{% if next %}
<input name="next" type="hidden" value="{{ next }}" />
{% endif %}
<div style="float: left; margin-right: 10px;">
<img style="margin-top: 12px;" src="{{ MEDIA_URL }}images/icons/{{ form_icon|default:'question.png' }}" alt="{% trans 'form icon' %}" />
</div>
<div style="float: left; width: 90%;">
{% if title %}
<h3>{{ title }}</h3>
{% else %}
@@ -47,6 +51,10 @@
</a>
{% endif %}
</div>
</div>
<div class="clear"></div>
</form>
</div>
</div>

View File

@@ -10,28 +10,26 @@
{% include subtemplate %}
</div>
{% 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 %}
<div class="generic_subform">
{{ rendered_subtemplate }}
</div>
{% endwith %}
{% else %}
{% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %}
{{ rendered_subtemplate }}
{% endif %}
</div>
{% if subtemplate.grid_clear or not subtemplate.grid %}
<div class=""></div>
{% endif %}
{% endwith %}
{% endfor %}
{% endblock %}
{% block stylesheets %}
@@ -45,84 +43,34 @@
{% endblock %}
{% block content %}
<div class="container_12">
{% if form %}
{% with "true" as read_only %}
<div class="grid_{{ grid|default:11 }}">
<div class="">
<div class="generic_subform">
{% include "generic_form_subtemplate.html" %}
</div>
</div>
{% if grid_clear or not grid %}
<div class="clear"></div>
<div class=""></div>
{% 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 %}
<div class="grid_{{ form.grid|default:11 }}">
{% with form.form as form %}
<div class="generic_subform">
{% include "generic_form_subtemplate.html" %}
<div class="container_12">
{% for subtemplate in subtemplates_list %}
<div class="grid_{{ subtemplate.grid|default:12 }}">
{% with "true" as read_only %}
{% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %}
<div class="generic_subform">
{{ rendered_subtemplate }}
</div>
{% endwith %}
</div>
{% endwith %}
</div>
{% if form.grid_clear or not form.grid %}
{% if subtemplate.grid_clear or not subtemplate.grid %}
<div class="clear"></div>
{% endif %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %}
{% for subtemplate in subtemplates_list %}
<div class="grid_{{ subtemplate.grid|default:11 }}">
{% 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 %}
<div class="generic_subform">
{% include subtemplate.name %}
</div>
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% else %}
{% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %}
{{ rendered_subtemplate }}
{% endif %}
</div>
{% if subtemplate.grid_clear or not subtemplate.grid %}
<div class="clear"></div>
{% endif %}
{% endfor %}
</div>
{% endfor %}
</div>
{% endblock %}

View File

@@ -19,49 +19,5 @@
</div><!--end generic_subform-->
{% 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 %}

View File

@@ -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 %}
<div class="generic_subform">
{% include subtemplate %}
</div><!--end subform-->
{% 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 %}
<div class="generic_subform">
{{ rendered_subtemplate }}
</div>
{% 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 %}
<div class="container_12">
{% if form %}
<div class="grid_{{ form.grid|default:11 }}">
<div class="generic_subform">
{% include "generic_form_subtemplate.html" %}
</div>
{% if form.grid_clear or not form.grid %}
{% endif %}
<div class="container_12">
{% for subtemplate in subtemplates_list %}
<div class="grid_{{ subtemplate.grid|default:12 }}">
{% if subtemplate.form %}
{% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %}
<div class="generic_subform">
{{ rendered_subtemplate }}
</div>
{% else %}
{% render_subtemplate subtemplate.name subtemplate.context as rendered_subtemplate %}
{{ rendered_subtemplate }}
{% endif %}
</div>
{% if subtemplate.grid_clear or not subtemplate.grid %}
<div class="clear"></div>
{% 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 %}
<div class="grid_{{ form.grid|default:11 }}">
{% with form.form as form %}
{% include "generic_form_subtemplate.html" %}
{% endwith %}
</div>
{% if form.grid_clear or not form.grid %}
<div class="clear"></div>
{% endif %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %}
{% endfor %}
</div>
{% 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 %}

View File

@@ -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 %}
<div class="generic_subform">
{% include subtemplate %}
</div><!--end subform-->
{% 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 %}

View File

@@ -85,7 +85,7 @@
{% endif %}
</tr>
{% empty %}
<tr><td colspan=99 class="tc">{% blocktrans %}There are no {{ title }}{% endblocktrans %}</td></tr>
<tr><td colspan=99 class="tc">{% blocktrans with title|striptags as stripped_title %}There are no {{ stripped_title }}{% endblocktrans %}</td></tr>
{% endfor %}
</tbody>
</table>

View File

@@ -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 = []

View File

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

49
apps/common/widgets.py Normal file
View File

@@ -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'<ul class="%s">' % 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'<li><a href="%s">%s</a></li>' % (
self.queryset.get(pk=index).get_absolute_url(),
string)
except AttributeError:
output += u'<li>%s</li>' % (string)
else:
output += u'<li>%s</li>' % string
else:
output += u'<li>%s</li>' % _(u"None")
return mark_safe(output + u'</ul>\n')

View File

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

View File

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

View File

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

View File

@@ -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'<a href="%(url)s">%(value)s</a>'
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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('document_indexing.views',
url(r'^(?P<index_id>\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'),
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 <EMAIL@ADDRESS>, 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 <rosario_r@jp.pr.gov>\n"
"Language-Team: LANGUAGE <LL@li.org>\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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

@@ -5,14 +5,23 @@
{% block title %} :: {% trans "About this program" %}{% endblock %}
{% block content %}
<div class="content">
<h3 class="tc">
{% project_name %} ({% trans "Version" %} {% app_version "main" %})<br /><br />
{% trans 'Open source, Django based document manager with custom metadata indexing, file serving integration and OCR capabilities' %}<br /><br />
<a href="http://www.github.com/rosarior/mayan/">http://www.github.com/rosarior/mayan/</a><br /><br />
<br/>{% trans "Released under the GPL V3 License" %}
</h3>
<div class="tc"><img src="{{ MEDIA_URL }}images/1299551545_egypt200.png"/></div>
<div class="content tc">
<h3>{% project_name %} ({% trans "Version" %} {% app_version "main" %})</h3>
<p>
{% trans "Open source, Django based electronic document manager with custom metadata, indexing, tagging, file serving integration and OCR capabilities" %}
</p>
<p>
<a href="http://bit.ly/mayan-edms">http://bit.ly/mayan-edms</a>
</p>
<p>
<a href="http://www.github.com/rosarior/mayan/">http://www.github.com/rosarior/mayan/</a>
</p>
<p>
{% trans "Released under the GPL V3 License" %}
</p>
<div class="tc">
<img src="{{ MEDIA_URL }}images/392336_7079-small.png"/>
</div>
</div>
{% endblock %}
{% block footer %}

View File

@@ -26,11 +26,10 @@
{% block web_theme_project_name %}{% project_name %}{% if debug %} {% trans "(DEBUG Mode)" %} {% endif %}{% endblock %}
{% block web_theme_stylesheets %}
<link rel="stylesheet" href="{{ MEDIA_URL }}css/override.css" type="text/css" media="screen" />
<link rel="stylesheet" href="{{ MEDIA_URL }}css/famfamfam-silk-sprite.css" type="text/css" media="screen" />
<link rel="stylesheet" href="{{ MEDIA_URL }}packages/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.css" type="text/css" media="screen" />
{#<link rel="stylesheet" href="{{ MEDIA_URL }}packages/nathansmith-960-Grid-System-30906f2/code/css/960.css" type="text/css" media="screen" />#}
<link rel="stylesheet" href="{{ MEDIA_URL }}css/960-fluid.css" type="text/css" media="screen" />
<link rel="stylesheet" href="{{ MEDIA_URL }}css/override.css" type="text/css" media="screen" />
<link rel="stylesheet" href="{{ MEDIA_URL }}packages/jquery.fancybox-1.3.4/fancybox/jquery.fancybox-1.3.4.css" type="text/css" media="screen" />
<style type="text/css">
#fancybox-left-ico {
left: 20px;

View File

@@ -2,9 +2,9 @@
{% load project_tags %}
{% load i18n %}
{% block content %}
<div class="content">
<div class="tc"><h1>{% project_name %}</h1></div>
<div class="tc"><img src="{{ MEDIA_URL }}images/1068504_92921456.jpg" /></div>
<div class="content tc">
<h1>{% project_name %}</h1>
<img src="{{ MEDIA_URL }}images/1068504_92921456.jpg" />
</div>
{% endblock %}
{% block footer %}

25
apps/metadata/__init__.py Normal file
View File

@@ -0,0 +1,25 @@
from django.utils.translation import ugettext_lazy as _
from navigation.api import register_links, register_multi_item_links
from permissions.api import register_permissions
from documents.models import Document
PERMISSION_METADATA_DOCUMENT_EDIT = u'metadata_document_edit'
PERMISSION_METADATA_DOCUMENT_ADD = u'metadata_document_add'
PERMISSION_METADATA_DOCUMENT_REMOVE = u'metadata_document_remove'
register_permissions('metadata', [
{'name': PERMISSION_METADATA_DOCUMENT_EDIT, 'label': _(u'Edit a document\'s metadata')},
{'name': PERMISSION_METADATA_DOCUMENT_ADD, 'label': _(u'Add metadata to a document')},
{'name': PERMISSION_METADATA_DOCUMENT_REMOVE, 'label': _(u'Remove metadata from a document')},
])
metadata_edit = {'text': _(u'edit metadata'), 'view': 'metadata_edit', 'args': 'object.id', 'famfam': 'xhtml_go', 'permissions': {'namespace': 'metadata', 'permissions': [PERMISSION_METADATA_DOCUMENT_EDIT]}}
metadata_multiple_edit = {'text': _(u'edit metadata'), 'view': 'metadata_multiple_edit', 'famfam': 'xhtml_go', 'permissions': {'namespace': 'metadata', 'permissions': [PERMISSION_METADATA_DOCUMENT_EDIT]}}
metadata_add = {'text': _(u'add metadata'), 'view': 'metadata_add', 'args': 'object.id', 'famfam': 'xhtml_add', 'permissions': {'namespace': 'metadata', 'permissions': [PERMISSION_METADATA_DOCUMENT_ADD]}}
metadata_multiple_add = {'text': _(u'add metadata'), 'view': 'metadata_multiple_add', 'famfam': 'xhtml_add', 'permissions': {'namespace': 'metadata', 'permissions': [PERMISSION_METADATA_DOCUMENT_ADD]}}
metadata_remove = {'text': _(u'remove metadata'), 'view': 'metadata_remove', 'args': 'object.id', 'famfam': 'xhtml_delete', 'permissions': {'namespace': 'metadata', 'permissions': [PERMISSION_METADATA_DOCUMENT_REMOVE]}}
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(['document_datagroup_view', 'document_list', 'document_list_recent'], [metadata_multiple_add, metadata_multiple_edit, metadata_multiple_remove])

30
apps/metadata/admin.py Normal file
View File

@@ -0,0 +1,30 @@
from django.contrib import admin
from metadata.models import MetadataType, MetadataSet, MetadataSetItem, \
DocumentMetadata
class MetadataTypeAdmin(admin.ModelAdmin):
list_display = ('name', 'title', 'default', 'lookup')
class MetadataSetItemInline(admin.StackedInline):
model = MetadataSetItem
extra = 1
classes = ('collapse-open',)
allow_add = True
class DocumentMetadataInline(admin.StackedInline):
model = DocumentMetadata
extra = 0
classes = ('collapse-open',)
allow_add = False
class MetadataSetAdmin(admin.ModelAdmin):
inlines = [MetadataSetItemInline]
admin.site.register(MetadataType, MetadataTypeAdmin)
admin.site.register(MetadataSet, MetadataSetAdmin)

88
apps/metadata/api.py Normal file
View File

@@ -0,0 +1,88 @@
"""Metadata handling commonalities"""
from urllib import unquote_plus
from django.shortcuts import get_object_or_404
from metadata.models import DocumentMetadata, MetadataType
def decode_metadata_from_url(url_dict):
"""Parse 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, create=False):
"""
Take a list of metadata values and associate a document to it
"""
for item in metadata_list:
save_metadata(item, document, create)
def save_metadata(metadata_dict, document, create=False):
"""save metadata_dict"""
if create:
# 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']
),
)
else:
document_metadata = DocumentMetadata.objects.get(
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 u', '.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

12
apps/metadata/classes.py Normal file
View File

@@ -0,0 +1,12 @@
from django.utils.translation import ugettext_lazy as _
class MetadataObject(object):
def __init__(self, dictionary):
self.dictionary = dictionary
def __getattr__(self, name):
if name in self.dictionary:
return self.dictionary.get(name)
else:
raise AttributeError(_(u'\'metadata\' object has no attribute \'%s\'') % name)

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,27 @@
"""Configuration options for the metadata app"""
import datetime
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
from smart_settings.api import register_settings
default_available_functions = {
'current_date': datetime.datetime.now().date,
}
default_available_models = {
'User': User
}
register_settings(
namespace=u'metadata',
module=u'metadata.conf.settings',
settings=[
# Definition
{'name': u'AVAILABLE_FUNCTIONS', 'global_name': u'METADATA_AVAILABLE_FUNCTIONS', 'default': default_available_functions},
{'name': u'AVAILABLE_MODELS', 'global_name': u'METADATA_AVAILABLE_MODELS', 'default': default_available_models},
]
)

67
apps/metadata/forms.py Normal file
View File

@@ -0,0 +1,67 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.forms.formsets import formset_factory
from metadata.conf.settings import AVAILABLE_MODELS
from metadata.conf.settings import AVAILABLE_FUNCTIONS
from metadata.models import MetadataType
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)
# FIXME:
#required = self.document_type.documenttypemetadatatype_set.get(metadata_type=self.metadata_type).required
required = False
required_string = u''
if required:
self.fields['value'].required = True
required_string = ' (%s)' % _(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.pk
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)
update = forms.BooleanField(initial=True, label=_(u'Update'), required=False)
MetadataFormSet = formset_factory(MetadataForm, extra=0)
class AddMetadataForm(forms.Form):
metadata_type = forms.ModelChoiceField(queryset=MetadataType.objects.all(), label=_(u'Metadata type'))
class MetadataRemoveForm(MetadataForm):
update = forms.BooleanField(initial=False, label=_(u'Remove'), required=False)
MetadataRemoveFormSet = formset_factory(MetadataRemoveForm, extra=0)

74
apps/metadata/models.py Normal file
View File

@@ -0,0 +1,74 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from documents.models import Document
from metadata.conf.settings import AVAILABLE_MODELS
from metadata.conf.settings import AVAILABLE_FUNCTIONS
available_models_string = (_(u' Available models: %s') % u','.join([name for name, model in AVAILABLE_MODELS.items()])) if AVAILABLE_MODELS else u''
available_functions_string = (_(u' Available functions: %s') % u','.join([u'%s()' % name for name, function in AVAILABLE_FUNCTIONS.items()])) if AVAILABLE_FUNCTIONS else u''
class MetadataType(models.Model):
name = models.CharField(unique=True, max_length=48, verbose_name=_(u'name'), help_text=_(u'Do not use python reserved words, or spaces.'))
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 MetadataSet(models.Model):
title = models.CharField(max_length=48, verbose_name=_(u'title'))
def __unicode__(self):
return self.title if self.title else self.name
class Meta:
verbose_name = _(u'metadata set')
verbose_name_plural = _(u'metadata set')
class MetadataSetItem(models.Model):
"""
Define the set of metadata that relates to a set or group of
metadata fields
"""
metadata_set = models.ForeignKey(MetadataSet, verbose_name=_(u'metadata set'))
metadata_type = models.ForeignKey(MetadataType, verbose_name=_(u'metadata type'))
#required = models.BooleanField(default=True, verbose_name=_(u'required'))
def __unicode__(self):
return unicode(self.metadata_type)
class Meta:
verbose_name = _(u'metadata set item')
verbose_name_plural = _(u'metadata set items')
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'type'))
value = models.TextField(blank=True, null=True, verbose_name=_(u'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')

23
apps/metadata/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
"""}

10
apps/metadata/urls.py Normal file
View File

@@ -0,0 +1,10 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('metadata.views',
url(r'^(?P<document_id>\d+)/edit/$', 'metadata_edit', (), 'metadata_edit'),
url(r'^multiple/edit/$', 'metadata_multiple_edit', (), 'metadata_multiple_edit'),
url(r'^(?P<document_id>\d+)/add/$', 'metadata_add', (), 'metadata_add'),
url(r'^multiple/add/$', 'metadata_multiple_add', (), 'metadata_multiple_add'),
url(r'^(?P<document_id>\d+)/remove/$', 'metadata_remove', (), 'metadata_remove'),
url(r'^multiple/remove/$', 'metadata_multiple_remove', (), 'metadata_multiple_remove'),
)

247
apps/metadata/views.py Normal file
View File

@@ -0,0 +1,247 @@
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import get_object_or_404
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.utils.http import urlencode
from documents.models import Document, RecentDocument
from permissions.api import check_permissions
from document_indexing.api import update_indexes, delete_indexes
from metadata import PERMISSION_METADATA_DOCUMENT_EDIT, \
PERMISSION_METADATA_DOCUMENT_ADD, PERMISSION_METADATA_DOCUMENT_REMOVE
from metadata.forms import MetadataFormSet, AddMetadataForm, MetadataRemoveFormSet
from metadata.api import save_metadata_list
from metadata.models import DocumentMetadata, MetadataType
def metadata_edit(request, document_id=None, document_id_list=None):
check_permissions(request.user, 'metadata', [PERMISSION_METADATA_DOCUMENT_EDIT])
if document_id:
documents = [get_object_or_404(Document, pk=document_id)]
if documents[0].documentmetadata_set.count() == 0:
messages.warning(request, _(u'The selected document doesn\'t have any metadata.'))
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
elif document_id_list:
documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')]
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 document.documentmetadata_set.all():
value = item.value
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] if value else u''
initial = []
for key, value in metadata.items():
initial.append({
'metadata_type': key,
'value': u', '.join(value)
})
formset = MetadataFormSet(initial=initial)
if request.method == 'POST':
formset = MetadataFormSet(request.POST)
if formset.is_valid():
for document in documents:
warnings = delete_indexes(document)
if request.user.is_staff or request.user.is_superuser:
for warning in warnings:
messages.warning(request, _(u'Error deleting document indexes; %s') % warning)
errors = []
for form in formset.forms:
if form.cleaned_data['update']:
try:
save_metadata_list([form.cleaned_data], document)
except Exception, e:
errors.append(e)
if errors:
for error in errors:
messages.error(request, _(u'Error editing metadata for document %(document)s; %(error)s.') % {
'document': document, 'error': error})
else:
messages.success(request, _(u'Metadata for document %s edited successfully.') % document)
warnings = update_indexes(document)
if warnings and (request.user.is_staff or request.user.is_superuser):
for warning in warnings:
messages.warning(request, _(u'Error updating document indexes; %s') % warning)
else:
messages.success(request, _(u'Document indexes updated successfully.'))
if len(documents) == 1:
return HttpResponseRedirect(document.get_absolute_url())
elif len(documents) > 1:
return HttpResponseRedirect(reverse('document_list_recent'))
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 metadata_multiple_edit(request):
return metadata_edit(request, document_id_list=request.GET.get('id_list', []))
def metadata_add(request, document_id=None, document_id_list=None):
check_permissions(request.user, 'metadata', [PERMISSION_METADATA_DOCUMENT_ADD])
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(',')]
else:
messages.error(request, _(u'Must provide at least one document.'))
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
for document in documents:
RecentDocument.objects.add_document_for_user(request.user, document)
if request.method == 'POST':
form = AddMetadataForm(request.POST)
if form.is_valid():
metadata_type = form.cleaned_data['metadata_type']
for document in documents:
document_metadata, created = DocumentMetadata.objects.get_or_create(document=document, metadata_type=metadata_type, defaults={'value': u''})
if created:
messages.success(request, _(u'Metadata type: %(metadata_type)s successfully added to document %(document)s.') % {
'metadata_type': metadata_type, 'document': document})
else:
messages.warning(request, _(u'Metadata type: %(metadata_type)s already present in document %(document)s.') % {
'metadata_type': metadata_type, 'document': document})
if len(documents) == 1:
return HttpResponseRedirect(reverse(metadata_edit, args=[document.pk]))
elif len(documents) > 1:
return HttpResponseRedirect(u'%s?%s' % (reverse('metadata_multiple_edit'), urlencode({'id_list': document_id_list})))
else:
form = AddMetadataForm()
context = {
#'form_display_mode_table': True,
'form': form,
}
if len(documents) == 1:
context['object'] = documents[0]
context['title'] = _(u'Add metadata type to document: %s') % ', '.join([unicode(d) for d in documents])
elif len(documents) > 1:
context['title'] = _(u'Add metadata type to documents: %s') % ', '.join([unicode(d) for d in documents])
return render_to_response('generic_form.html', context,
context_instance=RequestContext(request))
def metadata_multiple_add(request):
return metadata_add(request, document_id_list=request.GET.get('id_list', []))
def metadata_remove(request, document_id=None, document_id_list=None):
check_permissions(request.user, 'metadata', [PERMISSION_METADATA_DOCUMENT_REMOVE])
if document_id:
documents = [get_object_or_404(Document, pk=document_id)]
if documents[0].documentmetadata_set.count() == 0:
messages.warning(request, _(u'The selected document doesn\'t have any metadata.'))
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
elif document_id_list:
documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')]
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 document.documentmetadata_set.all():
value = item.value
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] if value else u''
initial = []
for key, value in metadata.items():
initial.append({
'metadata_type': key,
'value': u', '.join(value)
})
formset = MetadataRemoveFormSet(initial=initial)
if request.method == 'POST':
formset = MetadataRemoveFormSet(request.POST)
if formset.is_valid():
for document in documents:
warnings = delete_indexes(document)
if request.user.is_staff or request.user.is_superuser:
for warning in warnings:
messages.warning(request, _(u'Error deleting document indexes; %s') % warning)
for form in formset.forms:
if form.cleaned_data['update']:
metadata_type = get_object_or_404(MetadataType, pk=form.cleaned_data['id'])
try:
document_metadata = DocumentMetadata.objects.get(document=document, metadata_type=metadata_type)
document_metadata.delete()
messages.success(request, _(u'Successfully remove metadata type: %(metadata_type)s from document: %(document)s.') % {
'metadata_type': metadata_type, 'document': document})
except:
messages.error(request, _(u'Error removing metadata type: %(metadata_type)s from document: %(document)s.') % {
'metadata_type': metadata_type, 'document': document})
warnings = update_indexes(document)
if warnings and (request.user.is_staff or request.user.is_superuser):
for warning in warnings:
messages.warning(request, _(u'Error updating document indexes; %s') % warning)
else:
messages.success(request, _(u'Document indexes updated successfully.'))
if len(documents) == 1:
return HttpResponseRedirect(document.get_absolute_url())
elif len(documents) > 1:
return HttpResponseRedirect(reverse('document_list_recent'))
context = {
'form_display_mode_table': True,
'form': formset,
}
if len(documents) == 1:
context['object'] = documents[0]
context['title'] = _(u'Remove metadata types to document: %s') % ', '.join([unicode(d) for d in documents])
elif len(documents) > 1:
context['title'] = _(u'Remove metadata types to documents: %s') % ', '.join([unicode(d) for d in documents])
return render_to_response('generic_form.html', context,
context_instance=RequestContext(request))
def metadata_multiple_remove(request):
return metadata_remove(request, document_id_list=request.GET.get('id_list', []))

View File

@@ -1,5 +1,3 @@
import copy
object_navigation = {}
multi_object_navigation = {}
menu_links = []
@@ -8,47 +6,25 @@ sidebar_templates = {}
def register_multi_item_links(src, links, menu_name=None):
if menu_name in multi_object_navigation:
if hasattr(src, '__iter__'):
for one_src in src:
if one_src in object_navigation[menu_name]:
multi_object_navigation[menu_name][one_src]['links'].extend(links)
else:
multi_object_navigation[menu_name][one_src] = {'links': copy.copy(links)}
else:
if src in multi_object_navigation[menu_name]:
multi_object_navigation[menu_name][src]['links'].extend(links)
else:
multi_object_navigation[menu_name][src] = {'links': links}
multi_object_navigation.setdefault(menu_name, {})
if hasattr(src, '__iter__'):
for one_src in src:
multi_object_navigation[menu_name].setdefault(one_src, {'links': []})
multi_object_navigation[menu_name][one_src]['links'].extend(links)
else:
multi_object_navigation[menu_name] = {}
if hasattr(src, '__iter__'):
for one_src in src:
multi_object_navigation[menu_name][one_src] = {'links': links}
else:
multi_object_navigation[menu_name] = {src: {'links': links}}
multi_object_navigation[menu_name].setdefault(src, {'links': []})
multi_object_navigation[menu_name][src]['links'].extend(links)
def register_links(src, links, menu_name=None):
if menu_name in object_navigation:
if hasattr(src, '__iter__'):
for one_src in src:
if one_src in object_navigation[menu_name]:
object_navigation[menu_name][one_src]['links'].extend(links)
else:
object_navigation[menu_name][one_src] = {'links': copy.copy(links)}
else:
if src in object_navigation[menu_name]:
object_navigation[menu_name][src]['links'].extend(links)
else:
object_navigation[menu_name][src] = {'links': links}
object_navigation.setdefault(menu_name, {})
if hasattr(src, '__iter__'):
for one_src in src:
object_navigation[menu_name].setdefault(one_src, {'links': []})
object_navigation[menu_name][one_src]['links'].extend(links)
else:
object_navigation[menu_name] = {}
if hasattr(src, '__iter__'):
for one_src in src:
object_navigation[menu_name][one_src] = {'links': links}
else:
object_navigation[menu_name] = {src: {'links': links}}
object_navigation[menu_name].setdefault(src, {'links': []})
object_navigation[menu_name][src]['links'].extend(links)
def register_menu(links):
@@ -59,15 +35,11 @@ def register_menu(links):
def register_model_list_columns(model, columns):
if model in model_list_columns:
model_list_columns[model].extend(columns)
else:
model_list_columns[model] = copy.copy(columns)
model_list_columns.setdefault(model, [])
model_list_columns[model].extend(columns)
def register_sidebar_template(source_list, template_name):
for source in source_list:
if source in sidebar_templates:
sidebar_templates[source].append(template_name)
else:
sidebar_templates[source] = [template_name]
sidebar_templates.setdefault(source, [])
sidebar_templates[source].append(template_name)

View File

@@ -111,6 +111,7 @@ def do_document_ocr(document):
f.close()
cleanup(ocr_output)
finally:
os.close(desc)
cleanup(filepath)
if imagefile:
cleanup(imagefile)

View File

@@ -28,7 +28,7 @@ if CACHE_URI:
cache_backend = get_cache(CACHE_URI)
except ImportError:
# TODO: display or log error
cache_backend = None
cache_backend = None
else:
cache_backend = None
@@ -95,7 +95,7 @@ def task_process_document_queues():
# reset_orphans()
# Causes problems with big clusters increased latency
# Disabled until better solution
# Disabled until better solution
q_pending = Q(state=QUEUEDOCUMENT_STATE_PENDING)
q_delayed = Q(delay=True)
q_delay_interval = Q(datetime_submitted__lt=datetime.now() - timedelta(seconds=REPLICATION_DELAY))

View File

@@ -103,7 +103,8 @@ def queue_document_delete(request, queue_document_id=None, queue_document_id_lis
context = {
'next': next,
'previous': previous,
'delete_view': True
'delete_view': True,
'form_icon': u'hourglass_delete.png',
}
if len(queue_documents) == 1:
@@ -260,6 +261,7 @@ def all_document_ocr_cleanup(request):
'previous': previous,
'next': next,
'message': _(u'On large databases this operation may take some time to execute.'),
'form_icon': u'text_strikethroungh.png',
}, context_instance=RequestContext(request))
else:
try:

View File

@@ -1,8 +1,5 @@
#from django.contrib.auth.models import User
#from django.contrib.auth.models import Group
from django.db.utils import DatabaseError
from django.shortcuts import get_object_or_404
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.core.exceptions import PermissionDenied
@@ -11,7 +8,7 @@ from permissions import PERMISSION_ROLE_VIEW, PERMISSION_ROLE_EDIT, \
PERMISSION_ROLE_CREATE, PERMISSION_ROLE_DELETE, \
PERMISSION_PERMISSION_GRANT, PERMISSION_PERMISSION_REVOKE
from permissions.models import Permission, Role
from permissions.models import Permission
def register_permissions(namespace, permissions):

View File

@@ -2,7 +2,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.contrib.auth.models import User, Group
from django.contrib.auth.models import User
class PermissionManager(models.Manager):
@@ -26,10 +26,10 @@ class Permission(models.Model):
def __unicode__(self):
return self.label
def get_holders(self):
return [holder.holder_object for holder in self.permissionholder_set.all()]
def has_permission(self, requester):
if isinstance(requester, User):
if requester.is_superuser or requester.is_staff:
@@ -38,8 +38,8 @@ class Permission(models.Model):
# Request is one of the permission's holders?
if requester in self.get_holders():
return True
# If not check if the requesters memberships objects is one of
# If not check if the requesters memberships objects is one of
# the permission's holder?
roles = RoleMember.objects.get_roles_for_member(requester)
@@ -47,7 +47,7 @@ class Permission(models.Model):
groups = requester.groups.all()
else:
groups = []
for membership in list(set(roles) | set(groups)):
if self.has_permission(membership):
return True

View File

@@ -1,4 +1,5 @@
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
@@ -10,7 +11,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User, Group
from common.forms import ChoiceForm
from common.views import assign_remove
from common.utils import generate_choices_w_labels
from permissions.models import Role, Permission, PermissionHolder, RoleMember
@@ -38,18 +39,18 @@ def role_list(request):
def _role_permission_link(requester, permission, permission_list):
ct = ContentType.objects.get_for_model(requester)
template = '<span class="nowrap"><a href="%(url)s"><span class="famfam active famfam-%(icon)s"></span>%(text)s</a></span>'
template = u'<span class="nowrap"><a href="%(url)s"><span class="famfam active famfam-%(icon)s"></span>%(text)s</a></span>'
if permission in permission_list:
return template % {
'url': reverse('permission_revoke',
args=[permission.id, ct.app_label, ct.model, requester.id]),
'icon': 'delete', 'text': _(u'Revoke')}
args=[permission.pk, ct.app_label, ct.model, requester.pk]),
'icon': u'delete', 'text': ugettext(u'Revoke')}
else:
return template % {
'url': reverse('permission_grant',
args=[permission.id, ct.app_label, ct.model, requester.id]),
'icon': 'add', 'text': _(u'Grant')}
args=[permission.pk, ct.app_label, ct.model, requester.pk]),
'icon': u'add', 'text': ugettext(u'Grant')}
def role_permissions(request, role_id):
@@ -61,20 +62,20 @@ def role_permissions(request, role_id):
role_permissions_list = Permission.objects.get_for_holder(role)
subtemplates_list = [
{
'name':'generic_list_subtemplate.html',
'name': u'generic_list_subtemplate.html',
'context': {
'title':_(u'permissions'),
'object_list':Permission.objects.all(),
'extra_columns':[
{'name':_(u'namespace'), 'attribute':'namespace'},
{'name':_(u'name'), 'attribute':'label'},
'title': _(u'permissions'),
'object_list': Permission.objects.all(),
'extra_columns': [
{'name': _(u'namespace'), 'attribute': u'namespace'},
{'name': _(u'name'), 'attribute': u'label'},
{
'name':_(u'state'),
'attribute':lambda x: _role_permission_link(role, x, role_permissions_list),
'attribute': lambda x: _role_permission_link(role, x, role_permissions_list),
}
],
'hide_link':True,
'hide_object':True,
'hide_link': True,
'hide_object': True,
}
},
]
@@ -117,6 +118,7 @@ def role_delete(request, role_id):
'next': next,
'previous': previous,
'object_name': _(u'role'),
'form_icon': u'medal_gold_delete.png',
})
@@ -143,7 +145,7 @@ def permission_grant_revoke(request, permission_id, app_label, module_name, pk,
if request.method == 'POST':
if action == 'grant':
permission_holder, created = PermissionHolder.objects.get_or_create(permission=permission, holder_type=ct, holder_id=requester.id)
permission_holder, created = PermissionHolder.objects.get_or_create(permission=permission, holder_type=ct, holder_id=requester.pk)
if created:
messages.success(request, _(u'Permission "%(permission)s" granted to %(ct_name)s: %(requester)s.') % {
'permission': permission, 'ct_name': ct.name, 'requester': requester})
@@ -152,7 +154,7 @@ def permission_grant_revoke(request, permission_id, app_label, module_name, pk,
'ct_name': ct.name, 'requester': requester, 'permission': permission})
elif action == 'revoke':
try:
permission_holder = PermissionHolder.objects.get(permission=permission, holder_type=ct, holder_id=requester.id)
permission_holder = PermissionHolder.objects.get(permission=permission, holder_type=ct, holder_id=requester.pk)
permission_holder.delete()
messages.success(request, _(u'Permission "%(permission)s" revoked from %(ct_name)s: %(requester)s.') % {
'permission': permission, 'ct_name': ct.name, 'requester': requester})
@@ -184,70 +186,33 @@ def get_non_role_members(role):
return list(users | groups)
def add_role_member(role, selection):
model, pk = selection.split(u',')
ct = ContentType.objects.get(model=model)
new_member, created = RoleMember.objects.get_or_create(role=role, member_type=ct, member_id=pk)
if not created:
raise Exception
def remove_role_member(role, selection):
model, pk = selection.split(u',')
ct = ContentType.objects.get(model=model)
member = RoleMember.objects.get(role=role, member_type=ct, member_id=pk)
member.delete()
def role_members(request, role_id):
check_permissions(request.user, 'permissions', [PERMISSION_ROLE_EDIT])
role = get_object_or_404(Role, pk=role_id)
if request.method == 'POST':
if 'unselected-users-submit' in request.POST.keys():
unselected_users_form = ChoiceForm(request.POST,
prefix='unselected-users',
choices=generate_choices_w_labels(get_non_role_members(role)))
if unselected_users_form.is_valid():
for selection in unselected_users_form.cleaned_data['selection']:
model, pk = selection.split(u',')
ct = ContentType.objects.get(model=model)
obj = ct.get_object_for_this_type(pk=pk)
new_member, created = RoleMember.objects.get_or_create(role=role, member_type=ct, member_id=pk)
if created:
messages.success(request, _(u'%(obj)s added successfully to the role: %(role)s.') % {
'obj': generate_choices_w_labels([obj])[0][1], 'role': role})
elif 'selected-users-submit' in request.POST.keys():
selected_users_form = ChoiceForm(request.POST,
prefix='selected-users',
choices=generate_choices_w_labels(get_role_members(role)))
if selected_users_form.is_valid():
for selection in selected_users_form.cleaned_data['selection']:
model, pk = selection.split(u',')
ct = ContentType.objects.get(model=model)
obj = ct.get_object_for_this_type(pk=pk)
try:
member = RoleMember.objects.get(role=role, member_type=ct, member_id=pk)
member.delete()
messages.success(request, _(u'%(obj)s removed successfully from the role: %(role)s.') % {
'obj': generate_choices_w_labels([obj])[0][1], 'role': role})
except member.DoesNotExist:
messages.error(request, _(u'Unable to remove %(obj)s from the role: %(role)s.') % {
'obj': generate_choices_w_labels([obj])[0][1], 'role': role})
unselected_users_form = ChoiceForm(prefix='unselected-users',
choices=generate_choices_w_labels(get_non_role_members(role)))
selected_users_form = ChoiceForm(prefix='selected-users',
choices=generate_choices_w_labels(get_role_members(role)))
context = {
'object': role,
'object_name': _(u'role'),
'form_list': [
{
'form': unselected_users_form,
'title': _(u'non members of role: %s') % role,
'grid': 6,
'grid_clear': False,
'submit_label': _(u'Add'),
},
{
'form': selected_users_form,
'title': _(u'members of role: %s') % role,
'grid': 6,
'grid_clear': True,
'submit_label': _(u'Remove'),
},
],
}
return render_to_response('generic_form.html', context,
context_instance=RequestContext(request))
return assign_remove(
request,
left_list=lambda: generate_choices_w_labels(get_non_role_members(role)),
right_list=lambda: generate_choices_w_labels(get_role_members(role)),
add_method=lambda x: add_role_member(role, x),
remove_method=lambda x: remove_role_member(role, x),
left_list_title=_(u'non members of role: %s') % role,
right_list_title=_(u'members of role: %s') % role,
obj=role,
object_name=_(u'role'),
)

View File

@@ -7,11 +7,11 @@ settings = {}
def register_setting(namespace, module, name, global_name, default, exists=False, description=u'', hidden=False):
# Create namespace if it doesn't exists
settings.setdefault(namespace, [])
# If passed a string and not a module, import it
if isinstance(module, basestring):
module = import_module(module)
setting = {
'module': module,
'name': name,
@@ -21,14 +21,14 @@ def register_setting(namespace, module, name, global_name, default, exists=False
'default': default,
'hidden': hidden,
}
# Avoid multiple appends
if setting not in settings[namespace]:
settings[namespace].append(setting)
# Get the global value
value = getattr(django_settings, global_name, default)
# Create the local entity
setattr(module, name, value)
return value

View File

@@ -44,7 +44,7 @@ register_links(Tag, [tag_tagged_item_list, tag_edit])
register_multi_item_links(['tag_list'], [tag_multiple_delete])
register_sidebar_template(['document_view', 'document_view_simple'], 'tags_sidebar_template.html')
register_sidebar_template(['document_view_advanced', 'document_view_simple'], 'tags_sidebar_template.html')
tags_menu = [
{

View File

@@ -116,6 +116,7 @@ def tag_delete(request, tag_id=None, tag_id_list=None):
'delete_view': True,
'previous': previous,
'next': next,
'form_icon': u'tag_blue_delete.png',
}
if len(tags) == 1:
context['object'] = tags[0]

View File

@@ -24,7 +24,7 @@ register_permissions('user_management', [
{'name': PERMISSION_GROUP_CREATE, 'label': _(u'Create new groups')},
{'name': PERMISSION_GROUP_EDIT, 'label': _(u'Edit existing groups')},
{'name': PERMISSION_GROUP_VIEW, 'label': _(u'View existing groups')},
{'name': PERMISSION_GROUP_DELETE, 'label': _(u'Delete existing groups')},
{'name': PERMISSION_GROUP_DELETE, 'label': _(u'Delete existing groups')},
])
user_list = {'text': _(u'user list'), 'view': 'user_list', 'famfam': 'user', 'permissions': {'namespace': 'user_management', 'permissions': [PERMISSION_USER_VIEW]}}
@@ -43,9 +43,9 @@ group_multiple_delete = {u'text': _('delete'), 'view': 'group_multiple_delete',
group_members = {'text': _(u'members'), 'view': 'group_members', 'args': 'object.id', 'famfam': 'group_link', 'permissions': {'namespace': 'user_management', 'permissions': [PERMISSION_GROUP_EDIT]}}
register_links(User, [user_edit, user_set_password, user_delete])
register_links(['user_multiple_set_password', 'user_set_password', 'user_multiple_delete', 'user_delete', 'user_edit', 'user_list','user_add'], [user_add, user_list], menu_name=u'sidebar')
register_links(['user_multiple_set_password', 'user_set_password', 'user_multiple_delete', 'user_delete', 'user_edit', 'user_list', 'user_add'], [user_add, user_list], menu_name=u'sidebar')
register_multi_item_links(['user_list'], [user_multiple_set_password, user_multiple_delete])
register_links(Group, [group_edit, group_members, group_delete])
register_links(['group_multiple_delete', 'group_delete', 'group_edit', 'group_list','group_add', 'group_members'], [group_add, group_list], menu_name=u'sidebar')
register_links(['group_multiple_delete', 'group_delete', 'group_edit', 'group_list', 'group_add', 'group_members'], [group_add, group_list], menu_name=u'sidebar')
register_multi_item_links(['group_list'], [group_multiple_delete])

View File

@@ -8,7 +8,7 @@ urlpatterns = patterns('user_management.views',
url(r'^user/multiple/delete/$', 'user_multiple_delete', (), 'user_multiple_delete'),
url(r'^user/(?P<user_id>\d+)/set_password/$', 'user_set_password', (), 'user_set_password'),
url(r'^user/multiple/set_password/$', 'user_multiple_set_password', (), 'user_multiple_set_password'),
url(r'^group/list/$', 'group_list', (), 'group_list'),
url(r'^group/add/$', 'group_add', (), 'group_add'),
url(r'^group/(?P<group_id>\d+)/edit/$', 'group_edit', (), 'group_edit'),

View File

@@ -6,11 +6,10 @@ from django.contrib import messages
from django.views.generic.list_detail import object_list
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User, Group
from django.contrib.contenttypes.models import ContentType
from permissions.api import check_permissions
from common.forms import ChoiceForm
from common.utils import generate_choices_w_labels, two_state_template
from common.views import assign_remove
from user_management import PERMISSION_USER_VIEW, \
PERMISSION_USER_EDIT, PERMISSION_USER_CREATE, \
@@ -38,7 +37,8 @@ def user_list(request):
{
'name': _(u'email'),
'attribute': 'email'
}, {
},
{
'name': _(u'active'),
'attribute': lambda x: two_state_template(x.is_active),
}
@@ -56,7 +56,7 @@ def user_edit(request, user_id):
if user.is_superuser or user.is_staff:
messages.error(request, _(u'Super user and staff user editing is not allowed, use the admin interface for these cases.'))
return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
if request.method == 'POST':
form = UserForm(instance=user, data=request.POST)
if form.is_valid():
@@ -130,6 +130,7 @@ def user_delete(request, user_id=None, user_id_list=None):
'delete_view': True,
'previous': previous,
'next': next,
'form_icon': u'user_delete.png',
}
if len(users) == 1:
context['object'] = users[0]
@@ -145,8 +146,8 @@ def user_multiple_delete(request):
return user_delete(
request, user_id_list=request.GET.get('id_list', [])
)
def user_set_password(request, user_id=None, user_id_list=None):
check_permissions(request.user, 'user_management', [PERMISSION_USER_EDIT])
post_action_redirect = None
@@ -181,7 +182,7 @@ def user_set_password(request, user_id=None, user_id_list=None):
except Exception, e:
messages.error(request, _(u'Error reseting password for user "%(user)s": %(error)s') % {
'user': user, 'error': e
})
})
return HttpResponseRedirect(next)
else:
@@ -198,7 +199,7 @@ def user_set_password(request, user_id=None, user_id_list=None):
context['title'] = _(u'Reseting password for user: %s') % ', '.join([unicode(d) for d in users])
elif len(users) > 1:
context['title'] = _(u'Reseting password for users: %s') % ', '.join([unicode(d) for d in users])
return render_to_response('generic_form.html', context,
context_instance=RequestContext(request))
@@ -207,7 +208,7 @@ def user_multiple_set_password(request):
return user_set_password(
request, user_id_list=request.GET.get('id_list', [])
)
def group_list(request):
check_permissions(request.user, 'user_management', [PERMISSION_GROUP_VIEW])
@@ -304,6 +305,7 @@ def group_delete(request, group_id=None, group_id_list=None):
'delete_view': True,
'previous': previous,
'next': next,
'form_icon': u'group_delete.png',
}
if len(groups) == 1:
context['object'] = groups[0]
@@ -319,7 +321,7 @@ def group_multiple_delete(request):
return group_delete(
request, group_id_list=request.GET.get('id_list', [])
)
def get_group_members(group):
return group.user_set.all()
@@ -333,61 +335,15 @@ def group_members(request, group_id):
check_permissions(request.user, 'user_management', [PERMISSION_GROUP_EDIT])
group = get_object_or_404(Group, pk=group_id)
if request.method == 'POST':
if 'unselected-users-submit' in request.POST.keys():
unselected_users_form = ChoiceForm(request.POST,
prefix='unselected-users',
choices=generate_choices_w_labels(get_non_group_members(group), display_object_type=False))
if unselected_users_form.is_valid():
for selection in unselected_users_form.cleaned_data['selection']:
model, pk = selection.split(u',')
ct = ContentType.objects.get(model=model)
obj = ct.get_object_for_this_type(pk=pk)
group.user_set.add(obj)
messages.success(request, _(u'%(obj)s added successfully to the group: %(group)s.') % {
'obj': generate_choices_w_labels([obj])[0][1], 'group': group})
elif 'selected-users-submit' in request.POST.keys():
selected_users_form = ChoiceForm(request.POST,
prefix='selected-users',
choices=generate_choices_w_labels(get_group_members(group), display_object_type=False))
if selected_users_form.is_valid():
for selection in selected_users_form.cleaned_data['selection']:
model, pk = selection.split(u',')
ct = ContentType.objects.get(model=model)
obj = ct.get_object_for_this_type(pk=pk)
try:
group.user_set.remove(obj)
messages.success(request, _(u'%(obj)s removed successfully from the group: %(group)s.') % {
'obj': generate_choices_w_labels([obj])[0][1], 'group': group})
except member.DoesNotExist:
messages.error(request, _(u'Unable to remove %(obj)s from the group: %(group)s.') % {
'obj': generate_choices_w_labels([obj])[0][1], 'group': group})
unselected_users_form = ChoiceForm(prefix='unselected-users',
choices=generate_choices_w_labels(get_non_group_members(group), display_object_type=False))
selected_users_form = ChoiceForm(prefix='selected-users',
choices=generate_choices_w_labels(get_group_members(group), display_object_type=False))
context = {
'object': group,
'object_name': _(u'group'),
'form_list': [
{
'form': unselected_users_form,
'title': _(u'non members of group: %s') % group,
'grid': 6,
'grid_clear': False,
'submit_label': _(u'Add'),
},
{
'form': selected_users_form,
'title': _(u'members of group: %s') % group,
'grid': 6,
'grid_clear': True,
'submit_label': _(u'Remove'),
},
],
}
return render_to_response('generic_form.html', context,
context_instance=RequestContext(request))
return assign_remove(
request,
left_list=lambda: generate_choices_w_labels(get_non_group_members(group), display_object_type=False),
right_list=lambda: generate_choices_w_labels(get_group_members(group), display_object_type=False),
add_method=lambda x: group.user_set.add(x),
remove_method=lambda x: group.user_set.remove(x),
left_list_title=_(u'non members of group: %s') % group,
right_list_title=_(u'members of group: %s') % group,
obj=group,
object_name=_(u'group'),
decode_content_type=True,
)

View File

@@ -0,0 +1,7 @@
mysqldump mayan documents_metadatatype -u root -p | replace 'documents_metadatatype' 'metadata_metadatatype' > new_types.mysql
cat new_types.mysql | mysql -u root -p mayan
mysqldump mayan documents_documentmetadata -u root -p | replace 'documents_documentmetadata' 'metadata_documentmetadata' > new_metadata.mysql
cat new_metadata.mysql | mysql -u root -p mayan

View File

@@ -116,10 +116,6 @@ Image 1299549805_unknown.png
Oxygen Team
http://www.oxygen-icons.org/
Image 1299551545_egypt200.png
Iconka
http://iconka.com/
django-sendfile - This is a wrapper around web-server specific methods
for sending files to web clients.
johnsensible (John Montgomery)
@@ -132,3 +128,5 @@ jQuery-Jail - Jquery Asynchronous Image Loader (JAIL)
django-taggit - is a reusable Django application for simple tagging
Alex Gaynor (alex.gaynor@gmail.com)
http://pypi.python.org/pypi/django-taggit
Image 392336_7079 (stock exchange)

View File

@@ -49,6 +49,8 @@ TODO, WISHLIST
* Android App - NOT NEEDED (Use CamScanner, (C) IntSig Information Co, Ltd.)
* Extract nagivation code into new navigation app - DONE
* TODO: Solve css:last column problem
* Change task manager from celery/rabbit to gearman
* Receive documents via email
* Mobile version
* Exif to metadata convertion
@@ -62,7 +64,7 @@ TODO, WISHLIST
Main
====
* Diagnostics (document file <-> document db mismatches, orphan files) - STARTED
* Diagnostics (document file <-> document db mismatches, orphan files) - DONE
* Sidebar search - DONE
* Convert home and about views to use generic_template
@@ -70,7 +72,7 @@ Common
======
* Filterform date filtering widget
* Divide navigation links search by object and by view
* Merge all generic templates into template widget object based rendering
* Merge generic templates into template widget object based rendering - DONE
* Multiple document select in generic list template - DONE
* Keyboard navigation
* Default button linking to 'Enter' and ESC key for cancel
@@ -80,7 +82,7 @@ Common
Permissions
===========
* Add permissions support to menus
* Add permissions support to menus - STARTED (secondary menus done)
* Role editing view under setup - DONE
* Implement permissions decorators
* Add user editing under roles menus - DONE
@@ -88,7 +90,8 @@ Permissions
Documents
=========
* Skip step 2 of wizard (metadata) if no document type metadata types have been defined
* Skip step 2 of wizard (metadata) if no document type metadata types - DONE
have been defined
* Tile based image server
* Do separate default transformations for staging and for local uploads
* Download a document in different formats: (jpg, png, pdf)
@@ -96,7 +99,7 @@ Documents
* Download original document or transformed document
* Display preferences 'document transformations' (Rotation, default zoom)
* Document view temp transformations
* Gallery view for document groups
* Gallery view for document groups - DONE
* Versioning support
* Generic document anotations using layer overlays
* Field for document language or autodetect
@@ -108,7 +111,7 @@ Documents
* Allow document type to be changed in document edit view
* Document model's delete method might not get called when deleting in bulk
from a queryset
* Allow metadata entry form to mix required and non required metadata
* Allow metadata entry form to mix required and non required metadata - DEFFERED
* Block Setup menu item to non staff and non superuser users
* Include annotations in transformed documents downloads
* Toggable option to include default transformation on document upload
@@ -126,7 +129,7 @@ Documents
* Use document preview code for staging file also - DONE
* Delete physical file on delete method - DEFFERED (Not needed until Django 1.3)
* Download individual document pages
* Document printing support
* Document printing support - STARTED
* Document group actions - DONE
Filesystem serving

View File

@@ -9,3 +9,4 @@ django-celery==2.2.2
django-sentry==1.6.0
django-taggit==0.9.3
South==0.7.3
django-mptt==0.4.2

View File

@@ -6,3 +6,4 @@ django-celery==2.2.2
django-sentry==1.6.0
django-taggit==0.9.3
South==0.7.3
django-mptt==0.4.2

View File

@@ -125,7 +125,7 @@ INSTALLED_APPS = (
'web_theme',
'main',
'common',
'documents',
'metadata',
'pagination',
'dynamic_search',
'filetransfers',
@@ -138,7 +138,6 @@ INSTALLED_APPS = (
'sentry',
'sentry.client',
'sentry.client.celery',
'filesystem_serving',
'storage',
'folders',
'taggit',
@@ -146,6 +145,10 @@ INSTALLED_APPS = (
'document_comments',
'user_management',
'south',
'documents',
'grouping',
'mptt',
'document_indexing',
)
TEMPLATE_CONTEXT_PROCESSORS = (
@@ -161,7 +164,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
#--------- Pagination ------------------
#PAGINATION_DEFAULT_PAGINATION = 10
#--------- Web theme app ---------------
#WEB_THEME = 'default'
#WEB_THEME_THEME = 'default'
#-------------- Main -----------------
#MAIN_SIDE_BAR_SEARCH = False
#------------ Common --------------
@@ -177,12 +180,16 @@ TEMPLATE_CONTEXT_PROCESSORS = (
#STORAGE_GRIDFS_DATABASE_NAME = u'document_storage'
# Filebased
#STORAGE_FILESTORAGE_LOCATION = u'document_storage'
#---------- Metadata -----------------
# METADATA_AVAILABLE_FUNCTIONS = {}
# METADATA_AVAILABLE_MODELS = {}
#---------- Indexing -----------------
#DOCUMENT_INDEXING_AVAILABLE_INDEXING_FUNCTIONS = {}
# Flesystem serving
#DOCUMENT_INDEXING_FILESYSTEM_FILESERVING_ENABLE = True
#DOCUMENT_INDEXING_FILESYSTEM_FILESERVING_PATH = u'/tmp/mayan/documents'
#DOCUMENT_INDEXING_FILESYSTEM_SLUGIFY_PATHS = False
#---------- Documents ------------------
# Definition
#DOCUMENTS_METADATA_AVAILABLE_FUNCTIONS = {}
#DOCUMENTS_METADATA_AVAILABLE_MODELS = {}
#DOCUMENTS_INDEXING_AVAILABLE_FUNCTIONS = {}
# Upload
#DOCUMENTS_USE_STAGING_DIRECTORY = False
#DOCUMENTS_STAGING_DIRECTORY = u'/tmp/mayan/staging'
@@ -211,13 +218,8 @@ TEMPLATE_CONTEXT_PROCESSORS = (
#DOCUMENTS_ZOOM_MIN_LEVEL = 50
#DOCUMENTS_ROTATION_STEP = 90
# Groups
#DOCUMENTS_GROUP_SHOW_EMPTY = True
#------------ Filesystem serving --------------
#FILESYSTEM_FILESERVING_ENABLE = True
#FILESYSTEM_FILESERVING_PATH = u'/tmp/mayan/documents'
#FILESYSTEM_SLUGIFY_PATHS = False
#FILESYSTEM_MAX_RENAME_COUNT = 200
#------------- Groups --------------------
#GROUPING_SHOW_EMPTY_GROUPS = True
#------------ Converter --------------
#CONVERTER_DEFAULT_OPTIONS = u''
#CONVERTER_LOW_QUALITY_OPTIONS = u''

View File

@@ -0,0 +1,343 @@
/*
Variable Grid System (Fluid Version).
Learn more ~ http://www.spry-soft.com/grids/
Based on 960 Grid System - http://960.gs/ & 960 Fluid - http://www.designinfluences.com/
Licensed under GPL and MIT.
*/
/* Containers
----------------------------------------------------------------------------------------------------*/
.container_12 {
width: 100%;
/*margin-left: 4%;
margin-right: 4%;*/
}
/* Grid >> Global
----------------------------------------------------------------------------------------------------*/
.grid_1,
.grid_2,
.grid_3,
.grid_4,
.grid_5,
.grid_6,
.grid_7,
.grid_8,
.grid_9,
.grid_10,
.grid_11,
.grid_12 {
display:inline;
float: left;
position: relative;
/*margin-left: 1%;
margin-right: 1%;*/
}
/* Grid >> Children (Alpha ~ First, Omega ~ Last)
----------------------------------------------------------------------------------------------------*/
.alpha {
margin-left: 0;
}
.omega {
margin-right: 0;
}
/* Grid >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .grid_1 {
width:8.333%;
}
.container_12 .grid_2 {
width:16.667%;
}
.container_12 .grid_3 {
width:25.0%;
}
.container_12 .grid_4 {
width:33.333%;
}
.container_12 .grid_5 {
width:41.667%;
}
.container_12 .grid_6 {
width:50.0%;
}
.container_12 .grid_7 {
width:58.333%;
}
.container_12 .grid_8 {
width:66.667%;
}
.container_12 .grid_9 {
width:75.0%;
}
.container_12 .grid_10 {
width:83.333%;
}
.container_12 .grid_11 {
width:91.667%;
}
.container_12 .grid_12 {
width:100.0%;
}
/* Prefix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .prefix_1 {
padding-left:8.333%;
}
.container_12 .prefix_2 {
padding-left:16.667%;
}
.container_12 .prefix_3 {
padding-left:25.0%;
}
.container_12 .prefix_4 {
padding-left:33.333%;
}
.container_12 .prefix_5 {
padding-left:41.667%;
}
.container_12 .prefix_6 {
padding-left:50.0%;
}
.container_12 .prefix_7 {
padding-left:58.333%;
}
.container_12 .prefix_8 {
padding-left:66.667%;
}
.container_12 .prefix_9 {
padding-left:75.0%;
}
.container_12 .prefix_10 {
padding-left:83.333%;
}
.container_12 .prefix_11 {
padding-left:91.667%;
}
/* Suffix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .suffix_1 {
padding-right:8.333%;
}
.container_12 .suffix_2 {
padding-right:16.667%;
}
.container_12 .suffix_3 {
padding-right:25.0%;
}
.container_12 .suffix_4 {
padding-right:33.333%;
}
.container_12 .suffix_5 {
padding-right:41.667%;
}
.container_12 .suffix_6 {
padding-right:50.0%;
}
.container_12 .suffix_7 {
padding-right:58.333%;
}
.container_12 .suffix_8 {
padding-right:66.667%;
}
.container_12 .suffix_9 {
padding-right:75.0%;
}
.container_12 .suffix_10 {
padding-right:83.333%;
}
.container_12 .suffix_11 {
padding-right:91.667%;
}
/* Push Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .push_1 {
left:8.333%;
}
.container_12 .push_2 {
left:16.667%;
}
.container_12 .push_3 {
left:25.0%;
}
.container_12 .push_4 {
left:33.333%;
}
.container_12 .push_5 {
left:41.667%;
}
.container_12 .push_6 {
left:50.0%;
}
.container_12 .push_7 {
left:58.333%;
}
.container_12 .push_8 {
left:66.667%;
}
.container_12 .push_9 {
left:75.0%;
}
.container_12 .push_10 {
left:83.333%;
}
.container_12 .push_11 {
left:91.667%;
}
/* Pull Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .pull_1 {
left:-8.333%;
}
.container_12 .pull_2 {
left:-16.667%;
}
.container_12 .pull_3 {
left:-25.0%;
}
.container_12 .pull_4 {
left:-33.333%;
}
.container_12 .pull_5 {
left:-41.667%;
}
.container_12 .pull_6 {
left:-50.0%;
}
.container_12 .pull_7 {
left:-58.333%;
}
.container_12 .pull_8 {
left:-66.667%;
}
.container_12 .pull_9 {
left:-75.0%;
}
.container_12 .pull_10 {
left:-83.333%;
}
.container_12 .pull_11 {
left:-91.667%;
}
/* Clear Floated Elements
----------------------------------------------------------------------------------------------------*/
/* http://sonspring.com/journal/clearing-floats */
.clear {
clear: both;
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
/* http://perishablepress.com/press/2008/02/05/lessons-learned-concerning-the-clearfix-css-hack */
.clearfix:after {
clear: both;
content: ' ';
display: block;
font-size: 0;
line-height: 0;
visibility: hidden;
width: 0;
height: 0;
}
.clearfix {
display: inline-block;
}
* html .clearfix {
height: 1%;
}
.clearfix {
display: block;
}

View File

@@ -1,343 +1 @@
/*
Variable Grid System (Fluid Version).
Learn more ~ http://www.spry-soft.com/grids/
Based on 960 Grid System - http://960.gs/ & 960 Fluid - http://www.designinfluences.com/
Licensed under GPL and MIT.
*/
/* Containers
----------------------------------------------------------------------------------------------------*/
.container_12 {
width: 92%;
margin-left: 4%;
margin-right: 4%;
}
/* Grid >> Global
----------------------------------------------------------------------------------------------------*/
.grid_1,
.grid_2,
.grid_3,
.grid_4,
.grid_5,
.grid_6,
.grid_7,
.grid_8,
.grid_9,
.grid_10,
.grid_11,
.grid_12 {
display:inline;
float: left;
position: relative;
margin-left: 1%;
margin-right: 1%;
}
/* Grid >> Children (Alpha ~ First, Omega ~ Last)
----------------------------------------------------------------------------------------------------*/
.alpha {
margin-left: 0;
}
.omega {
margin-right: 0;
}
/* Grid >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .grid_1 {
width:6.333%;
}
.container_12 .grid_2 {
width:14.667%;
}
.container_12 .grid_3 {
width:23.0%;
}
.container_12 .grid_4 {
width:31.333%;
}
.container_12 .grid_5 {
width:39.667%;
}
.container_12 .grid_6 {
width:48.0%;
}
.container_12 .grid_7 {
width:56.333%;
}
.container_12 .grid_8 {
width:64.667%;
}
.container_12 .grid_9 {
width:73.0%;
}
.container_12 .grid_10 {
width:81.333%;
}
.container_12 .grid_11 {
width:89.667%;
}
.container_12 .grid_12 {
width:98.0%;
}
/* Prefix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .prefix_1 {
padding-left:8.333%;
}
.container_12 .prefix_2 {
padding-left:16.667%;
}
.container_12 .prefix_3 {
padding-left:25.0%;
}
.container_12 .prefix_4 {
padding-left:33.333%;
}
.container_12 .prefix_5 {
padding-left:41.667%;
}
.container_12 .prefix_6 {
padding-left:50.0%;
}
.container_12 .prefix_7 {
padding-left:58.333%;
}
.container_12 .prefix_8 {
padding-left:66.667%;
}
.container_12 .prefix_9 {
padding-left:75.0%;
}
.container_12 .prefix_10 {
padding-left:83.333%;
}
.container_12 .prefix_11 {
padding-left:91.667%;
}
/* Suffix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .suffix_1 {
padding-right:8.333%;
}
.container_12 .suffix_2 {
padding-right:16.667%;
}
.container_12 .suffix_3 {
padding-right:25.0%;
}
.container_12 .suffix_4 {
padding-right:33.333%;
}
.container_12 .suffix_5 {
padding-right:41.667%;
}
.container_12 .suffix_6 {
padding-right:50.0%;
}
.container_12 .suffix_7 {
padding-right:58.333%;
}
.container_12 .suffix_8 {
padding-right:66.667%;
}
.container_12 .suffix_9 {
padding-right:75.0%;
}
.container_12 .suffix_10 {
padding-right:83.333%;
}
.container_12 .suffix_11 {
padding-right:91.667%;
}
/* Push Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .push_1 {
left:8.333%;
}
.container_12 .push_2 {
left:16.667%;
}
.container_12 .push_3 {
left:25.0%;
}
.container_12 .push_4 {
left:33.333%;
}
.container_12 .push_5 {
left:41.667%;
}
.container_12 .push_6 {
left:50.0%;
}
.container_12 .push_7 {
left:58.333%;
}
.container_12 .push_8 {
left:66.667%;
}
.container_12 .push_9 {
left:75.0%;
}
.container_12 .push_10 {
left:83.333%;
}
.container_12 .push_11 {
left:91.667%;
}
/* Pull Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/
.container_12 .pull_1 {
left:-8.333%;
}
.container_12 .pull_2 {
left:-16.667%;
}
.container_12 .pull_3 {
left:-25.0%;
}
.container_12 .pull_4 {
left:-33.333%;
}
.container_12 .pull_5 {
left:-41.667%;
}
.container_12 .pull_6 {
left:-50.0%;
}
.container_12 .pull_7 {
left:-58.333%;
}
.container_12 .pull_8 {
left:-66.667%;
}
.container_12 .pull_9 {
left:-75.0%;
}
.container_12 .pull_10 {
left:-83.333%;
}
.container_12 .pull_11 {
left:-91.667%;
}
/* Clear Floated Elements
----------------------------------------------------------------------------------------------------*/
/* http://sonspring.com/journal/clearing-floats */
.clear {
clear: both;
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}
/* http://perishablepress.com/press/2008/02/05/lessons-learned-concerning-the-clearfix-css-hack */
.clearfix:after {
clear: both;
content: ' ';
display: block;
font-size: 0;
line-height: 0;
visibility: hidden;
width: 0;
height: 0;
}
.clearfix {
display: inline-block;
}
* html .clearfix {
height: 1%;
}
.clearfix {
display: block;
}
.container_12 {width: 100%;}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12 {display:inline;float: left;position: relative;}.alpha {margin-left: 0;}.omega {margin-right: 0;}.container_12 .grid_1 {width:8.333%;}.container_12 .grid_2 {width:16.667%;}.container_12 .grid_3 {width:25.0%;}.container_12 .grid_4 {width:33.333%;}.container_12 .grid_5 {width:41.667%;}.container_12 .grid_6 {width:50.0%;}.container_12 .grid_7 {width:58.333%;}.container_12 .grid_8 {width:66.667%;}.container_12 .grid_9 {width:75.0%;}.container_12 .grid_10 {width:83.333%;}.container_12 .grid_11 {width:91.667%;}.container_12 .grid_12 {width:100.0%;}.container_12 .prefix_1 {padding-left:8.333%;}.container_12 .prefix_2 {padding-left:16.667%;}.container_12 .prefix_3 {padding-left:25.0%;}.container_12 .prefix_4 {padding-left:33.333%;}.container_12 .prefix_5 {padding-left:41.667%;}.container_12 .prefix_6 {padding-left:50.0%;}.container_12 .prefix_7 {padding-left:58.333%;}.container_12 .prefix_8 {padding-left:66.667%;}.container_12 .prefix_9 {padding-left:75.0%;}.container_12 .prefix_10 {padding-left:83.333%;}.container_12 .prefix_11 {padding-left:91.667%;}.container_12 .suffix_1 {padding-right:8.333%;}.container_12 .suffix_2 {padding-right:16.667%;}.container_12 .suffix_3 {padding-right:25.0%;}.container_12 .suffix_4 {padding-right:33.333%;}.container_12 .suffix_5 {padding-right:41.667%;}.container_12 .suffix_6 {padding-right:50.0%;}.container_12 .suffix_7 {padding-right:58.333%;}.container_12 .suffix_8 {padding-right:66.667%;}.container_12 .suffix_9 {padding-right:75.0%;}.container_12 .suffix_10 {padding-right:83.333%;}.container_12 .suffix_11 {padding-right:91.667%;}.container_12 .push_1 {left:8.333%;}.container_12 .push_2 {left:16.667%;}.container_12 .push_3 {left:25.0%;}.container_12 .push_4 {left:33.333%;}.container_12 .push_5 {left:41.667%;}.container_12 .push_6 {left:50.0%;}.container_12 .push_7 {left:58.333%;}.container_12 .push_8 {left:66.667%;}.container_12 .push_9 {left:75.0%;}.container_12 .push_10 {left:83.333%;}.container_12 .push_11 {left:91.667%;}.container_12 .pull_1 {left:-8.333%;}.container_12 .pull_2 {left:-16.667%;}.container_12 .pull_3 {left:-25.0%;}.container_12 .pull_4 {left:-33.333%;}.container_12 .pull_5 {left:-41.667%;}.container_12 .pull_6 {left:-50.0%;}.container_12 .pull_7 {left:-58.333%;}.container_12 .pull_8 {left:-66.667%;}.container_12 .pull_9 {left:-75.0%;}.container_12 .pull_10 {left:-83.333%;}.container_12 .pull_11 {left:-91.667%;}.clear {clear: both;display: block;overflow: hidden;visibility: hidden;width: 0;height: 0;}.clearfix:after {clear: both;content: ' ';display: block;font-size: 0;line-height: 0;visibility: hidden;width: 0;height: 0;}.clearfix {display: inline-block;}* html .clearfix {height: 1%;}.clearfix {display: block;}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Some files were not shown because too many files have changed in this diff Show More