Skip to content

Commit

Permalink
Fixed #20 - added get_objects_for_user shortcut function
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukasz Balcerzak committed Jan 12, 2011
1 parent 01c9e86 commit d07d5d4
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 20 deletions.
15 changes: 15 additions & 0 deletions docs/api/guardian.shortcuts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,29 @@ get_perms_for_model

.. autofunction:: guardian.shortcuts.get_perms_for_model


.. _api-shortcuts-get_users_with_perms:

get_users_with_perms
--------------------

.. autofunction:: guardian.shortcuts.get_users_with_perms


.. _api-shortcuts-get_groups_with_perms:

get_groups_with_perms
---------------------

.. autofunction:: guardian.shortcuts.get_groups_with_perms


.. _api-shortcuts-get_objects_for_user:

.. shortcut:: get_objects_for_user

get_objects_for_user
--------------------

.. autofunction:: guardian.shortcuts.get_objects_for_user

5 changes: 5 additions & 0 deletions docs/exts.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@ def setup(app):
rolename = "setting",
indextemplate = "pair: %s; setting",
)
app.add_crossref_type(
directivename = "shortcut",
rolename = "shortcut",
indextemplate = "pair: %s; shortcut",
)

29 changes: 29 additions & 0 deletions docs/userguide/check.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ both ``User`` and ``Group`` instances. And if we need to do some more work we
can use lower level ``ObjectPermissionChecker`` class which is described in next
section.

get_objects_for_user
~~~~~~~~~~~~~~~~~~~~

Sometimes there is a need to extract list of objects based on particular user,
type of the object and provided permissions. For instance, lets say there is a
``Project`` model at ``projects`` application with custom ``view_project``
permission. We want to show our users projects they can actually *view*. This
could be easily achieved using :shortcut:`get_objects_for_user`:

.. code-block:: python
from django.shortcuts import render_to_response
from django.template import RequestContext
from projects.models import Project
from guardian.shortcuts import get_objects_for_user
def user_dashboard(request, template_name='projects/dashboard.html'):
projects = get_objects_for_user(request.user, 'projects.view_project')
return render_to_response(template_name, {'projects': projects},
RequestContext(request))
It is also possible to provide list of permissions rather than single string,
own queryset (as ``klass`` argument) or control if result should be computed
with (default) or without user's groups permissions.

.. seealso::
Documentation for :shortcut:`get_objects_for_user`


ObjectPermissionChecker
~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
44 changes: 24 additions & 20 deletions guardian/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,25 +234,29 @@ def get_groups_with_perms(obj, attach_perms=False):
groups[group] = get_perms(group, obj)
return groups

