Access related set from a Custom QuerySet in Django? - python

I want to annotate additional field to my model via custom queryset and access it with a function, here is a simplified code:
class CustomQuerySet(models.QuerySet):
def my_func(self):
return self.annotate(b_count=self.b_set.count()) # 'CustomQuerySet' object has no attribute 'b_set'
class A(models.Model):
objects = CustomQuerySet.as_manager()
class B(models.Model):
a = models.ForeignKey(A)
I want to access it like this models.A.objects.my_func().all(), so an extra field would be added to my model when I want to get it.
But I can't access b_set from a CustomQuerySet.
Previously I was using a #property in model A, but it makes an additional DB request every time.
How could I access a set of related model from a Custom QuerySet?

Probably, you should take a look at the Manager example that does exactly what you want: https://docs.djangoproject.com/en/3.2/topics/db/managers/#adding-extra-manager-methods
from django.db import models
from django.db.models.functions import Coalesce
class PollManager(models.Manager):
def with_counts(self):
return self.annotate(
num_responses=Coalesce(models.Count("response"), 0)
)
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
objects = PollManager()
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
# ...
In your example, I think you can't access b_set because this attribute belongs to a model instance, not to the queryset itself.

Related

Does Django have a way for ModelViewSet to represent both parent and child models?

I need to have extra fields in response if they are available, but not all objects of that class have this property. So for example we have
class Car(models.Model):
brand = model.CharField()
wheelcount = model.IntField()
class Truck(Car):
max_load = model.IntField()
class Bus(Car):
max_people = model.IntField()
and a view
class CarView(ReadOnlyModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
And I want to have max_load and max_people when i check all available cars.
Is there a way to either write CarSerializer to somehow serialize child objects differently, or a way to make view class choose a serializer based on class or additional field(like having an enum CarType)?
You can specify which fields you'd like to serialize normally in the meta class.
ex:
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = [<whatever specific fields you want to serialize>]
if you need to serialize objects based on certain conditions you can use the SerializerMethodField. https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
From django rest documentation:
This is a read-only field. It gets its value by calling a method on
the serializer class it is attached to. It can be used to add any sort
of data to the serialized representation of your object.
Signature: SerializerMethodField(method_name=None)
method_name - The name of the method on the serializer to be called.
If not included this defaults to get_<field_name>. The serializer
method referred to by the method_name argument should accept a single
argument (in addition to self), which is the object being serialized.
It should return whatever you want to be included in the serialized
representation of the object. For example:
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
days_since_joined = serializers.SerializerMethodField()
class Meta:
model = User
fields = '__all__'
def get_days_since_joined(self, obj):
return (now() - obj.date_joined).days
in your case:
class CarSerializer(serializers.ModelSerializer):
object = serializers.SerializerMethodField()
def get_object(self, instance):
if instance.CarType:
return <your desired object>

django rest framework - global default model serializer

In short, I want to have a global default serializer per model. My use case here is to create dynamic serializer- i.e creating ModelSerializer classes on the fly.
class Customer(models.Model):
name = models.CharField(max_length=200)
code = models.CharField(max_length=200)
# many more fields..
class CustomerTicket(models.Model):
customer = models.ForeignKey(Customer)
date = models.DateTimeField(auto_now_add=True)
# more fields..
Customer will be referenced by many other models, and hence it will be serialized as a nested object. I don't want the 'code' field to appear in the output - no matter what it should always be excluded.
Now I'd like to create a function:
def serialize_default(model, fields, queryset):
class S(serializers.ModelSerializer):
class Meta:
model = model
fields = fields
depth = 1
return S(queryset, many=True)
if I serialize CustomerTicket queryset using this function, I will get all the customer fields as a nested object. I know I can override it locally, but I want to define a CustomerSerializer that will be used by default (for the nested Customer here) unless other serializer is specified as a field. How to achieve this?
Would something like that work for you?
class DefaultCustomerSerializer(serializers.ModelSerializer):
# whatever fields you want
class DefaultCustomerSerializerModel(serializers.ModelSerializer):
customer = DefaultCustomerSerializer()
# You can inherit from this to have default customer serializer
# on serializers you want.
class CustomerTicketSerializer(DefaultCustomerSerializerModel):
# Other fields

Expose multiple similar database fields as enumerable collection

I have a Django (1.8) Model for an underlying database table that has multiple columns that are logically a fixed-size array. For example:
from django.db import models
class Widget(models.Model):
# ...
description_1 = models.CharField(max_length=255)
description_2 = models.CharField(max_length=255)
description_3 = models.CharField(max_length=255)
# ...
I would like to be able to access these columns as if they were a collection on the model instance, e.g.:
instance = Widget.objects.get(...)
for description in instance.descriptions:
# do something with each description
My primary motivation is that I am exposing this model via Django Rest Framework (DRF), and would like the API clients to be able to easily enumerate the descriptions associated with the model. As it stands, the clients have to reference each logical 'index' manually, which makes the code repetitive.
My DRF serializer code is currently like this:
class WidgetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Widget
There are a fixed number of descriptions for each Widget, and their ordering is important.
Is there a clean way to expose these fields as a collection on the Model object?
It really was as easy as adding a method to the Model class that returns the fields as a sequence, and then (for API clients), manually specifying that new method as a field to serialize.
So the Model definition becomes:
from django.db import models
class Widget(models.Model):
description_1 = models.CharField(max_length=255)
description_2 = models.CharField(max_length=255)
description_3 = models.CharField(max_length=255)
def descriptions(self):
return self.description_1, self.description_2, self.description_3
And the DRF serializer is updated like:
class WidgetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Widget
fields = ('url', 'descriptions',)
This causes the API to return a JSON array for descriptions and omit all of the individual description_x fields.

Standard way to pull a specific model from a common view in Django/DRF?

I currently have two models that are essentially the same (inheriting from a base model), but I have problems referring to them from a common view:
Models:
class BaseModel(models.Model):
name = models.CharField(...)
owner = ForeignKey(...)
class Cat(BaseModel):
...
class Dog(BaseModel):
...
View:
class CommonViewset(viewsets.ModelViewSet):
#link()
def set_owner(self, request, pk=None):
#how do I get Cat or Dog models cleanly here?
#super fugly/unstable way
url_path = request.META['PATH_INFO']
if 'cats' in url_path:
Cat.objects.get(pk=pk).owner = ...
elif 'dogs' in url_path:
Dog.objects.get(pk=pk).owner = ...
I can also put the set_owner link in separate views, but that feels un-DRY. Thanks in advance for looking into this!
You can pass the model to use in the as_view method of your class:
url(r'^cats/my-url/$', CommonViewSet.as_view(model=Cat)),
The ModelViewSet class inherits from Django's View class, so this will set the model attribute on the instance of your viewset. You can then use self.model to get the right model for the current url.

Can Django-Rest-Framework create an API that returns a single attribute of an Object Model instance?

In my Django app, I have the following Models:
class MyModelA(models.Model):
myAttributeA = models.CharField(max_length=255)
class MyModelB(models.Model):
myParent = models.OneToOneField(myModelA)
myAttributeB = models.CharField(max_length=255)
My settings.py has the following Rest Permission settings:
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',),
I also have the following Serializers:
class MyModelASerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModelA
fields = ('myAttributeA',)
class MyModelBSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MyModelB
fields = ('myParent', 'myAttributeB',)
Now I want to write a simple Django-Rest-Framework API that will allow any user (weather authenticated or not) to retrieve the value of myParent on any instance of MyModelB assuming they have the Primary Key of the MyModelB instance. This should be rather simple. I'm not trying to update, create, or delete anything. I just want to retrieve the value of one attribute of the instance. But I also want my urls.py to match this endpoint to the API:
url(r'^api/AttrMyModelA/(?P<myModelAID>\d+)/?$', SOMETHING HERE. NOT SURE WHAT)
However, I cannot figure out which pattern to use from the tutorial to make this work. Should I use function based or class based views? Should I use Generic API views? Do I need a decorator or no? Mixins? Can someone please show me what my view should look like and what the urls.py endpoint should look like?
Thanks
You require a RetrieveAPIView-derived class to tie things together:
class MyModelAView(RetrieveAPIView):
model = MyModelA
serializer_class = MyModelASerializer
The route mentioned by you would then look like this:
url(r'^api/AttrMyModelA/(?P<pk>\d+)/?$', MyModelAView.as_view())
Note that pk is the default look-up field used by APIView-derived classes when performing single object queries.
You have defined a default permission class (in settings.py), so unless you want to override that you don't need to specify a permission_classes value in your view class.

Categories

Resources