You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

179 lines
5.6 KiB

from django.db import transaction
from django.db.models import Q
from rest_framework import mixins
from rest_framework import viewsets
from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.generics import get_object_or_404
from dynamic_preferences import models
from dynamic_preferences import exceptions
from dynamic_preferences.settings import preferences_settings
from . import serializers
class PreferenceViewSet(
mixins.UpdateModelMixin,
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet,
):
"""
- list preferences
- detail given preference
- batch update preferences
- update a single preference
"""
def get_queryset(self):
"""
We just ensure preferences are actually populated before fetching
from db
"""
self.init_preferences()
queryset = super(PreferenceViewSet, self).get_queryset()
section = self.request.query_params.get("section")
if section:
queryset = queryset.filter(section=section)
return queryset
def get_manager(self):
return self.queryset.model.registry.manager()
def init_preferences(self):
manager = self.get_manager()
manager.all()
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
identifier = self.kwargs[lookup_url_kwarg]
section, name = self.get_section_and_name(identifier)
filter_kwargs = {"section": section, "name": name}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
def get_section_and_name(self, identifier):
try:
section, name = identifier.split(preferences_settings.SECTION_KEY_SEPARATOR)
except ValueError:
# no section given
section, name = None, identifier
return section, name
@action(detail=False, methods=["post"])
@transaction.atomic
def bulk(self, request, *args, **kwargs):
"""
Update multiple preferences at once
this is a long method because we ensure everything is valid
before actually persisting the changes
"""
manager = self.get_manager()
errors = {}
preferences = []
payload = request.data
# first, we check updated preferences actually exists in the registry
try:
for identifier, value in payload.items():
try:
preferences.append(self.queryset.model.registry.get(identifier))
except exceptions.NotFoundInRegistry:
errors[identifier] = "invalid preference"
except (TypeError, AttributeError):
return Response("invalid payload", status=400)
if errors:
return Response(errors, status=400)
# now, we generate an optimized Q objects to retrieve all matching
# preferences at once from database
queries = [Q(section=p.section.name, name=p.name) for p in preferences]
query = queries[0]
for q in queries[1:]:
query |= q
preferences_qs = self.get_queryset().filter(query)
# next, we generate a serializer for each database preference
serializer_objects = []
for p in preferences_qs:
s = self.get_serializer_class()(
p, data={"value": payload[p.preference.identifier()]}
)
serializer_objects.append(s)
validation_errors = {}
# we check if any serializer is invalid
for s in serializer_objects:
if s.is_valid():
continue
validation_errors[s.instance.preference.identifier()] = s.errors
if validation_errors:
return Response(validation_errors, status=400)
for s in serializer_objects:
s.save()
return Response(
[s.data for s in serializer_objects],
status=200,
)
class GlobalPreferencePermission(permissions.DjangoModelPermissions):
perms_map = {
"GET": ["%(app_label)s.change_%(model_name)s"],
"OPTIONS": ["%(app_label)s.change_%(model_name)s"],
"HEAD": ["%(app_label)s.change_%(model_name)s"],
"POST": ["%(app_label)s.change_%(model_name)s"],
"PUT": ["%(app_label)s.change_%(model_name)s"],
"PATCH": ["%(app_label)s.change_%(model_name)s"],
"DELETE": ["%(app_label)s.change_%(model_name)s"],
}
class GlobalPreferencesViewSet(PreferenceViewSet):
queryset = models.GlobalPreferenceModel.objects.all()
serializer_class = serializers.GlobalPreferenceSerializer
permission_classes = [GlobalPreferencePermission]
class PerInstancePreferenceViewSet(PreferenceViewSet):
def get_manager(self):
return self.queryset.model.registry.manager(
instance=self.get_related_instance()
)
def get_queryset(self):
return (
super(PerInstancePreferenceViewSet, self)
.get_queryset()
.filter(instance=self.get_related_instance())
)
def get_related_instance(self):
"""
Override this to the instance bound to the preferences
"""
raise NotImplementedError