Merge remote-tracking branch 'origin/development' into development

This commit is contained in:
Roberto Rosario
2015-06-19 18:30:06 -04:00
104 changed files with 986 additions and 606 deletions

View File

@@ -37,7 +37,11 @@ What's new in Mayan EDMS v2.0
* New document converter
* New class based transformations
* Removal of the DOCUMENT_RESTRICTIONS_OVERRIDE permission
* Use of Font awesome icons
* Move document OCR content code to the OCR app
* Add new permissions PERMISSION_OCR_CONTENT_VIEW
* Removed the page_label field
Upgrading from a previous version
=================================

View File

@@ -4,19 +4,19 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'acls.views',
url(r'^new_holder_for/(?P<app_label>[-\w]+)/(?P<model_name>[-\w]+)/(?P<object_id>\d+)/$', 'acl_new_holder_for', (), 'acl_new_holder_for'),
url(r'^list_for/(?P<app_label>[-\w]+)/(?P<model_name>[-\w]+)/(?P<object_id>\d+)/$', 'acl_list', (), 'acl_list'),
url(r'^details/(?P<access_object_gid>[.\w]+)/holder/(?P<holder_object_gid>[.\w]+)/$', 'acl_detail', (), 'acl_detail'),
url(r'^holder/new/(?P<access_object_gid>[.\w]+)/$', 'acl_holder_new', (), 'acl_holder_new'),
url(r'^new_holder_for/(?P<app_label>[-\w]+)/(?P<model_name>[-\w]+)/(?P<object_id>\d+)/$', 'acl_new_holder_for', name='acl_new_holder_for'),
url(r'^list_for/(?P<app_label>[-\w]+)/(?P<model_name>[-\w]+)/(?P<object_id>\d+)/$', 'acl_list', name='acl_list'),
url(r'^details/(?P<access_object_gid>[.\w]+)/holder/(?P<holder_object_gid>[.\w]+)/$', 'acl_detail', name='acl_detail'),
url(r'^holder/new/(?P<access_object_gid>[.\w]+)/$', 'acl_holder_new', name='acl_holder_new'),
url(r'^multiple/grant/$', 'acl_grant', (), 'acl_multiple_grant'),
url(r'^multiple/revoke/$', 'acl_revoke', (), 'acl_multiple_revoke'),
url(r'^multiple/grant/$', 'acl_grant', name='acl_multiple_grant'),
url(r'^multiple/revoke/$', 'acl_revoke', name='acl_multiple_revoke'),
url(r'^class/$', 'acl_setup_valid_classes', (), 'acl_setup_valid_classes'),
url(r'^class/details/(?P<access_object_class_gid>[.\w]+)/holder/(?P<holder_object_gid>[.\w]+)/$', 'acl_class_acl_detail', (), 'acl_class_acl_detail'),
url(r'^class/list_for/(?P<access_object_class_gid>[.\w]+)/$', 'acl_class_acl_list', (), 'acl_class_acl_list'),
url(r'^class/holder/new/(?P<access_object_class_gid>[.\w]+)/$', 'acl_class_new_holder_for', (), 'acl_class_new_holder_for'),
url(r'^class/$', 'acl_setup_valid_classes', name='acl_setup_valid_classes'),
url(r'^class/details/(?P<access_object_class_gid>[.\w]+)/holder/(?P<holder_object_gid>[.\w]+)/$', 'acl_class_acl_detail', name='acl_class_acl_detail'),
url(r'^class/list_for/(?P<access_object_class_gid>[.\w]+)/$', 'acl_class_acl_list', name='acl_class_acl_list'),
url(r'^class/holder/new/(?P<access_object_class_gid>[.\w]+)/$', 'acl_class_new_holder_for', name='acl_class_new_holder_for'),
url(r'^class/multiple/grant/$', 'acl_class_multiple_grant', (), 'acl_class_multiple_grant'),
url(r'^class/multiple/revoke/$', 'acl_class_multiple_revoke', (), 'acl_class_multiple_revoke'),
url(r'^class/multiple/grant/$', 'acl_class_multiple_grant', name='acl_class_multiple_grant'),
url(r'^class/multiple/revoke/$', 'acl_class_multiple_revoke', name='acl_class_multiple_revoke'),
)

View File

@@ -101,18 +101,20 @@
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{% get_menu_links 'main menu' as menu_links %}
{% for link in menu_links %}
{% with 'true' as as_li %}
{% with 'true' as hide_active_anchor %}
{% with 'active' as li_class_active %}
{% with 'first' as li_class_first %}
{% with ' ' as link_classes %}
{% include 'navigation/generic_subnavigation.html' %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% for link_set in menu_links %}
{% for link in link_set %}
{% with 'true' as as_li %}
{% with 'true' as hide_active_anchor %}
{% with 'active' as li_class_active %}
{% with 'first' as li_class_first %}
{% with ' ' as link_classes %}
{% include 'navigation/generic_subnavigation.html' %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %}
{% endfor %}
</ul>
<ul class="nav navbar-nav navbar-right">
@@ -170,9 +172,9 @@
<li><a class="btn-sm" href="{{ entry.url }}">{{ entry.text }}</a></li>
{% endfor %}
{% if not forloop.last and links_set %}
<li class="divider"></li>
{% endif %}
{% if not forloop.last and links_set %}
<li class="divider"></li>
{% endif %}
{% endfor %}
</ul>
</div>
@@ -182,15 +184,15 @@
{% if form_navigation_links %}
<div class="pull-right list-group">
{% if form_navigation_links %}
{% with form_navigation_links as object_navigation_links %}
{% with 'true' as hide_active_anchor %}
{% with 'active' as link_class_active %}
{% with 'list-group-item btn-sm' as link_classes %}
{% include 'navigation/generic_navigation.html' %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endwith %}
{% for object_navigation_links in form_navigation_links %}
{% with 'true' as hide_active_anchor %}
{% with 'active' as link_class_active %}
{% with 'list-group-item btn-sm' as link_classes %}
{% include 'navigation/generic_navigation.html' %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endfor %}
{% endif %}
</div>
{% endif %}

View File

@@ -13,7 +13,9 @@
<div class="well center-block">
<div class="row">
{% with 'navigation/large_button_link.html' as link_template %}
{% include 'navigation/generic_navigation.html' %}
{% for object_navigation_links in resolved_links %}
{% include 'navigation/generic_navigation.html' %}
{% endfor %}
{% endwith %}
</div>
</div>

View File

@@ -51,7 +51,7 @@
<th>{% trans 'Identifier' %}</th>
{% endif %}
{% for column in object_list.0|get_model_list_columns %}
{% for column in object_list|get_model_list_columns %}
<th>{{ column.name }}</th>
{% endfor %}
@@ -101,17 +101,17 @@
{% endfor %}
{% if not hide_links %}
<td class="last">
{% get_menu_links 'object menu' source=object as links %}
{% with links as object_navigation_links %}
{% with 'true' as horizontal %}
{% include 'navigation/generic_navigation.html' %}
{% endwith %}
{% endwith %}
{% get_menu_links 'object menu' source=object as resolved_links %}
{% for object_navigation_links in resolved_links %}
{% with 'true' as horizontal %}
{% include 'navigation/generic_navigation.html' %}
{% endwith %}
{% endfor %}
</td>
{% endif %}
</tr>
{% empty %}
<tr><td colspan=99 class="tc">{% trans 'No results' %}</td></tr>
<tr><td class="text-center" colspan=99>{% trans 'No results' %}</td></tr>
{% endfor %}
</tbody>
</table>

View File

@@ -38,10 +38,12 @@
<div class="well center-block">
<div class="row">
{% get_menu_links 'front page menu' as object_navigation_links %}
{% get_menu_links 'front page menu' as resolved_links %}
{% with 'navigation/large_button_link.html' as link_template %}
{% with 'col-xs-12 col-sm-6 col-md-4 col-lg-4' as div_class %}
{% include 'navigation/generic_navigation.html' %}
{% for object_navigation_links in resolved_links %}
{% include 'navigation/generic_navigation.html' %}
{% endfor %}
{% endwith %}
{% endwith %}
</div>

View File

@@ -11,8 +11,8 @@
{% block content_plain %}
<div class="row">
<div class="col-xs-10 col-xs-offset-1 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4">
{% auto_admin_properties %}
{% if auto_admin_properties.account %}
{% autoadmin_properties %}
{% if autoadmin_properties.account %}
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">{% trans "First time login" %}</h3>
@@ -20,9 +20,9 @@
<div class="panel-body">
<p>{% trans 'You have just finished installing <strong>Mayan EDMS</strong>, congratulations!' %}</p>
<p>{% trans 'Login using the following credentials:' %}</p>
<p>{% blocktrans with auto_admin_properties.account as account %}Username: <strong>{{ account }}</strong>{% endblocktrans %}</p>
<p>{% blocktrans with auto_admin_properties.account.email as email %}Email: <strong>{{ email }}</strong>{% endblocktrans %}</p>
<p>{% blocktrans with auto_admin_properties.password as password %}Password: <strong>{{ password }}</strong>{% endblocktrans %}</p>
<p>{% blocktrans with autoadmin_properties.account as account %}Username: <strong>{{ account }}</strong>{% endblocktrans %}</p>
<p>{% blocktrans with autoadmin_properties.account.email as email %}Email: <strong>{{ email }}</strong>{% endblocktrans %}</p>
<p>{% blocktrans with autoadmin_properties.password as password %}Password: <strong>{{ password }}</strong>{% endblocktrans %}</p>
<p>{% trans 'Be sure to change the password to increase security and to disable this message.' %}</p>
</div>
</div>

View File

@@ -62,7 +62,7 @@ class APICheckedoutDocumentListView(generics.ListCreateAPIView):
DocumentCheckout.objects.create(
document=document,
expiration_datetime=timezone.localize(serializer.data['expiration_datetime']),
user_object=request.user,
user=request.user,
block_new_version=serializer.data['block_new_version']
)
except Exception as exception:
@@ -105,7 +105,7 @@ class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView):
document = self.get_object().document
if document.checkout_info().user_object == request.user:
if document.checkout_info().user == request.user:
try:
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CHECKIN])
except PermissionDenied:

View File

