I have a model of Gun, and each gun has multiple Loads
What I want to do is add a Load count to the gun GET request object, but not in the model it self. So the count is done when I do the GET request, and only for the GET request if possible. Otherwise I can just make the POST load_count = 0, because there is no such field and It should not be saved.
class Gun(models.Model):
name = models.CharField(max_length=128)
class Load(models.Model):
gun = models.ForeignKey(Gun, related_name='gun')
foo = ...
Serializers:
class GunSerializer(serializers.HyperlinkedModelSerializer):
??? load_count = Field() -- reverse lookup and count loads
id = serializers.Field()
class Meta:
model = Gun
fields = ('id',"name", ???? "load_count")
class LoadSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field()
class Meta:
model = Load
Try using an IntegerField for the set count:
class GunSerializer(serializers.HyperlinkedModelSerializer):
load_count = serializers.IntegerField(source='load_set.count')
class Meta:
model = Gun
fields = ('id', "name", "load_count")
However, you are using the related_name of gun in your Load model which I would recommend changing to loads (or just omitting and then use the default load_set and the example above will work). If you want to keep the related name of gun then define the load_count serializer field as:
load_count = serializers.IntegerField(source='gun.count')
Related
I have three models in a django DRF project:
class ModelA(models.Model):
name = ....
other fields...
class ModelB(models.Model):
name = ....
other fields...
class ModelC(models.Model):
name = ....
model_a = FKField(ModelA)
model_b = FKField(ModelB)
I was using the default ModelViewSet serializers for each model.
On my react frontend, I'm displaying a table containing 100 objects of ModelC. The request took 300ms. The problem is that instead of displaying just the pk id of modelA and ModelB in my table, I want to display their names. I've tried the following ways to get that data when I use the list() method of the viewset (retreive all modelc objects), but it significantly increases call times:
Serializing the fields in ModelCSerializer
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelASerializer(read_only=True)
model_b = ModelBSerializer(read_only=True)
class Meta:
model = ModelC
fields = '__all__'
Creating a new serializer to only return the name of the FK object
class ModelCSerializer(serializers.ModelSerializer):
model_a = ModelANameSerializer(read_only=True) (serializer only returns id and name)
model_b = ModelBNameSerializer(read_only=True) (serializer only returns id and name)
class Meta:
model = ModelC
fields = '__all__'
StringRelatedField
class ModelCSerializer(serializers.ModelSerializer):
model_a = serializer.StringRelatedField()
model_b = serializer.StringRelatedField()
class Meta:
model = ModelC
fields = '__all__'
Every way returns the data I need (except number 3 takes more work to get the FKobject's id) but now my table request takes 5.5 seconds. Is there a way to do this without significantly increasing call times? I guess this is due to the DB looking up 3 objects for every object I retrieve.
Also I wouldn't be able to make the primary_key of ModelA & ModelB the name field because they aren't unique.
Thanks
EDIT Answer for my example thanks to bdbd below:
class ModelCViewSet(viewsets.ModelViewSet):
queryset = ModelC.objects.select_related('model_a', 'model_b').all()
# ...
You can use select_related for this to optimise your queries and make sure that every object in your ModelC does not do extra DB hits
I am trying to update one of my models (which is a nested model - three level actually as you can see below) and I am getting the following error:
AssertionError: The .update() method does not support writable nestedfields by default. Write an explicit .update() method for serializer SystemSettingsSerializer, or set read_only=True on nested serializer fields.
All day I have been reading about nested models and nested serializers, trying to add update and create methods setting fields as read_only=True but no matter what I did, it just didn't work :( :(
These are my models:
class SystemSettings(models.Model):
# ... some fields
class Components(models.Model):
settings = models.ForeignKey(SystemSettings, related_name="Components")
class SysComponent(models.Model):
class Meta:
abstarct = True
index = models.PositiveIntegerField(primery_key=True)
is_active = models.BooleanField(default=False)
component = NotImplemented
class Foo(SysComponent):
component = models.ForeignKey(Components, related_name="Foo")
class Bar(SysComponent):
component = models.ForeignKey(Components, related_name="Bar")
task_id = models.PositiveIntegerField(default=0)
and serializers:
class SystemSettingsSerializer(ModelSerializer):
Components = ComponentsSerializer(many=True)
class Meta:
model = SystemSettings
fields = [# some fields,
Components]
class ComponentsSerializer(ModelSerializer):
Foo = FooSerializer(many=True)
Bar = BarSerializer(many=True)
class Meta:
model = Components
fields = ['Foo',
'Bar']
class FooSerializer(ModelSerializer):
class Meta:
model = Foo
class BarSerializer(ModelSerializer):
class Meta:
model = Bar
My logic is the following:
I am fetching the SystemSettings via GET and display it in a form.
The user changes it as much as he want and by clicking submit I send it back via PUT.
As I said I am getting the error above after clicking submit.
Any help would be appreciated.
EDIT: I am using django 1.7.8 by the way
I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.
For example:
class CarImage(models.Model):
image = models.ForeignKey(Image)
car = models.ForeignKey(Car)
message = models.TextField()
message = 'awesome car'
for image in Image.objects.all():
for car in Car.objects.all():
if not CarImage.objects.filter(
Q(image=image),
Q(car=car),
Q(message=message),
):
carimage = CarImage()
carimage.image = image
carimage.car = car
carimage.message = message
carimage.save()
Is there a more efficient way to create unique models based on multiple fields? Would a signature field make this better? Or would Django Signals fix the problem - whenever Car, Image, message is created send signal to create CarImage?
You can add unique_together constraint in CarImage model Meta.
Sets of field names that, taken together, must be unique.
Django will ensure that whenever you create a CarImage object, set of image, car and message fields taken together are unique.
class CarImage(models.Model):
image = models.ForeignKey(Image)
car = models.ForeignKey(Car)
message = models.TextField()
class Meta:
unique_together = (('image', 'car', 'message'),) # define unique_together constraint
I am attempting to fetch nested objects but not quite sure how to achieve this. My model is as shown:
class Brand(models.Model)
name = models.CharField(max_length=128)
class Size(models.Model)
name = models.CharField(max_length=128)
class Brand_Size(models.Model)
brand = models.ForeignKey(Brand)
size = models.ForeignKey(Size)
class Brand_Size_Location(models.Model)
location = models.ForeignKey(Location)
brand_size = models.ForeignKey(Brand_Size)
I filter objects in Brand_Size_Location by location which can occur 1..x. I want my serializer to output the results in terms of the model Brand (BrandSerializer). Since my resultset can be 1..x and furthermore the occurrence of Brand can be duplicates i would like to eliminate these aswell at the same time.
You should be able to do this fairly easily by including a serializer field in your BrandSerializer:
class BrandSerializer(serializers.ModelSerializer):
brand_sizes = BrandSizeSerializer(
source='brand_size_set',
read_only=True
)
class Meta:
model = Brand
fields = (
'id',
...add more fields you want here
'brand_sizes')
You can simlarly create the brand size serializer to nest the locations
Filtering on this relationship occurs in the view and will need a custom filter. Here's a basic example using a ListView:
class BrandFilter(django_filters.FilterSet):
location = django_filters.ModelMultipleChoiceFilter(
queryset=Brand_Size_Location.objects.all(),
name='brand_size__brand_size_location__location'
)
location_name = django_filters.CharFilter(
name='brand_size__brand_size_location__location__name'
)
class Meta:
model = Brand
fields = [
'location',
'location_name'
]
class BrandList(LoginRequiredMixin, generics.ListCreateAPIView):
model = Brand
serializer_class = BrandSerializer
filter_class = BrandFilter
You can then use query parameters to filter on the URL like:
http://somehost/api/brands/?location=42
which uses a PK to filter, or with the location name (assuming you have a name field in the Location model):
http://somehost/api/brands/?location_name=Atlantis