Skip to content

Commit

Permalink
Major update of the permission app
Browse files Browse the repository at this point in the history
  • Loading branch information
ellmetha committed Apr 13, 2015
1 parent feae38d commit 44b1c7c
Show file tree
Hide file tree
Showing 60 changed files with 607 additions and 226 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ env:
- DJANGO="django>=1.5,<1.6"
- DJANGO="django>=1.6,<1.7"
- DJANGO="django>=1.7,<1.8"
- DJANGO="django>=1.8,<1.9"
- TOXENV=lint

matrix:
Expand Down
22 changes: 5 additions & 17 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ Requirements
------------

* `Python`_ 2.7, 3.3 or 3.4
* `Django`_ 1.4.x, 1.5.x, 1.6.x or 1.7.x
* `Django`_ 1.4.x, 1.5.x, 1.6.x, 1.7.x or 1.8.x
* `Pillow`_ 2.2. or higher
* `Django-model-utils`_ 2.0. or higher
* `Django-mptt`_ 0.7. or higher
* `Django-guardian`_ 1.2. or higher
* `Django-haystack`_ 2.1. or higher
* `Django-markdown`_ 0.7. or higher
* `Django-compressor`_ 1.4. or higher
* `Django-bootstrap3`_ 3.0. or higher
* `South`_ 1.0.1 or higher if you are using Django < 1.7


.. warning:: While *django-machina* is compatible with Django 1.5.x, this version of Django
is no longer supported by the Django team. Please upgrade to
Django 1.6.x or 1.7.x immediately.
.. warning:: While *django-machina* is compatible with Django 1.5.x and Django 1.6.x, these versions of Django
are no longer supported by the Django team. Please upgrade to
Django 1.7.x or 1.8.x immediately.

.. note::

Expand All @@ -34,7 +33,6 @@ Requirements
.. _Pillow: http://python-pillow.github.io/
.. _Django-model-utils: https://github.com/carljm/django-model-utils
.. _Django-mptt: https://github.com/django-mptt/django-mptt
.. _Django-guardian: https://github.com/lukaszb/django-guardian
.. _Django-haystack: https://github.com/django-haystack/django-haystack
.. _Django-markdown: https://github.com/klen/django_markdown
.. _Django-compressor: https://github.com/django-compressor/django-compressor
Expand Down Expand Up @@ -73,7 +71,6 @@ First update your ``INSTALLED_APPS`` in your project's settings module. Modify i
# Machina related apps:
'mptt',
'guardian',
'haystack',

'bootstrap3',
Expand All @@ -85,7 +82,7 @@ First update your ``INSTALLED_APPS`` in your project's settings module. Modify i

As previously stated, *django-markdown* is the default syntax used for forum messages and *django-compressor* and *django-bootstrap3* are used in templates to handle static files and form rendering. These modules are optional really and you may decide to override the *django-machina*'s templates to use other modules.

*Django-machina* uses *django-mptt* to handle the tree of forum instances and *django-guardian* is used to allow per-forum permissions management. Search capabilities are provided by *django-haystack*.
*Django-machina* uses *django-mptt* to handle the tree of forum instances. Search capabilities are provided by *django-haystack*.

Then update your ``TEMPLATE_CONTEXT_PROCESSORS`` setting as follows::

Expand Down Expand Up @@ -122,15 +119,6 @@ Finally you have to add a new cache to your settings. This cache will be used to
}
}

Django-guardian settings
~~~~~~~~~~~~~~~~~~~~~~~~

*Django-machina* uses the *django-guardian* module to allow you define your forum permissions in a permissive way. As *django-guardian* provides object permissions capabilities, you can define specific user or group permissions for each forum you create.

*Django-machina* supports anonymous posting and handle anonymous users. So you need to create an ``ANONYMOUS_USER_ID`` setting in order to allow *django-machina* to properly handle anonymous user permissions inside your forum::

ANONYMOUS_USER_ID = -1

Django-haystack settings
~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Permission

