From b6120235936ab318febe9c60578e1b4b5865dece Mon Sep 17 00:00:00 2001 From: Shai Berger Date: Mon, 15 Sep 2014 23:30:17 +0300 Subject: [PATCH 1/3] Made 'issues' app not depend on 'shultze' app The shultze app has non-trivial dependencies, and the issues app may well be useful without shultze. So this commit makes it possible (at a settings level) to break the dependency. In fact, it makes "not dependent" the default. --- src/issues/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/issues/views.py b/src/issues/views.py index bec67955..b2da3959 100644 --- a/src/issues/views.py +++ b/src/issues/views.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.db.models.aggregates import Max from django.http.response import HttpResponse, HttpResponseBadRequest, \ HttpResponseForbidden @@ -19,7 +20,7 @@ from ocd.base_views import CommunityMixin, AjaxFormView, json_response from ocd.validation import enhance_html from ocd.base_managers import ConfidentialSearchQuerySet -from shultze_vote import send_issue_ranking +#from shultze_vote import send_issue_ranking from users.default_roles import DefaultGroups from users.models import Membership from users.permissions import has_community_perm @@ -30,6 +31,11 @@ import mimetypes from datetime import date +if getattr(settings, 'USE_SHULTZE', False): + from shultze_vote import send_issue_ranking +else: + from stubs.order_issues import send_issue_ranking + class IssueMixin(CommunityMixin): From 105afd35cbf260d0e98d14b77cd8b07c75335bcb Mon Sep 17 00:00:00 2001 From: Shai Berger Date: Mon, 15 Sep 2014 23:38:07 +0300 Subject: [PATCH 2/3] Removed redundant "community" argument to Community methods Used self instead. --- src/communities/models.py | 16 ++++++++-------- src/communities/notifications.py | 3 +-- src/communities/views.py | 13 +++++-------- src/issues/views.py | 3 +-- src/meetings/views.py | 3 +-- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/communities/models.py b/src/communities/models.py index b470904d..e8cbc283 100644 --- a/src/communities/models.py +++ b/src/communities/models.py @@ -130,23 +130,23 @@ def get_absolute_url(self): def get_upcoming_absolute_url(self): return "community", (str(self.pk),) - def upcoming_issues(self, user=None, community=None, upcoming=True): + def upcoming_issues(self, user=None, upcoming=True): l = issues_models.IssueStatus.IS_UPCOMING if upcoming else \ issues_models.IssueStatus.NOT_IS_UPCOMING if self.issues.all(): rv = self.issues.object_access_control( - user=user, community=community).filter( + user=user, community=self).filter( active=True, status__in=(l)).order_by( 'order_in_upcoming_meeting') else: rv = None return rv - def available_issues(self, user=None, community=None): + def available_issues(self, user=None): if self.issues.all(): rv = self.issues.object_access_control( - user=user, community=community).filter( + user=user, community=self).filter( active=True, status=issues_models.IssueStatus.OPEN).order_by( '-created_at') else: @@ -159,8 +159,8 @@ def available_issues_by_rank(self): ).order_by('order_by_votes') def issues_ready_to_close(self, user=None, community=None): - if self.upcoming_issues(user=user, community=community): - rv = self.upcoming_issues(user=user, community=community).filter( + if self.upcoming_issues(user=user): + rv = self.upcoming_issues(user=user).filter( proposals__active=True, proposals__decided_at_meeting=None, proposals__status__in=[ @@ -287,7 +287,7 @@ def sum_vote_results(self, only_when_over=True): def _get_upcoming_proposals(self, user=None, community=None): proposals = [] - upcoming = self.upcoming_issues(user=user, community=community) + upcoming = self.upcoming_issues(user=user) if upcoming: for issue in upcoming: if issue.proposals.all(): @@ -357,7 +357,7 @@ def close_meeting(self, m, user, community): self.voting_ends_at = None self.save() - for i, issue in enumerate(self.upcoming_issues(user=user, community=community)): + for i, issue in enumerate(self.upcoming_issues(user=user)): proposals = issue.proposals.filter( active=True, diff --git a/src/communities/notifications.py b/src/communities/notifications.py index 3d033153..11cf486f 100644 --- a/src/communities/notifications.py +++ b/src/communities/notifications.py @@ -198,8 +198,7 @@ def _base_send_mail(community, notification_type, sender, send_to, data=None, can_straw_vote = community.upcoming_proposals_any( {'is_open': True}, user=recipient, community=community)\ and community.upcoming_meeting_is_published - upcoming_issues = community.upcoming_issues(user=recipient, - community=community) + upcoming_issues = community.upcoming_issues(user=recipient) issues = [] for i in upcoming_issues: diff --git a/src/communities/views.py b/src/communities/views.py index 8ba28dd7..61e9a375 100644 --- a/src/communities/views.py +++ b/src/communities/views.py @@ -106,7 +106,7 @@ def post(self, request, *args, **kwargs): add_to_meeting = request.POST['set'] == "0" issue.status = IssueStatus.IN_UPCOMING_MEETING if add_to_meeting\ else IssueStatus.OPEN - last = self.get_object().upcoming_issues(user=self.request.user, community=self.community).aggregate( + last = self.get_object().upcoming_issues(user=self.request.user).aggregate( last=Max('order_in_upcoming_meeting'))['last'] issue.order_in_upcoming_meeting = (last or 0) + 1 issue.save() @@ -116,7 +116,7 @@ def post(self, request, *args, **kwargs): if 'issues[]' in request.POST: issues = [int(x) for x in request.POST.getlist('issues[]')] - qs = self.get_object().upcoming_issues(user=self.request.user, community=self.community) + qs = self.get_object().upcoming_issues(user=self.request.user) for i, iid in enumerate(issues): qs.filter(id=iid).update(order_in_upcoming_meeting=i) @@ -137,10 +137,8 @@ def get_context_data(self, **kwargs): sorted_issues['by_rank'].append(i.id) d['sorted'] = json.dumps(sorted_issues) - d['upcoming_issues'] = self.object.upcoming_issues( - user=self.request.user, community=self.community) - d['available_issues'] = self.object.available_issues( - user=self.request.user, community=self.community) + d['upcoming_issues'] = self.object.upcoming_issues(user=self.request.user) + d['available_issues'] = self.object.available_issues(user=self.request.user) d['has_straw_votes'] = self.object.has_straw_votes( user=self.request.user, community=self.community) return d @@ -155,8 +153,7 @@ def get_context_data(self, **kwargs): d['can_straw_vote'] = self.community.upcoming_proposals_any( {'is_open': True}, user=self.request.user, community=self.community)\ and self.community.upcoming_meeting_is_published - upcoming_issues = self.community.upcoming_issues( - user=self.request.user, community=self.community) + upcoming_issues = self.community.upcoming_issues(user=self.request.user) d['issue_container'] = [] for i in upcoming_issues: proposals = i.proposals.object_access_control( diff --git a/src/issues/views.py b/src/issues/views.py index b2da3959..aacf8336 100644 --- a/src/issues/views.py +++ b/src/issues/views.py @@ -157,8 +157,7 @@ def get_context_data(self, **kwargs): d['proposals'] = self.object.proposals.object_access_control( user=self.request.user, community=self.community).open() - d['upcoming_issues'] = self.object.community.upcoming_issues( - user=self.request.user, community=self.community) + d['upcoming_issues'] = self.object.community.upcoming_issues(user=self.request.user) d['agenda_items'] = self.object.agenda_items.all() for ai in d['agenda_items']: diff --git a/src/meetings/views.py b/src/meetings/views.py index c9f8051e..b28ed534 100644 --- a/src/meetings/views.py +++ b/src/meetings/views.py @@ -99,8 +99,7 @@ def get_context_data(self, **kwargs): def get_form_kwargs(self): kwargs = super(MeetingCreateView, self).get_form_kwargs() - kwargs['issues'] = self.community.upcoming_issues( - user=self.request.user, community=self.community) + kwargs['issues'] = self.community.upcoming_issues(user=self.request.user) return kwargs From 21d11dceec23e841db6b65ec72762a36f98ef105 Mon Sep 17 00:00:00 2001 From: Shai Berger Date: Mon, 15 Sep 2014 23:45:46 +0300 Subject: [PATCH 3/3] Hardened and simplified confidential-related querysets and managers. --- src/ocd/base_managers.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/ocd/base_managers.py b/src/ocd/base_managers.py index 117d250a..77683833 100644 --- a/src/ocd/base_managers.py +++ b/src/ocd/base_managers.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import AnonymousUser from django.db import models from django.db.models.query import QuerySet from haystack.query import SearchQuerySet @@ -15,6 +16,9 @@ class ActiveQuerySetMixin(object): def active(self): return self.get_query_set().filter(active=True) +GROUPS_ALLOWED_CONFIDENTIAL = frozenset([DefaultGroups.BOARD, + DefaultGroups.CHAIRMAN, + DefaultGroups.SECRETARY]) class ConfidentialQuerySetMixin(object): @@ -24,30 +28,31 @@ class ConfidentialQuerySetMixin(object): """ - def object_access_control(self, user=None, community=None): + def object_access_control(self, user, community): - if not user or not community: + if not user: + user = AnonymousUser() + if not community: raise ValueError('The object access control method requires ' - 'both a user and a community object.') - - if hasattr(user, '_is_mock') and user._is_mock is True: - return self.filter(is_confidential=False) + 'a community object.') - elif user.is_superuser: + if user.is_superuser: return self.all() - elif user.is_anonymous(): + try: + memberships = user.memberships + except AttributeError: + # No memberships -- not a proper user return self.filter(is_confidential=False) - else: - # we have a membership. return according to member's level. - # TODO: hook properly into permission system. - memberships = user.memberships.filter(community=community) - lookup = [m.default_group_name for m in memberships] - if DefaultGroups.MEMBER in lookup and len(lookup) == 1: - return self.filter(is_confidential=False) - else: + #TODO: integrate with permissions properly + groups = (memberships.filter(community=community) + .values_list('default_group_name', flat=True)) + + if GROUPS_ALLOWED_CONFIDENTIAL & frozenset(groups): return self.all() + else: + return self.filter(is_confidential=False) class ConfidentialQuerySet(QuerySet, ConfidentialQuerySetMixin):