@@ -3,14 +3,16 @@ from __future__ import absolute_import, unicode_literals
from datetime import timedelta
from django import apps
from django.db.models.signals import pre_save
from django.utils.translation import ugettext_lazy as _
from acls.api import class_permissions
from common.menus import menu_facet, menu_main, menu_sidebar
from documents.models import Document
from documents.models import Document, DocumentVersion
from mayan.celery import app
from rest_api.classes import APIEndPoint
from .handlers import check_if_new_versions_allowed
from .links import (
link_checkin_document, link_checkout_document, link_checkout_info,
link_checkout_list
@@ -18,7 +20,7 @@ from .links import (
from .models import DocumentCheckout
from .permissions import (
PERMISSION_DOCUMENT_CHECKIN, PERMISSION_DOCUMENT_CHECKIN_OVERRIDE,
PERMISSION_DOCUMENT_CHECKOUT, PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE
PERMISSION_DOCUMENT_CHECKOUT
)
CHECK_EXPIRED_CHECK_OUTS_INTERVAL = 60 # Lowest check out expiration allowed
@@ -33,7 +35,6 @@ class CheckoutsApp(apps.AppConfig):
Document.add_to_class('check_in', lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user))
Document.add_to_class('checkout_info', lambda document: DocumentCheckout.objects.document_checkout_info(document))
Document.add_to_class('checkout_state', lambda document: DocumentCheckout.objects.document_checkout_state(document))
Document.add_to_class('is_new_versions_allowed', lambda document, user=None: DocumentCheckout.objects.is_document_new_versions_allowed(document, user))
app.conf.CELERYBEAT_SCHEDULE.update({
'check_expired_check_outs': {
@@ -47,11 +48,12 @@ class CheckoutsApp(apps.AppConfig):
PERMISSION_DOCUMENT_CHECKOUT,
PERMISSION_DOCUMENT_CHECKIN,
PERMISSION_DOCUMENT_CHECKIN_OVERRIDE,
PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE
])
menu_facet.bind_links(links=[link_checkout_info], sources=[Document])
menu_main.bind_links(links=[link_checkout_list])
menu_sidebar.bind_links(links=[link_checkout_document, link_checkin_document], sources=['checkouts:checkout_info', 'checkouts:checkout_document', 'checkouts:checkin_document'])
pre_save.connect(check_if_new_versions_allowed, dispatch_uid='document_index_delete', sender=DocumentVersion)
APIEndPoint('checkouts')

View File

@@ -3,16 +3,38 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext
class DocumentNotCheckedOut(Exception):
class DocumentCheckoutError(Exception):
"""
Base checkout exception
"""
pass
class DocumentCheckoutWarning(Warning):
"""
Base checkout warning
"""
pass
class DocumentNotCheckedOut(DocumentCheckoutError):
"""
Raised when trying to checkin a document that is not checkedout
"""
pass
class DocumentAlreadyCheckedOut(Exception):
class DocumentAlreadyCheckedOut(DocumentCheckoutError):
"""
Raised when trying to checkout an already checkedout document
"""
def __unicode__(self):
return ugettext('Document already checked out.')
class NewDocumentVersionNotAllowed(DocumentCheckoutWarning):
"""
Uploading new versions for this document is not allowed
Current reasons: Document is in checked out state
"""
pass

View File

@@ -11,4 +11,4 @@ class DocumentCheckoutForm(forms.ModelForm):
class Meta:
model = DocumentCheckout
exclude = ('document', 'checkout_datetime', 'user_content_type', 'user_object_id')
exclude = ('document', 'checkout_datetime', 'user')

View File

@@ -0,0 +1,11 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from .exceptions import NewDocumentVersionNotAllowed
from .models import DocumentCheckout
def check_if_new_versions_allowed(sender, **kwargs):
if not DocumentCheckout.objects.are_document_new_versions_allowed(kwargs['instance'].document):
raise NewDocumentVersionNotAllowed(_('New versions not allowed for the checkedout document: %s' % kwargs['instance'].document))

View File

@@ -16,12 +16,14 @@ from .events import (
)
from .exceptions import DocumentNotCheckedOut
from .literals import STATE_CHECKED_OUT, STATE_CHECKED_IN
from .permissions import PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE
logger = logging.getLogger(__name__)
class DocumentCheckoutManager(models.Manager):
def checkout_document(self, document, expiration_datetime, user, block_new_version=True):
self.create(document=document, expiration_datetime=expiration_datetime, user=user, block_new_version=block_new_version)
def checked_out_documents(self):
return Document.objects.filter(pk__in=self.model.objects.all().values_list('document__pk', flat=True))
@@ -47,7 +49,7 @@ class DocumentCheckoutManager(models.Manager):
raise DocumentNotCheckedOut
else:
if user:
if self.document_checkout_info(document).user_object != user:
if self.document_checkout_info(document).user != user:
event_document_forceful_check_in.commit(actor=user, target=document)
else:
event_document_check_in.commit(actor=user, target=document)
@@ -68,33 +70,10 @@ class DocumentCheckoutManager(models.Manager):
else:
return STATE_CHECKED_IN
def is_document_new_versions_allowed(self, document, user=None):
def are_document_new_versions_allowed(self, document, user=None):
try:
checkout_info = self.document_checkout_info(document)
except DocumentNotCheckedOut:
return True
else:
if not user:
return not checkout_info.block_new_version
else:
if user.is_staff or user.is_superuser:
# Allow anything to superusers and staff
return True
if user == checkout_info.user_object:
# Allow anything to the user who checked out this document
return True
else:
# If not original user check to see if user has global or this document's PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE permission
try:
Permission.objects.check_permissions(user, [PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE])
except PermissionDenied:
try:
AccessEntry.objects.check_accesses([PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE], user, document)
except PermissionDenied:
# Last resort check if original user enabled restriction
return not checkout_info.block_new_version
else:
return True
else:
return True
return not checkout_info.block_new_version

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('checkouts', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='documentcheckout',
name='user',
field=models.ForeignKey(verbose_name='User', blank=True, to=settings.AUTH_USER_MODEL, null=True),
preserve_default=True,
),
]

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def move_from_content_type_user_to_foreign_key_field_user(apps, schema_editor):
# The model references the use who checked out the document using a
# generic.GenericForeignKey. This migrations changes that to a simpler
# ForeignKey to the User model
DocumentCheckout = apps.get_model('checkouts', 'DocumentCheckout')
for document_checkout in DocumentCheckout.objects.all():
document_checkout.user = document_checkout.user_object
document_checkout.save()
class Migration(migrations.Migration):
dependencies = [
('checkouts', '0002_documentcheckout_user'),
]
operations = [
migrations.RunPython(move_from_content_type_user_to_foreign_key_field_user),
]

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('checkouts', '0003_auto_20150617_0325'),
]
operations = [
migrations.RemoveField(
model_name='documentcheckout',
name='user_content_type',
),
migrations.RemoveField(
model_name='documentcheckout',
name='user_object_id',
),
migrations.AlterField(
model_name='documentcheckout',
name='user',
field=models.ForeignKey(verbose_name='User', to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
]

View File

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import logging
from django.conf import settings
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
@@ -27,10 +28,7 @@ class DocumentCheckout(models.Model):
checkout_datetime = models.DateTimeField(verbose_name=_('Check out date and time'), auto_now_add=True)
expiration_datetime = models.DateTimeField(verbose_name=_('Check out expiration date and time'), help_text=_('Amount of time to hold the document checked out in minutes.'))
# TODO: simplify user_object to an instance of User
user_content_type = models.ForeignKey(ContentType, null=True, blank=True) # blank and null added for ease of db migration
user_object_id = models.PositiveIntegerField(null=True, blank=True)
user_object = generic.GenericForeignKey(ct_field='user_content_type', fk_field='user_object_id')
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User'))
block_new_version = models.BooleanField(default=True, verbose_name=_('Block new version upload'), help_text=_('Do not allow new version of this document to be uploaded.'))
@@ -50,7 +48,7 @@ class DocumentCheckout(models.Model):
result = super(DocumentCheckout, self).save(*args, **kwargs)
if new_checkout:
event_document_check_out.commit(actor=self.user_object, target=self.document)
event_document_check_out.commit(actor=self.user, target=self.document)
return result
def get_absolute_url(self):

View File

@@ -9,4 +9,3 @@ namespace = PermissionNamespace('checkouts', _('Document checkout'))
PERMISSION_DOCUMENT_CHECKOUT = Permission.objects.register(namespace, 'checkout_document', _('Check out documents'))
PERMISSION_DOCUMENT_CHECKIN = Permission.objects.register(namespace, 'checkin_document', _('Check in documents'))
PERMISSION_DOCUMENT_CHECKIN_OVERRIDE = Permission.objects.register(namespace, 'checkin_document_override', _('Forcefully check in documents'))
PERMISSION_DOCUMENT_RESTRICTIONS_OVERRIDE = Permission.objects.register(namespace, 'checkout_restrictions_override', _('Allow overriding check out restrictions'))

View File

@@ -15,7 +15,6 @@ class DocumentCheckoutSerializer(serializers.ModelSerializer):
class Meta:
model = DocumentCheckout
read_only_fields = ('user_content_type', 'user_object_id')
class NewDocumentCheckoutSerializer(serializers.Serializer):

View File

@@ -33,7 +33,7 @@ class CheckoutListView(DocumentListView):
'title': _('Documents checked out'),
'hide_links': True,
'extra_columns': [
{'name': _('Checkout user'), 'attribute': encapsulate(lambda document: get_object_name(document.checkout_info().user_object))},
{'name': _('Checkout user'), 'attribute': encapsulate(lambda document: get_object_name(document.checkout_info().user))},
{'name': _('Checkout time and date'), 'attribute': encapsulate(lambda document: document.checkout_info().checkout_datetime)},
{'name': _('Checkout expiration'), 'attribute': encapsulate(lambda document: document.checkout_info().expiration_datetime)},
],
@@ -51,7 +51,7 @@ def checkout_info(request, document_pk):
if document.is_checked_out():
checkout_info = document.checkout_info()
paragraphs.append(_('User: %s') % get_object_name(checkout_info.user_object))
paragraphs.append(_('User: %s') % get_object_name(checkout_info.user))
paragraphs.append(_('Check out time: %s') % checkout_info.checkout_datetime)
paragraphs.append(_('Check out expiration: %s') % checkout_info.expiration_datetime)
paragraphs.append(_('New versions allowed: %s') % (_('Yes') if not checkout_info.block_new_version else _('No')))
@@ -74,10 +74,12 @@ def checkout_document(request, document_pk):
form = DocumentCheckoutForm(data=request.POST, initial={'document': document})
if form.is_valid():
try:
document_checkout = form.save(commit=False)
document_checkout.user_object = request.user
document_checkout.document = document
document_checkout.save()
DocumentCheckout.objects.checkout_document(
document=document,
expiration_datetime=form.cleaned_data['expiration_datetime'],
user=request.user,
block_new_version=form.cleaned_data['block_new_version'],
)
except DocumentAlreadyCheckedOut:
messages.error(request, _('Document already checked out.'))
return HttpResponseRedirect(reverse('checkouts:checkout_info', args=[document.pk]))
@@ -110,7 +112,7 @@ def checkin_document(request, document_pk):
# If the user trying to check in the document is the same as the check out
# user just check for the normal permission otherwise check for the forceful
# checkin permission
if document.checkout_info().user_object == request.user:
if document.checkout_info().user == request.user:
try:
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_CHECKIN])
except PermissionDenied:
@@ -139,7 +141,7 @@ def checkin_document(request, document_pk):
'object': document,
}
if document.checkout_info().user_object != request.user:
if document.checkout_info().user != request.user:
context['title'] = _('You didn\'t originally checked out this document. Are you sure you wish to forcefully check in document: %s?') % document
else:
context['title'] = _('Are you sure you wish to check in document: %s?') % document

View File

@@ -2,7 +2,6 @@ from __future__ import unicode_literals
from django.contrib import admin
from .models import AutoAdminSingleton, SharedUploadedFile
from .models import SharedUploadedFile
admin.site.register(AutoAdminSingleton)
admin.site.register(SharedUploadedFile)

View File

@@ -5,8 +5,6 @@ import tempfile
from django import apps
from django.conf import settings
from django.contrib.auth import models as auth_models
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in
from django.db.models.signals import post_migrate, post_save
from django.utils.translation import ugettext_lazy as _
@@ -14,8 +12,7 @@ from django.utils.translation import ugettext_lazy as _
from common import settings as common_settings
from .handlers import (
auto_admin_account_passwd_change, user_locale_profile_session_config,
user_locale_profile_create
user_locale_profile_session_config, user_locale_profile_create
)
from .links import (
link_about, link_admin_site, link_current_user_details,
@@ -26,48 +23,15 @@ from .links import (
from .menus import (
menu_facet, menu_main, menu_secondary, menu_setup, menu_tools
)
from .models import (
AnonymousUserSingleton, AutoAdminSingleton, UserLocaleProfile
)
from .settings import (
AUTO_ADMIN_USERNAME, AUTO_ADMIN_PASSWORD, AUTO_CREATE_ADMIN,
TEMPORARY_DIRECTORY
)
from .models import AnonymousUserSingleton
from .settings import TEMPORARY_DIRECTORY
from .utils import validate_path
logger = logging.getLogger(__name__)
def create_superuser_and_anonymous_user(sender, **kwargs):
"""
From https://github.com/lambdalisue/django-qwert/blob/master/qwert/autoscript/__init__.py
From http://stackoverflow.com/questions/1466827/ --
Prevent interactive question about wanting a superuser created. (This code
has to go in this otherwise empty "models" module so that it gets processed by
the "syncdb" command during database creation.)
Create our own admin super user automatically.
"""
if kwargs['app_config'].__class__ == CommonApp:
AutoAdminSingleton.objects.get_or_create()
AnonymousUserSingleton.objects.get_or_create()
if AUTO_CREATE_ADMIN:
try:
auth_models.User.objects.get(username=AUTO_ADMIN_USERNAME)
except auth_models.User.DoesNotExist:
logger.info('Creating super admin user -- login: %s, password: %s', AUTO_ADMIN_USERNAME, AUTO_ADMIN_PASSWORD)
assert auth_models.User.objects.create_superuser(AUTO_ADMIN_USERNAME, 'autoadmin@autoadmin.com', AUTO_ADMIN_PASSWORD)
admin = auth_models.User.objects.get(username=AUTO_ADMIN_USERNAME)
# Store the auto admin password properties to display the first login message
auto_admin_properties, created = AutoAdminSingleton.objects.get_or_create()
auto_admin_properties.account = admin
auto_admin_properties.password = AUTO_ADMIN_PASSWORD
auto_admin_properties.password_hash = admin.password
auto_admin_properties.save()
else:
logger.info('Super admin user already exists. -- login: %s', AUTO_ADMIN_USERNAME)
def create_anonymous_user(sender, **kwargs):
AnonymousUserSingleton.objects.get_or_create()
class CommonApp(apps.AppConfig):
@@ -90,10 +54,9 @@ class CommonApp(apps.AppConfig):
menu_setup.bind_links(links=[link_admin_site])
menu_tools.bind_links(links=[link_maintenance_menu])
post_migrate.connect(create_superuser_and_anonymous_user, dispatch_uid='create_superuser_and_anonymous_user')
post_save.connect(auto_admin_account_passwd_change, dispatch_uid='auto_admin_account_passwd_change', sender=User)
user_logged_in.connect(user_locale_profile_session_config, dispatch_uid='user_locale_profile_session_config', sender=User)
post_save.connect(user_locale_profile_create, dispatch_uid='user_locale_profile_create', sender=User)
post_migrate.connect(create_anonymous_user, dispatch_uid='create_anonymous_user', sender=self)
user_logged_in.connect(user_locale_profile_session_config, dispatch_uid='user_locale_profile_session_config', sender=settings.AUTH_USER_MODEL)
post_save.connect(user_locale_profile_create, dispatch_uid='user_locale_profile_create', sender=settings.AUTH_USER_MODEL)
if (not validate_path(TEMPORARY_DIRECTORY)) or (not TEMPORARY_DIRECTORY):
setattr(common_settings, 'TEMPORARY_DIRECTORY', tempfile.mkdtemp())

View File

@@ -83,4 +83,3 @@ class MissingItem(object):
self.description = description
self.view = view
self.__class__._registry.append(self)

View File

@@ -5,7 +5,6 @@ import os
from django import forms
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.db import models
from django.utils.html import escape
@@ -13,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _
from .models import UserLocaleProfile
from .utils import return_attrib
from .widgets import DetailSelectMultiple, EmailInput, PlainWidget
from .widgets import DetailSelectMultiple, PlainWidget
class DetailForm(forms.ModelForm):

View File

@@ -2,19 +2,7 @@ from __future__ import unicode_literals
from django.conf import settings
from .models import (
AnonymousUserSingleton, AutoAdminSingleton, UserLocaleProfile
)
def auto_admin_account_passwd_change(sender, instance, **kwargs):
auto_admin_properties = AutoAdminSingleton.objects.get()
if instance == auto_admin_properties.account and instance.password != auto_admin_properties.password_hash:
# Only delete the auto admin properties when the password has been changed
auto_admin_properties.account = None
auto_admin_properties.password = None
auto_admin_properties.password_hash = None
auto_admin_properties.save()
from .models import UserLocaleProfile
def user_locale_profile_session_config(sender, request, user, **kwargs):

View File

@@ -6,11 +6,14 @@ from django.conf import settings
from django.core import management
from django.utils.crypto import get_random_string
from ...signals import post_initial_setup
class Command(management.BaseCommand):
help = 'Gets Mayan EDMS ready to be used (initializes database, creates a secret key, etc).'
def _generate_secret_key(self):
@staticmethod
def _generate_secret_key():
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
return get_random_string(50, chars)
@@ -26,7 +29,9 @@ class Command(management.BaseCommand):
'',
'from .base import *',
'',
"SECRET_KEY = '{0}'".format(self._generate_secret_key()),
"SECRET_KEY = '{0}'".format(Command._generate_secret_key()),
'',
]))
management.call_command('syncdb', migrate=True, interactive=False)
management.call_command('migrate', interactive=False)
management.call_command('createautoadmin', interactive=False)
post_initial_setup.send(sender=self)

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('common', '0002_auto_20150608_1902'),
]
operations = [
migrations.RemoveField(
model_name='autoadminsingleton',
name='account',
),
migrations.DeleteModel(
name='AutoAdminSingleton',
),
]

View File

@@ -29,7 +29,7 @@ class ObjectListPermissionFilterMixin(object):
if self.object_permission:
try:
# Check to see if the user has the permissions globally
Permission.objects.check_permissions(self.request.user, [self.object_permission])
Permission.objects.check_permissions(self.request.user, (self.object_permission,))
except PermissionDenied:
# No global permission, filter ther queryset per object + permission
return AccessEntry.objects.filter_objects_by_access(self.object_permission, self.request.user, queryset)
@@ -43,13 +43,16 @@ class ObjectListPermissionFilterMixin(object):
class ObjectPermissionCheckMixin(object):
object_permission = None
def get_permission_object(self):
return self.get_object()
def dispatch(self, request, *args, **kwargs):
if self.object_permission:
try:
Permission.objects.check_permissions(request.user, [self.object_permission])
Permission.objects.check_permissions(request.user, (self.object_permission,))
except PermissionDenied:
AccessEntry.objects.check_access(self.object_permission, request.user, self.get_object())
AccessEntry.objects.check_access(self.object_permission, request.user, self.get_permission_object())
return super(ObjectPermissionCheckMixin, self).dispatch(request, *args, **kwargs)
@@ -80,6 +83,6 @@ class ViewPermissionCheckMixin(object):
def dispatch(self, request, *args, **kwargs):
if self.view_permission:
Permission.objects.check_permissions(self.request.user, [self.view_permission])
Permission.objects.check_permissions(self.request.user, (self.view_permission,))
return super(ViewPermissionCheckMixin, self).dispatch(request, *args, **kwargs)

