diff --git a/bluebottle/initiatives/documents.py b/bluebottle/initiatives/documents.py index b5e1fdfe63..fe023e291f 100644 --- a/bluebottle/initiatives/documents.py +++ b/bluebottle/initiatives/documents.py @@ -80,6 +80,9 @@ class InitiativeDocument(Document): } ) + has_public_activities = fields.BooleanField() + has_closed_activities = fields.BooleanField() + activities = fields.NestedField(properties={ 'id': fields.LongField(), 'title': fields.KeywordField(), @@ -156,6 +159,7 @@ def prepare_segments(self, instance): { 'id': segment.id, 'name': segment.name, + 'closed': segment.closed, 'segment_type': segment.segment_type.slug } for segment in activity.segments.all() @@ -192,3 +196,15 @@ def prepare_country(self, instance): if instance.location and instance.location.country_id: countries += [instance.location.country_id] return countries + + def prepare_has_public_activities(self, instance): + hidden_statuses = ['draft', 'needs_work', 'submitted', 'rejected', 'deleted'] + return instance.activities.\ + exclude(segments__closed=True).\ + exclude(status__in=hidden_statuses).exists() + + def prepare_has_closed_activities(self, instance): + hidden_statuses = ['draft', 'needs_work', 'submitted', 'rejected', 'deleted'] + return instance.activities.\ + filter(segments__closed=True).\ + exclude(status__in=hidden_statuses).exists() diff --git a/bluebottle/initiatives/filters.py b/bluebottle/initiatives/filters.py index 15113032d8..b3baf5a39f 100644 --- a/bluebottle/initiatives/filters.py +++ b/bluebottle/initiatives/filters.py @@ -1,6 +1,6 @@ import re from elasticsearch_dsl import Q -from elasticsearch_dsl.query import Term, Nested +from elasticsearch_dsl.query import Term, Nested, Terms from bluebottle.utils.filters import ElasticSearchFilter from bluebottle.initiatives.documents import InitiativeDocument @@ -10,24 +10,26 @@ class InitiativeSearchFilter(ElasticSearchFilter): document = InitiativeDocument sort_fields = { - 'date': ('-created', ), - 'activity_date': ({ - 'activities.status_score': { - 'order': 'desc', - 'mode': 'max', - 'nested': { - 'path': 'activities' + 'date': ('-created',), + 'activity_date': ( + { + 'activities.status_score': { + 'order': 'desc', + 'mode': 'max', + 'nested': { + 'path': 'activities' + } + }, + 'activities.activity_date': { + 'order': 'desc', + 'mode': 'max', + 'nested': { + 'path': 'activities' + } } }, - 'activities.activity_date': { - 'order': 'desc', - 'mode': 'max', - 'nested': { - 'path': 'activities' - } - } - }, ), - 'alphabetical': ('title_keyword', ), + ), + 'alphabetical': ('title_keyword',), } default_sort_field = 'date' @@ -56,24 +58,57 @@ def get_default_filters(self, request): permission = 'initiatives.api_read_initiative' - if not request.user.has_perm(permission): - filters = [Term(owner_id=request.user.id)] - - if 'owner.id' not in fields: - filters.append(Term(status='approved')) - - return filters - elif 'owner.id' in fields and request.user.is_authenticated: - value = request.user.pk - return [ - Nested(path='owner', query=Term(owner__id=value)) | - Nested(path='promoter', query=Term(promoter__id=value)) | - Nested(path='activity_managers', query=Term(activity_managers__id=value)) | - Nested(path='activity_owners', query=Term(activity_owners__id=value)) | - Term(status='approved') - ] + user_id = None + if request.user.is_authenticated: + user_id = request.user.pk + + public_filter = Term(status='approved') & ( + Nested(path='owner', query=Term(owner__id=user_id)) | + Nested(path='promoter', query=Term(promoter__id=user_id)) | + Nested(path='activity_managers', query=Term(activity_managers__id=user_id)) | + Nested(path='activity_owners', query=Term(activity_owners__id=user_id)) | + ( + Term(has_public_activities=True) | + Term(has_closed_activities=False) | + Nested( + path='segments', + query=( + Terms( + segments__id=[ + segment.id for segment in request.user.segments.filter(closed=True) + ] if request.user.is_authenticated else [] + ) + ) + ) + ) + ) + + owned_filter = ~Term(status='deleted') & ( + Nested(path='owner', query=Term(owner__id=user_id)) | + Nested(path='promoter', query=Term(promoter__id=user_id)) | + Nested(path='activity_managers', query=Term(activity_managers__id=user_id)) | + Nested(path='activity_owners', query=Term(activity_owners__id=user_id)) + ) + + if not request.user.has_perm(permission) and user_id: + # Not allowed to read initiatives through API unless owned. + filters = [owned_filter] + elif 'owner.id' in fields and user_id: + # Filter on user id. + # If owned then show also initiatives that are not approved. + filters = [owned_filter | public_filter] + elif user_id: + # Approved & owner or no closed segments or user has that segment + filters = [public_filter] else: - return [Term(status='approved')] + # Guest user. Just approved projects without closed activities + filters = [ + Term(status='approved') & ( + Term(has_public_activities=True) | + Term(has_closed_activities=False) + ) + ] + return filters def get_filters(self, request): filters = super(InitiativeSearchFilter, self).get_filters(request) diff --git a/bluebottle/initiatives/tests/test_api.py b/bluebottle/initiatives/tests/test_api.py index 63eb74e9d2..1195f1ac04 100644 --- a/bluebottle/initiatives/tests/test_api.py +++ b/bluebottle/initiatives/tests/test_api.py @@ -42,6 +42,7 @@ class InitiativeAPITestCase(TestCase): """ def setUp(self): + super().setUp() self.client = JSONAPITestClient() self.owner = BlueBottleUserFactory.create() self.visitor = BlueBottleUserFactory.create() @@ -809,7 +810,7 @@ def test_only_owner_permission_owner(self): ) response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', HTTP_AUTHORIZATION="JWT {0}".format(self.owner.get_jwt_token()) ) data = json.loads(response.content) @@ -859,6 +860,116 @@ def test_filter_segment(self): self.assertEqual(data['meta']['pagination']['count'], 1) self.assertEqual(data['data'][0]['id'], str(first.pk)) + def _create_closed_segments_initiatives(self): + self.closed_segment = SegmentFactory.create( + closed=True + ) + self.open_segment = SegmentFactory.create( + closed=False + ) + self.another_closed_segment = SegmentFactory.create( + closed=True + ) + + self.first = InitiativeFactory.create( + status='approved', + owner=self.owner + ) + activity = DateActivityFactory.create( + status='open', + title='hup', + initiative=self.first, + ) + activity.segments.add(self.closed_segment) + activity.segments.add(self.another_closed_segment) + activity.segments.add(self.open_segment) + + activity = DateActivityFactory.create( + status='open', + initiative=self.first, + ) + activity.segments.add(self.closed_segment) + + self.second = InitiativeFactory.create( + owner=self.owner, + status='approved' + ) + activity = DateActivityFactory.create( + status='open', + initiative=self.second, + ) + activity.segments.add(self.closed_segment) + activity.segments.add(self.open_segment) + + activity = DateActivityFactory.create( + status='open', + initiative=self.second, + ) + activity.segments.add(self.open_segment) + + self.third = InitiativeFactory.create( + owner=self.owner, + status='draft' + ) + activity = DateActivityFactory.create( + status='draft', + initiative=self.third, + ) + activity.segments.add(self.closed_segment) + activity.segments.add(self.open_segment) + + self.fourth = InitiativeFactory.create( + owner=self.owner, + status='approved' + ) + + def test_filter_closed_segment_owner(self): + self._create_closed_segments_initiatives() + response = self.client.get( + self.url, + user=self.owner + ) + data = json.loads(response.content) + self.assertEqual(data['meta']['pagination']['count'], 3) + response = self.client.get( + self.url + f'?filter[owner.id]={self.owner.id}', + user=self.owner + ) + data = json.loads(response.content) + self.assertEqual(data['meta']['pagination']['count'], 4) + + def test_filter_closed_segment_user_without_segment(self): + self._create_closed_segments_initiatives() + user = BlueBottleUserFactory.create() + user.segments.add(self.open_segment) + + response = self.client.get( + self.url, + user=user + ) + data = json.loads(response.content) + self.assertEqual(data['meta']['pagination']['count'], 2) + + def test_filter_closed_segment_user_with_segment(self): + self._create_closed_segments_initiatives() + user = BlueBottleUserFactory.create() + user.segments.add(self.closed_segment) + + response = self.client.get( + self.url, + user=user + ) + data = json.loads(response.content) + self.assertEqual(data['meta']['pagination']['count'], 3) + + def test_filter_closed_segment_guest(self): + self._create_closed_segments_initiatives() + response = self.client.get( + self.url + ) + data = json.loads(response.content) + self.assertEqual(data['meta']['pagination']['count'], 2) + def test_filter_owner(self): owned_initiatives = InitiativeFactory.create_batch( 2, status='submitted', owner=self.owner @@ -870,7 +981,7 @@ def test_filter_owner(self): InitiativeFactory.create_batch(4, status='submitted') response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', HTTP_AUTHORIZATION="JWT {0}".format(self.owner.get_jwt_token()) ) @@ -891,7 +1002,7 @@ def test_filter_owner_activity(self): activity = DateActivityFactory.create(owner=self.owner, initiative=with_activity) response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', HTTP_AUTHORIZATION="JWT {0}".format(self.owner.get_jwt_token()) ) @@ -958,7 +1069,7 @@ def test_filter_not_owner(self): InitiativeFactory.create_batch(3, status='approved') response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', user=self.visitor ) @@ -975,7 +1086,7 @@ def test_filter_activity_manager(self): InitiativeFactory.create_batch(4, status='approved') response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', HTTP_AUTHORIZATION="JWT {0}".format(self.owner.get_jwt_token()) ) @@ -992,7 +1103,7 @@ def test_filter_promoter(self): InitiativeFactory.create_batch(4, status='approved') response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', HTTP_AUTHORIZATION="JWT {0}".format(self.owner.get_jwt_token()) ) @@ -1009,7 +1120,7 @@ def test_filter_owner_and_activity_manager(self): InitiativeFactory.create_batch(4, status='approved') response = self.client.get( - self.url + '?filter[owner.id]={}'.format(self.owner.pk), + self.url + f'?filter[owner.id]={self.owner.pk}', HTTP_AUTHORIZATION="JWT {0}".format(self.owner.get_jwt_token()) )