Default: ``[]``

*Django-machina* relies on a permission system based on per-forum permissions. These permissions are provided by the *django-guardian* module. This allows you to define which permissions should be applied for each forum, for each user and for each group of users. However you might want to grant the same permissions to all the users and for all the forums you created. In that case, this setting can be used in order to define which permissions should be granted to all authenticated users. Note that the permissions specified in this list are granted only if the considered forum does not have any permission for the considered authenticated user. For example, the setting could be specified as follows::
*Django-machina* relies on a permission system based on per-forum permissions. This allows you to define which permissions should be applied for each forum, for each user and for each group of users. However you might want to grant the same permissions to all the users and for all the forums you created. In that case, this setting can be used in order to define which permissions should be granted to all authenticated users. Note that the permissions specified in this list are granted only if the considered forum does not have any permission for the considered authenticated user. For example, the setting could be specified as follows::

MACHINA_DEFAULT_AUTHENTICATED_USER_FORUM_PERMISSIONS = [
'can_see_forum',
Expand Down
4 changes: 0 additions & 4 deletions example_projects/vanilla/src/example_project/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@
'compressor',
'loginas',
'crispy_forms',
'guardian',
'mptt',
'haystack',
'bootstrap3',
Expand Down Expand Up @@ -174,9 +173,6 @@
# Specific machina settings
# --------------------------------------

# Django guardian
ANONYMOUS_USER_ID = -1

# Attachment cache backend
CACHES = {
'default': {
Expand Down
3 changes: 1 addition & 2 deletions machina/apps/forum/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from guardian.admin import GuardedModelAdmin
from mptt.exceptions import InvalidMove

# Local application / specific library imports
Expand All @@ -18,7 +17,7 @@
Forum = get_model('forum', 'Forum')


class ForumAdmin(GuardedModelAdmin):
class ForumAdmin(admin.ModelAdmin):
"""
The ForumAdmin class is a subclass of GuardedModelAdmin and so provides common tools for
assigning user permissions or group permissions to any forums.
Expand Down
18 changes: 18 additions & 0 deletions machina/apps/forum/migrations/0002_auto_20150413_2352.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('forum', '0001_initial'),
]

operations = [
migrations.AlterModelOptions(
name='forum',
options={'ordering': ['tree_id', 'lft'], 'verbose_name': 'Forum', 'verbose_name_plural': 'Forums', 'permissions': [('can_see_forum', 'Can see forum'), ('can_read_forum', 'Can read forum'), ('can_start_new_topics', 'Can start new topics'), ('can_reply_to_topics', 'Can reply to topics'), ('can_post_announcements', 'Can post announcements'), ('can_post_stickies', 'Can post stickies'), ('can_delete_own_posts', 'Can delete own posts'), ('can_edit_own_posts', 'Can edit own posts'), ('can_post_without_approval', 'Can post without approval'), ('can_create_poll', 'Can create poll'), ('can_vote_in_polls', 'Can vote in polls'), ('can_attach_file', 'Can attach file'), ('can_download_file', 'Can download file'), ('can_close_topics', 'Can close topics'), ('can_move_topics', 'Can move topics'), ('can_edit_posts', 'Can edit posts'), ('can_delete_posts', 'Can delete posts'), ('can_move_posts', 'Can move posts'), ('can_approve_posts', 'Can approve posts')]},
),
]
3 changes: 2 additions & 1 deletion machina/apps/forum/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
from machina.conf import settings as machina_settings
from machina.core.db.models import get_model
from machina.core.loading import get_class
from machina.views.mixins import PermissionRequiredMixin

Forum = get_model('forum', 'Forum')
Topic = get_model('forum_conversation', 'Topic')

PermissionHandler = get_class('forum_permission.handler', 'PermissionHandler')
perm_handler = PermissionHandler()

PermissionRequiredMixin = get_class('forum_permission.mixins', 'PermissionRequiredMixin')


class IndexView(ListView):
template_name = 'forum/index.html'
Expand Down
2 changes: 1 addition & 1 deletion machina/apps/forum_conversation/abstract_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class AbstractTopic(DatedModel):
last_post_on = models.DateTimeField(verbose_name=_('Last post added on'), blank=True, null=True)

# Many users can subscribe to this topic
subscribers = models.ManyToManyField(AUTH_USER_MODEL, related_name='subscriptions', verbose_name=_('Subscribers'), blank=True, null=True)
subscribers = models.ManyToManyField(AUTH_USER_MODEL, related_name='subscriptions', verbose_name=_('Subscribers'), blank=True)

objects = models.Manager()
approved_objects = ApprovedManager()
Expand Down
3 changes: 2 additions & 1 deletion machina/apps/forum_conversation/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import F
from django.utils.translation import ugettext_lazy as _
from guardian.utils import get_anonymous_user

# Local application / specific library imports
from machina.conf import settings as machina_settings
Expand All @@ -19,6 +18,8 @@
Topic = get_model('forum_conversation', 'Topic')
TopicPoll = get_model('forum_polls', 'TopicPoll')

get_anonymous_user = get_class('forum_permission.shortcuts', 'get_anonymous_user')

PermissionHandler = get_class('forum_permission.handler', 'PermissionHandler')
perm_handler = PermissionHandler()

Expand Down
3 changes: 2 additions & 1 deletion machina/apps/forum_conversation/forum_attachments/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
# Local application / specific library imports
from machina.core.db.models import get_model
from machina.core.loading import get_class
from machina.views.mixins import PermissionRequiredMixin

Attachment = get_model('forum_attachments', 'Attachment')

PermissionHandler = get_class('forum_permission.handler', 'PermissionHandler')
perm_handler = PermissionHandler()

PermissionRequiredMixin = get_class('forum_permission.mixins', 'PermissionRequiredMixin')


class AttachmentView(PermissionRequiredMixin, DetailView):
model = Attachment
Expand Down
3 changes: 2 additions & 1 deletion machina/apps/forum_conversation/forum_polls/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# Local application / specific library imports
from machina.core.db.models import get_model
from machina.core.loading import get_class
from machina.views.mixins import PermissionRequiredMixin

TopicPoll = get_model('forum_polls', 'TopicPoll')
TopicPollVote = get_model('forum_polls', 'TopicPollVote')
Expand All @@ -24,6 +23,8 @@
PermissionHandler = get_class('forum_permission.handler', 'PermissionHandler')
perm_handler = PermissionHandler()

PermissionRequiredMixin = get_class('forum_permission.mixins', 'PermissionRequiredMixin')


class TopicPollVoteView(PermissionRequiredMixin, UpdateView):
model = TopicPoll
Expand Down
3 changes: 2 additions & 1 deletion machina/apps/forum_conversation/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from machina.conf import settings as machina_settings
from machina.core.db.models import get_model
from machina.core.loading import get_class
from machina.views.mixins import PermissionRequiredMixin

Attachment = get_model('forum_attachments', 'Attachment')
Forum = get_model('forum', 'Forum')
Expand All @@ -39,6 +38,8 @@
PermissionHandler = get_class('forum_permission.handler', 'PermissionHandler')
perm_handler = PermissionHandler()

PermissionRequiredMixin = get_class('forum_permission.mixins', 'PermissionRequiredMixin')


class TopicView(PermissionRequiredMixin, ListView):
template_name = 'forum_conversation/topic_detail.html'
Expand Down
95 changes: 81 additions & 14 deletions machina/apps/forum_permission/abstract_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,46 +4,113 @@
from __future__ import unicode_literals

# Third party imports
from django.core.exceptions import ValidationError
from django.contrib.auth.models import Group
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from guardian.models import GroupObjectPermissionBase
from guardian.models import UserObjectPermissionBase

# Local application / specific library imports
from machina.core.compat import AUTH_USER_MODEL


@python_2_unicode_compatible
class AbstractForumUserObjectPermission(UserObjectPermissionBase):
class AbstractForumPermission(models.Model):
"""
Represents a single forum permission.
"""
codename = models.CharField(max_length=150, verbose_name=_('Permission codename'), unique=True)
name = models.CharField(max_length=255, verbose_name=_('Permission name'), blank=True, null=True)

is_global = models.BooleanField(
verbose_name=_('Global permission'),
help_text=_('This permission can be granted globally to all the forums'),
default=False)
is_local = models.BooleanField(
verbose_name=_('Local permission'),
help_text=_('This permission can be granted individually for each forum'),
default=True)

class Meta:
abstract = True
app_label = 'forum_permission'
verbose_name = _('Forum permission')
verbose_name_plural = _('Forum permissions')

def __str__(self):
if self.name:
return '{} - {}'.format(self.codename, self.name)
return self.codename

def clean(self):
super(AbstractForumPermission, self).clean()
if not self.is_global and not self.is_local:
raise ValidationError(_('A forum permission should be at least either global or local'))


class BaseAuthForumPermission(models.Model):
"""
Represents a per-auth-component forum object permission.
"""
permission = models.ForeignKey('forum_permission.ForumPermission', verbose_name=_('Forum permission'))
has_perm = models.BooleanField(verbose_name=_('Has perm'), default=True)

# The forum related to a UserForumPermission instance can be null if the
# considered permission should be granted globally.
forum = models.ForeignKey('forum.Forum', verbose_name=_('Forum'), blank=True, null=True)

class Meta:
abstract = True

def clean(self):
super(BaseAuthForumPermission, self).clean()
if self.forum is None and not self.permission.is_global:
raise ValidationError(
_('The following permission cannot be granted globally: {}'.format(self.permission)))


@python_2_unicode_compatible
class AbstractUserForumPermission(BaseAuthForumPermission):
"""
Represents a per-user forum object permission.
The 'real' permission handling is part of django-guardian.
"""
content_object = models.ForeignKey('forum.Forum', verbose_name=_('Forum'))
user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_('User'), null=True, blank=True)
anonymous_user = models.BooleanField(verbose_name=_('Target anonymous user'), default=False)

class Meta:
abstract = True
unique_together = ('permission', 'forum', 'user', 'anonymous_user', )
app_label = 'forum_permission'
verbose_name = _('User forum object permission')
verbose_name_plural = _('User forum object permissions')
verbose_name = _('User forum permission')
verbose_name_plural = _('User forum permissions')

def __str__(self):
return '{} - {}'.format(self.permission, self.content_object)
if self.forum:
return '{} - {} - {}'.format(self.permission, self.user, self.forum)
return '{} - {}'.format(self.permission, self.user)

def clean(self):
super(AbstractUserForumPermission, self).clean()
if (self.user is None and not self.anonymous_user) \
or (self.user and self.anonymous_user):
raise ValidationError(_('A permission should target either a user or an anonymous user'))


@python_2_unicode_compatible
class AbstractForumGroupObjectPermission(GroupObjectPermissionBase):
class AbstractGroupForumPermission(BaseAuthForumPermission):
"""
Represents a per-group forum object permission.
The 'real' permission handling is part of django-guardian.
"""
content_object = models.ForeignKey('forum.Forum', verbose_name=_('Forum'))
group = models.ForeignKey(Group, verbose_name=_('Group'))

class Meta:
abstract = True
unique_together = ('permission', 'forum', 'group', )
app_label = 'forum_permission'
verbose_name = _('Group forum object permission')
verbose_name_plural = _('Group forum object permissions')
verbose_name = _('Group forum permission')
verbose_name_plural = _('Group forum permissions')

def __str__(self):
return '{} - {}'.format(self.permission, self.content_object)
if self.forum:
return '{} - {} - {}'.format(self.permission, self.group, self.forum)
return '{} - {}'.format(self.permission, self.group)

0 comments on commit 44b1c7c

Please sign in to comment.