View File

@@ -3,7 +3,6 @@ from __future__ import unicode_literals
from pytz import common_timezones
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _, ugettext
@@ -32,15 +31,6 @@ class AnonymousUserSingleton(SingletonModel):
verbose_name = verbose_name_plural = _('Anonymous user')
class AutoAdminSingleton(SingletonModel):
account = models.ForeignKey(User, null=True, blank=True, related_name='auto_admin_account', verbose_name=_('Account'))
password = models.CharField(null=True, blank=True, verbose_name=_('Password'), max_length=128)
password_hash = models.CharField(null=True, blank=True, verbose_name=_('Password hash'), max_length=128)
class Meta:
verbose_name = verbose_name_plural = _('Auto admin properties')
@python_2_unicode_compatible
class SharedUploadedFile(models.Model):
file = models.FileField(upload_to=upload_to, storage=shared_storage_backend, verbose_name=_('File'))
@@ -61,7 +51,7 @@ class SharedUploadedFile(models.Model):
@python_2_unicode_compatible
class UserLocaleProfile(models.Model):
user = models.OneToOneField(User, related_name='locale_profile', verbose_name=_('User'))
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='locale_profile', verbose_name=_('User'))
timezone = models.CharField(choices=zip(common_timezones, common_timezones), max_length=48, verbose_name=_('Timezone'))
language = models.CharField(choices=settings.LANGUAGES, max_length=8, verbose_name=_('Language'))

View File

@@ -1,6 +1,5 @@
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.utils.translation import ugettext_lazy as _
from smart_settings.api import register_setting
@@ -15,30 +14,6 @@ TEMPORARY_DIRECTORY = register_setting(
exists=True
)
register_setting(
namespace='common',
module='common.settings',
name='AUTO_CREATE_ADMIN',
global_name='COMMON_AUTO_CREATE_ADMIN',
default=True,
)
register_setting(
namespace='common',
module='common.settings',
name='AUTO_ADMIN_USERNAME',
global_name='COMMON_AUTO_ADMIN_USERNAME',
default='admin',
)
register_setting(
namespace='common',
module='common.settings',
name='AUTO_ADMIN_PASSWORD',
global_name='COMMON_AUTO_ADMIN_PASSWORD',
default=User.objects.make_random_password(),
)
register_setting(
namespace='common',
module='common.settings',

View File

@@ -0,0 +1,5 @@
from __future__ import unicode_literals
from django.dispatch import Signal
post_initial_setup = Signal(use_caching=True)

View File

@@ -13,8 +13,23 @@ def object_property(value, arg):
@register.filter
def get_model_list_columns(obj):
try:
# Is it a query set?
obj = obj.model
except AttributeError:
# Is not a query set
try:
# Is iterable?
obj = obj[0]
except TypeError:
# It is not
pass
except IndexError:
# It a list and it's empty
pass
for key, value in model_list_columns.items():
if isinstance(obj, key):
if key == obj or isinstance(obj, key):
return value
return []

View File

@@ -14,4 +14,3 @@ def project_name():
@register.simple_tag
def project_version():
return mayan.__version__

View File

@@ -16,4 +16,3 @@ def render_subtemplate(context, template_name, template_context):
new_context = Context(context)
new_context.update(Context(template_context))
return get_template(template_name).render(new_context)

View File

@@ -11,8 +11,6 @@ from django.contrib.contenttypes.models import ContentType
from django.utils.datastructures import MultiValueDict
from django.utils.http import urlquote as django_urlquote
from django.utils.http import urlencode as django_urlencode
from django.utils.importlib import import_module
from django.utils.translation import ugettext_lazy as _
logger = logging.getLogger(__name__)
@@ -87,7 +85,6 @@ def get_descriptor(file_input, read=True):
def get_object_name(obj):
ct_label = ContentType.objects.get_for_model(obj).name
if isinstance(obj, User):
label = obj.get_full_name() if obj.get_full_name() else obj
else:

View File

@@ -5,12 +5,11 @@ from json import dumps, loads
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.auth.views import login, password_change
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render_to_response
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _
@@ -20,7 +19,6 @@ from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import ListView
from dynamic_search.classes import SearchModel
from permissions.models import Permission
from .api import tools
from .classes import MissingItem
@@ -33,7 +31,6 @@ from .mixins import (
ExtraContextMixin, ObjectListPermissionFilterMixin,
ObjectPermissionCheckMixin, RedirectionMixin, ViewPermissionCheckMixin
)
from .utils import return_attrib
class AssignRemoveView(TemplateView):
@@ -138,6 +135,10 @@ class AboutView(TemplateView):
template_name = 'appearance/about.html'
class ConfirmView(ObjectListPermissionFilterMixin, ViewPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, TemplateView):
template_name = 'appearance/generic_confirm.html'
class CurrentUserDetailsView(TemplateView):
template_name = 'appearance/generic_form.html'
@@ -294,6 +295,8 @@ class MultiFormView(FormView):
return self.forms_invalid(forms)
# TODO: check/test if ViewPermissionCheckMixin, ObjectPermissionCheckMixin are
# in the right MRO
class SingleObjectEditView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, ExtraContextMixin, RedirectionMixin, UpdateView):
template_name = 'appearance/generic_form.html'
@@ -395,7 +398,7 @@ class ParentChildListView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin,
is_empty = len(self.object_list) == 0
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.")
% {'class_name': self.__class__.__name__})
% {'class_name': self.__class__.__name__})
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
@@ -456,7 +459,7 @@ class SetupListView(TemplateView):
def get_context_data(self, **kwargs):
data = super(SetupListView, self).get_context_data(**kwargs)
data.update({
'object_navigation_links': menu_setup.resolve(context=RequestContext(self.request)),
'resolved_links': menu_setup.resolve(context=RequestContext(self.request)),
'title': _('Setup items'),
})
return data
@@ -468,7 +471,7 @@ class ToolsListView(TemplateView):
def get_context_data(self, **kwargs):
data = super(ToolsListView, self).get_context_data(**kwargs)
data.update({
'object_navigation_links': menu_tools.resolve(context=RequestContext(self.request)),
'resolved_links': menu_tools.resolve(context=RequestContext(self.request)),
'title': _('Tools'),
})
return data

View File

@@ -11,15 +11,6 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
class PlainWidget(forms.widgets.Widget):
"""
Class to define a form widget that effectively nulls the htmls of a
widget and reduces the output to only it's value
"""
def render(self, name, value, attrs=None):
return mark_safe('%s' % value)
class DetailSelectMultiple(forms.widgets.SelectMultiple):
def __init__(self, queryset=None, *args, **kwargs):
self.queryset = queryset
@@ -61,43 +52,6 @@ class DetailSelectMultiple(forms.widgets.SelectMultiple):
return mark_safe(output + '</ul>\n')
def exists_widget(path):
try:
return two_state_template(os.path.exists(path))
except Exception as exception:
return exception
def two_state_template(state, ok_icon='fa fa-check', fail_icon='fa fa-times'):
if state:
return mark_safe('<i class="text-success {}"></i>'.format(ok_icon))
else:
return mark_safe('<i class="text-danger {}"></i>'.format(fail_icon))
class TextAreaDiv(forms.widgets.Widget):
"""
Class to define a form widget that simulates the behavior of a
Textarea widget but using a div tag instead
"""
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
default_attrs = {'class': 'text_area_div'}
if attrs:
default_attrs.update(attrs)
super(TextAreaDiv, self).__init__(default_attrs)
def render(self, name, value, attrs=None):
if value is None:
value = ''
flat_attrs = flatatt(self.build_attrs(attrs, name=name))
content = conditional_escape(force_unicode(value))
result = '<pre%s>%s</pre>' % (flat_attrs, content)
return mark_safe(result)
# From: http://www.peterbe.com/plog/emailinput-html5-django
class EmailInput(forms.widgets.Input):
"""
@@ -115,6 +69,15 @@ class EmailInput(forms.widgets.Input):
return super(EmailInput, self).render(name, value, attrs=attrs)
class PlainWidget(forms.widgets.Widget):
"""
Class to define a form widget that effectively nulls the htmls of a
widget and reduces the output to only it's value
"""
def render(self, name, value, attrs=None):
return mark_safe('%s' % value)
class ScrollableCheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
"""
Class for a form widget composed of a selection of checkboxes wrapped
@@ -146,3 +109,40 @@ class ScrollableCheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple):
output.append('</ul>')
return mark_safe('<div class="text_area_div">%s</div>' % '\n'.join(output))
class TextAreaDiv(forms.widgets.Widget):
"""
Class to define a form widget that simulates the behavior of a
Textarea widget but using a div tag instead
"""
def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness.
default_attrs = {'class': 'text_area_div'}
if attrs:
default_attrs.update(attrs)
super(TextAreaDiv, self).__init__(default_attrs)
def render(self, name, value, attrs=None):
if value is None:
value = ''
flat_attrs = flatatt(self.build_attrs(attrs, name=name))
content = conditional_escape(force_unicode(value))
result = '<pre%s>%s</pre>' % (flat_attrs, content)
return mark_safe(result)
def exists_widget(path):
try:
return two_state_template(os.path.exists(path))
except Exception as exception:
return exception
def two_state_template(state, ok_icon='fa fa-check', fail_icon='fa fa-times'):
if state:
return mark_safe('<i class="text-success {}"></i>'.format(ok_icon))
else:
return mark_safe('<i class="text-danger {}"></i>'.format(fail_icon))

View File

@@ -4,8 +4,8 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'converter.views',
url(r'^create_for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', 'transformation_create', (), 'transformation_create'),
url(r'^list_for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', 'transformation_list', (), 'transformation_list'),
url(r'^delete/(?P<object_id>\d+)/$', 'transformation_delete', (), 'transformation_delete'),
url(r'^edit/(?P<object_id>\d+)/$', 'transformation_edit', (), 'transformation_edit'),
url(r'^create_for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', 'transformation_create', name='transformation_create'),
url(r'^list_for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', 'transformation_list', name='transformation_list'),
url(r'^delete/(?P<object_id>\d+)/$', 'transformation_delete', name='transformation_delete'),
url(r'^edit/(?P<object_id>\d+)/$', 'transformation_edit', name='transformation_edit'),
)

View File

@@ -2,9 +2,9 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'django_gpg.views',
url(r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete', (), 'key_delete'),
url(r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete', name='key_delete'),
url(r'^list/private/$', 'key_list', {'secret': True}, 'key_private_list'),
url(r'^list/public/$', 'key_list', {'secret': False}, 'key_public_list'),
url(r'^query/$', 'key_query', (), 'key_query'),
url(r'^receive/(?P<key_id>.+)/$', 'key_receive', (), 'key_receive'),
url(r'^query/$', 'key_query', name='key_query'),
url(r'^receive/(?P<key_id>.+)/$', 'key_receive', name='key_receive'),
)

View File

@@ -4,8 +4,8 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'document_comments.views',
url(r'^comment/(?P<comment_id>\d+)/delete/$', 'comment_delete', (), 'comment_delete'),
url(r'^comment/multiple/delete/$', 'comment_multiple_delete', (), 'comment_multiple_delete'),
url(r'^(?P<document_id>\d+)/comment/add/$', 'comment_add', (), 'comment_add'),
url(r'^(?P<document_id>\d+)/comment/list/$', 'comments_for_document', (), 'comments_for_document'),
url(r'^comment/(?P<comment_id>\d+)/delete/$', 'comment_delete', name='comment_delete'),
url(r'^comment/multiple/delete/$', 'comment_multiple_delete', name='comment_multiple_delete'),
url(r'^(?P<document_id>\d+)/comment/add/$', 'comment_add', name='comment_add'),
url(r'^(?P<document_id>\d+)/comment/list/$', 'comments_for_document', name='comments_for_document'),
)

View File

