From 9cc3de6d0245bdc4ed87b810a2208efdd09b6277 Mon Sep 17 00:00:00 2001 From: Matthew Lemon Date: Mon, 19 Oct 2020 16:21:03 +0100 Subject: can now filter out private events not belonging to logged in user --- ctrack/organisations/tests/test_views.py | 137 ++++++++++++++++++++++++++++++- ctrack/organisations/utils.py | 13 +++ ctrack/organisations/views.py | 14 +++- 3 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 ctrack/organisations/utils.py (limited to 'ctrack') diff --git a/ctrack/organisations/tests/test_views.py b/ctrack/organisations/tests/test_views.py index dbd717f..a567fab 100644 --- a/ctrack/organisations/tests/test_views.py +++ b/ctrack/organisations/tests/test_views.py @@ -9,8 +9,8 @@ from ctrack.organisations.tests.factories import ( OrganisationFactory, SingleDateTimeEventFactory, ) -from ctrack.organisations.views import IncidentReportCreateView - +from ctrack.organisations.views import IncidentReportCreateView, OrganisationDetailView +from ..utils import filter_private_events from ..views import OrganisationListView pytestmark = pytest.mark.django_db @@ -42,6 +42,139 @@ def test_meetings_in_organisation_detail_view(user, client, org_with_people): assert "First Meeting" in html +def test_private_event_filter(user, org_with_people): + """ + In this test we are creating five events, using two different users. + Each event will be set to either private or not private. We are testing + a function that will only allow private notes belonging to the logged in, + or request.user user to be added to the view context. The context is not + referred to here - only the utility function under test. The output from + that filter function will go forward into the view context. + """ + person = org_with_people.person_set.first() + e1_user = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="First Event with user", + private=True, + user=user, + ) + e2_user = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="Second Event with user", + private=False, + user=user, + ) + e3_user = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="Third Event with user", + private=True, + user=user, + ) + e1_user.participants.add(person) + e1_user.save() + e2_user.participants.add(person) + e2_user.save() + e3_user.participants.add(person) + e3_user.save() + user2 = get_user_model().objects.create(username="sam", email="asd@asdsd.com", password="123") + e1_user2 = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="First Event with user2", + private=False, + user=user2, + ) + e2_user2 = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="Second Event with user2", + private=True, + user=user2, + ) + e1_user2.participants.add(person) + e1_user2.save() + e2_user2.participants.add(person) + e2_user2.save() + # This user needs permission to access the list view + org_list_permission = Permission.objects.get(name="Can view organisation") + assert user.user_permissions.count() == 0 + user.user_permissions.add(org_list_permission) + assert user.has_perm("organisations.view_organisation") + user.save() + factory = RequestFactory() + request = factory.get(reverse("organisations:detail", args=[org_with_people.slug])) + request.user = user + response = OrganisationDetailView.as_view()(request, slug=org_with_people.slug) + assert response.status_code == 200 + events = person.get_single_datetime_events() + assert events.count() == 5 + assert len(filter_private_events(events, user2)) == 3 + + +def test_logged_in_user_can_only_see_their_private_events( + user, org_with_people, client +): + org_list_permission = Permission.objects.get(name="Can view organisation") + assert user.user_permissions.count() == 0 + user.user_permissions.add(org_list_permission) + assert user.has_perm("organisations.view_organisation") + user.save() + person = org_with_people.person_set.first() + + # This user creates three events + e1 = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="First Event", + private=True, + user=user, + ) + e2 = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="Second Event", + private=False, + user=user, + ) + e3 = SingleDateTimeEventFactory( + type_descriptor="MEETING", + short_description="Third Event", + private=True, + user=user, + ) + e1.participants.add(person) + e1.save() + e2.participants.add(person) + e2.save() + e3.participants.add(person) + e3.save() + response = client.get( + reverse("organisations:detail", kwargs={"slug": org_with_people.slug}) + ) + assert response.status_code == 200 + html = response.content.decode("utf-8") + assert "First Event" in html + assert "Second Event" in html + assert "Third Event" in html + assert "PRIVATE" in html + + # A second user adds events based on this person/organisation + user2 = get_user_model().objects.create( + username="bobbins", email="bobbins@gog.com", password="bobbins123345" + ) + user2.user_permissions.add(org_list_permission) + assert user2.has_perm("organisations.view_organisation") + user2.save() + client.logout() + client.force_login(user2) + response2 = client.get( + reverse("organisations:detail", kwargs={"slug": org_with_people.slug}) + ) + html2 = response2.content.decode("utf-8") + assert response2.status_code == 200 + # They should not be able to see First Event which was created by another + # user and marked private. + assert "First Event" not in html2 + assert "Second Event" in html2 + assert "Third Event" not in html2 + + # https://docs.djangoproject.com/en/3.0/topics/testing/advanced/#example def test_organisation_list_view(): OrganisationFactory.create() diff --git a/ctrack/organisations/utils.py b/ctrack/organisations/utils.py new file mode 100644 index 0000000..121a694 --- /dev/null +++ b/ctrack/organisations/utils.py @@ -0,0 +1,13 @@ +from django.contrib.auth.models import User +from django.db.models import QuerySet, Q + + +def filter_private_events(events: QuerySet, user: User): + """ + Given a QuerySet containing SingleDateTimeEvent objects, + ensure that any objects whose user==user and private==True + are filtered out. This supports OrganisationDetailView, which + lists all events for an organisation but which must hide private + events for the logged-in user. + """ + return events.exclude(~Q(user=user) & Q(private=True)) diff --git a/ctrack/organisations/views.py b/ctrack/organisations/views.py index e8affdb..81af8d4 100644 --- a/ctrack/organisations/views.py +++ b/ctrack/organisations/views.py @@ -16,6 +16,9 @@ from .models import IncidentReport, Organisation, Person # TODO - needs a permission on this view +from .utils import filter_private_events + + def essential_service_detail(request, pk): es = EssentialService.objects.get(pk=pk) org = es.organisation @@ -87,12 +90,19 @@ class OrganisationDetailView(PermissionRequiredMixin, DetailView): cafs = org.caf_set.all() # simple datetime events for org - _sdes = [person.get_single_datetime_events() for person in peoples] + _sdes = [ + filter_private_events( + person.get_single_datetime_events(), self.request.user + ) + for person in peoples + ] flat_sdes = list(itertools.chain.from_iterable(_sdes)) # Some events will not involve a participant, which is what ties an event to an organisation. # Because we want to list events to an organisation here we must related it via the CAF object too... - engagement_events = EngagementEvent.objects.filter(Q(participants__in=peoples) | Q(related_caf__in=cafs)).order_by("-date") + engagement_events = EngagementEvent.objects.filter( + Q(participants__in=peoples) | Q(related_caf__in=cafs) + ).order_by("-date") essential_services = EssentialService.objects.filter(organisation=org) no_addr = org.addresses.count() -- cgit v1.2.3