I have a model, Parent which contains a Django ContentType GenericForeignKey relationship to various child models (ChildA, ChildB) in Parent.child
I'm trying to get ListCreateAPIView and other listing views working with this setup. Originally I handled the serialization of the child instance using a SerializerMethodField which looked something like:
class ParentSerializer(serializers.ModelSerializer):
child = serializers.SerializerMethodField('get_child')
(other fields)
def get_child(self, obj):
if obj.content_type == "child_a":
return ChildASerializer(obj.child).data
...
Now I want to take advantage of Django Rest Framework to its full (including deserialization/creation/validation) so I want to avoid my current approach and increase DRYness by doing:
class ParentSerializer(serializers.ModelSerializer):
(code for serializing parent fields except 'child' attribute)
class ChildASerializer(serializers.ModelSerializer):
(code for ChildA fields)
class ParentTypeASerializer(ParentSerializer):
child = ChildASerializer()
If i'm reading the docs right, this means POST/PUT will go through the serializer's process without me having to override the post methods in views and other uglyness. This is important as ChildA,ChildB,ChildC come from plugins and the core Parent/ParentSerializer should be as unaware of them as possible.
My thinking was to override get_serializer() in the view, but when listing many objects, I don't see how I can provide ParentTypeASerializer, ParentTypeBSerializer etc in the view.
def get_serializer(self, instance=None, data=None, files=None, many=False, partial=True):
serializer_class = None
if instance and instance.content_type == "child_a":
serializer_class = ParentTypeASerializer
if instance and instance.content_type == "child_b":
serializer_class = ParentTypeBSerializer
...
# What about many=True ?!
return serializer_class(instance,data=data,files=files,many-many,partial=partial,context=context)
Another idea I had was to write a PolymorphicField class extending WritableField that does the decision. Unsure if this is the simplest approach:
class ParentSerializer(serializers.ModelSerializer):
child = PolymorphicChildSerializerProxy() # Passes through/wraps the right serializer
question: is there any dynamic/runtime/per-object way to provide the right serializer for an Generic/polymorphic nested object in such a way either at the view level or the parent serializer? Ideally something like the second example and an override in the view that works for List/Create/Destroy generics or like the first example except I return a serializer class rather than serialized data?
Related
I have a website that is essentially a wiki for a DnD campaign that I am participating in. As such it has articles of Creatures, Characters, Locations and more. I wanted to use Viewsets to access them easily and wanted to use a Viewset action (together with a custom router) to be able to look for individual records not through pk, but through various query-parameters.
I already have something that works for this, now I would like to apply some inheritance to it to not repeat myself. What I'd like to do is something like this:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
#action(detail=True, url_name="detail-params", url_path=detail_with_params_url_pattern_suffix)
def detail_through_params(self, request, **kwargs):
if self.detail_with_params_url_pattern_suffix == "":
raise InvalidViewsetURLException("URL of view 'detail_through_params' of WikiBaseViewset is not defined!")
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
detail_with_params_url_pattern_suffix = "(?P<name__iexact>.+)"
However, I'm struggling over the fact that the decorator absolutely requires the URL parameter in the base class. Otherwise the code just doesn't compile due to a NameError complaining that detail_with_params_url_pattern_suffix is not defined. If you were to set detail_with_params_url_pattern_suffix="" in the base-class in order to not get an Error when your code is compiled, that still wouldn't matter, as the decorator from my experiments so far still grabs the value of that variable from WikiBaseViewset not CharacterViewSet.
How can I rewrite my BaseClass so that this works? Is there even a way?
I did not find a fully satisfying answer to this problem, but in the end acquiesced with this solution, as it was better than copy pasting.
You might not be able to inherit viewset actions, but you sure can inherit individual methods and then just overwrite them in the child and throw a decorator on top. This lead to this structure:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
def detail_through_params(self, request, **kwargs):
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
#action(detail=True, url_name="detail-params", url_path="(?P<name__iexact>.+)")
def detail_through_params(self, request, **kwargs):
return super().detail_through_params(request, **kwargs)
I have a table to store view events, that is, if a user views an entity, a record will be stored into that table. This table is represented by a model that has a generic relation, that is, it can be related to any other model.
I have defined a mixin ViewTracked that should be extended by any model that can be tracked (i.e. class SomeModel(ViewTracked)).
I want to have a custom method for queryset of objects manager called custom_method for example. I know that I can define a custom Manager and override the objects manager with it easily, but the problem is that the tracked model can already have a custom manager that has his own custom queryset, so I can't simply override it and lose the custom queryset that it has.
Unfortunately, I couldn't find a proper way of doing this, so I tried to add a metaclass to override the manager's get_queryset and add my custom method to it, but for some reason, when I call SomeModel.objects it always returns None.
Here's what I tried:
# Meta class
class ViewTrackedMeta(ModelBase):
def __new__(mcs, class_name, base_classes, attributes_dict):
# let ModelBase do its magic
new_class = super().__new__(mcs, class_name, base_classes, attributes_dict)
if hasattr(new_class, 'objects'):
objects_manager = new_class.objects
if isinstance(objects_manager, Manager):
queryset = objects_manager.get_queryset()
def custom_method(queryset):
return queryset.filter(...)
def get_extended_queryset(manager):
queryset.custom_method = types.MethodType(custom_method, queryset)
objects_manager.get_queryset = types.MethodType(get_extended_queryset, objects_manager)
return new_class
# Mixin
class ViewTracked(Model, metaclass=ViewTrackedMeta):
class Meta:
abstract = True
...
# Models
class SomeModel(ViewTracked):
objects = CustomManager()
class SomeOtherModel(ViewTracked):
... # default django objects manager
class SomeOtherModel(ViewTracked):
objects = OtherCustomManager()
Is there any other way I can achieve what I want? Why SomeModel.objects is always returning None?
Other than instaniating your manager classes, you should be using from_queryset. Here are the docs.
class CustomQuerySet(models.QuerySet):
def manager_and_queryset_method(self):
return
class MyModel(models.Model):
objects = models.Manager.from_queryset(CustomQuerySet)()
Now you can do:
MyModel.objects.manager_and_queryset_method()
as well as
MyModel.objects.filter(something="else").manager_and_queryset_method()
I'm using Django with the REST Framework. In a serializer, I would like to assign a field value based on a view or request (request.data['type']) parameter, so I need the view/request in the context.
I succeeded, but only in a cumbersome way, and I am looking into ways to simplify the code. Here's the successful approach (omitting irrelevant fields):
class TypeDefault(object):
def set_context(self, serializer_field):
view = serializer_field.context['view'] # or context['request']
self.type = view.kwargs['type'].upper()
def __call__(self):
return self.type
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=serializers.CreateOnlyDefault(TypeDefault()))
class Meta:
model = RRset
fields = ('type',)
read_only_fields = ('type',)
To simplify things, I tried removing the TypeDefault class, and replacing the type serializer field by
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context.get('view').kwargs['type'].upper() # also tried self._context
However, context.get('view') returns None. I am unsure why the view context is not available here. My impression is that it should be possible to get the desired functionality without resorting to an extra class.
As a bonus, it would be nice to specify the default in the field declaration itself, like
type = serializers.CharField(default=self.context.get('view').kwargs['type'].upper())
However, self is not defined here, and I'm not sure what the right approach would be.
Also, I am interested if there is any difference in retrieving information from the view or from the request data. While the context approach should work for both, maybe there's a simpler way to get the CreateOnlyDefault functionality when the value is obtained from request data, as the serializers deals with the request data anyways.
Edit: Per Geotob's request, here is the code of the view that calls the serializer:
class RRsetsDetail(generics.ListCreateAPIView):
serializer_class = RRsetSerializer
# permission_classes = ... # some permission constraints
def get_queryset(self):
name = self.kwargs['name']
type = self.kwargs.get('type')
# Note in the following that the RRset model has a `domain` foreign-key field which is referenced here. It is irrelevant for the current problem though.
if type is not None:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk, type=type)
else:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)
In urls.py, I have (among others):
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets'),
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/(?P<type>[A-Z]+)/$', RRsetsDetail.as_view(), name='rrsets-type'),
SerializerMethodField is a read-only field so I do not think it will work unless you set a default value... and you are back to the same problem as with CharField.
To simply things you could get rid of serializers.CreateOnlyDefault:
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=TypeDefault())
If you want something more dynamic, I can only think of something like this:
class FromContext(object):
def __init__(self, value_fn):
self.value_fn = value_fn
def set_context(self, serializer_field):
self.value = self.value_fn(serializer_field.context)
def __call__(self):
return self.value
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True,
default=FromContext(lambda context: context.get('view').kwargs['type'].upper()))
FromContext takes a function during instantiation that will be used to retrieve the value you want from context.
All in all, your second approach above is the correct one:
Use serializers.SerializerMethodField and access self.context from the serializer method:
class SomeSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context['view'].kwargs['type'].upper()
The view, request and format keys are automatically added to your serializer context by all of the DRF generic views (http://www.django-rest-framework.org/api-guide/generic-views/#methods at the end of the section). This works just fine.
If you are creating a serializer instance manually, you will have to pass context=contextDict as an argument, where contextDict is whatever you need it to be (http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context).
As #Michael has pointed out in another answer, the SerializerMethodField will be read only. But going by your first example (type = serializers.CharField(read_only=True.....) this seems to be what you want.
In the DRF source code, there's a get_serializer method. It wasn't inherited from object and it's not a method in the CreateModelMixin class. Where does this method come from?
serializer = self.get_serializer(data=request.data)
Here's the larger chunk of code for context.
from __future__ import unicode_literals
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': data[api_settings.URL_FIELD_NAME]}
except (TypeError, KeyError):
return {}
There are a few SO posts that that also use this method. Like this, this, and this. But I still can't figure out where the implementation is.
CreateModelMixin along with all other mixin classes (Eg. ListModelMixin, UpdateModelMixin etc) are defined in rest_framework/mixins.py file.
These mixin classes provide all the basic CRUD operations on a model. You just need to define a serializer_class and queryset in your generic view to perform all these operations. DRF has separated out these common functionality in separate mixin classes so that they can be injected/mixed-in in a view and used as and when required.
In the DRF source code, there's a get_serializer method. It wasn't
inherited from object and it's not a method in the CreateModelMixin
class. Where does this method come from?
In the GenericAPIView, get_serializer method is defined. The combination of different mixin classes along with GenericAPIView class provide us different generic views for different use cases.
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Other generic views then inherit the relevant mixin along with GenericAPIView.
Eg. CreateAPIView inherit the CreateModelMixin along with GenericAPIView to provide create-only endpoints.
# rest_framework/generics.py
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
...
It helps if you understand class inheritance (not suggesting you don't, though).
CreateModelMixin will be used by a class based view, for example:
class YourViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
get_serializer is a method available through GenericViewSet (although there are probably other View classes that it's available on, too).
So, basically if you would use CreateModelMixin only get_serializer would not be available. (You probably wouldn't do that anyway). It's only because you inherit from another class besides CreateModelMixin that the get_serializer method is available at the moment that the create method is executed.
I could give a simple example of what's happening, while I am at it. It's just to give a more simple overview of what's happening.
disclaimer: you might want to keep in mind that I am a junior developer, so this may not be the most Pythonic code out there :P.
class MyMixin:
def create(self):
serializer = self.get_serializer()
return serializer
class FakeView:
def get_serializer(self):
return "Sadly I am just a string, but I could've been a serializer."
class MyFakeView(MyMixin, FakeView):
pass
view = MyFakeView()
serializer = view.create()
print(serializer)
executing this would show you:
Sadly I am just a string, but I could've been a serializer.
Where if you would define MyFakeView like below,
class MyFakeView(MyMixin):
pass
you would receive the following error:
AttributeError: 'MyFakeView' object has no attribute 'get_serializer'
You can see the __file__ or __module__ member of the method (if it has them) to learn that. inspect also has getsourcefile and getsourcelines that use data from the function's code object, specifically, <function>.f_code.co_filename and .co_firstline.
For example, this retrieves source information for a method inherited from DictMixin:
>>> c=ConfigParser._Chainmap()
>>> f=c.__len__
>>> dump(f.__code__) # my custom function that dumps attributes,
# see https://github.com/native-api/dump
<...>
co_filename : /usr/lib/python2.7/UserDict.py
co_firstlineno : 179
<...>
# same with inspect
>>> inspect.getsourcefile(f)
'/usr/lib/python2.7/UserDict.py'
>>> inspect.getsourcelines(f)
([' def __len__(self):\n', ' return len(self.keys())\n'], 179)
>>> inspect.getsource(f)
' def __len__(self):\n return len(self.keys())\n'
# same with __file__
>>> sys.modules[f.__module__].__file__
'/usr/lib/python2.7/UserDict.pyc'
Is it possible to use something similar to the inline relational items from the Django admin to represent embedded models in a ListField?
For Example, I've got the following models:
class CartEntry(model.Model):
product_name=model.CharField( max_length=20 )
quantity = model.IntegerField()
class Cart(model.Model):
line_items = ListField(EmbeddedModelField('CartEntry'))
I've tried using the standard inlining, but I know it's not right:
class CartEntryInline( admin.StackedInline ):
model=CartEntry
class CartAdmin(admin.ModelAdmin)
inlines=[CartEntryInline]
But obviously that doesn't work, since there's no foreign key relation. Is there any way to do this in django-nonrel?
This is not so easy to do out of the box. You will need to manage ListField and EmbeddedModelField type fields in Django's admin module and do some hacking to get it done. You'll have to implement two parts:
Use EmbeddedModelField in Django's admin
You need to define a class that handles EmbeddedModelField objects to make it work with Django's admin. Here is a link where you can find great sample codes. Below are just code blocks for demonstration:
Add this class into your models.py file and use EmbedOverrideField instead of EmbeddedModelField in Cart model:
class EmbedOverrideField(EmbeddedModelField):
def formfield(self, **kwargs):
return models.Field.formfield(self, ObjectListField, **kwargs)
Implement a class in forms.py that has two methods:
class ObjectListField(forms.CharField):
def prepare_value(self, value):
pass # you should actually implement this method
def to_python(self, value):
pass # Implement this method as well
Use ListFields in Django's admin
You also need to define a class that handles ListField objects to make it work with Django's admin. Here is a link where you can find great sample codes. Below are just code blocks for demonstration:
Add this class into your models.py file and ItemsField instead of ListField in Cart model:
class ItemsField(ListField):
def formfield(self, **kwargs):
return models.Field.formfield(self, StringListField, **kwargs)
Implement a class in forms.py that has two methods:
class StringListField(forms.CharField):
def prepare_value(self, value):
pass # you should actually implement this method
def to_python(self, value):
pass # Implement this method as well