@@ -23,9 +23,9 @@ def is_not_root_node(context):
link_document_index_list = Link(permissions=[PERMISSION_DOCUMENT_INDEXING_VIEW, PERMISSION_DOCUMENT_VIEW], text=_('Indexes'), view='indexing:document_index_list', args='object.pk')
link_index_list = Link(permissions=[PERMISSION_DOCUMENT_INDEXING_VIEW], text=_('Index list'), view='indexing:index_list')
link_index_main_menu = Link(icon='fa fa-sitemap', text=_('Indexes'), view='indexing:index_list')
link_index_main_menu = Link(icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_list')
link_index_parent = Link(condition=is_not_instance_root_node, permissions=[PERMISSION_DOCUMENT_INDEXING_VIEW], text=_('Go up one level'), view='indexing:index_instance_node_view', args='object.parent.pk')
link_index_setup = Link(icon='fa fa-sitemap', permissions=[PERMISSION_DOCUMENT_INDEXING_SETUP], text=_('Indexes'), view='indexing:index_setup_list')
link_index_setup = Link(icon='fa fa-list-ul', permissions=[PERMISSION_DOCUMENT_INDEXING_SETUP], text=_('Indexes'), view='indexing:index_setup_list')
link_index_setup_list = Link(permissions=[PERMISSION_DOCUMENT_INDEXING_SETUP], text=_('Indexes'), view='indexing:index_setup_list')
link_index_setup_create = Link(permissions=[PERMISSION_DOCUMENT_INDEXING_CREATE], text=_('Create index'), view='indexing:index_setup_create')
link_index_setup_edit = Link(permissions=[PERMISSION_DOCUMENT_INDEXING_EDIT], text=_('Edit'), view='indexing:index_setup_edit', args='resolved_object.pk')

View File

@@ -12,22 +12,22 @@ from .views import SetupIndexDocumentTypesView
urlpatterns = patterns(
'document_indexing.views',
url(r'^setup/index/list/$', 'index_setup_list', (), 'index_setup_list'),
url(r'^setup/index/create/$', 'index_setup_create', (), 'index_setup_create'),
url(r'^setup/index/(?P<index_pk>\d+)/edit/$', 'index_setup_edit', (), 'index_setup_edit'),
url(r'^setup/index/(?P<index_pk>\d+)/delete/$', 'index_setup_delete', (), 'index_setup_delete'),
url(r'^setup/index/(?P<index_pk>\d+)/view/$', 'index_setup_view', (), 'index_setup_view'),
url(r'^setup/index/list/$', 'index_setup_list', name='index_setup_list'),
url(r'^setup/index/create/$', 'index_setup_create', name='index_setup_create'),
url(r'^setup/index/(?P<index_pk>\d+)/edit/$', 'index_setup_edit', name='index_setup_edit'),
url(r'^setup/index/(?P<index_pk>\d+)/delete/$', 'index_setup_delete', name='index_setup_delete'),
url(r'^setup/index/(?P<index_pk>\d+)/view/$', 'index_setup_view', name='index_setup_view'),
url(r'^setup/index/(?P<index_pk>\d+)/document_types/$', SetupIndexDocumentTypesView.as_view(), name='index_setup_document_types'),
url(r'^setup/template/node/(?P<parent_pk>\d+)/create/child/$', 'template_node_create', (), 'template_node_create'),
url(r'^setup/template/node/(?P<node_pk>\d+)/edit/$', 'template_node_edit', (), 'template_node_edit'),
url(r'^setup/template/node/(?P<node_pk>\d+)/delete/$', 'template_node_delete', (), 'template_node_delete'),
url(r'^setup/template/node/(?P<parent_pk>\d+)/create/child/$', 'template_node_create', name='template_node_create'),
url(r'^setup/template/node/(?P<node_pk>\d+)/edit/$', 'template_node_edit', name='template_node_edit'),
url(r'^setup/template/node/(?P<node_pk>\d+)/delete/$', 'template_node_delete', name='template_node_delete'),
url(r'^index/list/$', 'index_list', (), 'index_list'),
url(r'^instance/node/(?P<index_instance_node_pk>\d+)/$', 'index_instance_node_view', (), 'index_instance_node_view'),
url(r'^index/list/$', 'index_list', name='index_list'),
url(r'^instance/node/(?P<index_instance_node_pk>\d+)/$', 'index_instance_node_view', name='index_instance_node_view'),
url(r'^rebuild/all/$', 'rebuild_index_instances', (), 'rebuild_index_instances'),
url(r'^list/for/document/(?P<document_id>\d+)/$', 'document_index_list', (), 'document_index_list'),
url(r'^rebuild/all/$', 'rebuild_index_instances', name='rebuild_index_instances'),
url(r'^list/for/document/(?P<document_id>\d+)/$', 'document_index_list', name='document_index_list'),
)
api_urls = patterns(

View File

@@ -4,8 +4,8 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'document_signatures.views',
url(r'^verify/(?P<document_pk>\d+)/$', 'document_verify', (), 'document_verify'),
url(r'^upload/signature/(?P<document_pk>\d+)/$', 'document_signature_upload', (), 'document_signature_upload'),
url(r'^download/signature/(?P<document_pk>\d+)/$', 'document_signature_download', (), 'document_signature_download'),
url(r'^document/(?P<document_pk>\d+)/signature/delete/$', 'document_signature_delete', (), 'document_signature_delete'),
url(r'^verify/(?P<document_pk>\d+)/$', 'document_verify', name='document_verify'),
url(r'^upload/signature/(?P<document_pk>\d+)/$', 'document_signature_upload', name='document_signature_upload'),
url(r'^download/signature/(?P<document_pk>\d+)/$', 'document_signature_download', name='document_signature_download'),
url(r'^document/(?P<document_pk>\d+)/signature/delete/$', 'document_signature_delete', name='document_signature_delete'),
)

View File

@@ -66,6 +66,10 @@ class DocumentStatesApp(apps.AppConfig):
'name': _('Date and time'),
'attribute': encapsulate(lambda workflow: getattr(workflow.get_last_log_entry(), 'datetime', _('None')))
},
{
'name': _('Completion'),
'attribute': encapsulate(lambda workflow: getattr(workflow.get_current_state(), 'completion', _('None')))
},
])
register_model_list_columns(WorkflowInstanceLogEntry, [
@@ -92,6 +96,10 @@ class DocumentStatesApp(apps.AppConfig):
'name': _('Is initial state?'),
'attribute': 'initial'
},
{
'name': _('Completion'),
'attribute': 'completion'
},
])
register_model_list_columns(WorkflowTransition, [

View File

@@ -16,7 +16,7 @@ class WorkflowForm(forms.ModelForm):
class WorkflowStateForm(forms.ModelForm):
class Meta:
fields = ('initial', 'label')
fields = ('initial', 'label', 'completion')
model = WorkflowState

View File

@@ -5,11 +5,11 @@ from django.utils.translation import ugettext_lazy as _
from navigation import Link
link_document_workflow_instance_list = Link(text=_('Workflows'), view='document_states:document_workflow_instance_list', args='object.pk')
link_setup_workflow_create = Link(text=_('Create'), view='document_states:setup_workflow_create')
link_setup_workflow_create = Link(text=_('Create workflow'), view='document_states:setup_workflow_create')
link_setup_workflow_delete = Link(tags='dangerous', text=_('Delete'), view='document_states:setup_workflow_delete', args='object.pk')
link_setup_workflow_document_types = Link(text=_('Document types'), view='document_states:setup_workflow_document_types', args='object.pk')
link_setup_workflow_edit = Link(text=_('Edit'), view='document_states:setup_workflow_edit', args='object.pk')
link_setup_workflow_list = Link(icon='fa fa-table', text=_('Workflows'), view='document_states:setup_workflow_list')
link_setup_workflow_list = Link(icon='fa fa-sitemap', text=_('Workflows'), view='document_states:setup_workflow_list')
link_setup_workflow_state_create = Link(text=_('Create state'), view='document_states:setup_workflow_state_create', args='object.pk')
link_setup_workflow_state_delete = Link(tags='dangerous', text=_('Delete'), view='document_states:setup_workflow_state_delete', args='object.pk')
link_setup_workflow_state_edit = Link(text=_('Edit'), view='document_states:setup_workflow_state_edit', args='object.pk')

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('document_states', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='workflowstate',
name='completion',
field=models.IntegerField(default=0, help_text='Enter the percent of completion that this state represents in relation to the workflow. Use numbers without the percent sign.', verbose_name='Completion', blank=True),
preserve_default=True,
),
]

View File

@@ -1,8 +1,10 @@
from __future__ import unicode_literals
import logging
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.db import models
from django.db import IntegrityError, models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
@@ -10,6 +12,8 @@ from documents.models import Document, DocumentType
from .managers import WorkflowManager
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class Workflow(models.Model):
@@ -31,7 +35,13 @@ class Workflow(models.Model):
return None
def launch_for(self, document):
self.instances.create(document=document)
try:
logger.info('Launching workflow %s for document %s', self, document)
self.instances.create(document=document)
except IntegrityError:
logger.info('Workflow %s already launched for document %s', self, document)
else:
logger.info('Workflow %s launched for document %s', self, document)
class Meta:
verbose_name = _('Workflow')
@@ -43,6 +53,7 @@ class WorkflowState(models.Model):
workflow = models.ForeignKey(Workflow, related_name='states', verbose_name=_('Workflow'))
label = models.CharField(max_length=255, verbose_name=_('Label'))
initial = models.BooleanField(default=False, help_text=_('Select if this will be the state with which you want the workflow to start in. Only one state can be the initial state.'), verbose_name=_('Initial'))
completion = models.IntegerField(blank=True, default=0, help_text=_('Enter the percent of completion that this state represents in relation to the workflow. Use numbers without the percent sign.'), verbose_name=_('Completion'))
def __str__(self):
return self.label

View File

@@ -3,14 +3,14 @@ from __future__ import unicode_literals
from django.conf.urls import patterns, url
from .views import (
SetupWorkflowCreateView, SetupWorkflowDeleteView,
SetupWorkflowDocumentTypesView, SetupWorkflowEditView,
SetupWorkflowListView, SetupWorkflowStateCreateView,
SetupWorkflowStateDeleteView, SetupWorkflowStateEditView,
SetupWorkflowStateListView, SetupWorkflowTransitionListView,
SetupWorkflowTransitionCreateView, SetupWorkflowTransitionDeleteView,
SetupWorkflowTransitionEditView, WorkflowInstanceDetailView,
WorkflowInstanceTransitionView, DocumentWorkflowInstanceListView
DocumentWorkflowInstanceListView, SetupWorkflowCreateView,
SetupWorkflowDeleteView, SetupWorkflowDocumentTypesView,
SetupWorkflowEditView, SetupWorkflowListView,
SetupWorkflowStateCreateView, SetupWorkflowStateDeleteView,
SetupWorkflowStateEditView, SetupWorkflowStateListView,
SetupWorkflowTransitionListView, SetupWorkflowTransitionCreateView,
SetupWorkflowTransitionDeleteView, SetupWorkflowTransitionEditView,
WorkflowInstanceDetailView, WorkflowInstanceTransitionView
)
urlpatterns = patterns(

View File

@@ -7,13 +7,13 @@ from django.db.utils import IntegrityError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.views.generic import FormView
from django.views.generic import FormView, View
from acls.models import AccessEntry
from common.utils import generate_choices_w_labels
from common.views import (
AssignRemoveView, SingleObjectCreateView, SingleObjectDeleteView,
SingleObjectEditView, SingleObjectListView
AssignRemoveView, ConfirmView, SingleObjectCreateView,
SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
)
from documents.models import Document
from permissions.models import Permission
@@ -60,8 +60,6 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
class WorkflowInstanceDetailView(SingleObjectListView):
template_name = 'appearance/generic_multi_subtemplates.html'
def dispatch(self, request, *args, **kwargs):
try:
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_WORKFLOW_VIEW])
@@ -77,33 +75,12 @@ class WorkflowInstanceDetailView(SingleObjectListView):
return self.get_workflow_instance().log_entries.order_by('-datetime')
def get_context_data(self, **kwargs):
form = WorkflowInstanceDetailForm(
instance=self.get_workflow_instance(), extra_fields=[
{'label': _('Current state'), 'field': 'get_current_state'},
{'label': _('Last transition'), 'field': 'get_last_transition'},
]
)
context = {
'navigation_object_list': ['object', 'workflow_instance'],
'object': self.get_workflow_instance().document,
'subtemplates_list': [
{
'name': 'appearance/generic_form_subtemplate.html',
'context': {
'form': form,
'read_only': True,
}
},
{
'name': 'appearance/generic_list_subtemplate.html',
'context': {
'object_list': self.get_queryset(),
'title': _('Log entries'),
'hide_object': True,
}
}
],
'object_list': self.get_queryset(),
'title': _('Log entries'),
'hide_object': True,
'title': _('Detail of workflow: %(workflow)s') % {
'workflow': self.get_workflow_instance()
},
@@ -193,6 +170,8 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
def add(self, item):
self.workflow.document_types.add(item)
# TODO: add task launching this workflow for all the document types of
# item
def dispatch(self, request, *args, **kwargs):
self.workflow = get_object_or_404(Workflow, pk=self.kwargs['pk'])
@@ -212,6 +191,8 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
def remove(self, item):
self.workflow.document_types.remove(item)
# TODO: add task deleting this workflow for all the document types of
# item
def get_context_data(self, **kwargs):
data = super(SetupWorkflowDocumentTypesView, self).get_context_data(**kwargs)

View File

@@ -206,6 +206,8 @@ class APIDocumentImageView(generics.GenericAPIView):
try:
task = task_get_document_page_image.apply_async(kwargs=dict(document_page_id=document_page.pk, size=size, zoom=zoom, rotation=rotation, as_base64=True, version=version), queue='converter')
# TODO: prepend 'data:%s;base64,%s' based on format specified in
# async call
return Response({
'status': 'success',
'data': task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT)

View File

@@ -15,6 +15,7 @@ from common import (
)
from common.api import register_maintenance_links
from common.classes import ModelAttribute
from common.signals import post_initial_setup
from common.utils import encapsulate, validate_path
from converter.links import link_transformation_list
from converter.permissions import (
@@ -29,16 +30,16 @@ from rest_api.classes import APIEndPoint
from statistics.classes import StatisticNamespace
from documents import settings as document_settings
from .handlers import create_default_document_type
from .links import (
link_clear_image_cache, link_document_acl_list,
link_document_clear_transformations, link_document_content,
link_document_delete, link_document_document_type_edit,
link_document_events_view, link_document_multiple_document_type_edit,
link_document_download, link_document_edit, link_document_list,
link_document_list_recent, link_document_multiple_delete,
link_document_clear_transformations, link_document_delete,
link_document_document_type_edit, link_document_events_view,
link_document_multiple_document_type_edit, link_document_download,
link_document_edit, link_document_list, link_document_list_recent,
link_document_multiple_delete,
link_document_multiple_clear_transformations,
link_document_multiple_download,
link_document_multiple_update_page_count,
link_document_multiple_download, link_document_multiple_update_page_count,
link_document_page_navigation_first, link_document_page_navigation_last,
link_document_page_navigation_next,
link_document_page_navigation_previous, link_document_page_return,
@@ -108,7 +109,7 @@ class DocumentsApp(apps.AppConfig):
document_search.add_model_field('label', label=_('Label'))
document_search.add_model_field('metadata__metadata_type__name', label=_('Metadata type'))
document_search.add_model_field('metadata__value', label=_('Metadata value'))
document_search.add_model_field('versions__pages__content', label=_('Content'))
document_search.add_model_field('versions__pages__ocr_content__content', label=_('Content'))
document_search.add_model_field('description', label=_('Description'))
document_search.add_model_field('tags__label', label=_('Tags'))
@@ -127,7 +128,6 @@ class DocumentsApp(apps.AppConfig):
# Document facet links
menu_facet.bind_links(links=[link_document_acl_list], sources=[Document])
menu_facet.bind_links(links=[link_document_preview], sources=[Document], position=0)
menu_facet.bind_links(links=[link_document_content], sources=[Document], position=1)
menu_facet.bind_links(links=[link_document_properties], sources=[Document], position=2)
menu_facet.bind_links(links=[link_document_events_view, link_document_version_list], sources=[Document], position=2)
menu_facet.bind_links(links=[link_document_pages], sources=[Document])
@@ -146,6 +146,8 @@ class DocumentsApp(apps.AppConfig):
namespace.add_statistic(DocumentStatistics(name='document_stats', label=_('Document tendencies')))
namespace.add_statistic(DocumentUsageStatistics(name='document_usage', label=_('Document usage')))
post_initial_setup.connect(create_default_document_type, dispatch_uid='create_default_document_type')
registry.register(Document)
register_maintenance_links([link_clear_image_cache], namespace='documents', title=_('Documents'))

View File

@@ -7,3 +7,5 @@ from events.classes import Event
event_document_create = Event(name='documents_document_create', label=_('Document created'))
event_document_properties_edit = Event(name='documents_document_edit', label=_('Document properties edited'))
event_document_type_change = Event(name='documents_document_type_change', label=_('Document type changed'))
event_document_new_version = Event(name='documents_document_new_version', label=_('New version uploaded'))
event_document_version_revert = Event(name='documents_document_version_revert', label=_('Document version reverted'))

View File

@@ -1,9 +0,0 @@
from __future__ import unicode_literals
class NewDocumentVersionNotAllowed(Exception):
"""
Uploading new versions for this document is not allowed
Current reasons: Document is in checked out state
"""
pass

View File

@@ -87,34 +87,6 @@ class DocumentPropertiesForm(DetailForm):
model = Document
class DocumentContentForm(forms.Form):
"""
Form that concatenates all of a document pages' text content into a
single textarea widget
"""
def __init__(self, *args, **kwargs):
self.document = kwargs.pop('document', None)
super(DocumentContentForm, self).__init__(*args, **kwargs)
content = []
self.fields['contents'].initial = ''
try:
document_pages = self.document.pages.all()
except AttributeError:
document_pages = []
for page in document_pages:
if page.content:
content.append(conditional_escape(force_unicode(page.content)))
content.append('\n\n\n<hr/><div class="document-page-content-divider">- %s -</div><hr/>\n\n\n' % (ugettext('Page %(page_number)d') % {'page_number': page.page_number}))
self.fields['contents'].initial = mark_safe(''.join(content))
contents = forms.CharField(
label=_('Contents'),
widget=TextAreaDiv(attrs={'class': 'text_area_div full-height', 'data-height-difference': 360})
)
class DocumentTypeSelectForm(forms.Form):
"""
Form to select the document type of a document to be created, used

View File

@@ -0,0 +1,9 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from .models import DocumentType
def create_default_document_type(sender, **kwargs):
DocumentType.objects.create(name=_('Default'))

View File

@@ -42,7 +42,6 @@ def is_min_zoom(context):
# Facet
link_document_acl_list = Link(permissions=[ACLS_VIEW_ACL], text=_('ACLs'), view='documents:document_acl_list', args='object.pk')
link_document_content = Link(permissions=[PERMISSION_DOCUMENT_VIEW], text=_('Content'), view='documents:document_content', args='object.id')
link_document_events_view = Link(permissions=[PERMISSION_EVENTS_VIEW], text=_('Events'), view='events:events_for_object', args=['"documents"', '"document"', 'object.id'])
link_document_preview = Link(permissions=[PERMISSION_DOCUMENT_VIEW], text=_('Preview'), view='documents:document_preview', args='object.id')
link_document_properties = Link(permissions=[PERMISSION_DOCUMENT_VIEW], text=_('Properties'), view='documents:document_properties', args='object.id')

View File

@@ -37,6 +37,10 @@ class DocumentTypeManager(models.Manager):
class DocumentManager(models.Manager):
def invalidate_cache(self):
for document in self.model.objects.all():
document.invalidate_cache()
@transaction.atomic
def new_document(self, document_type, file_object, label=None, command_line=False, description=None, expand=False, language=None, user=None):
versions_created = []

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0003_auto_20150608_1915'),
]
operations = [
migrations.AlterModelOptions(
name='document',
options={'ordering': ('-date_added',), 'verbose_name': 'Document', 'verbose_name_plural': 'Documents'},
),
migrations.AlterModelOptions(
name='documentpage',
options={'ordering': ('page_number',), 'verbose_name': 'Document page', 'verbose_name_plural': 'Document pages'},
),
migrations.AlterModelOptions(
name='documenttype',
options={'ordering': ('name',), 'verbose_name': 'Document type', 'verbose_name_plural': 'Documents types'},
),
migrations.AlterModelOptions(
name='documenttypefilename',
options={'ordering': ('filename',), 'verbose_name': 'Document type quick rename filename', 'verbose_name_plural': 'Document types quick rename filenames'},
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0004_auto_20150616_1930'),
]
operations = [
migrations.RenameField(
model_name='documentpage',
old_name='content',
new_name='content_old',
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0005_auto_20150617_0358'),
('ocr', '0003_auto_20150617_0401'),
]
operations = [
migrations.RemoveField(
model_name='documentpage',
name='content_old',
),
]

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0006_remove_documentpage_content_old'),
]
operations = [
migrations.RemoveField(
model_name='documentpage',
name='page_label',
),
]

View File

@@ -28,8 +28,10 @@ from converter.literals import (
from converter.models import Transformation
from mimetype.api import get_mimetype
from .events import event_document_create
from .exceptions import NewDocumentVersionNotAllowed
from .events import (
event_document_create, event_document_new_version,
event_document_version_revert
)
from .managers import (
DocumentManager, DocumentTypeManager, RecentDocumentManager
)
@@ -70,7 +72,7 @@ class DocumentType(models.Model):
class Meta:
verbose_name = _('Document type')
verbose_name_plural = _('Documents types')
ordering = ['name']
ordering = ('name',)
@python_2_unicode_compatible
@@ -91,7 +93,7 @@ class Document(models.Model):
class Meta:
verbose_name = _('Document')
verbose_name_plural = _('Documents')
ordering = ['-date_added']
ordering = ('-date_added',)
def set_document_type(self, document_type, force=False):
has_changed = self.document_type != document_type
@@ -101,12 +103,9 @@ class Document(models.Model):
if has_changed or force:
post_document_type_change.send(sender=self.__class__, instance=self)
@staticmethod
def clear_image_cache():
for the_file in os.listdir(CACHE_PATH):
file_path = os.path.join(CACHE_PATH, the_file)
if os.path.isfile(file_path):
os.unlink(file_path)
def invalidate_cache(self):
for document_version in self.versions.all():
document_version.invalidate_cache()
def __str__(self):
return self.label
@@ -128,13 +127,6 @@ class Document(models.Model):
else:
event_document_create.commit(target=self)
def invalidate_cached_image(self, page):
pass
#try:
# os.unlink(self.get_cached_image_name(page, self.latest_version.pk)[0])
#except OSError:
# pass
def add_as_recent_document_for_user(self, user):
RecentDocument.objects.add_document_for_user(user, self)
@@ -148,10 +140,7 @@ class Document(models.Model):
return self.latest_version.size
def new_version(self, file_object, user=None, comment=None):
logger.debug('creating new document version')
# TODO: move this restriction to a signal processor of the checkouts app
if not self.is_new_versions_allowed(user=user):
raise NewDocumentVersionNotAllowed
logger.info('Creating a new document version for document: %s', self)
new_version = DocumentVersion.objects.create(
document=self,
@@ -159,9 +148,9 @@ class Document(models.Model):
comment=comment or '',
)
logger.debug('new_version saved')
logger.info('New document version created for document: %s', self)
# TODO: new HISTORY for version updates
event_document_new_version.commit(actor=user, target=self)
return new_version
@@ -286,6 +275,10 @@ class DocumentVersion(models.Model):
post_version_upload.send(sender=self.__class__, instance=self)
def invalidate_cache(self):
for page in self.pages.all():
page.invalidate_cache()
def update_checksum(self, save=True):
"""
Open a document version's file and update the checksum field using the
@@ -322,10 +315,14 @@ class DocumentVersion(models.Model):
return detected_pages
def revert(self):
def revert(self, user=None):
"""
Delete the subsequent versions after this one
"""
logger.info('Reverting to document document: %s to version: %s', self.document, self)
event_document_version_revert.commit(actor=user, target=self.document)
for version in self.document.versions.filter(timestamp__gt=self.timestamp):
version.delete()
@@ -346,7 +343,11 @@ class DocumentVersion(models.Model):
self.save()
def delete(self, *args, **kwargs):
for page in self.pages.all():
page.delete()
self.file.storage.delete(self.file.path)
return super(DocumentVersion, self).delete(*args, **kwargs)
def exists(self):
@@ -414,7 +415,7 @@ class DocumentTypeFilename(models.Model):
return self.filename
class Meta:
ordering = ['filename']
ordering = ('filename',)
unique_together = ('document_type', 'filename')
verbose_name = _('Document type quick rename filename')
verbose_name_plural = _('Document types quick rename filenames')
@@ -423,11 +424,9 @@ class DocumentTypeFilename(models.Model):
@python_2_unicode_compatible
class DocumentPage(models.Model):
"""
Model that describes a document version page including it's content
Model that describes a document version page
"""
document_version = models.ForeignKey(DocumentVersion, verbose_name=_('Document version'), related_name='pages')
content = models.TextField(blank=True, null=True, verbose_name=_('Content'))
page_label = models.CharField(max_length=40, blank=True, null=True, verbose_name=_('Page label'))
page_number = models.PositiveIntegerField(default=1, editable=False, verbose_name=_('Page number'), db_index=True)
def __str__(self):
@@ -438,13 +437,17 @@ class DocumentPage(models.Model):
}
class Meta:
ordering = ['page_number']
ordering = ('page_number',)
verbose_name = _('Document page')
verbose_name_plural = _('Document pages')
def get_absolute_url(self):
return reverse('documents:document_page_view', args=[self.pk])
def delete(self, *args, **kwargs):
self.invalidate_cache()
super(DocumentPage, self).delete(*args, **kwargs)
@property
def siblings(self):
return DocumentPage.objects.filter(document_version=self.document_version)
@@ -454,15 +457,20 @@ class DocumentPage(models.Model):
def document(self):
return self.document_version.document
def invalidate_cache(self):
fs_cleanup(self.get_cache_filename())
def get_uuid(self):
return 'page-cache-{}'.format(self.pk)
# Make cache UUID a mix of document UUID, version ID and page ID to
# avoid using stale images
return 'page-cache-{}-{}-{}'.format(self.document.uuid, self.document_version.pk, self.pk)
def get_cache_filename(self):
return os.path.join(CACHE_PATH, self.get_uuid())
def get_image(self, *args, **kwargs):
as_base64 = kwargs.pop('as_base64', False)
transformations = kwargs.pop('transformations', [])
size = kwargs.pop('size', DISPLAY_SIZE)
rotation = kwargs.pop('rotation', DEFAULT_ROTATION)
zoom_level = kwargs.pop('zoom', DEFAULT_ZOOM_LEVEL)
@@ -475,8 +483,6 @@ class DocumentPage(models.Model):
rotation = rotation % 360
as_base64 = kwargs.pop('as_base64', False)
cache_filename = self.get_cache_filename()
if os.path.exists(cache_filename):
@@ -492,6 +498,7 @@ class DocumentPage(models.Model):
with open(cache_filename, 'wb+') as file_object:
file_object.write(page_image.getvalue())
except:
# Cleanup in case of error
fs_cleanup(cache_filename)
raise
@@ -515,6 +522,7 @@ class DocumentPage(models.Model):
page_image = converter.get_page()
if as_base64:
# TODO: don't prepend 'data:%s;base64,%s' part
return 'data:%s;base64,%s' % ('image/png', base64.b64encode(page_image.getvalue()))
else:
return page_image

View File

@@ -22,8 +22,10 @@ def task_get_document_page_image(document_page_id, *args, **kwargs):
@app.task(ignore_result=True)
def task_clear_image_cache():
# TODO: Error logging / notification
Document.clear_image_cache()
logger.info('Starting document cache invalidation')
# TODO: Notification of success and of errors
Document.objects.invalidate_cache()
logger.info('Finished document cache invalidation')
@app.task(ignore_result=True)
@@ -69,6 +71,9 @@ def task_upload_new_version(document_id, shared_uploaded_file_id, user_id, comme
user = None
with File(file=shared_file.file) as file_object:
document.new_version(comment=comment, file_object=file_object, user=user)
shared_file.delete()
try:
document.new_version(comment=comment, file_object=file_object, user=user)
except Warning as warning:
logger.info('Warning during attempt to create new document version for document:%s ; %s', document, warning)
finally:
shared_file.delete()

View File

@@ -19,58 +19,57 @@ urlpatterns = patterns(
url(r'^list/$', DocumentListView.as_view(), name='document_list'),
url(r'^list/recent/$', RecentDocumentListView.as_view(), name='document_list_recent'),
url(r'^(?P<document_id>\d+)/preview/$', 'document_preview', (), 'document_preview'),
url(r'^(?P<document_id>\d+)/content/$', 'document_content', (), 'document_content'),
url(r'^(?P<document_id>\d+)/properties/$', 'document_properties', (), 'document_properties'),
url(r'^(?P<document_id>\d+)/preview/$', 'document_preview', name='document_preview'),
url(r'^(?P<document_id>\d+)/properties/$', 'document_properties', name='document_properties'),
url(r'^(?P<document_id>\d+)/type/$', 'document_document_type_edit', name='document_document_type_edit'),
url(r'^multiple/type/$', 'document_multiple_document_type_edit', name='document_multiple_document_type_edit'),
url(r'^(?P<document_id>\d+)/delete/$', 'document_delete', (), 'document_delete'),
url(r'^multiple/delete/$', 'document_multiple_delete', (), 'document_multiple_delete'),
url(r'^(?P<document_id>\d+)/edit/$', 'document_edit', (), 'document_edit'),
url(r'^(?P<document_id>\d+)/print/$', 'document_print', (), 'document_print'),
url(r'^(?P<document_id>\d+)/reset_page_count/$', 'document_update_page_count', (), 'document_update_page_count'),
url(r'^multiple/reset_page_count/$', 'document_multiple_update_page_count', (), 'document_multiple_update_page_count'),
url(r'^(?P<document_id>\d+)/delete/$', 'document_delete', name='document_delete'),
url(r'^multiple/delete/$', 'document_multiple_delete', name='document_multiple_delete'),
url(r'^(?P<document_id>\d+)/edit/$', 'document_edit', name='document_edit'),
url(r'^(?P<document_id>\d+)/print/$', 'document_print', name='document_print'),
url(r'^(?P<document_id>\d+)/reset_page_count/$', 'document_update_page_count', name='document_update_page_count'),
url(r'^multiple/reset_page_count/$', 'document_multiple_update_page_count', name='document_multiple_update_page_count'),
url(r'^(?P<document_id>\d+)/acls/$', 'document_acl_list', (), 'document_acl_list'),
url(r'^(?P<document_id>\d+)/acls/$', 'document_acl_list', name='document_acl_list'),
url(r'^(?P<document_id>\d+)/display/$', 'get_document_image', {'size': DISPLAY_SIZE}, 'document_display'),
url(r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {'size': PRINT_SIZE}, 'document_display_print'),
url(r'^(?P<document_id>\d+)/download/$', 'document_download', (), 'document_download'),
url(r'^multiple/download/$', 'document_multiple_download', (), 'document_multiple_download'),
url(r'^(?P<document_id>\d+)/clear_transformations/$', 'document_clear_transformations', (), 'document_clear_transformations'),
url(r'^(?P<document_id>\d+)/download/$', 'document_download', name='document_download'),
url(r'^multiple/download/$', 'document_multiple_download', name='document_multiple_download'),
url(r'^(?P<document_id>\d+)/clear_transformations/$', 'document_clear_transformations', name='document_clear_transformations'),
url(r'^(?P<document_pk>\d+)/version/all/$', 'document_version_list', (), 'document_version_list'),
url(r'^document/version/(?P<document_version_pk>\d+)/download/$', 'document_download', (), 'document_version_download'),
url(r'^document/version/(?P<document_version_pk>\d+)/revert/$', 'document_version_revert', (), 'document_version_revert'),
url(r'^(?P<document_pk>\d+)/version/all/$', 'document_version_list', name='document_version_list'),
url(r'^document/version/(?P<document_version_pk>\d+)/download/$', 'document_download', name='document_version_download'),
url(r'^document/version/(?P<document_version_pk>\d+)/revert/$', 'document_version_revert', name='document_version_revert'),
url(r'^(?P<document_id>\d+)/acls/$', 'document_acl_list', (), 'document_acl_list'),
url(r'^(?P<document_id>\d+)/acls/$', 'document_acl_list', name='document_acl_list'),
url(r'^(?P<pk>\d+)/pages/all/$', DocumentPageListView.as_view(), name='document_pages'),
url(r'^multiple/clear_transformations/$', 'document_multiple_clear_transformations', (), 'document_multiple_clear_transformations'),
url(r'^maintenance/clear_image_cache/$', 'document_clear_image_cache', (), 'document_clear_image_cache'),
url(r'^multiple/clear_transformations/$', 'document_multiple_clear_transformations', name='document_multiple_clear_transformations'),
url(r'^maintenance/clear_image_cache/$', 'document_clear_image_cache', name='document_clear_image_cache'),
url(r'^page/(?P<document_page_id>\d+)/$', 'document_page_view', (), 'document_page_view'),
url(r'^page/(?P<document_page_id>\d+)/navigation/next/$', 'document_page_navigation_next', (), 'document_page_navigation_next'),
url(r'^page/(?P<document_page_id>\d+)/navigation/previous/$', 'document_page_navigation_previous', (), 'document_page_navigation_previous'),
url(r'^page/(?P<document_page_id>\d+)/navigation/first/$', 'document_page_navigation_first', (), 'document_page_navigation_first'),
url(r'^page/(?P<document_page_id>\d+)/navigation/last/$', 'document_page_navigation_last', (), 'document_page_navigation_last'),
url(r'^page/(?P<document_page_id>\d+)/zoom/in/$', 'document_page_zoom_in', (), 'document_page_zoom_in'),
url(r'^page/(?P<document_page_id>\d+)/zoom/out/$', 'document_page_zoom_out', (), 'document_page_zoom_out'),
url(r'^page/(?P<document_page_id>\d+)/rotate/right/$', 'document_page_rotate_right', (), 'document_page_rotate_right'),
url(r'^page/(?P<document_page_id>\d+)/rotate/left/$', 'document_page_rotate_left', (), 'document_page_rotate_left'),
url(r'^page/(?P<document_page_id>\d+)/reset/$', 'document_page_view_reset', (), 'document_page_view_reset'),
url(r'^page/(?P<document_page_id>\d+)/$', 'document_page_view', name='document_page_view'),
url(r'^page/(?P<document_page_id>\d+)/navigation/next/$', 'document_page_navigation_next', name='document_page_navigation_next'),
url(r'^page/(?P<document_page_id>\d+)/navigation/previous/$', 'document_page_navigation_previous', name='document_page_navigation_previous'),
url(r'^page/(?P<document_page_id>\d+)/navigation/first/$', 'document_page_navigation_first', name='document_page_navigation_first'),
url(r'^page/(?P<document_page_id>\d+)/navigation/last/$', 'document_page_navigation_last', name='document_page_navigation_last'),
url(r'^page/(?P<document_page_id>\d+)/zoom/in/$', 'document_page_zoom_in', name='document_page_zoom_in'),
url(r'^page/(?P<document_page_id>\d+)/zoom/out/$', 'document_page_zoom_out', name='document_page_zoom_out'),
url(r'^page/(?P<document_page_id>\d+)/rotate/right/$', 'document_page_rotate_right', name='document_page_rotate_right'),
url(r'^page/(?P<document_page_id>\d+)/rotate/left/$', 'document_page_rotate_left', name='document_page_rotate_left'),
url(r'^page/(?P<document_page_id>\d+)/reset/$', 'document_page_view_reset', name='document_page_view_reset'),
# Admin views
url(r'^type/list/$', 'document_type_list', (), 'document_type_list'),
url(r'^type/create/$', 'document_type_create', (), 'document_type_create'),
url(r'^type/(?P<document_type_id>\d+)/edit/$', 'document_type_edit', (), 'document_type_edit'),
url(r'^type/(?P<document_type_id>\d+)/delete/$', 'document_type_delete', (), 'document_type_delete'),
url(r'^type/list/$', 'document_type_list', name='document_type_list'),
url(r'^type/create/$', 'document_type_create', name='document_type_create'),
url(r'^type/(?P<document_type_id>\d+)/edit/$', 'document_type_edit', name='document_type_edit'),
url(r'^type/(?P<document_type_id>\d+)/delete/$', 'document_type_delete', name='document_type_delete'),
url(r'^type/(?P<document_type_id>\d+)/filename/list/$', 'document_type_filename_list', (), 'document_type_filename_list'),
url(r'^type/filename/(?P<document_type_filename_id>\d+)/edit/$', 'document_type_filename_edit', (), 'document_type_filename_edit'),
url(r'^type/filename/(?P<document_type_filename_id>\d+)/delete/$', 'document_type_filename_delete', (), 'document_type_filename_delete'),
url(r'^type/(?P<document_type_id>\d+)/filename/create/$', 'document_type_filename_create', (), 'document_type_filename_create'),
url(r'^type/(?P<document_type_id>\d+)/filename/list/$', 'document_type_filename_list', name='document_type_filename_list'),
url(r'^type/filename/(?P<document_type_filename_id>\d+)/edit/$', 'document_type_filename_edit', name='document_type_filename_edit'),
url(r'^type/filename/(?P<document_type_filename_id>\d+)/delete/$', 'document_type_filename_delete', name='document_type_filename_delete'),
url(r'^type/(?P<document_type_id>\d+)/filename/create/$', 'document_type_filename_create', name='document_type_filename_create'),
)
api_urls = patterns(

View File

@@ -33,10 +33,9 @@ from .events import (
event_document_properties_edit, event_document_type_change
)
from .forms import (
DocumentContentForm, DocumentDownloadForm, DocumentForm, DocumentPageForm,
DocumentPreviewForm, DocumentPropertiesForm, DocumentTypeForm,
DocumentTypeFilenameForm, DocumentTypeFilenameForm_create,
DocumentTypeSelectForm, PrintForm
DocumentDownloadForm, DocumentForm, DocumentPageForm, DocumentPreviewForm,
DocumentPropertiesForm, DocumentTypeForm, DocumentTypeFilenameForm,
DocumentTypeFilenameForm_create, DocumentTypeSelectForm, PrintForm
)
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
from .models import (
@@ -188,28 +187,6 @@ def document_preview(request, document_id):
}, context_instance=RequestContext(request))
def document_content(request, document_id):
document = get_object_or_404(Document, pk=document_id)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_VIEW])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_DOCUMENT_VIEW, request.user, document)
document.add_as_recent_document_for_user(request.user)
content_form = DocumentContentForm(document=document)
return render_to_response('appearance/generic_form.html', {
'document': document,
'form': content_form,
'hide_labels': True,
'object': document,
'read_only': True,
'title': _('Content of document: %s') % document,
}, context_instance=RequestContext(request))
def document_delete(request, document_id=None, document_id_list=None):
post_action_redirect = None
@@ -1095,7 +1072,7 @@ def document_version_revert(request, document_version_pk):
if request.method == 'POST':
try:
document_version.revert()
document_version.revert(user=request.user)
messages.success(request, _('Document version reverted successfully'))
except Exception as exception:
messages.error(request, _('Error reverting document version; %s') % exception)

View File

@@ -8,10 +8,10 @@ from .api_views import (
urlpatterns = patterns(
'dynamic_search.views',
url(r'^$', 'search', (), 'search'),
url(r'^$', 'search', name='search'),
url(r'^advanced/$', 'search', {'advanced': True}, 'search_advanced'),
url(r'^again/$', 'search_again', (), 'search_again'),
url(r'^results/$', 'results', (), 'results'),
url(r'^again/$', 'search_again', name='search_again'),
url(r'^results/$', 'results', name='results'),
)
api_urls = patterns(

View File

@@ -4,7 +4,7 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'events.views',
url(r'^all/$', 'events_list', (), 'events_list'),
url(r'^for_object/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<object_id>\d+)/$', 'events_list', (), 'events_for_object'),
url(r'^by_verb/(?P<verb>[\w\-]+)/$', 'events_list', (), 'events_by_verb'),
url(r'^all/$', 'events_list', name='events_list'),
url(r'^for_object/(?P<app_label>[\w\-]+)/(?P<module_name>[\w\-]+)/(?P<object_id>\d+)/$', 'events_list', name='events_for_object'),
url(r'^by_verb/(?P<verb>[\w\-]+)/$', 'events_list', name='events_by_verb'),
)

View File

@@ -11,17 +11,17 @@ from .views import FolderDetailView, FolderListView
urlpatterns = patterns(
'folders.views',
url(r'^list/$', FolderListView.as_view(), name='folder_list'),
url(r'^create/$', 'folder_create', (), 'folder_create'),
url(r'^(?P<folder_id>\d+)/edit/$', 'folder_edit', (), 'folder_edit'),
url(r'^(?P<folder_id>\d+)/delete/$', 'folder_delete', (), 'folder_delete'),
url(r'^create/$', 'folder_create', name='folder_create'),
url(r'^(?P<folder_id>\d+)/edit/$', 'folder_edit', name='folder_edit'),
url(r'^(?P<folder_id>\d+)/delete/$', 'folder_delete', name='folder_delete'),
url(r'^(?P<pk>\d+)/$', FolderDetailView.as_view(), name='folder_view'),
url(r'^(?P<folder_id>\d+)/remove/document/multiple/$', 'folder_document_multiple_remove', (), 'folder_document_multiple_remove'),
url(r'^(?P<folder_id>\d+)/remove/document/multiple/$', 'folder_document_multiple_remove', name='folder_document_multiple_remove'),
url(r'^document/(?P<document_id>\d+)/folder/add/$', 'folder_add_document', (), 'folder_add_document'),
url(r'^document/multiple/folder/add/$', 'folder_add_multiple_documents', (), 'folder_add_multiple_documents'),
url(r'^document/(?P<document_id>\d+)/folder/list/$', 'document_folder_list', (), 'document_folder_list'),
url(r'^document/(?P<document_id>\d+)/folder/add/$', 'folder_add_document', name='folder_add_document'),
url(r'^document/multiple/folder/add/$', 'folder_add_multiple_documents', name='folder_add_multiple_documents'),
url(r'^document/(?P<document_id>\d+)/folder/list/$', 'document_folder_list', name='document_folder_list'),
url(r'^(?P<folder_pk>\d+)/acl/list/$', 'folder_acl_list', (), 'folder_acl_list'),
url(r'^(?P<folder_pk>\d+)/acl/list/$', 'folder_acl_list', name='folder_acl_list'),
)
api_urls = patterns(

View File

@@ -6,6 +6,6 @@ from navigation import Link
from .permissions import PERMISSION_INSTALLATION_DETAILS
link_menu_link = Link(icon='fa fa-flag', permissions=[PERMISSION_INSTALLATION_DETAILS], text=_('Installation details'), view='installation:namespace_list')
link_menu_link = Link(icon='fa fa-check-square-o', permissions=[PERMISSION_INSTALLATION_DETAILS], text=_('Installation details'), view='installation:namespace_list')
link_namespace_details = Link(permissions=[PERMISSION_INSTALLATION_DETAILS], text=_('Details'), view='installation:namespace_details', args='object.id')
link_namespace_list = Link(permissions=[PERMISSION_INSTALLATION_DETAILS], text=_('Installation property namespaces'), view='installation:namespace_list')

View File

@@ -4,6 +4,6 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'installation.views',
url(r'^$', 'namespace_list', (), 'namespace_list'),
url(r'^(?P<namespace_id>\w+)/details/$', 'namespace_details', (), 'namespace_details'),
url(r'^$', 'namespace_list', name='namespace_list'),
url(r'^(?P<namespace_id>\w+)/details/$', 'namespace_details', name='namespace_details'),
)

View File

@@ -6,19 +6,19 @@ from .views import SetupSmartLinkDocumentTypesView
urlpatterns = patterns(
'linking.views',
url(r'^document/(?P<document_id>\d+)/$', 'smart_link_instances_for_document', (), 'smart_link_instances_for_document'),
url(r'^document/(?P<document_id>\d+)/smart_link/(?P<smart_link_pk>\d+)/$', 'smart_link_instance_view', (), 'smart_link_instance_view'),
url(r'^document/(?P<document_id>\d+)/$', 'smart_link_instances_for_document', name='smart_link_instances_for_document'),
url(r'^document/(?P<document_id>\d+)/smart_link/(?P<smart_link_pk>\d+)/$', 'smart_link_instance_view', name='smart_link_instance_view'),
url(r'^setup/list/$', 'smart_link_list', (), 'smart_link_list'),
url(r'^setup/create/$', 'smart_link_create', (), 'smart_link_create'),
url(r'^setup/(?P<smart_link_pk>\d+)/delete/$', 'smart_link_delete', (), 'smart_link_delete'),
url(r'^setup/(?P<smart_link_pk>\d+)/edit/$', 'smart_link_edit', (), 'smart_link_edit'),
url(r'^setup/list/$', 'smart_link_list', name='smart_link_list'),
url(r'^setup/create/$', 'smart_link_create', name='smart_link_create'),
url(r'^setup/(?P<smart_link_pk>\d+)/delete/$', 'smart_link_delete', name='smart_link_delete'),
url(r'^setup/(?P<smart_link_pk>\d+)/edit/$', 'smart_link_edit', name='smart_link_edit'),
url(r'^setup/(?P<smart_link_pk>\d+)/document_types/$', SetupSmartLinkDocumentTypesView.as_view(), name='smart_link_document_types'),
url(r'^setup/(?P<smart_link_pk>\d+)/condition/list/$', 'smart_link_condition_list', (), 'smart_link_condition_list'),
url(r'^setup/(?P<smart_link_pk>\d+)/condition/create/$', 'smart_link_condition_create', (), 'smart_link_condition_create'),
url(r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/edit/$', 'smart_link_condition_edit', (), 'smart_link_condition_edit'),
url(r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/delete/$', 'smart_link_condition_delete', (), 'smart_link_condition_delete'),
url(r'^setup/(?P<smart_link_pk>\d+)/condition/list/$', 'smart_link_condition_list', name='smart_link_condition_list'),
url(r'^setup/(?P<smart_link_pk>\d+)/condition/create/$', 'smart_link_condition_create', name='smart_link_condition_create'),
url(r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/edit/$', 'smart_link_condition_edit', name='smart_link_condition_edit'),
url(r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/delete/$', 'smart_link_condition_delete', name='smart_link_condition_delete'),
url(r'^(?P<smart_link_pk>\d+)/acl/list/$', 'smart_link_acl_list', (), 'smart_link_acl_list'),
url(r'^(?P<smart_link_pk>\d+)/acl/list/$', 'smart_link_acl_list', name='smart_link_acl_list'),
)

View File

@@ -4,6 +4,6 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'mailer.views',
url(r'^(?P<document_id>\d+)/send/link/$', 'send_document_link', (), 'send_document_link'),
url(r'^(?P<document_id>\d+)/send/link/$', 'send_document_link', name='send_document_link'),
url(r'^(?P<document_id>\d+)/send/document/$', 'send_document_link', {'as_attachment': True}, 'send_document'),
)

View File

@@ -25,4 +25,4 @@ link_setup_document_type_metadata_required = Link(permissions=[PERMISSION_DOCUME
link_setup_metadata_type_create = Link(permissions=[PERMISSION_METADATA_TYPE_CREATE], text=_('Create new'), view='metadata:setup_metadata_type_create')
link_setup_metadata_type_delete = Link(permissions=[PERMISSION_METADATA_TYPE_DELETE], tags='dangerous', text=_('Delete'), view='metadata:setup_metadata_type_delete', args='object.pk')
link_setup_metadata_type_edit = Link(permissions=[PERMISSION_METADATA_TYPE_EDIT], text=_('Edit'), view='metadata:setup_metadata_type_edit', args='object.pk')
link_setup_metadata_type_list = Link(icon='fa fa-list', permissions=[PERMISSION_METADATA_TYPE_VIEW], text=_('Metadata types'), view='metadata:setup_metadata_type_list')
link_setup_metadata_type_list = Link(icon='fa fa-pencil', permissions=[PERMISSION_METADATA_TYPE_VIEW], text=_('Metadata types'), view='metadata:setup_metadata_type_list')

View File

@@ -17,18 +17,18 @@ from .views import (
urlpatterns = patterns(
'metadata.views',
url(r'^(?P<document_id>\d+)/edit/$', 'metadata_edit', (), 'metadata_edit'),
url(r'^(?P<document_id>\d+)/view/$', 'metadata_view', (), 'metadata_view'),
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'),
url(r'^(?P<document_id>\d+)/edit/$', 'metadata_edit', name='metadata_edit'),
url(r'^(?P<document_id>\d+)/view/$', 'metadata_view', name='metadata_view'),
url(r'^multiple/edit/$', 'metadata_multiple_edit', name='metadata_multiple_edit'),
url(r'^(?P<document_id>\d+)/add/$', 'metadata_add', name='metadata_add'),
url(r'^multiple/add/$', 'metadata_multiple_add', name='metadata_multiple_add'),
url(r'^(?P<document_id>\d+)/remove/$', 'metadata_remove', name='metadata_remove'),
url(r'^multiple/remove/$', 'metadata_multiple_remove', name='metadata_multiple_remove'),
url(r'^setup/type/list/$', 'setup_metadata_type_list', (), 'setup_metadata_type_list'),
url(r'^setup/type/create/$', 'setup_metadata_type_create', (), 'setup_metadata_type_create'),
url(r'^setup/type/(?P<metadatatype_id>\d+)/edit/$', 'setup_metadata_type_edit', (), 'setup_metadata_type_edit'),
url(r'^setup/type/(?P<metadatatype_id>\d+)/delete/$', 'setup_metadata_type_delete', (), 'setup_metadata_type_delete'),
url(r'^setup/type/list/$', 'setup_metadata_type_list', name='setup_metadata_type_list'),
url(r'^setup/type/create/$', 'setup_metadata_type_create', name='setup_metadata_type_create'),
url(r'^setup/type/(?P<metadatatype_id>\d+)/edit/$', 'setup_metadata_type_edit', name='setup_metadata_type_edit'),
url(r'^setup/type/(?P<metadatatype_id>\d+)/delete/$', 'setup_metadata_type_delete', name='setup_metadata_type_delete'),
url(r'^setup/document/type/(?P<document_type_id>\d+)/metadata/edit/$', SetupDocumentTypeMetadataOptionalView.as_view(), name='setup_document_type_metadata'),
url(r'^setup/document/type/(?P<document_type_id>\d+)/metadata/edit/required/$', SetupDocumentTypeMetadataRequiredView.as_view(), name='setup_document_type_metadata_required'),

View File

@@ -86,29 +86,43 @@ class Menu(object):
pass
for resolved_navigation_object in resolved_navigation_object_list:
for source, links in self.bound_links.iteritems():
resolved_links = []
for bound_source, links in self.bound_links.iteritems():
try:
if inspect.isclass(source) and isinstance(resolved_navigation_object, source) or source == CombinedSource(obj=resolved_navigation_object.__class__, view=current_view):
if inspect.isclass(bound_source) and isinstance(resolved_navigation_object, bound_source) or source == CombinedSource(obj=resolved_navigation_object.__class__, view=current_view):
for link in links:
resolved_link = link.resolve(context=context, resolved_object=resolved_navigation_object)
if resolved_link:
result.append(resolved_link)
resolved_links.append(resolved_link)
break # No need for further content object match testing
except TypeError:
# When source is a dictionary
pass
if resolved_links:
result.append(resolved_links)
resolved_links = []
# View links
for link in self.bound_links.get(current_view, []):
resolved_link = link.resolve(context)
if resolved_link:
result.append(resolved_link)
resolved_links.append(resolved_link)
if resolved_links:
result.append(resolved_links)
resolved_links = []
# Main menu links
for link in self.bound_links.get(None, []):
resolved_link = link.resolve(context)
if resolved_link:
result.append(resolved_link)
resolved_links.append(resolved_link)
if resolved_links:
result.append(resolved_links)
return result

View File

@@ -20,16 +20,20 @@ def get_menus_links(context, names, source=None):
result = []
for name in names.split(','):
links = Menu.get(name=name).resolve(context)
if links:
result.append(links)
for links in Menu.get(name=name).resolve(context):
if links:
result.append(links)
return result
@register.simple_tag(takes_context=True)
def get_multi_item_links_form(context, object_list):
actions = [(link.url, link.text) for link in Menu.get('multi item menu').resolve(context=context, source=object_list[0])]
actions = []
for link_set in Menu.get('multi item menu').resolve(context=context, source=object_list[0]):
for link in link_set:
actions.append((link.url, link.text))
form = MultiItemForm(actions=actions)
context.update({'multi_item_form': form, 'multi_item_actions': actions})
return ''

View File

@@ -8,7 +8,9 @@ from django import apps
from django.utils.translation import ugettext_lazy as _
from acls.api import class_permissions
from common import menu_multi_item, menu_object, menu_secondary, menu_tools
from common import (
menu_facet, menu_multi_item, menu_object, menu_secondary, menu_tools
)
from common.api import register_maintenance_links
from common.utils import encapsulate
from documents.models import Document, DocumentVersion
@@ -20,12 +22,13 @@ from rest_api.classes import APIEndPoint
from .handlers import post_version_upload_ocr
from .links import (
link_document_submit, link_document_submit_multiple, link_entry_delete,
link_document_content, link_document_submit,
link_document_submit_multiple, link_entry_delete,
link_entry_delete_multiple, link_entry_list, link_entry_re_queue,
link_entry_re_queue_multiple
)
from .models import DocumentVersionOCRError
from .permissions import PERMISSION_OCR_DOCUMENT
from .permissions import PERMISSION_OCR_DOCUMENT, PERMISSION_OCR_CONTENT_VIEW
from .settings import PDFTOTEXT_PATH, TESSERACT_PATH, UNPAPER_PATH
from .tasks import task_do_ocr
@@ -50,8 +53,13 @@ class OCRApp(apps.AppConfig):
Document.add_to_class('submit_for_ocr', document_ocr_submit)
DocumentVersion.add_to_class('submit_for_ocr', document_version_ocr_submit)
class_permissions(Document, [PERMISSION_OCR_DOCUMENT])
class_permissions(
Document, [
PERMISSION_OCR_DOCUMENT, PERMISSION_OCR_CONTENT_VIEW
]
)
menu_facet.bind_links(links=[link_document_content], sources=[Document])
menu_multi_item.bind_links(links=[link_document_submit_multiple], sources=[Document])
menu_multi_item.bind_links(links=[link_entry_re_queue_multiple, link_entry_delete_multiple], sources=[DocumentVersionOCRError])
menu_object.bind_links(links=[link_document_submit], sources=[Document])

View File

@@ -4,8 +4,6 @@ import logging
import os
import tempfile
import sh
from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _
@@ -18,6 +16,7 @@ from .exceptions import UnpaperError
from .literals import (
DEFAULT_OCR_FILE_EXTENSION, DEFAULT_OCR_FILE_FORMAT, UNPAPER_FILE_FORMAT
)
from .models import DocumentPageContent
from .parsers import parse_document_page
from .parsers.exceptions import ParserError, ParserUnknownFile
from .settings import UNPAPER_PATH
@@ -34,11 +33,13 @@ class OCRBackendBase(object):
for page in document_version.pages.all():
image = page.get_image()
logger.info('Processing page: %d', page.page_number)
page.content = self.execute(file_object=image, language=language)
page.save()
logger.info('Processing page: %d of document version: %s', page.page_number, document_version)
document_page_content, created = DocumentPageContent.objects.get_or_create(document_page=page)
result = self.execute(file_object=image, language=language)
document_page_content.content = self.execute(file_object=image, language=language)
document_page_content.save()
image.close()
logger.info('Finished processing page: %d', page.page_number)
logger.info('Finished processing page: %d of document version: %s', page.page_number, document_version)
def execute(self, file_object, language=None, transformations=None):
if not transformations:

43
mayan/apps/ocr/forms.py Normal file
View File

@@ -0,0 +1,43 @@
from __future__ import unicode_literals
from django import forms
from django.utils.encoding import force_unicode
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _, ugettext
from common.widgets import TextAreaDiv
from .models import DocumentPageContent
class DocumentContentForm(forms.Form):
"""
Form that concatenates all of a document pages' text content into a
single textarea widget
"""
def __init__(self, *args, **kwargs):
self.document = kwargs.pop('document', None)
super(DocumentContentForm, self).__init__(*args, **kwargs)
content = []
self.fields['contents'].initial = ''
try:
document_pages = self.document.pages.all()
except AttributeError:
document_pages = []
for page in document_pages:
try:
page_content = page.ocr_content.content
except DocumentPageContent.DoesNotExist:
pass
else:
content.append(conditional_escape(force_unicode(page_content)))
content.append('\n\n\n<hr/><div class="document-page-content-divider">- %s -</div><hr/>\n\n\n' % (ugettext('Page %(page_number)d') % {'page_number': page.page_number}))
self.fields['contents'].initial = mark_safe(''.join(content))
contents = forms.CharField(
label=_('Contents'),
widget=TextAreaDiv(attrs={'class': 'text_area_div full-height', 'data-height-difference': 360})
)

View File

@@ -5,9 +5,11 @@ from django.utils.translation import ugettext_lazy as _
from navigation import Link
from .permissions import (
PERMISSION_OCR_DOCUMENT, PERMISSION_OCR_DOCUMENT_DELETE
PERMISSION_OCR_CONTENT_VIEW, PERMISSION_OCR_DOCUMENT,
PERMISSION_OCR_DOCUMENT_DELETE
)
link_document_content = Link(permissions=[PERMISSION_OCR_CONTENT_VIEW], text=_('Content'), view='ocr:document_content', args='resolved_object.id')
link_document_submit = Link(permissions=[PERMISSION_OCR_DOCUMENT], text=_('Submit to OCR queue'), view='ocr:document_submit', args='object.id')
link_document_submit_multiple = Link(text=_('Submit to OCR queue'), view='ocr:document_submit_multiple')
link_entry_delete = Link(permissions=[PERMISSION_OCR_DOCUMENT_DELETE], text=_('Delete'), view='ocr:entry_delete', args='object.id')

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0005_auto_20150617_0358'),
('ocr', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='DocumentPageContent',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('content', models.TextField(verbose_name='Content', blank=True)),
('document_page', models.OneToOneField(related_name='ocr_content', verbose_name='Document page', to='documents.DocumentPage')),
],
options={
'verbose_name': 'Document page content',
'verbose_name_plural': 'Document pages contents',
},
bases=(models.Model,),
),
]

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
def move_content_from_documents_to_ocr_app(apps, schema_editor):
DocumentPage = apps.get_model('documents', 'DocumentPage')
DocumentPageContent = apps.get_model('ocr', 'DocumentPageContent')
for document_page in DocumentPage.objects.all():
document_page_content = DocumentPageContent(
document_page=document_page, content=document_page.content_old
)
document_page_content.save()
class Migration(migrations.Migration):
dependencies = [
('ocr', '0002_documentpagecontent'),
]
operations = [
]
operations = [
migrations.RunPython(move_content_from_documents_to_ocr_app),
]

View File

@@ -4,7 +4,7 @@ from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from documents.models import DocumentVersion
from documents.models import DocumentVersion, DocumentPage
@python_2_unicode_compatible
@@ -20,3 +20,19 @@ class DocumentVersionOCRError(models.Model):
ordering = ('datetime_submitted',)
verbose_name = _('Document Version OCR Error')
verbose_name_plural = _('Document Version OCR Errors')
@python_2_unicode_compatible
class DocumentPageContent(models.Model):
"""
Model that describes a document page content
"""
document_page = models.OneToOneField(DocumentPage, related_name='ocr_content', verbose_name=_('Document page'))
content = models.TextField(blank=True, verbose_name=_('Content'))
def __str__(self):
return unicode(self.document_page)
class Meta:
verbose_name = _('Document page content')
verbose_name_plural = _('Document pages contents')

View File

@@ -93,7 +93,6 @@ class SlateParser(Parser):
raise ParserError('No output')
document_page.content = pdf_pages[document_page.page_number - 1]
document_page.page_label = _('Text extracted from PDF')
document_page.save()
@@ -170,7 +169,6 @@ class PopplerParser(Parser):
raise ParserError('No output')
document_page.content = output
document_page.page_label = _('Text extracted from PDF')
document_page.save()

View File

@@ -7,3 +7,4 @@ from permissions.models import Permission, PermissionNamespace
ocr_namespace = PermissionNamespace('ocr', _('OCR'))
PERMISSION_OCR_DOCUMENT = Permission.objects.register(ocr_namespace, 'ocr_document', _('Submit documents for OCR'))
PERMISSION_OCR_DOCUMENT_DELETE = Permission.objects.register(ocr_namespace, 'ocr_document_delete', _('Delete documents from OCR queue'))
PERMISSION_OCR_CONTENT_VIEW = Permission.objects.register(ocr_namespace, 'ocr_content_view', _('Can view the transcribed text from document'))

View File

@@ -6,14 +6,15 @@ from .api_views import DocumentVersionOCRView
urlpatterns = patterns(
'ocr.views',
url(r'^document/(?P<pk>\d+)/submit/$', 'document_submit', (), 'document_submit'),
url(r'^document/multiple/submit/$', 'document_submit_multiple', (), 'document_submit_multiple'),
url(r'^(?P<document_id>\d+)/content/$', 'document_content', name='document_content'),
url(r'^document/(?P<pk>\d+)/submit/$', 'document_submit', name='document_submit'),
url(r'^document/multiple/submit/$', 'document_submit_multiple', name='document_submit_multiple'),
url(r'^all/$', 'entry_list', (), 'entry_list'),
url(r'^(?P<pk>\d+)/delete/$', 'entry_delete', (), 'entry_delete'),
url(r'^multiple/delete/$', 'entry_delete_multiple', (), 'entry_delete_multiple'),
url(r'^(?P<pk>\d+)/re-queue/$', 'entry_re_queue', (), 'entry_re_queue'),
url(r'^multiple/re-queue/$', 'entry_re_queue_multiple', (), 'entry_re_queue_multiple'),
url(r'^all/$', 'entry_list', name='entry_list'),
url(r'^(?P<pk>\d+)/delete/$', 'entry_delete', name='entry_delete'),
url(r'^multiple/delete/$', 'entry_delete_multiple', name='entry_delete_multiple'),
url(r'^(?P<pk>\d+)/re-queue/$', 'entry_re_queue', name='entry_re_queue'),
url(r'^multiple/re-queue/$', 'entry_re_queue_multiple', name='entry_re_queue_multiple'),
)
api_urls = patterns(

View File

@@ -13,12 +13,36 @@ from acls.models import AccessEntry
from documents.models import Document, DocumentVersion
from permissions.models import Permission
from .forms import DocumentContentForm
from .models import DocumentVersionOCRError
from .permissions import (
PERMISSION_OCR_DOCUMENT, PERMISSION_OCR_DOCUMENT_DELETE
PERMISSION_OCR_CONTENT_VIEW, PERMISSION_OCR_DOCUMENT,
PERMISSION_OCR_DOCUMENT_DELETE
)
def document_content(request, document_id):
document = get_object_or_404(Document, pk=document_id)
try:
Permission.objects.check_permissions(request.user, [PERMISSION_OCR_CONTENT_VIEW])
except PermissionDenied:
AccessEntry.objects.check_access(PERMISSION_OCR_CONTENT_VIEW, request.user, document)
document.add_as_recent_document_for_user(request.user)
content_form = DocumentContentForm(document=document)
return render_to_response('appearance/generic_form.html', {
'document': document,
'form': content_form,
'hide_labels': True,
'object': document,
'read_only': True,
'title': _('Content of document: %s') % document,
}, context_instance=RequestContext(request))
def document_submit(request, pk):
document = get_object_or_404(Document, pk=pk)

View File

@@ -15,6 +15,6 @@ link_permission_revoke = Link(permissions=[PERMISSION_PERMISSION_REVOKE], text=_
link_role_create = Link(permissions=[PERMISSION_ROLE_CREATE], text=_('Create new role'), view='permissions:role_create')
link_role_delete = Link(permissions=[PERMISSION_ROLE_DELETE], tags='dangerous', text=_('Delete'), view='permissions:role_delete', args='object.id')
link_role_edit = Link(permissions=[PERMISSION_ROLE_EDIT], text=_('Edit'), view='permissions:role_edit', args='object.id')
link_role_list = Link(icon='fa fa-child', permissions=[PERMISSION_ROLE_VIEW], text=_('Roles'), view='permissions:role_list')
link_role_list = Link(icon='fa fa-user-secret', permissions=[PERMISSION_ROLE_VIEW], text=_('Roles'), view='permissions:role_list')
link_role_members = Link(permissions=[PERMISSION_ROLE_EDIT], text=_('Members'), view='permissions:role_members', args='object.id')
link_role_permissions = Link(permissions=[PERMISSION_PERMISSION_GRANT, PERMISSION_PERMISSION_REVOKE], text=_('Role permissions'), view='permissions:role_permissions', args='object.id')

View File

@@ -9,15 +9,15 @@ from .views import (
urlpatterns = patterns(
'permissions.views',
url(r'^role/list/$', 'role_list', (), 'role_list'),
url(r'^role/list/$', 'role_list', name='role_list'),
url(r'^role/create/$', RoleCreateView.as_view(), name='role_create'),
url(r'^role/(?P<role_id>\d+)/permissions/$', 'role_permissions', (), 'role_permissions'),
url(r'^role/(?P<role_id>\d+)/permissions/$', 'role_permissions', name='role_permissions'),
url(r'^role/(?P<pk>\d+)/edit/$', RoleEditView.as_view(), name='role_edit'),
url(r'^role/(?P<pk>\d+)/delete/$', RoleDeleteView.as_view(), name='role_delete'),
url(r'^role/(?P<role_id>\d+)/members/$', SetupRoleMembersView.as_view(), name='role_members'),
url(r'^permissions/multiple/grant/$', 'permission_grant', (), 'permission_multiple_grant'),
url(r'^permissions/multiple/revoke/$', 'permission_revoke', (), 'permission_multiple_revoke'),
url(r'^permissions/multiple/grant/$', 'permission_grant', name='permission_multiple_grant'),
url(r'^permissions/multiple/revoke/$', 'permission_revoke', name='permission_multiple_revoke'),
)
api_urls = patterns(

View File

@@ -9,4 +9,4 @@ def is_superuser(context):
return context['request'].user.is_staff or context['request'].user.is_superuser
link_check_settings = Link(condition=is_superuser, icon='fa fa-gear', text=_('Settings'), view='settings:setting_list')
link_check_settings = Link(condition=is_superuser, icon='fa fa-sliders', text=_('Settings'), view='settings:setting_list')

View File

@@ -4,5 +4,5 @@ from django.conf.urls import patterns, url
urlpatterns = patterns(
'smart_settings.views',
url(r'^list/$', 'setting_list', (), 'setting_list'),
url(r'^list/$', 'setting_list', name='setting_list'),
)

View File

@@ -7,6 +7,7 @@ from common import (
MissingItem, menu_front_page, menu_object, menu_secondary, menu_sidebar,
menu_setup
)
from common.signals import post_initial_setup
from common.utils import encapsulate
from converter.links import link_transformation_list
from documents.models import Document
@@ -14,6 +15,7 @@ from navigation.api import register_model_list_columns
from rest_api.classes import APIEndPoint
from .classes import StagingFile
from .handlers import create_default_document_source
from .links import (
link_document_create_multiple, link_document_create_siblings,
link_setup_sources, link_setup_source_create_imap_email,
@@ -49,3 +51,5 @@ class SourcesApp(apps.AppConfig):
encapsulate(lambda x: staging_file_thumbnail(x, gallery_name='sources:staging_list', title=x.filename, size='100'))
},
])
post_initial_setup.connect(create_default_document_source, dispatch_uid='create_default_document_source')

View File

@@ -0,0 +1,10 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
from .literals import SOURCE_UNCOMPRESS_CHOICE_ASK
from .models import WebFormSource
def create_default_document_source(sender, **kwargs):
WebFormSource.objects.create(title=_('Default'), uncompress=SOURCE_UNCOMPRESS_CHOICE_ASK)

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('sources', '0003_sourcelog'),
]
operations = [
migrations.AlterModelOptions(
name='sourcelog',
options={'ordering': ('-datetime',), 'get_latest_by': 'datetime', 'verbose_name': 'Log entry', 'verbose_name_plural': 'Log entries'},
),
]

View File

@@ -352,4 +352,4 @@ class SourceLog(models.Model):
verbose_name = _('Log entry')
verbose_name_plural = _('Log entries')
get_latest_by = 'datetime'
ordering = ['-datetime']
ordering = ('-datetime',)

View File

@@ -23,16 +23,16 @@ urlpatterns = patterns(
# Setup views
url(r'^setup/list/$', 'setup_source_list', (), 'setup_source_list'),
url(r'^setup/(?P<source_id>\d+)/edit/$', 'setup_source_edit', (), 'setup_source_edit'),
url(r'^setup/list/$', 'setup_source_list', name='setup_source_list'),
url(r'^setup/(?P<source_id>\d+)/edit/$', 'setup_source_edit', name='setup_source_edit'),
url(r'^setup/(?P<pk>\d+)/logs/$', SourceLogListView.as_view(), name='setup_source_logs'),
url(r'^setup/(?P<source_id>\d+)/delete/$', 'setup_source_delete', (), 'setup_source_delete'),
url(r'^setup/(?P<source_type>\w+)/create/$', 'setup_source_create', (), 'setup_source_create'),
url(r'^setup/(?P<source_id>\d+)/delete/$', 'setup_source_delete', name='setup_source_delete'),
url(r'^setup/(?P<source_type>\w+)/create/$', 'setup_source_create', name='setup_source_create'),
# Document create views
url(r'^create/from/local/multiple/$', DocumentCreateWizard.as_view(), name='document_create_multiple'),
url(r'^(?P<document_id>\d+)/create/siblings/$', 'document_create_siblings', (), 'document_create_siblings'),
url(r'^(?P<document_id>\d+)/create/siblings/$', 'document_create_siblings', name='document_create_siblings'),
)
api_urls = patterns(

View File

@@ -17,7 +17,7 @@ link_single_document_multiple_tag_remove = Link(permissions=[PERMISSION_TAG_REMO
link_tag_acl_list = Link(permissions=[ACLS_VIEW_ACL], text=_('ACLs'), view='tags:tag_acl_list', args='object.pk')
link_tag_attach = Link(permissions=[PERMISSION_TAG_ATTACH], text=_('Attach tag'), view='tags:tag_attach', args='object.pk')
link_tag_create = Link(permissions=[PERMISSION_TAG_CREATE], text=_('Create new tag'), view='tags:tag_create')
link_tag_delete = Link(permissions=[PERMISSION_TAG_DELETE], text=_('Delete'), view='tags:tag_delete', args='object.id')
link_tag_delete = Link(permissions=[PERMISSION_TAG_DELETE], tags='dangerous', text=_('Delete'), view='tags:tag_delete', args='object.id')
link_tag_edit = Link(permissions=[PERMISSION_TAG_EDIT], text=_('Edit'), view='tags:tag_edit', args='object.id')
link_tag_document_list = Link(permissions=[PERMISSION_TAG_REMOVE, PERMISSION_TAG_ATTACH], text=_('Tags'), view='tags:document_tags', args='object.pk')
link_tag_list = Link(icon='fa fa-tag', text=_('Tags'), view='tags:tag_list')

View File

@@ -10,22 +10,22 @@ from .views import TagTaggedItemListView
urlpatterns = patterns(
'tags.views',
url(r'^list/$', 'tag_list', (), 'tag_list'),
url(r'^create/$', 'tag_create', (), 'tag_create'),
url(r'^(?P<tag_id>\d+)/delete/$', 'tag_delete', (), 'tag_delete'),
url(r'^(?P<tag_id>\d+)/edit/$', 'tag_edit', (), 'tag_edit'),
url(r'^list/$', 'tag_list', name='tag_list'),
url(r'^create/$', 'tag_create', name='tag_create'),
url(r'^(?P<tag_id>\d+)/delete/$', 'tag_delete', name='tag_delete'),
url(r'^(?P<tag_id>\d+)/edit/$', 'tag_edit', name='tag_edit'),
url(r'^(?P<pk>\d+)/documents/$', TagTaggedItemListView.as_view(), name='tag_tagged_item_list'),
url(r'^multiple/delete/$', 'tag_multiple_delete', (), 'tag_multiple_delete'),
url(r'^multiple/delete/$', 'tag_multiple_delete', name='tag_multiple_delete'),
url(r'^multiple/remove/document/(?P<document_id>\d+)/$', 'single_document_multiple_tag_remove', (), 'single_document_multiple_tag_remove'),
url(r'^multiple/remove/document/multiple/$', 'multiple_documents_selection_tag_remove', (), 'multiple_documents_selection_tag_remove'),
url(r'^multiple/remove/document/(?P<document_id>\d+)/$', 'single_document_multiple_tag_remove', name='single_document_multiple_tag_remove'),
url(r'^multiple/remove/document/multiple/$', 'multiple_documents_selection_tag_remove', name='multiple_documents_selection_tag_remove'),
url(r'^selection/attach/document/(?P<document_id>\d+)/$', 'tag_attach', (), 'tag_attach'),
url(r'^selection/attach/document/multiple/$', 'tag_multiple_attach', (), 'multiple_documents_tag_attach'),
url(r'^selection/attach/document/(?P<document_id>\d+)/$', 'tag_attach', name='tag_attach'),
url(r'^selection/attach/document/multiple/$', 'tag_multiple_attach', name='multiple_documents_tag_attach'),
url(r'^for/document/(?P<document_id>\d+)/$', 'document_tags', (), 'document_tags'),
url(r'^for/document/(?P<document_id>\d+)/$', 'document_tags', name='document_tags'),
url(r'^(?P<tag_pk>\d+)/acl/list/$', 'tag_acl_list', (), 'tag_acl_list'),
url(r'^(?P<tag_pk>\d+)/acl/list/$', 'tag_acl_list', name='tag_acl_list'),
)
api_urls = patterns(

View File

@@ -10,20 +10,20 @@ from .views import GroupMembersView, UserGroupsView
urlpatterns = patterns(
'user_management.views',
url(r'^user/list/$', 'user_list', (), 'user_list'),
url(r'^user/add/$', 'user_add', (), 'user_add'),
url(r'^user/(?P<user_id>\d+)/edit/$', 'user_edit', (), 'user_edit'),
url(r'^user/(?P<user_id>\d+)/delete/$', 'user_delete', (), 'user_delete'),
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'^user/list/$', 'user_list', name='user_list'),
url(r'^user/add/$', 'user_add', name='user_add'),
url(r'^user/(?P<user_id>\d+)/edit/$', 'user_edit', name='user_edit'),
url(r'^user/(?P<user_id>\d+)/delete/$', 'user_delete', name='user_delete'),
url(r'^user/multiple/delete/$', 'user_multiple_delete', name='user_multiple_delete'),
url(r'^user/(?P<user_id>\d+)/set_password/$', 'user_set_password', name='user_set_password'),
url(r'^user/multiple/set_password/$', 'user_multiple_set_password', name='user_multiple_set_password'),
url(r'^user/(?P<user_id>\d+)/groups/$', UserGroupsView.as_view(), name='user_groups'),
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'),
url(r'^group/(?P<group_id>\d+)/delete/$', 'group_delete', (), 'group_delete'),
url(r'^group/multiple/delete/$', 'group_multiple_delete', (), 'group_multiple_delete'),
url(r'^group/list/$', 'group_list', name='group_list'),
url(r'^group/add/$', 'group_add', name='group_add'),
url(r'^group/(?P<group_id>\d+)/edit/$', 'group_edit', name='group_edit'),
url(r'^group/(?P<group_id>\d+)/delete/$', 'group_delete', name='group_delete'),
url(r'^group/multiple/delete/$', 'group_multiple_delete', name='group_multiple_delete'),
url(r'^group/(?P<group_id>\d+)/members/$', GroupMembersView.as_view(), name='group_members'),
)

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