I have 2 models:
class Model(models.Model):
...
related = models.ForeignKey(
'RelatedModel',
on_delete=models.CASCADE,
related_name='related_model'
)
class RelatedModel(models.Model):
...
flag = models.BooleanField()
I need to pass value of 'flag' attribute of RelatedModel in Model instance serializer and additionally this value must be reversed i.e. if it is 'True', I should return 'False' as boolean data type.
Already implemented this with method:
class ModelSerializer(serializers.ModelSerializer):
...
flag = serializers.SerializerMethodField()
#staticmethod
def get_flag(obj):
return not obj.related.flag
class Meta:
model = Model
fields = (
...
flag
)
But maybe there is opportunity to use only serializer fields like this but with reverse value?
flag = serializers.BooleanField(
source='related.flag', read_only=True
)
If you need to reverse the value you can' t use a BooleanField, the simplest solution is to use a SerializerMethodField as you have already done. Or you could also create a custom field class, but that is more complicated.
Related
I have multiple models which has fields like "created_at" "updated_at" which I don't want to get with objects.values().
Does Django has any way to exclude fields in values()?
I know people refer to defer(), but it doesn't return QuerySet<Dict> like values() instead returns QuerySet<Model>.
I tried objects.defer("created_at", "updated_at").values(), but it includes those 2 deferred fields in the resulting Dict.
I see defer().query only selecting the non-exluded fields in the SQL, but using defer(..).values() resets the deferred fields and selects all fields.
I cannot specify which field I want, since different model has different fields, I can only specity which fields I don't want. So I cannot use values('name', 'age', ...)
I'm planning to use a CustomeManager, which I can use in all model.
Example:
class CustomManager(models.Manager):
def values_excluded(self):
return self.values() # somehow exlude the fields and return QuerySet<Dict>
class ExampleModel(models.Model):
name = models.CharField(max_length=20)
age = models.IntegerField()
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
objects = CustomManager()
ExampleModel.objects.values_excluded()
Is there any way in Django or do I have to manually delete those keys from the resulting Dict from values()?
esclude_fields = ['created_at', 'updated_at']
keys = [f.name for f in Model._meta.local_fields if f.name not in esclude_fields]
queryset.values(*keys)
This should work:
class CustomManager(models.Manager):
def values_excluded(self, *excluded_fields):
included_fields = [f.name for f in self.model._meta.fields if f.name not in excluded_fields]
return self.values(*included_fields)
I have an Item class which can be annotated using a custom queryset add_is_favorite_for method:
class ItemQuerySet(QuerySet):
def add_is_favorite_for(self, user):
"""add a boolean to know if the item is favorited by the given user"""
condition = Q(id__in=Item.objects.filter(favoriters=user).values("id"))
return self.annotate(is_favorite=Condition(condition)) # True or False
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
It works as expected. For example:
>>> user = User.objects.get(id=1)
>>> Item.objects.add_is_favorite_for(user) # each item has now a `is_favorite` field
Then, I added a Factory model and link Item model to it using a 1->N relationship:
class Factory(Model):
pass # ...
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
advised_in = models.ForeignKey(
Factory,
on_delete=models.CASCADE,
related_name="advised_items",
)
Now, I'd like to be able to return a Factory QuerySet, whose advised_items fields will all contain the is_favorite annotation too.
I don't know how to do this, I saw no example of such a thing in the doc, maybe I missed it.
You can work with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
Factory.objects.prefetch_related(
Prefetch('advised_items', queryset=Item.objects.add_is_favorite_for(some_user))
)
Let's say we have three Models : ModelA, related to ModelB and ModelC.
ModelA is defined as :
class ModelA(models.Model):
field_b = models.ForeignKey(ModelB, on_delete=models.CASCADE)
field_c = models.ForeignKey(ModelC, on_delete=models.CASCADE)
other_field = models.CharField(max_length=30)
class Meta:
constraints = [
models.UniqueConstraint(fields=['field_b', 'field_c'], name='unique_modelA')
]
How to generate a ModelASerializer instance from instances of ModelB
and ModelC ?
Then, will there be a serialized representation of
field_b and field_c into ModelASerializer ?
The UniqueConstraint will
it be checked when calling .is_valid() onto ModelASerializer
instance ?
I tried the following :
class ModelASerializer(serializers.ModelSerializer):
field_b = ModelBSerializer(read_only=True)
field_c = ModelCSerializer(read_only=True)
other_field = serializers.CharField(required=False)
class Meta:
model = ModelA
fields = ('field_b', 'field_c', 'other_field',)
validators = [
UniqueTogetherValidator(
queryset=ModelA.objects.all(),
fields=['field_b', 'field_c'],
message='A Model A instance for this couple of ModelB and ModelC instances already exists.'
)
]
def create(self, validated_data):
"""
Create and return a new `ModelA` instance, given the validated data.
"""
return ModelA.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `ModelA` instance, given the validated data.
"""
instance.other_field= validated_data.get('other_field', instance.other_field)
instance.save()
return instance
But I cannot find any way to create the serializer :
model_b = ModelB()
model_c = ModelC()
model_b.save()
model_c.save()
other_field = "Dummy content"
First try
model_a_serializer = ModelASerializer(model_b, model_c, other_field)
The serializer is looking for an ID field and can't find it
Anyway, no data field being provided, we can't call .is_valid() onto the serializer, and thus, can't check the integrity constraint
Second try
model_b_serializer = ModelBSerializer(model_b)
model_c_serializer = ModelCSerializer(model_c)
data = {'model_b':model_b_serializer , 'model_c':model_c_serializer , 'other_field':other_field}
model_a_serializer = ModelASerializer(data=data)
if model_a_serializer.is_valid():
model_a_serializer.save()
Here, the serializer tries to recreate the ModelB and ModelC instances when is_valid() is called... And I don't want that.
Any ideas? Thank you very much by advance.
Not sure if I understood your question properly but...
How to generate a ModelASerializer instance from instances of ModelB and ModelC ?
If you want to generate a ModelA instance from ModelB and ModelCthen you should remove read_only=True argument and you have to explictly specify the relationship in create and update method.
class ModelBSerializer(serializers.ModelSerializer):
modelA_set = ModelASerializer() # this field name can be changed by adding related_name attribute in ModelA in models.py
class Meta:
model = ModelB
fields = ('modelA_set' , 'other_fields')
def create(self,validate_data):
modelA_data = validate_data.pop('modelA_data') # whatever field name you will use to send data for modelA
b = ModelB.objects.create(**validate_data)
for data in modelA_data:
modelA_instance = ModelA.objects.create(field_b=b ,**data)
return b
modelA_set is an automatically generated field name when we are trying to serialize ModelA from ModelB serializer . It can be changed by passing related_name='some_name' argument like this:
field_b = models.ForeignKey(ModelB, on_delete=models.CASCADE , related_name="field_a")
Then, will there be a serialized representation of field_b and field_c into ModelASerializer ?
Yes there will be.
Again correct me if I misunderstood your question.
I need to add a static field to my serializer. It should always return the same value, regardless of the passed object. Currently I implemented it like so:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
#staticmethod
def get_type(obj):
return 'question'
class Meta:
model = Question
fields = ('type',)
But is there a simpler way to do it, without the SerializerMethodField?
using a ReadOnlyField worked for me:
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.ReadOnlyField(default='question')
class Meta:
model = Question
fields = ('type',)
https://www.django-rest-framework.org/api-guide/fields/#readonlyfield
The only alternative would be to override to_representation and add the value there:
def to_representation(self, obj):
data = super().to_representation(obj)
data['type'] = 'question'
return data
Not a much better option though.
You can use serializers.HiddenField
class QuestionSerializer(serializers.ModelSerializer):
type = serializers.HiddenField(default='question')
class Meta:
model = Question
fields = ('type',)
http://www.django-rest-framework.org/api-guide/fields/#hiddenfield
If you don't care about adding a line to your model, it would be easier to add static field to your model.
class Question(models.Model):
type = 'question'
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ('type',)
I have this code
# Models
class NestedSample(models.Model):
something = models.CharField(max_length=255)
class Sample(models.Model):
thing = models.BooleanField()
nested = models.ForeignKey(NestedSample)
# Serializers
class NestedSampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample
class SampleSerializer(serializers.HyperlinkedModelSerializer):
nested = NestedSampleSerializer() # HERE filter delete=false
nested2 = NestedSample2Serializer() # HERE filter deletefalse
class Meta:
model = api_models.Sample
In my view I am overrding the queryset for delete=False but it is not applying to nested serializers.
delete=False in queryset will only filter Sample. To filter queryset in nested serializer you can use serializers.ListSerializer like:
class FilterDeleteListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(delete=False)
return super(FilterDeleteListSerializer, self).to_representation(data)
class NestedSampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample
list_serializer_class = FilterDeleteListSerializer
class NestedSample2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample2
list_serializer_class = FilterDeleteListSerializer
class SampleSerializer(serializers.HyperlinkedModelSerializer):
nested = NestedSampleSerializer() # HERE filter delete=false
nested2 = NestedSample2Serializer() # HERE filter deletefalse
class Meta:
model = api_models.Sample
Learn more here
I didn't exactly understand your question, but from what I figured you've got a boolean field in your Model which is set to True if you delete the object instead of actually deleting it from the database (SQL DELETE).
Now coming to your question, if you just want to filter the nested serializer then you could use the SerializerMethodField. You need to specify the method to call as an argument or add a method with the name 'get_' followed by the field name. In this method you can filter the queryset serialize it and return the data of that queryset.
class UserSerializer(serializers.ModelSerializer):
delete_filtered_items = serializers.SerializerMethodField()
class Meta:
model = User
def get_delete_filtered_items(self, obj):
items = Item.objects.filter(user=obj,deleted=False)
serializer = ItemsSerializer(instance=items, many=True)
return serializer.data
The above solution should work for your requirements, but if what you've implemented is similar to a soft delete then it would seem cleaner and more moduler to create a custom model manager.