def get_objects_for_user(user, perms, klass=None):
def get_objects_for_user(user, perms, klass=None, use_groups=True):
"""
Returns queryset of objects for which given ``user`` has *all*
permissions from ``perms``.
permissions present at ``perms``.
:param user: ``User`` instance for which objects would be returned
:param perms: sequence with permissions as strings which should be checked.
if ``klass`` parameter is not given, those should be full permission
If ``klass`` parameter is not given, those should be full permission
names rather than only codenames (i.e. ``auth.change_user``). If more than
one permission is present within sequence, theirs content type **must** be
the same or ``MixedContentTypeError`` exception would be raised. For
convenience, may be given as single permission (string).
:param klass: may be a Model, Manager or QuerySet object. If not given
this parameter would be computed based on given ``params``.
:param use_groups: if ``False``, wouldn't check user's groups object
permissions. Default is ``True``.
:raises MixedContentTypeError:
:raises WrongAppError:
:raises MixedContentTypeError: when computed content type for ``perms``
and/or ``klass`` clashes.
:raises WrongAppError: if cannot compute app label for given ``perms``/
``klass``.
Excample::
Example::
>>> from guardian.shortcuts import get_objects_for_user
>>> joe = User.objects.get(username='joe')
Expand Down Expand Up @@ -315,23 +319,23 @@ def get_objects_for_user(user, perms, klass=None):
return queryset

# Now we should extract list of pk values for which we would filter queryset
q1 = Q(
permission__content_type=ctype,
permission__codename__in=codenames,
)
q2 = Q(
user__groups__groupobjectpermission__permission__content_type=ctype,
user__groups__groupobjectpermission__permission__codename__in=codenames,
)
qset = q1 | q2
rows = UserObjectPermission.objects\
user_obj_perms = UserObjectPermission.objects\
.filter(user=user)\
.filter(qset)\
.filter(permission__content_type=ctype)\
.filter(permission__codename__in=codenames)\
.values_list('object_pk', 'permission__codename')
keyfunc = lambda row: row[0] # sorting/grouping by pk (first in result tuple)
rows = sorted(rows, key=keyfunc)
data = list(user_obj_perms)
if use_groups:
groups_obj_perms = GroupObjectPermission.objects\
.filter(group__user=user)\
.filter(permission__content_type=ctype)\
.filter(permission__codename__in=codenames)\
.values_list('object_pk', 'permission__codename')
data += list(groups_obj_perms)
keyfunc = lambda t: t[0] # sorting/grouping by pk (first in result tuple)
data = sorted(data, key=keyfunc)
pk_list = []
for pk, group in groupby(rows, keyfunc):
for pk, group in groupby(data, keyfunc):
obj_codenames = set((e[1] for e in group))
if codenames.issubset(obj_codenames):
pk_list.append(pk)
Expand Down
53 changes: 53 additions & 0 deletions guardian/tests/shortcuts_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,21 @@ def setUp(self):
self.user = User.objects.create(username='joe')
self.group = Group.objects.create(name='group')

def test_superuser(self):
self.user.is_superuser = True
ctypes = ContentType.objects.all()
objects = get_objects_for_user(self.user,
['contenttypes.change_contenttype'], ctypes)
self.assertEqual(set(ctypes), set(objects))

def test_mixed_perms(self):
self.assertRaises(MixedContentTypeError, get_objects_for_user,
self.user, ['auth.change_user', 'auth.change_group'])

def test_perms_with_mixed_apps(self):
self.assertRaises(MixedContentTypeError, get_objects_for_user,
self.user, ['auth.change_user', 'contenttypes.change_contenttype'])

def test_mixed_perms_and_klass(self):
self.assertRaises(MixedContentTypeError, get_objects_for_user,
self.user, ['auth.change_group'], User)
Expand Down Expand Up @@ -415,3 +426,45 @@ def test_multiple_perms_to_check(self):
set(objects.values_list('name', flat=True)),
set([groups[1].name]))

def test_groups_perms(self):
group1 = Group.objects.create(name='group1')
group2 = Group.objects.create(name='group2')
group3 = Group.objects.create(name='group3')
groups = [group1, group2, group3]
for group in groups:
self.user.groups.add(group)

# Objects to operate on
ctypes = dict(((ct.id, ct) for ct in ContentType.objects.all()))

assign('change_contenttype', self.user, ctypes[1])
assign('change_contenttype', self.user, ctypes[2])
assign('delete_contenttype', self.user, ctypes[2])
assign('delete_contenttype', self.user, ctypes[3])

assign('change_contenttype', groups[0], ctypes[4])
assign('change_contenttype', groups[1], ctypes[4])
assign('change_contenttype', groups[2], ctypes[5])
assign('delete_contenttype', groups[0], ctypes[1])

objects = get_objects_for_user(self.user,
['contenttypes.change_contenttype'])
self.assertEqual(
set(objects.values_list('id', flat=True)),
set([1, 2, 4, 5]))

objects = get_objects_for_user(self.user,
['contenttypes.change_contenttype',
'contenttypes.delete_contenttype'])
self.assertEqual(
set(objects.values_list('id', flat=True)),
set([1, 2]))

objects = get_objects_for_user(self.user,
['contenttypes.change_contenttype'])
self.assertEqual(
set(objects.values_list('id', flat=True)),
set([1, 2, 4, 5]))



0 comments on commit d07d5d4

Please sign in to comment.