Merge lp:~salgado/django-group-access/delegate-to-parent-relation into lp:django-group-access

Proposed by Guilherme Salgado
Status: Merged
Merge reported by: Nicola Heald
Merged at revision: not available
Proposed branch: lp:~salgado/django-group-access/delegate-to-parent-relation
Merge into: lp:django-group-access
Diff against target: 179 lines (+114/-5)
3 files modified
models.py (+8/-4)
sandbox/models.py (+35/-1)
tests.py (+71/-0)
To merge this branch: bzr merge lp:~salgado/django-group-access/delegate-to-parent-relation
Reviewer Review Type Date Requested Status
Nicola Heald Approve
Review via email: mp+76042@code.launchpad.net

Description of the change

This makes it possible to use access_relation (former access_child_relation) to do the access-group checking on a parent as well as a child relation.

I've also removed a .filter() call which seemed redundant in accessible_by_user().

To post a comment you must log in.
5. By Guilherme Salgado

Revert my removal of the (seemingly redundant) .filter() call and add a comment explaining why it's there

Revision history for this message
Nicola Heald (notnownikki) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'models.py'
2--- models.py 2011-09-13 13:50:21 +0000
3+++ models.py 2011-09-19 15:07:26 +0000
4@@ -11,18 +11,22 @@
5 def accessible_by_user(self, user):
6 if AccessGroup.objects.filter(members=user, supergroup=True).count():
7 return self.all()
8- if hasattr(self.model, 'access_child_relation'):
9- acr = getattr(self.model, 'access_child_relation')
10+ if hasattr(self.model, 'access_relation'):
11+ acr = getattr(self.model, 'access_relation')
12 k = '%s__access_groups__in' % acr
13 access_groups_dict = {k: AccessGroup.objects.filter(members=user)}
14 k = '%s__isnull' % acr
15- no_child_records = {k: True}
16+ no_related_records = {k: True}
17 k = '%s__owner' % acr
18 direct_owner_dict = {k: user}
19 available = self.filter(
20 models.Q(**access_groups_dict) |
21 models.Q(**direct_owner_dict) |
22- models.Q(**no_child_records)).distinct()
23+ models.Q(**no_related_records)).distinct()
24+ # Although this extra .filter() call seems redundant it turns out
25+ # to be a huge performance optimization. Without it the ORM will
26+ # join on the related tables and .distinct() them, which killed
27+ # performance in HEXR leading to 30+ seconds to load a page.
28 return self.filter(pk__in=available)
29 else:
30 available = self.filter(
31
32=== modified file 'sandbox/models.py'
33--- sandbox/models.py 2011-09-16 18:17:01 +0000
34+++ sandbox/models.py 2011-09-19 15:07:26 +0000
35@@ -9,7 +9,7 @@
36
37 class AccessRestrictedParent(models.Model):
38 name = models.CharField(max_length = 64)
39- access_child_relation = 'accessrestrictedmodel'
40+ access_relation = 'accessrestrictedmodel'
41
42 objects = AccessManager()
43
44@@ -33,4 +33,38 @@
45 def __unicode__(self):
46 return self.name
47
48+
49+class Project(AccessGroupMixin):
50+ owner = models.ForeignKey(User, null=True)
51+ name = models.CharField(max_length = 64)
52+ objects = AccessManager()
53+
54+ def __unicode__(self):
55+ return self.name
56+
57+
58+class Build(AccessGroupMixin):
59+ owner = models.ForeignKey(User, null=True)
60+ name = models.CharField(max_length = 64)
61+ project = models.ForeignKey(Project)
62+ access_relation = 'project'
63+ objects = AccessManager()
64+
65+ def __unicode__(self):
66+ return self.name
67+
68+
69+class Release(models.Model):
70+ owner = models.ForeignKey(User, null=True)
71+ name = models.CharField(max_length = 64)
72+ build = models.ForeignKey(Build)
73+ # We don't have a direct reference to the parent which is
74+ # accessgroup-controlled, so we have to go via Build.
75+ access_relation = 'build__project'
76+ objects = AccessManager()
77+
78+ def __unicode__(self):
79+ return self.name
80+
81+
82 post_save.connect(process_auto_share_groups, AccessRestrictedModel)
83
84=== modified file 'tests.py'
85--- tests.py 2011-09-16 18:17:01 +0000
86+++ tests.py 2011-09-19 15:07:26 +0000
87@@ -1,3 +1,5 @@
88+import itertools
89+
90 from django.test import TestCase
91 from django.conf import settings
92 from django.core.management import call_command
93@@ -6,6 +8,9 @@
94 from django_group_access.sandbox.models import (
95 AccessRestrictedModel,
96 AccessRestrictedParent,
97+ Build,
98+ Project,
99+ Release,
100 )
101 from django_group_access.models import AccessGroup, Invitation
102
103@@ -31,6 +36,64 @@
104 loading.cache.loaded = False
105
106
107+class AccessRelationTests(SyncingTestCase):
108+
109+ def setUp(self):
110+ super(AccessRelationTests, self).setUp()
111+ self.owner = _create_user()
112+ self.project = Project(owner=self.owner, name='project')
113+ self.project.save()
114+ self.build = Build(
115+ owner=self.owner, name='build', project=self.project)
116+ self.build.save()
117+ self.release = Release(
118+ owner=self.owner, name='release', build=self.build)
119+ self.release.save()
120+ group = self._create_access_group_with_one_member()
121+ self.project.access_groups.add(group)
122+ self.project.save()
123+ self.user_with_access = group.members.all()[0]
124+ self.user_without_access = _create_user()
125+
126+ def _create_access_group_with_one_member(self):
127+ g = AccessGroup(name='oem')
128+ g.save()
129+ g.members.add(_create_user())
130+ g.save()
131+ return g
132+
133+ def test_direct_reference(self):
134+ # Build has a foreign key to Project so its access_relation just need
135+ # to point to project and we'll take advantage of Django's ORM to do
136+ # the access group checks on Project.
137+ query_method = Build.objects.accessible_by_user
138+
139+ self.assertEqual('project', Build.access_relation)
140+ self.assertEqual(
141+ [self.build.name], [b.name for b in query_method(self.owner)])
142+ self.assertEqual(
143+ [self.build.name],
144+ [b.name for b in query_method(self.user_with_access)])
145+ self.assertEqual(
146+ [], [b for b in query_method(self.user_without_access)])
147+
148+ def test_indirect_reference(self):
149+ # Release has no foreign key to Project, but it has one to Build
150+ # and it can use that to tell us to do the access group checks on
151+ # Project.
152+ query_method = Release.objects.accessible_by_user
153+
154+ self.assertEqual('build__project', Release.access_relation)
155+ self.assertEqual(
156+ [self.release.name], [r.name for r in query_method(self.owner)])
157+ self.assertEqual(
158+ [self.release.name],
159+ [r.name for r in query_method(self.user_with_access)])
160+ self.assertEqual(
161+ [], [r for r in query_method(self.user_without_access)])
162+
163+
164+
165 class InvitationTest(TestCase):
166
167 def setUp(self):
168@@ -238,3 +301,11 @@
169 self.assertEqual(available[6].name, 'the cabal record 2')
170 self.assertEqual(available[7].name, 'the cabal record extra')
171 self.assertEqual(available[8].name, 'the stonecutters record 1')
172+
173+
174+counter = itertools.count()
175+def _create_user():
176+ random_string = 'asdfg%d' % counter.next()
177+ user = User.objects.create_user(
178+ random_string, '%s@example.com' % random_string)
179+ return user

Subscribers

People subscribed via source and target branches