Merge remote-tracking branch 'origin/development' into development
This commit is contained in:
@@ -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
|
||||
=================================
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
11
mayan/apps/checkouts/handlers.py
Normal file
11
mayan/apps/checkouts/handlers.py
Normal 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))
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
27
mayan/apps/checkouts/migrations/0003_auto_20150617_0325.py
Normal file
27
mayan/apps/checkouts/migrations/0003_auto_20150617_0325.py
Normal 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),
|
||||
]
|
||||
29
mayan/apps/checkouts/migrations/0004_auto_20150617_0330.py
Normal file
29
mayan/apps/checkouts/migrations/0004_auto_20150617_0330.py
Normal 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,
|
||||
),
|
||||
]
|
||||
@@ -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):
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -83,4 +83,3 @@ class MissingItem(object):
|
||||
self.description = description
|
||||
self.view = view
|
||||
self.__class__._registry.append(self)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
21
mayan/apps/common/migrations/0003_auto_20150614_0723.py
Normal file
21
mayan/apps/common/migrations/0003_auto_20150614_0723.py
Normal 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',
|
||||
),
|
||||
]
|
||||
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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',
|
||||
|
||||
5
mayan/apps/common/signals.py
Normal file
5
mayan/apps/common/signals.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
post_initial_setup = Signal(use_caching=True)
|
||||
@@ -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 []
|
||||
|
||||
@@ -14,4 +14,3 @@ def project_name():
|
||||
@register.simple_tag
|
||||
def project_version():
|
||||
return mayan.__version__
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -16,7 +16,7 @@ class WorkflowForm(forms.ModelForm):
|
||||
|
||||
class WorkflowStateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ('initial', 'label')
|
||||
fields = ('initial', 'label', 'completion')
|
||||
model = WorkflowState
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
9
mayan/apps/documents/handlers.py
Normal file
9
mayan/apps/documents/handlers.py
Normal 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'))
|
||||
@@ -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')
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
30
mayan/apps/documents/migrations/0004_auto_20150616_1930.py
Normal file
30
mayan/apps/documents/migrations/0004_auto_20150616_1930.py
Normal 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'},
|
||||
),
|
||||
]
|
||||
19
mayan/apps/documents/migrations/0005_auto_20150617_0358.py
Normal file
19
mayan/apps/documents/migrations/0005_auto_20150617_0358.py
Normal 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',
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ''
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
43
mayan/apps/ocr/forms.py
Normal 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})
|
||||
)
|
||||
@@ -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')
|
||||
|
||||
28
mayan/apps/ocr/migrations/0002_documentpagecontent.py
Normal file
28
mayan/apps/ocr/migrations/0002_documentpagecontent.py
Normal 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,),
|
||||
),
|
||||
]
|
||||
29
mayan/apps/ocr/migrations/0003_auto_20150617_0401.py
Normal file
29
mayan/apps/ocr/migrations/0003_auto_20150617_0401.py
Normal 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),
|
||||
]
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
|
||||
10
mayan/apps/sources/handlers.py
Normal file
10
mayan/apps/sources/handlers.py
Normal 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)
|
||||
18
mayan/apps/sources/migrations/0004_auto_20150616_1931.py
Normal file
18
mayan/apps/sources/migrations/0004_auto_20150616_1931.py
Normal 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'},
|
||||
),
|
||||
]
|
||||
@@ -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',)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user