Merge branch 'new_metadata'
Conflicts: requirements/development.txt requirements/production.txt settings.py
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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
49
apps/common/widgets.py
Normal 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')
|
||||
@@ -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
|
||||
|
||||
23
apps/document_indexing/__init__.py
Normal file
23
apps/document_indexing/__init__.py
Normal 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'))
|
||||
27
apps/document_indexing/admin.py
Normal file
27
apps/document_indexing/admin.py
Normal 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)
|
||||
211
apps/document_indexing/api.py
Normal file
211
apps/document_indexing/api.py
Normal 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
|
||||
25
apps/document_indexing/conf/settings.py
Normal file
25
apps/document_indexing/conf/settings.py
Normal 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}
|
||||
]
|
||||
)
|
||||
92
apps/document_indexing/filesystem.py
Normal file
92
apps/document_indexing/filesystem.py
Normal 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))
|
||||
60
apps/document_indexing/models.py
Normal file
60
apps/document_indexing/models.py
Normal 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')
|
||||
8
apps/document_indexing/os_agnostic.py
Normal file
8
apps/document_indexing/os_agnostic.py
Normal 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
|
||||
7
apps/document_indexing/urls.py
Normal file
7
apps/document_indexing/urls.py
Normal 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'),
|
||||
)
|
||||
23
apps/document_indexing/utils.py
Normal file
23
apps/document_indexing/utils.py
Normal 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
|
||||
}
|
||||
}
|
||||
64
apps/document_indexing/views.py
Normal file
64
apps/document_indexing/views.py
Normal 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)
|
||||
@@ -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'))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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},
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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'})
|
||||
)
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
@@ -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 %}
|
||||
|
||||
@@ -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'))
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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}
|
||||
]
|
||||
)
|
||||
Binary file not shown.
@@ -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"
|
||||
@@ -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')
|
||||
@@ -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'),
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
11
apps/grouping/__init__.py
Normal 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
16
apps/grouping/admin.py
Normal 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)
|
||||
0
apps/grouping/conf/__init__.py
Normal file
0
apps/grouping/conf/__init__.py
Normal file
13
apps/grouping/conf/settings.py
Normal file
13
apps/grouping/conf/settings.py
Normal 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
88
apps/grouping/forms.py
Normal 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
27
apps/grouping/literals.py
Normal 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
82
apps/grouping/managers.py
Normal 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
40
apps/grouping/models.py
Normal 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
23
apps/grouping/tests.py
Normal 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
6
apps/grouping/urls.py
Normal 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
36
apps/grouping/utils.py
Normal 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
40
apps/grouping/views.py
Normal 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))
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
25
apps/metadata/__init__.py
Normal 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
30
apps/metadata/admin.py
Normal 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
88
apps/metadata/api.py
Normal 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
12
apps/metadata/classes.py
Normal 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)
|
||||
1
apps/metadata/conf/__init__.py
Normal file
1
apps/metadata/conf/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
27
apps/metadata/conf/settings.py
Normal file
27
apps/metadata/conf/settings.py
Normal 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
67
apps/metadata/forms.py
Normal 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
74
apps/metadata/models.py
Normal 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
23
apps/metadata/tests.py
Normal 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
10
apps/metadata/urls.py
Normal 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
247
apps/metadata/views.py
Normal 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', []))
|
||||
@@ -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)
|
||||
|
||||
@@ -111,6 +111,7 @@ def do_document_ocr(document):
|
||||
f.close()
|
||||
cleanup(ocr_output)
|
||||
finally:
|
||||
os.close(desc)
|
||||
cleanup(filepath)
|
||||
if imagefile:
|
||||
cleanup(imagefile)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
{
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
7
contrib/metadata_conversion.txt
Normal file
7
contrib/metadata_conversion.txt
Normal 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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
17
docs/TODO
17
docs/TODO
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
32
settings.py
32
settings.py
@@ -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''
|
||||
|
||||
343
site_media/css/960-fluid-devel.css
Normal file
343
site_media/css/960-fluid-devel.css
Normal 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;
|
||||
}
|
||||
@@ -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 |
BIN
site_media/images/392336_7079-small.png
Normal file
BIN
site_media/images/392336_7079-small.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
site_media/images/icons/comment_delete.png
Normal file
BIN
site_media/images/icons/comment_delete.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
site_media/images/icons/folder_delete.png
Normal file
BIN
site_media/images/icons/folder_delete.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
site_media/images/icons/folder_link.png
Normal file
BIN
site_media/images/icons/folder_link.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
site_media/images/icons/group_delete.png
Normal file
BIN
site_media/images/icons/group_delete.png
Normal file
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
Reference in New Issue
Block a user