Problem - I have a REST server using django-rest-framework (django v1.7.7, django-rest-framework v3.1.1). In the notifications, I let a user know if they've received a new friend request, or have earned a new badge. There are other notification types as well, but this simple example can explain my problem.
In my GET response, I want to get the notification with a dynamic related object, which is determined by type. if the type is friendreq then I want the relatedObject to be a User instance, with a UserSerializer. If the type is badge, I want to have the relatedObject be a Badge instance, with a BadgeSerializer.
Note: I already have these other serializers (UserSerializer, BadgeSerializer).
Below is what I am wanting to achieve in a response:
{
"id": 1,
"title": "Some Title",
"type": "friendreq"
"relatedObject": {
// this is the User instance. For badge it would be a Badge instance
"id": 1,
"username": "foo",
"email": "foo#bar.com",
}
}
And here are what I have for models and serializer:
# models.py
class Notification(models.Model):
"""
Notifications are sent to users to let them know about something. The
notifications will be about earning a badge, receiving friend request,
or a special message from the site admins.
"""
TYPE_CHOICES = (
('badge', 'badge'),
('friendreq', 'friend request'),
('system', 'system'),
)
title = models.CharField(max_length=30)
type = models.CharField(max_length=10, choices=TYPE_CHOICES)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="user")
related_id = models.PositiveIntegerField(null=True, blank=True)
# serializers.py
class NotificationSerializer(serializers.ModelSerializer):
if self.type == "badge":
related_object = BadgeSerializer(
read_only=True,
queryset=Badge.objects.get(id=self.related_id)
)
elif self.type == "friendreq":
related_object = FriendRequestSerializer(
read_only=True,
queryset=FriendRequest.objects.get(id=self.related_id)
)
class Meta:
model = Notification
This code does not work but hopefully it explains what I'm trying to accomplish and the direction I'm trying to go. Maybe that direction is completely wrong and I should be trying to accomplish this by using some other method.
Another option I tried would be to use a SerializerMethodField and perform this in a method, but that seemed not as clean for this case of trying to return a Serialized object based upon another field.
I believe what you're going to want to use is the .to_representation() approach mentioned here in the DRF documentation: http://www.django-rest-framework.org/api-guide/relations/#generic-relationships
Related
I am trying to understand how I should be structuring my post call when I want to do a many to many relationship.
class School(models.Model):
name = models.CharField(unique=True, max_length=30, blank=True)
teachersAtSchool = models.ManyToManyField('Teacher', blank=True)
class Teacher(models.Model):
account = models.ForeignKey(Account, default=1, on_delete=models.SET_DEFAULT)
schoolsTeachingAt = models.ManyToManyField('School', blank=True)
I send in the following JSON:
{
"name": "school Name",
"teachersAtSchool": 0
}
and get the following result:
{
"id": 13,
"name": "school Name",
"teachersAtSchool": []
}
Edit: Showing serialize
class SchoolModelSerializer (serializers.ModelSerializer):
class Meta():
model = models.School
fields = '__all__'
depth = 1
class TeacherModelSerializer (serializers.ModelSerializer):
class Meta():
model = models.Teacher
fields = '__all__'
depth = 1
I have checked there are indeed teachers in the database that I can get with my GET call. I even tried with different id's but I get the same result. Its possible this is something super simple I just don't understand. Please help me.
Regards
#simple_code said: "I don't think you need depth=1. As ModelSerializers do primary keys by default. – simple_code"
This resulted in it defaulting back to id which was the solution.
I found this answer out by following their suggestion "Also try and play with the serializer by itself because the json input it expects is the same as the json output it creates. So if you have a valid school with teachers, print what the serializer would output. That should give you valid input for the serializer. - simple_code" and noticing the json I received back was the whole serialized class.
Thank you everyone for you help and suggestions. I hope this post helps anyone.
Hello Stackoverflowers!
I've searched for a solution for this for a while, but have not been able to find anything addressing this usecase specifically.
Say we have the following models:
class Machine(models.model):
machine_name = models.CharField(max_length=30)
machine_data_template = models.JSONField()
class Event(models.model):
machine_name = models.ForeignKey(Machine, on_delete=models.CASCADE
machine_data = models.JSONField()
We have a machine for which there can be multiple events, the details of which are stored as JSON in a JSONField. The JSONField in event should be validated using the json-schema defined in Machine.machine_data_template.
I've looked at various Python/Django implementations of json-schemas, however I have not been able to find a way where we can define custom validation on a per-object basis. For example, solutions such as the one presented by Corwin Cole here defines the validation logic directly in the model, which would mean each instance of event would be subject to the same validation logic regardless of the machine:
MY_JSON_FIELD_SCHEMA = {
'schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'my_key': {
'type': 'string'
}
},
'required': ['my_key']
}
class MyModel(models.Model):
my_json_field = JSONField(
default=dict,
validators=[JSONSchemaValidator(limit_value=MY_JSON_FIELD_SCHEMA)]
)
Does anyone have any suggestion how I can achieve the desired "fully polymorphic" behavior?
BR - K
You can override the Model.clean() method and add custom validation there. You have access to the Event and it's related Machine so can access the schema. Add an example using jsonschema similar to the linked answer
import jsonschema
class Machine(models.Model):
machine_name = models.CharField(max_length=30)
machine_data_template = models.JSONField()
class Event(models.Model):
machine_name = models.ForeignKey(Machine, on_delete=models.CASCADE)
machine_data = models.JSONField()
def clean(self):
try:
jsonschema.validate(self.machine_data, self.machine_name.machine_data_template)
except jsonschema.exceptions.ValidationError as e:
raise ValidationError({'machine_data': str(e)})
I'm building a sample iOS app where users would buy and sell products, and I'm trying to design a page where the upper section will have basic profile details and the lower section will have the products that they're currently selling, something like this:
Sample Frontend Image
So I'm trying to create a Serializer / API endpoint which would give me the Profile, with the products that they are currently selling.
My Product model has a ForeignKey relationship to User:
class Product(models.Model):
def __str__(self):
return self.name
name = models.CharField(max_length=200)
seller = models.ForeignKey(User, on_delete=models.CASCADE, related_name="product_seller")
category = models.ForeignKey("Category", on_delete=models.CASCADE)
(...)
And I have a Profile model which has a one-to-one relationship with the default Django user. As this view will be based on Profiles, I think it would make more sense to serialize the User or the Profile model and get the products where they are the "seller".
So the JSON response that I want is something like this:
{
"id": 1,
"username": “some username”,
"profile_image": "http://192.168.1.101:8000/images/profile_pictures/732339C5-E419-4A3D-9022-A314416F5F02.png",
"description": “Some description for this particular profile.”
“products”: [
{ “id”: 1,
“image” = http://192.168.1.101:8000/images/abc.jpg,
},
{ “id”: 2,
“image” = http://192.168.1.101:8000/images/abc.jpg,
},
{ “id”: 3,
“image” = http://192.168.1.101:8000/images/abc.jpg,
}
]
}
What would be the best way to approach this using django-rest-framework?
I've tried using Nested Serializers, but my User or Profile models don't explicitly have a relationship to Product, so they haven't worked so far:
class SellerProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id", "images"]
class SellerSerializer(serializers.ModelSerializer):
products = SellerProductsSerializer(read_only=True, many=True)
profile_image = serializers.CharField(source="profile.profile_image")
description = serializers.CharField(source="profile.description")
class Meta:
model = User
fields = ["id", "username", "profile_image", "description", "products"]
I've also tried using the SerializerMethodField, which I think would work in this case, but I haven't quite figured out how I would filter the products where the particular user is the seller. The endpoints look like this:
.../application/api/seller/1
If I could access the "1" - which is the user id, I could filter with something like:
class SellerSerializer(serializers.ModelSerializer):
products = serializers.SerializerMethodField()
profile_image = serializers.CharField(source="profile.profile_image")
description = serializers.CharField(source="profile.description")
class Meta:
model = User
fields = ["id", "username", "profile_image", "description", "products"]
def get_products(self):
# get the id from the request / or the url
# filter the Product model.
I'm used to doing filtering on the viewsets, but not on the serializer itself. I think filtering on the viewset is not possible in this case, as I'm working with two different models and the one that I'm trying to filter is not the main model for the serializer.
I feel like there has to be a simple way to do this but I have been stuck for quite some time. Any help would be greatly appreciated.
You can retrieve a user with pk in the url,and return a response with that particular user, and the result of the following query:
Product.objects.filter(seller_id=pk)
And add the results to a list,and return a response that looks like this:
Response({
"Seller":Fetched User,
"Their Products":list of products
})
I have model with many links into it:
class Travel(BaseAbstractModel):
tags = models.ManyToManyField(
Tag,
related_name='travels',
)
owner = models.ForeignKey(
'users.TravelUser',
related_name='travel_owner'
)
payment = models.ForeignKey(
Payment,
related_name='travels',
)
country = models.ForeignKey(
Country,
related_name='travels,
)
........
Many of these models have only two fields with unique name and image.
I create serializer for each of these models and put them in TravelSerializer
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
Based on docs I override create() and update.
The problem is, when I sent JSON data, Django create each model from nested serializers. But I want to create only Travel instance. Also I want receive and respond serialized object not only pk field.
UPDATE
I solved this problem, put code in the answer. Now I can receive and respond with Serializer data without creating object.
But I think the DRF provides more elegant approach then I do. It is my first project with DRF, maybe I miss something and there's an easier solution.
I decide override to_internal_value() put it in custom serailizer and inherit all nested serializers from it:
class NestedRelatedSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
try:
pk = data['pk']
except (TypeError, KeyError):
# parse pk from request JSON
raise serializers.ValidationError({'_error': 'object must provide pk!'})
return pk
Get all pk from it and save in create and updated methods:
def update(self, instance, validated_data):
# If don't get instance from db, m2m field won't update immediately
# I don't understand why
instance = Travel.objects.get(pk=instance.pk)
instance.payment_id = validated_data.get('payment', instance.payment_id)
instance.country_id = validated_data.get('country', instance.country_id)
# update m2m links
instance.tags.clear()
instance.tags.add(*validated_data.get('tags'))
instance.save()
return instance
I'm not exactly sure I understand what you want to do, but could setting read_only_fields is the Meta class be what you need ?
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
class Meta:
read_only_fields = ('tags',)
See this section in the docs.
I am creating an api end point using django-rest-framework for a specific json input. I have two related models like so (let's assume post can only have one category):
class Category(models.Model):
name = models.CharField(max_length=10)
slug = models.SlugField()
class Post(models.Model):
category = models.ForeignKey()
title = models.CharField(max_length=100)
text = models.CharField(max_length=256)
and my serializer are simple model serializers:
class CategorySerializer(ModelSerializer):
id = serializers.IntegerField(required=True)
class Meta:
model = Category
class PostSerializer(ModelSerializer):
id = serializers.IntegerField(required=True)
category = CategorySerializer()
class Meta:
model = Post
and my api view is very simple also:
class PostAPIView(mixins.CreateModelMixin, GenericAPIView):
serializer_class = PostSerializer
permission_classes = (IsAuthenticated,)
now in order to create posts I need to parse a json input like this:
{
"id": 10,
"pk": 10
"title": "Some title",
"text": "Some text",
"category": {
"id": 15,
"pk": 15
"name": "Best category",
"slug": "best-category"
}
}
in here 'pk' parameters are crucial for me, I want data to be created on my db using exact pk provided in json. Now if I make a post request and there are no posts with id:10 and categories with id:15 all is fine and data is written to db new records get inserted, but if there are any when rest-framework returns an error like ['Post id 10 already exists'], I would like matching records to be updated according to input instead. How can I do that?
You just need to add the UpdateMixin, just import it like the CreateModelMixin.
This mixin will implement the update and partial update methods, that will do what you want.
But you can not send a POST, for it you will need a PUT, or PATCH. You you want to do this on POST, I recommend you to implement your own create view method.