feat: Add access controls to Person views

See #6
This commit is contained in:
James Graham
2020-02-27 08:16:01 +00:00
parent e47ee453bd
commit 6d12202c8a
4 changed files with 66 additions and 14 deletions

View File

@@ -8,6 +8,7 @@ from django.views.generic import DetailView, ListView, View
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from people import models as people_models from people import models as people_models
from people import permissions
from . import models from . import models
@@ -54,20 +55,23 @@ class ActivityDetailView(DetailView):
return context return context
class ActivityAttendanceView(SingleObjectMixin, View): class ActivityAttendanceView(permissions.UserIsLinkedPersonMixin, SingleObjectMixin, View):
""" """
View to add or delete attendance of an activity. View to add or delete attendance of an activity.
""" """
model = models.Activity model = models.Activity
def get_test_person(self) -> people_models.Person:
data = json.loads(self.request.body)
self.person = people_models.Person.objects.get(pk=data['pk'])
return self.person
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if request.is_ajax(): if request.is_ajax():
data = json.loads(request.body) self.object.attendance_list.add(self.person)
person = people_models.Person.objects.get(pk=data['pk'])
self.object.attendance_list.add(person)
return HttpResponse(status=204) return HttpResponse(status=204)
@@ -77,10 +81,7 @@ class ActivityAttendanceView(SingleObjectMixin, View):
self.object = self.get_object() self.object = self.get_object()
if request.is_ajax(): if request.is_ajax():
data = json.loads(request.body) self.object.attendance_list.remove(self.person)
person = people_models.Person.objects.get(pk=data['pk'])
self.object.attendance_list.remove(person)
return HttpResponse(status=204) return HttpResponse(status=204)

View File

@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h2>Error 403 at {{ request.path }}</h2>
<p>{{ exception }}</p>
{% endblock content %}

33
people/permissions.py Normal file
View File

@@ -0,0 +1,33 @@
"""
Permission mixins for views relating to :class:`Person`s.
"""
from django.contrib.auth.mixins import UserPassesTestMixin
from . import models
class UserIsLinkedPersonMixin(UserPassesTestMixin):
"""
Grant access if the user is staff or has a :class:`Person` record and
this is the one referred to in the view.
"""
related_person_field = None
permission_denied_message = 'You do not have permission to view this page.'
def get_test_person(self) -> models.Person:
"""
Get the :class:`Person` to test the user against.
"""
if self.related_person_field is None:
test_person = self.get_object()
if not isinstance(test_person, models.Person):
raise AttributeError('View incorrectly configured: \'related_person_field\' must be defined.')
return test_person
return getattr(self.get_object(), self.related_person_field)
def test_func(self) -> bool:
return self.request.user.is_staff or self.get_test_person() == self.request.user.person

View File

@@ -5,7 +5,7 @@ Views for displaying or manipulating models in the 'people' app.
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import CreateView, DetailView, ListView, UpdateView from django.views.generic import CreateView, DetailView, ListView, UpdateView
from . import forms, models from . import forms, models, permissions
class PersonCreateView(CreateView): class PersonCreateView(CreateView):
@@ -33,7 +33,7 @@ class PersonListView(ListView):
template_name = 'people/person/list.html' template_name = 'people/person/list.html'
class ProfileView(DetailView): class ProfileView(permissions.UserIsLinkedPersonMixin, DetailView):
""" """
View displaying the profile of a :class:`Person` - who may be a user. View displaying the profile of a :class:`Person` - who may be a user.
""" """
@@ -50,10 +50,11 @@ class ProfileView(DetailView):
return super().get_object(queryset) return super().get_object(queryset)
except AttributeError: except AttributeError:
# pk was not provided in URL
return self.request.user.person return self.request.user.person
class PersonUpdateView(UpdateView): class PersonUpdateView(permissions.UserIsLinkedPersonMixin, UpdateView):
""" """
View for updating a :class:`Person` record. View for updating a :class:`Person` record.
""" """
@@ -62,15 +63,16 @@ class PersonUpdateView(UpdateView):
form_class = forms.PersonForm form_class = forms.PersonForm
class RelationshipDetailView(DetailView): class RelationshipDetailView(permissions.UserIsLinkedPersonMixin, DetailView):
""" """
View displaying details of a :class:`Relationship`. View displaying details of a :class:`Relationship`.
""" """
model = models.Relationship model = models.Relationship
template_name = 'people/relationship/detail.html' template_name = 'people/relationship/detail.html'
related_person_field = 'source'
class RelationshipCreateView(CreateView): class RelationshipCreateView(permissions.UserIsLinkedPersonMixin, CreateView):
""" """
View for creating a :class:`Relationship`. View for creating a :class:`Relationship`.
@@ -80,6 +82,15 @@ class RelationshipCreateView(CreateView):
template_name = 'people/relationship/create.html' template_name = 'people/relationship/create.html'
form_class = forms.RelationshipForm form_class = forms.RelationshipForm
def get_test_person(self) -> models.Person:
"""
"""
if self.request.method == 'POST':
return models.Person.objects.get(pk=self.request.POST.get('source'))
return models.Person.objects.get(pk=self.kwargs.get('person_pk'))
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.person = models.Person.objects.get(pk=self.kwargs.get('person_pk')) self.person = models.Person.objects.get(pk=self.kwargs.get('person_pk'))