diff --git a/core/forms.py b/core/forms.py index 242bc7b..df47217 100644 --- a/core/forms.py +++ b/core/forms.py @@ -1,10 +1,35 @@ from django import forms from django.contrib.auth.forms import UserCreationForm +from django.core.exceptions import FieldDoesNotExist from django.forms import ModelForm 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): @@ -35,7 +60,8 @@ class CustomUserCreationForm(UserCreationForm): fields = "__all__" -class HookForm(ModelForm): +# All string/multiple choice fields +class HookForm(RestrictedFormMixin, ModelForm): class Meta: model = Hook fields = ( @@ -45,7 +71,8 @@ class HookForm(ModelForm): ) -class AccountForm(ModelForm): +# All string/multiple choice fields +class AccountForm(RestrictedFormMixin, ModelForm): class Meta: model = Account fields = ( @@ -57,7 +84,8 @@ class AccountForm(ModelForm): ) -class StrategyForm(ModelForm): +# Restricted mixin for account and hooks +class StrategyForm(RestrictedFormMixin, ModelForm): class Meta: model = Strategy fields = ( @@ -85,7 +113,8 @@ class StrategyForm(ModelForm): ) -class TradeForm(ModelForm): +# Restricted mixin for account +class TradeForm(RestrictedFormMixin, ModelForm): class Meta: model = Trade fields = ( @@ -102,7 +131,7 @@ class TradeForm(ModelForm): ) -class TradingTimeForm(ModelForm): +class TradingTimeForm(RestrictedFormMixin, ModelForm): class Meta: model = TradingTime fields = ( diff --git a/core/views/__init__.py b/core/views/__init__.py index 4d3cc9c..403d985 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -1,5 +1,8 @@ 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.urls import reverse from django.views.generic.detail import DetailView @@ -12,6 +15,59 @@ from core.util import logs 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): def __init__(self, *args, **kwargs): self.title_singular = self.model._meta.verbose_name.title() # Hook @@ -26,7 +82,7 @@ class ObjectNameMixin(object): super().__init__(*args, **kwargs) -class ObjectList(ObjectNameMixin, ListView): +class ObjectList(RestrictedViewMixin, ObjectNameMixin, ListView): allowed_types = ["modal", "widget", "window", "page"] window_content = "window-content/objects.html" list_template = None @@ -44,6 +100,7 @@ class ObjectList(ObjectNameMixin, ListView): # copied from BaseListView def get(self, request, *args, **kwargs): + self.request = request self.object_list = self.get_queryset() allow_empty = self.get_allow_empty() @@ -103,7 +160,7 @@ class ObjectList(ObjectNameMixin, ListView): return self.render_to_response(context) -class ObjectCreate(ObjectNameMixin, CreateView): +class ObjectCreate(RestrictedViewMixin, ObjectNameMixin, CreateView): allowed_types = ["modal", "widget", "window", "page"] window_content = "window-content/object-form.html" parser_classes = [FormParser] @@ -178,14 +235,14 @@ class ObjectCreate(ObjectNameMixin, CreateView): return super().post(request, *args, **kwargs) -class ObjectRead(ObjectNameMixin, DetailView): +class ObjectRead(RestrictedViewMixin, ObjectNameMixin, DetailView): allowed_types = ["modal", "widget", "window", "page"] window_content = "window-content/object.html" model = None -class ObjectUpdate(ObjectNameMixin, UpdateView): +class ObjectUpdate(RestrictedViewMixin, ObjectNameMixin, UpdateView): allowed_types = ["modal", "widget", "window", "page"] window_content = "window-content/object-form.html" parser_classes = [FormParser] @@ -249,7 +306,7 @@ class ObjectUpdate(ObjectNameMixin, UpdateView): return super().post(request, *args, **kwargs) -class ObjectDelete(ObjectNameMixin, DeleteView): +class ObjectDelete(RestrictedViewMixin, ObjectNameMixin, DeleteView): model = None template_name = "partials/notify.html"