Implement permission checking in views and forms

This commit is contained in:
Mark Veidemanis 2022-11-28 18:09:41 +00:00
parent bb7d6d1b41
commit 7a64759ceb
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
2 changed files with 97 additions and 11 deletions

View File

@ -1,10 +1,35 @@
from django import forms from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import FieldDoesNotExist
from django.forms import ModelForm from django.forms import ModelForm
from .models import Account, Hook, Strategy, Trade, TradingTime, User from .models import Account, Hook, Strategy, Trade, TradingTime, User
# Create your forms here.
class RestrictedFormMixin:
"""
This mixin is used to restrict the queryset of a form to the current user.
The request object is passed from the view."""
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super().__init__(*args, **kwargs)
print(self.fields)
for field in self.fields:
# Check it's not something like a CharField which has no queryset
if not hasattr(self.fields[field], "queryset"):
continue
model = self.fields[field].queryset.model
# Check if the model has a user field
try:
model._meta.get_field("user")
# Add the user to the queryset filters
self.fields[field].queryset = model.objects.filter(
user=self.request.user
)
except FieldDoesNotExist:
pass
class NewUserForm(UserCreationForm): class NewUserForm(UserCreationForm):
@ -35,7 +60,8 @@ class CustomUserCreationForm(UserCreationForm):
fields = "__all__" fields = "__all__"
class HookForm(ModelForm): # All string/multiple choice fields
class HookForm(RestrictedFormMixin, ModelForm):
class Meta: class Meta:
model = Hook model = Hook
fields = ( fields = (
@ -45,7 +71,8 @@ class HookForm(ModelForm):
) )
class AccountForm(ModelForm): # All string/multiple choice fields
class AccountForm(RestrictedFormMixin, ModelForm):
class Meta: class Meta:
model = Account model = Account
fields = ( fields = (
@ -57,7 +84,8 @@ class AccountForm(ModelForm):
) )
class StrategyForm(ModelForm): # Restricted mixin for account and hooks
class StrategyForm(RestrictedFormMixin, ModelForm):
class Meta: class Meta:
model = Strategy model = Strategy
fields = ( fields = (
@ -85,7 +113,8 @@ class StrategyForm(ModelForm):
) )
class TradeForm(ModelForm): # Restricted mixin for account
class TradeForm(RestrictedFormMixin, ModelForm):
class Meta: class Meta:
model = Trade model = Trade
fields = ( fields = (
@ -102,7 +131,7 @@ class TradeForm(ModelForm):
) )
class TradingTimeForm(ModelForm): class TradingTimeForm(RestrictedFormMixin, ModelForm):
class Meta: class Meta:
model = TradingTime model = TradingTime
fields = ( fields = (

View File

@ -1,5 +1,8 @@
import uuid import uuid
from django.core.exceptions import ImproperlyConfigured
from django.core.paginator import Paginator
from django.db.models import QuerySet
from django.http import Http404, HttpResponseBadRequest from django.http import Http404, HttpResponseBadRequest
from django.urls import reverse from django.urls import reverse
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
@ -12,6 +15,59 @@ from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
class RestrictedViewMixin:
"""
This mixin overrides two helpers in order to pass the user object to the filters.
get_queryset alters the objects returned for list views.
get_form_kwargs passes the request object to the form class. Remaining permissions
checks are in forms.py
"""
allow_empty = True
queryset = None
model = None
paginate_by = None
paginate_orphans = 0
context_object_name = None
paginator_class = Paginator
page_kwarg = "page"
ordering = None
def get_queryset(self):
"""
This function is overriden to filter the objects by the requesting user.
"""
if self.queryset is not None:
queryset = self.queryset
if isinstance(queryset, QuerySet):
# queryset = queryset.all()
queryset = queryset.filter(user=self.request.user)
elif self.model is not None:
queryset = self.model._default_manager.filter(user=self.request.user)
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
"%(cls)s.model, %(cls)s.queryset, or override "
"%(cls)s.get_queryset()." % {"cls": self.__class__.__name__}
)
if hasattr(self, "get_ordering"):
ordering = self.get_ordering()
if ordering:
if isinstance(ordering, str):
ordering = (ordering,)
queryset = queryset.order_by(*ordering)
return queryset
def get_form_kwargs(self):
"""Passes the request object to the form class.
This is necessary to only display members that belong to a given user"""
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs
class ObjectNameMixin(object): class ObjectNameMixin(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.title_singular = self.model._meta.verbose_name.title() # Hook self.title_singular = self.model._meta.verbose_name.title() # Hook
@ -26,7 +82,7 @@ class ObjectNameMixin(object):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class ObjectList(ObjectNameMixin, ListView): class ObjectList(RestrictedViewMixin, ObjectNameMixin, ListView):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/objects.html" window_content = "window-content/objects.html"
list_template = None list_template = None
@ -44,6 +100,7 @@ class ObjectList(ObjectNameMixin, ListView):
# copied from BaseListView # copied from BaseListView
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
self.request = request
self.object_list = self.get_queryset() self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty() allow_empty = self.get_allow_empty()
@ -103,7 +160,7 @@ class ObjectList(ObjectNameMixin, ListView):
return self.render_to_response(context) return self.render_to_response(context)
class ObjectCreate(ObjectNameMixin, CreateView): class ObjectCreate(RestrictedViewMixin, ObjectNameMixin, CreateView):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/object-form.html" window_content = "window-content/object-form.html"
parser_classes = [FormParser] parser_classes = [FormParser]
@ -178,14 +235,14 @@ class ObjectCreate(ObjectNameMixin, CreateView):
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
class ObjectRead(ObjectNameMixin, DetailView): class ObjectRead(RestrictedViewMixin, ObjectNameMixin, DetailView):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/object.html" window_content = "window-content/object.html"
model = None model = None
class ObjectUpdate(ObjectNameMixin, UpdateView): class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView):
allowed_types = ["modal", "widget", "window", "page"] allowed_types = ["modal", "widget", "window", "page"]
window_content = "window-content/object-form.html" window_content = "window-content/object-form.html"
parser_classes = [FormParser] parser_classes = [FormParser]
@ -249,7 +306,7 @@ class ObjectUpdate(ObjectNameMixin, UpdateView):
return super().post(request, *args, **kwargs) return super().post(request, *args, **kwargs)
class ObjectDelete(ObjectNameMixin, DeleteView): class ObjectDelete(RestrictedViewMixin, ObjectNameMixin, DeleteView):
model = None model = None
template_name = "partials/notify.html" template_name = "partials/notify.html"