diff --git a/README.md b/README.md
index 70691cac63..e91aa08fa8 100755
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Features
* Previews for a great deal of image formats, including PDF
* Document OCR and searching
* Group documents by metadata automatically
+* Permissions and roles support
Requirements
---
diff --git a/apps/common/api.py b/apps/common/api.py
index da1af95e13..78df9d5bb4 100755
--- a/apps/common/api.py
+++ b/apps/common/api.py
@@ -2,9 +2,6 @@ import copy
from django.db.utils import DatabaseError
-from permissions.utils import has_permission
-from permissions.models import Permission
-
object_navigation = {}
menu_links = []
@@ -44,25 +41,3 @@ def register_model_list_columns(model, columns):
model_list_columns[model].extend(columns)
else:
model_list_columns[model] = copy.copy(columns)
-
-
-def register_permissions(app, permissions):
- if permissions:
- for permission in permissions:
- full_permission_name = "%s_%s" % (app, permission['name'])
- try:
- #if not Permission.objects.filter(codename=full_permission_name):
- # Permission(name=unicode(permission['label']), codename=full_permission_name).save()
- permission_obj, created = Permission.objects.get_or_create(codename=full_permission_name)
- permission_obj.name=unicode(permission['label'])
- permission_obj.save()
- except DatabaseError:
- #Special case for ./manage.py syncdb
- pass
-
-
-def check_permissions(object, user, permission_list):
- temp_role = []
- for permission in permission_list:
- if has_permission(object, user, permission):
- return True
diff --git a/apps/common/templates/generic_navigation.html b/apps/common/templates/generic_navigation.html
index b7c86dd7cf..b18bd8a1d9 100755
--- a/apps/common/templates/generic_navigation.html
+++ b/apps/common/templates/generic_navigation.html
@@ -1,7 +1,17 @@
{% load i18n %}
+{% load permission_tags %}
{% for link in object_navigation_links %}
+{% if link.permissions %}
+ {% check_permissions request.user link.permissions.namespace link.permissions.permissions %}
+ {% if permission %}
+ {% if as_li %}
{% endif %}
+ {% if link.famfam %}{% endif %}{{ link.text|capfirst }}{% if link.error %} - {{ link.error }}{% endif %}{% if link.active %}{% endif %}{% if horizontal %}{% if not forloop.last %} | {% endif %}{% endif %}
+ {% if as_li %}{% endif %}
+ {% endif %}
+{% else %}
{% if as_li %}{% endif %}
{% if link.famfam %}{% endif %}{{ link.text|capfirst }}{% if link.error %} - {{ link.error }}{% endif %}{% if link.active %}{% endif %}{% if horizontal %}{% if not forloop.last %} | {% endif %}{% endif %}
{% if as_li %}{% endif %}
+{% endif %}
{% endfor %}
diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py
index 1e36969cb7..7f616f1cf6 100755
--- a/apps/documents/__init__.py
+++ b/apps/documents/__init__.py
@@ -4,25 +4,42 @@ from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from common.api import register_links, register_menu, \
- register_model_list_columns, register_permissions
+ register_model_list_columns
from common.utils import pretty_size
+from permissions.api import register_permissions
+
from models import Document
from staging import StagingFile
from documents.conf import settings as documents_settings
+PERMISSION_DOCUMENT_CREATE = 'document_create'
+PERMISSION_DOCUMENT_PROPERTIES_EDIT = 'document_properties_edit'
+PERMISSION_DOCUMENT_METADATA_EDIT = 'document_metadata_edit'
+PERMISSION_DOCUMENT_VIEW = 'document_view'
+PERMISSION_DOCUMENT_DELETE = 'document_delete'
+PERMISSION_DOCUMENT_DOWNLOAD = 'document_download'
-document_list = {'text':_(u'documents list'), 'view':'document_list', 'famfam':'page'}
-document_create = {'text':_('upload a document'), 'view':'document_create', 'famfam':'page_add'}
-document_create_multiple = {'text':_('upload multiple documents'), 'view':'document_create_multiple', 'famfam':'page_add'}
-document_create_sibling = {'text':_('upload using same metadata'), 'view':'document_create_sibling', 'args':'object.id', 'famfam':'page_copy'}
-document_view = {'text':_('details'), 'view':'document_view', 'args':'object.id', 'famfam':'page'}
-document_delete = {'text':_('delete'), 'view':'document_delete', 'args':'object.id', 'famfam':'page_delete'}
-document_edit = {'text':_('edit'), 'view':'document_edit', 'args':'object.id', 'famfam':'page_edit'}
-document_edit_metadata = {'text':_('edit metadata'), 'view':'document_edit_metadata', 'args':'object.id', 'famfam':'page_edit'}
-document_preview = {'text':_('preview'), 'class':'fancybox', 'view':'document_preview', 'args':'object.id', 'famfam':'magnifier'}
-document_download = {'text':_('download'), 'view':'document_download', 'args':'object.id', 'famfam':'page_save'}
+register_permissions('documents', [
+ {'name':PERMISSION_DOCUMENT_CREATE, 'label':_(u'Create document')},
+ {'name':PERMISSION_DOCUMENT_PROPERTIES_EDIT, 'label':_(u'Edit document properties')},
+ {'name':PERMISSION_DOCUMENT_METADATA_EDIT, 'label':_(u'Edit document metadata')},
+ {'name':PERMISSION_DOCUMENT_VIEW, 'label':_(u'View document')},
+ {'name':PERMISSION_DOCUMENT_DELETE, 'label':_(u'Delete document')},
+ {'name':PERMISSION_DOCUMENT_DOWNLOAD, 'label':_(u'Download document')},
+])
+
+document_list = {'text':_(u'documents list'), 'view':'document_list', 'famfam':'page', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_VIEW]}}
+document_create = {'text':_('upload a document'), 'view':'document_create', 'famfam':'page_add', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_CREATE]}}
+document_create_multiple = {'text':_('upload multiple documents'), 'view':'document_create_multiple', 'famfam':'page_add', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_CREATE]}}
+document_create_sibling = {'text':_('upload using same metadata'), 'view':'document_create_sibling', 'args':'object.id', 'famfam':'page_copy', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_CREATE]}}
+document_view = {'text':_('details'), 'view':'document_view', 'args':'object.id', 'famfam':'page', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_VIEW]}}
+document_delete = {'text':_('delete'), 'view':'document_delete', 'args':'object.id', 'famfam':'page_delete', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_DELETE]}}
+document_edit = {'text':_('edit'), 'view':'document_edit', 'args':'object.id', 'famfam':'page_edit', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_PROPERTIES_EDIT]}}
+document_edit_metadata = {'text':_('edit metadata'), 'view':'document_edit_metadata', 'args':'object.id', 'famfam':'page_edit', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_METADATA_EDIT]}}
+document_preview = {'text':_('preview'), 'class':'fancybox', 'view':'document_preview', 'args':'object.id', 'famfam':'magnifier', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_VIEW]}}
+document_download = {'text':_('download'), 'view':'document_download', 'args':'object.id', 'famfam':'page_save', 'permissions':{'namespace':'documents', 'permissions':[PERMISSION_DOCUMENT_DOWNLOAD]}}
staging_file_preview = {'text':_('preview'), 'class':'fancybox', 'view':'staging_file_preview', 'args':'object.id', 'famfam':'drive_magnify'}
staging_file_delete = {'text':_('delete'), 'view':'staging_file_delete', 'args':'object.id', 'famfam':'drive_delete'}
@@ -50,21 +67,3 @@ register_menu([
],'famfam':'page','position':4}])
TEMPORARY_DIRECTORY = documents_settings.TEMPORARY_DIRECTORY if documents_settings.TEMPORARY_DIRECTORY else tempfile.mkdtemp()
-
-PERMISSION_DOCUMENT_CREATE = 'document_create'
-PERMISSION_DOCUMENT_PROPERTIES_EDIT = 'document_properties_edit'
-PERMISSION_DOCUMENT_METADATA_EDIT = 'document_metadata_edit'
-PERMISSION_DOCUMENT_VIEW = 'document_view'
-PERMISSION_DOCUMENT_DELETE = 'document_delete'
-PERMISSION_DOCUMENT_OCR = 'document_ocr'
-PERMISSION_DOCUMENT_DOWNLOAD = 'document_download'
-
-register_permissions('documents', [
- {'name':PERMISSION_DOCUMENT_CREATE, 'label':_(u'Create document')},
- {'name':PERMISSION_DOCUMENT_PROPERTIES_EDIT, 'label':_(u'Edit document properties')},
- {'name':PERMISSION_DOCUMENT_METADATA_EDIT, 'label':_(u'Edit document metadata')},
- {'name':PERMISSION_DOCUMENT_VIEW, 'label':_(u'View document')},
- {'name':PERMISSION_DOCUMENT_DELETE, 'label':_(u'Delete document')},
- {'name':PERMISSION_DOCUMENT_OCR, 'label':_(u'Submit document for OCR')},
- {'name':PERMISSION_DOCUMENT_DOWNLOAD, 'label':_(u'Download document')},
-])
diff --git a/apps/documents/views.py b/apps/documents/views.py
index ea853cfbca..065edbcfa0 100755
--- a/apps/documents/views.py
+++ b/apps/documents/views.py
@@ -11,7 +11,7 @@ from django.conf import settings
from django.utils.http import urlencode
from django.template.defaultfilters import slugify
-
+from permissions.api import check_permissions, Unauthorized
from filetransfers.api import serve_file
from converter.api import convert, in_image_cache, QUALITY_DEFAULT
from common.utils import pretty_size
@@ -38,13 +38,17 @@ from documents.conf.settings import GROUP_SHOW_EMPTY
from documents import PERMISSION_DOCUMENT_CREATE, \
PERMISSION_DOCUMENT_CREATE, PERMISSION_DOCUMENT_PROPERTIES_EDIT, \
PERMISSION_DOCUMENT_METADATA_EDIT, PERMISSION_DOCUMENT_VIEW, \
- PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_OCR, \
- PERMISSION_DOCUMENT_DOWNLOAD
+ PERMISSION_DOCUMENT_DELETE, PERMISSION_DOCUMENT_DOWNLOAD
-
from utils import save_metadata, save_metadata_list, decode_metadata_from_url
def document_list(request):
+ permissions = [PERMISSION_DOCUMENT_VIEW]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
return object_list(
request,
queryset=Document.objects.all(),
@@ -56,8 +60,10 @@ def document_list(request):
def document_create(request, multiple=True):
permissions = [PERMISSION_DOCUMENT_CREATE]
- if not check_permissions(main_object, request.user, permissions):
- raise Http404
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
if DocumentType.objects.all().count() == 1:
wizard = DocumentCreateWizard(
@@ -72,6 +78,12 @@ def document_create(request, multiple=True):
return wizard(request)
def document_create_sibling(request, document_id, multiple=True):
+ permissions = [PERMISSION_DOCUMENT_CREATE]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
urldata = []
for id, metadata in enumerate(document.documentmetadata_set.all()):
@@ -89,6 +101,12 @@ def document_create_sibling(request, document_id, multiple=True):
def upload_document_with_type(request, document_type_id, multiple=True):
+ permissions = [PERMISSION_DOCUMENT_CREATE]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document_type = get_object_or_404(DocumentType, pk=document_type_id)
local_form = DocumentForm(prefix='local', initial={'document_type':document_type})
if USE_STAGING_DIRECTORY:
@@ -206,6 +224,12 @@ def upload_document_with_type(request, document_type_id, multiple=True):
context_instance=RequestContext(request))
def document_view(request, document_id):
+ permissions = [PERMISSION_DOCUMENT_VIEW]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
form = DocumentForm_view(instance=document, extra_fields=[
{'label':_(u'Filename'), 'field':'file_filename'},
@@ -285,6 +309,12 @@ def document_view(request, document_id):
def document_delete(request, document_id):
+ permissions = [PERMISSION_DOCUMENT_DELETE]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
return delete_object(request, model=Document, object_id=document_id,
@@ -298,6 +328,12 @@ def document_delete(request, document_id):
def document_edit(request, document_id):
+ permissions = [PERMISSION_DOCUMENT_PROPERTIES_EDIT]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
if request.method == 'POST':
form = DocumentForm_edit(request.POST, initial={'document_type':document.document_type})
@@ -338,6 +374,12 @@ def document_edit(request, document_id):
def document_edit_metadata(request, document_id):
+ permissions = [PERMISSION_DOCUMENT_METADATA_EDIT]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
initial=[]
@@ -386,6 +428,12 @@ def document_edit_metadata(request, document_id):
def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_DEFAULT):
+ permissions = [PERMISSION_DOCUMENT_VIEW]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
try:
@@ -407,6 +455,12 @@ def get_document_image(request, document_id, size=PREVIEW_SIZE, quality=QUALITY_
def document_download(request, document_id):
+ permissions = [PERMISSION_DOCUMENT_DOWNLOAD]
+ try:
+ check_permissions(request.user, 'documents', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
try:
#Test permissions and trigger exception
@@ -417,6 +471,7 @@ def document_download(request, document_id):
return HttpResponseRedirect(request.META['HTTP_REFERER'])
+#TODO: Need permission
def staging_file_preview(request, staging_file_id):
try:
filepath = StagingFile.get(staging_file_id).filepath
@@ -425,7 +480,8 @@ def staging_file_preview(request, staging_file_id):
except Exception, e:
return serve_file(request, File(file=open('%simages/1297211435_error.png' % settings.MEDIA_ROOT, 'r')))
-
+
+#TODO: Need permission
def staging_file_delete(request, staging_file_id):
staging_file = StagingFile.get(staging_file_id)
next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', None)))
diff --git a/apps/ocr/__init__.py b/apps/ocr/__init__.py
index 341df0c0ac..44466ab757 100755
--- a/apps/ocr/__init__.py
+++ b/apps/ocr/__init__.py
@@ -1,11 +1,17 @@
from django.utils.translation import ugettext_lazy as _
from common.api import register_links, register_menu
+from permissions.api import register_permissions
from documents.models import Document
+OCR_DOCUMENT_OCR = 'document_ocr'
-submit_document = {'text':_('submit to OCR queue'), 'view':'submit_document', 'args':'object.id', 'famfam':'page_lightning'}
+register_permissions('ocr', [
+ {'name':OCR_DOCUMENT_OCR, 'label':_(u'Submit document for OCR')},
+])
+
+submit_document = {'text':_('submit to OCR queue'), 'view':'submit_document', 'args':'object.id', 'famfam':'page_lightning', 'permissions':{'namespace':'ocr', 'permissions':[OCR_DOCUMENT_OCR]}}
register_links(Document, [submit_document], menu_name='sidebar')
diff --git a/apps/ocr/views.py b/apps/ocr/views.py
index 19ae631664..f3a7fe3def 100755
--- a/apps/ocr/views.py
+++ b/apps/ocr/views.py
@@ -1,4 +1,4 @@
-from django.http import HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import RequestContext
from django.contrib import messages
@@ -8,13 +8,19 @@ from django.core.urlresolvers import reverse
from django.conf import settings
from django.utils.translation import ugettext as _
-
+from permissions.api import check_permissions, Unauthorized
from documents.models import Document
-
+from ocr import OCR_DOCUMENT_OCR
from api import ocr_document
def submit_document(request, document_id):
+ permissions = [OCR_DOCUMENT_OCR]
+ try:
+ check_permissions(request.user, 'ocr', permissions)
+ except Unauthorized, e:
+ raise Http404(e)
+
document = get_object_or_404(Document, pk=document_id)
try:
diff --git a/apps/permissions/__init__.py b/apps/permissions/__init__.py
new file mode 100644
index 0000000000..b2c3b5f6d6
--- /dev/null
+++ b/apps/permissions/__init__.py
@@ -0,0 +1,21 @@
+from django.contrib.auth.models import User
+from django.db.models.signals import post_save
+from django.core.exceptions import ObjectDoesNotExist
+
+from permissions.conf.settings import DEFAULT_ROLES
+
+from models import Role
+
+
+def user_post_save(sender, instance, **kwargs):
+ for default_role in DEFAULT_ROLES:
+ if isinstance(default_role, Role):
+ default_role.add_member(instance)
+ else:
+ try:
+ role = Role.objects.get(name=default_role)
+ role.add_member(instance)
+ except ObjectDoesNotExist:
+ pass
+
+post_save.connect(user_post_save, sender=User)
diff --git a/apps/permissions/admin.py b/apps/permissions/admin.py
new file mode 100644
index 0000000000..96ae45f5e5
--- /dev/null
+++ b/apps/permissions/admin.py
@@ -0,0 +1,33 @@
+from django.contrib import admin
+
+from models import Permission, PermissionHolder, Role, RoleMember
+from django.contrib.contenttypes import generic
+
+
+class PermissionHolderInline(admin.StackedInline):
+ model = PermissionHolder
+ extra = 1
+ classes = ('collapse-open',)
+ allow_add = True
+
+
+class PermissionAdmin(admin.ModelAdmin):
+ inlines = [PermissionHolderInline]
+ list_display = ('namespace', 'name', 'label')
+ list_display_links = list_display
+
+
+class RoleMemberInline(admin.StackedInline):
+ model = RoleMember
+ extra = 1
+ classes = ('collapse-open',)
+ allow_add = True
+
+
+class RoleAdmin(admin.ModelAdmin):
+ inlines = [RoleMemberInline]
+
+
+admin.site.register(Permission, PermissionAdmin)
+admin.site.register(Role, RoleAdmin)
+
diff --git a/apps/permissions/api.py b/apps/permissions/api.py
new file mode 100644
index 0000000000..ceacfb9862
--- /dev/null
+++ b/apps/permissions/api.py
@@ -0,0 +1,70 @@
+from django.http import Http404
+from django.contrib.auth.models import User
+from django.contrib.auth.models import Group
+from django.db.utils import DatabaseError
+from django.shortcuts import get_object_or_404
+from django.contrib.contenttypes.models import ContentType
+from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import ugettext
+
+from models import Permission, Role
+
+class Unauthorized(Exception):
+ pass
+
+
+def register_permissions(namespace, permissions):
+ if permissions:
+ for permission in permissions:
+ try:
+ permission_obj, created = Permission.objects.get_or_create(
+ namespace=namespace, name=permission['name'])
+ permission_obj.label=unicode(permission['label'])
+ permission_obj.save()
+ except DatabaseError:
+ #Special case for ./manage.py syncdb
+ pass
+
+#TODO: Handle anonymous users
+def check_permissions(requester, namespace, permission_list):
+ if isinstance(requester, User):
+ if requester.is_superuser:
+ return True
+
+ for permission_item in permission_list:
+ permission = get_object_or_404(Permission,
+ namespace=namespace, name=permission_item)
+ if check_permission(requester, permission):
+ return True
+
+ raise Unauthorized(ugettext(u'Insufficient permissions.'))
+
+
+def check_permission(requester, permission):
+ for permission_holder in permission.permissionholder_set.all():
+ if check_requester(requester, permission_holder):
+ return True
+
+
+def check_requester(requester, permission_holder):
+ ct = ContentType.objects.get_for_model(requester)
+ if permission_holder.holder_type == ct and permission_holder.holder_id == requester.id:
+ return True
+
+ if isinstance(permission_holder.holder_object, Role):
+ requester_list = [role_member.member_object for role_member in permission_holder.holder_object.rolemember_set.all()]
+ if check_elements(requester, requester_list):
+ return True
+
+ #Untested
+ if isinstance(permission_holder.holder_object, Group):
+ if check_elements(requester, permission_holder.holder_object.user_set.all()):
+ return True
+
+
+#TODO: a role may contain groups, make recursive
+def check_elements(requester, requester_list):
+ ct = ContentType.objects.get_for_model(requester)
+ for requester_object in requester_list:
+ if requester == requester_object:
+ return True
diff --git a/apps/permissions/conf/__init__.py b/apps/permissions/conf/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/apps/permissions/conf/settings.py b/apps/permissions/conf/settings.py
new file mode 100644
index 0000000000..af5146cc0f
--- /dev/null
+++ b/apps/permissions/conf/settings.py
@@ -0,0 +1,3 @@
+from django.conf import settings
+
+DEFAULT_ROLES = getattr(settings, 'ROLES_DEFAULT_ROLES', [])
diff --git a/apps/permissions/locale/es/LC_MESSAGES/django.po b/apps/permissions/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000000..e60738cb1f
--- /dev/null
+++ b/apps/permissions/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,67 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-02-13 04:12-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: LANGUAGE \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: api.py:40
+msgid "Insufficient permissions."
+msgstr ""
+
+#: models.py:8
+msgid "namespace"
+msgstr ""
+
+#: models.py:9
+msgid "name"
+msgstr ""
+
+#: models.py:10 models.py:40
+msgid "label"
+msgstr ""
+
+#: models.py:14 models.py:23
+msgid "permission"
+msgstr ""
+
+#: models.py:15
+msgid "permissions"
+msgstr ""
+
+#: models.py:31
+msgid "permission holder"
+msgstr ""
+
+#: models.py:32
+msgid "permission holders"
+msgstr ""
+
+#: models.py:44 models.py:58
+msgid "role"
+msgstr ""
+
+#: models.py:45
+msgid "roles"
+msgstr ""
+
+#: models.py:67
+msgid "role member"
+msgstr ""
+
+#: models.py:68
+msgid "role members"
+msgstr ""
diff --git a/apps/permissions/models.py b/apps/permissions/models.py
new file mode 100644
index 0000000000..1acf0e7896
--- /dev/null
+++ b/apps/permissions/models.py
@@ -0,0 +1,71 @@
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+
+
+class Permission(models.Model):
+ namespace = models.CharField(max_length=64, verbose_name=_(u'namespace'))
+ name = models.CharField(max_length=64, verbose_name=_(u'name'))
+ label = models.CharField(max_length=64, verbose_name=_(u'label'))
+
+ class Meta:
+ unique_together = ('namespace', 'name')
+ verbose_name = _(u'permission')
+ verbose_name_plural = _(u'permissions')
+
+
+ def __unicode__(self):
+ return self.label
+
+
+class PermissionHolder(models.Model):
+ permission = models.ForeignKey(Permission, verbose_name=_(u'permission'))
+ holder_type = models.ForeignKey(ContentType,
+ related_name='permission_holder',
+ limit_choices_to = {'model__in': ('user', 'group', 'role')})
+ holder_id = models.PositiveIntegerField()
+ holder_object = generic.GenericForeignKey(ct_field='holder_type', fk_field='holder_id')
+
+ class Meta:
+ verbose_name = _(u'permission holder')
+ verbose_name_plural = _(u'permission holders')
+
+ def __unicode__(self):
+ return unicode(self.holder_object)
+
+
+class Role(models.Model):
+ name = models.CharField(max_length=64, unique=True)
+ label = models.CharField(max_length=64, unique=True, verbose_name=_(u'label'))
+
+ class Meta:
+ ordering = ('label',)
+ verbose_name = _(u'role')
+ verbose_name_plural = _(u'roles')
+
+ def add_member(self, member):
+ role_member, created = RoleMember.objects.get_or_create(
+ role=self,
+ member_type = ContentType.objects.get_for_model(member),
+ member_id=member.id)
+
+ def __unicode__(self):
+ return self.label
+
+
+class RoleMember(models.Model):
+ role = models.ForeignKey(Role, verbose_name=_(u'role'))
+ member_type = models.ForeignKey(ContentType,
+ related_name='role_member',
+ limit_choices_to = {'model__in': ('user', 'group', 'role')})
+ member_id = models.PositiveIntegerField()
+ member_object = generic.GenericForeignKey(ct_field='member_type', fk_field='member_id')
+
+ class Meta:
+ #ordering = ('label',)
+ verbose_name = _(u'role member')
+ verbose_name_plural = _(u'role members')
+
+ def __unicode__(self):
+ return unicode(self.member_object)
diff --git a/apps/permissions/templatetags/__init__.py b/apps/permissions/templatetags/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/apps/permissions/templatetags/permission_tags.py b/apps/permissions/templatetags/permission_tags.py
new file mode 100644
index 0000000000..d4168af744
--- /dev/null
+++ b/apps/permissions/templatetags/permission_tags.py
@@ -0,0 +1,37 @@
+from django.template import TemplateSyntaxError, Library, \
+ VariableDoesNotExist, Node, Variable
+
+from permissions.api import check_permissions as check_permission_function, Unauthorized
+
+register = Library()
+
+
+class CheckPermissionsNode(Node):
+ def __init__(self, requester, namespace, permission_list, *args, **kwargs):
+ self.requester = requester
+ self.namespace = namespace
+ self.permission_list = permission_list
+
+ def render(self, context):
+ requester = Variable(self.requester).resolve(context)
+ namespace = Variable(self.namespace).resolve(context)
+ permission_list = Variable(self.permission_list).resolve(context)
+ try:
+ check_permission_function(requester, namespace, permission_list)
+ context['permission'] = True
+ return ''
+ except Unauthorized:
+ context['permission'] = False
+ return ''
+
+
+@register.tag
+def check_permissions(parser, token):
+ try:
+ # Splitting by None == splitting by spaces.
+ tag_name, args = token.contents.split(None, 1)
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires arguments" % token.contents.split()[0]
+
+ return CheckPermissionsNode(*args.split())
+
diff --git a/apps/permissions/tests.py b/apps/permissions/tests.py
new file mode 100644
index 0000000000..2247054b35
--- /dev/null
+++ b/apps/permissions/tests.py
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/apps/permissions/views.py b/apps/permissions/views.py
new file mode 100644
index 0000000000..60f00ef0ef
--- /dev/null
+++ b/apps/permissions/views.py
@@ -0,0 +1 @@
+# Create your views here.
diff --git a/docs/Changelog.txt b/docs/Changelog.txt
index f6cf3e2542..a4dfe3cb74 100644
--- a/docs/Changelog.txt
+++ b/docs/Changelog.txt
@@ -5,3 +5,4 @@
* Changed to a liquid css grid
* Added the ability to group documents by their metadata
* New abstracted options to adjust document conversion quality (default, low, high)
+* Added permissions and roles support
diff --git a/docs/TODO b/docs/TODO
index a294dc8f9f..c26f8a18ec 100755
--- a/docs/TODO
+++ b/docs/TODO
@@ -26,6 +26,8 @@
* If theres only one document type on db skip step 1 of wizard - DONE
* Be able to delete staging file - DONE
* Group documents by metadata - DONE
+* Permissions - DONE
+* Roles - DONE
* Document list filtering by metadata
* Filterform date filtering widget
* Validate GET data before saving file
@@ -42,8 +44,6 @@
* MuliThreading deferred OCR
* Versioning support
* Generic document anotations using layer overlays
-* Permissions
-* Roles
* Workflows
* Scheduled maintenance (cleanup, deferred OCR's)
* Add tags to documents
@@ -60,6 +60,6 @@
* Support spreadsheets, wordprocessing docs using openoffice in server mode
* WebDAV support
* Handle ziped or rar archives
-* Display preferences (Rotation, default zoom)
+* Display preferences 'document transformations' (Rotation, default zoom)
* Gallery view for document groups
* Assign default role to new users
diff --git a/runserver_plus.sh b/runserver_plus.sh
index 5be5b13db6..949bd57a0e 100755
--- a/runserver_plus.sh
+++ b/runserver_plus.sh
@@ -1,6 +1,6 @@
#!/bin/sh
if [ -n "$1" ]; then
- ./manage.py runserver_plus $1 --adminmedia ./site_media/admin_media/
+ ./manage.py runserver_plus $1 --adminmedia ./site_media/grappelli/
else
- ./manage.py runserver_plus --adminmedia ./site_media/admin_media/
+ ./manage.py runserver_plus --adminmedia ./site_media/grappelli/
fi
diff --git a/settings.py b/settings.py
index fe285046ce..fc425d2f54 100755
--- a/settings.py
+++ b/settings.py
@@ -77,7 +77,7 @@ MEDIA_URL = '/%s-site_media/' % PROJECT_NAME
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
-ADMIN_MEDIA_PREFIX = MEDIA_URL + 'admin_media/'
+ADMIN_MEDIA_PREFIX = MEDIA_URL + 'grappelli/'
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'om^a(i8^6&h+umbd2%pt91cj!qu_@oztw117rgxmn(n2lp^*c!'
@@ -215,6 +215,9 @@ LOGIN_EXEMPT_URLS = (
# OCR
#OCR_TESSERACT_PATH = u'/usr/bin/tesseract'
+# Permissions
+#ROLES_DEFAULT_ROLES = []
+
# Override
SEARCH_SHOW_OBJECT_TYPE = False
#======== End of configuration options =======
diff --git a/site_media/admin_media b/site_media/grappelli
similarity index 100%
rename from site_media/admin_media
rename to site_media/grappelli
diff --git a/urls.py b/urls.py
index 979a4397ca..e609835e84 100755
--- a/urls.py
+++ b/urls.py
@@ -12,6 +12,7 @@ urlpatterns = patterns('',
(r'^ocr/', include('ocr.urls')),
(r'^admin/doc/', include('django.contrib.admindocs.urls')),
(r'^admin/', include(admin.site.urls)),
+ (r'^grappelli/', include('grappelli.urls')),
)
if settings.DEVELOPMENT: