Creating a Serialiser for a Reverse Generic Relationship - python

Within my Page Serialisers PageSerializer shown below, I would like to get the Generic relationship from Collection and
show all Collection Items (which is a many-to-many) nested within PageSerializer.
I want to achieve an output something like this....
"results": [
{
"url": "http://127.0.0.1:8000/v1/page/00c8015e-9b03...",
"title": "Test Page",
"collections":
{
"title": "Test Collection",
[
{
"image": "http://www.demo.com/test.png",
"video": None
},
{
"image": None,
"video": "http://www.demo.com/test.mpeg"
}
]
}
}
]
}
This is what I currenrlty have....
class Page(models.Model):
title = models.CharField(max_length=80)
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = ("title", )
class Collection(models.Model):
title = models.CharField(max_length=80, help_text="Title of collection")
content_type = models.ForeignKey(ContentType)
object_id = models.UUIDField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
collection_items = models.ManyToManyField('collections.CollectionItem')
class CollectionItem(models.Model):
image = models.ImageField(upload_to='/test')
video = models.URLField(max_length=512, blank=True, null=True)
Becuase the Generic relationship is on the Collection Model how can this be done in DRF?
I was thinking of creating a method on the Page model itself which gets the data and adding this to the serializer.
I'm sure there is a better way. I have looked at http://www.django-rest-framework.org/api-guide/relations/#generic-relationships
but it only describes getting the relationship the other way around.

Create a GenericRelation on your Page model for example:
class Page(models.Model):
title = models.CharField(max_length=80)
stuff = GenericRelation('app_name_model_here')
Then used your nested serializer like this...
class PageSerializer(serializers.ModelSerializer):
stuff = YOURColltionserializer(many=True)
class Meta:
model = Page
fields = ("title", "stuff" )
Once you have defined your YOURColltionserializer this will work as excepted.

First of all, your JSON is incorrect, the propher way is something like this:
{
"results":[
{
"url":"http://127.0.0.1:8000/v1/page/00c8015e-9b03...",
"title":"Test Page",
"collections":{
"title":"Test Collection",
"collection_items":[
{
"image":"http://www.demo.com/test.png",
"video":null
},
{
"image":null,
"video":"http://www.demo.com/test.mpeg"
}
]
}
}
]
}
To achieve this You should define serializer like this:
class CollectionItemSerializer(serializers.ModelSerializer):
class Meta:
model = CollectionItem
fields = ['image', 'video']
class CollectionSerializer(serializers.ModelSerializer):
collection_items = CollectionItemSerializer(many=True, read_only=True)
class Meta:
model = Collection
fields = ['title', 'collection_items']
class PageSerializer(serializers.Serializer):
title = serializes.CharField(max_length=200)
url = serializers.SerializerMethodField()
collections = CollectionSerializer(many=true, read_only=True)
def get_url(self, obj):
return self.context.request.path_info
class ResultSerializer(serializers.Serializer):
result = PageSerializer(read_only=True)
Then in your view:
page = Page.objects.get(...)
collections = Collection.objects.filter(...)
result = {
'title': page.title,
'collections':collections
}
serializer = ResultSerializer(
{'result':result},
context={'request': request}
}
return Response(serializer.data)

Related

django drf about many-to-many RelatedField custom field

I use the latest version of The Django REST Framework,
and The table in model is many-to-many related
My current model code looks like this:
model.py
class LvsDeploy(models.Model):
MASTER = 'MASTER'
BACKUP = 'BACKUP'
ROLE_CHOICES = (
(MASTER, 'MASTER'),
(BACKUP, 'BACKUP')
)
id = models.AutoField(primary_key=True)
cluster_name = models.CharField(max_length=30)
dip = models.CharField(max_length=15, verbose_name='DR IP')
role = models.CharField(max_length=10, choices=ROLE_CHOICES, verbose_name="role")
class LoadBalance(models.Model):
id = models.AutoField(primary_key=True)
lb_name = models.CharField(max_length=30, verbose_name='load balance')
cluster = models.ManyToManyField('LvsDeploy',related_name='cluster')
vip = models.CharField(max_length=50, verbose_name='VIP')
port = models.IntegerField(verbose_name='port')
def __str__(self):
return self.lb_name
My serializer code looks like this:
serializers.py
class LvsDeployReadOnly(serializers.ModelSerializer):
class Meta:
model = LvsDeploy
fields = '__all__'
class LoadBalanceSerializer(serializers.ModelSerializer):
cluster_name = serializers.RelatedField(source='cluster.name',read_only=True)
cluster = LvsDeployReadOnly(many=True)
##rs = RSInfoSerializer(many=True, read_only=True)
class Meta:
model = LoadBalance
fields = '__all__'
##extra_fields = ['rs','cluster','cluster_name']
extra_fields = ['rs','cluster','cluster_name']
My views code:
class BalanceList(views.APIView):
def get(self, request):
queryset = LoadBalance.objects.all()
serializer = LoadBalanceSerializer(queryset, many=True)
print(serializer.data)
return Response(serializer.data)
and request actual output:
[
{
"id": 2,
"cluster_name": null,
"cluster": [
{
"id": 1,
"cluster_name": "lvs_sz01",
"dip": "1.1.1.6",
"role": "BACKUP",
},
{
"id": 2,
"cluster_name": "lvs_sz01",
"dip": "1.1.1.5",
"role": "BACKUP",
}
],
"lb_name": "lb001",
"vip": "1.1.1.1",
"port": 80,
}
]
But the cluster_name filed value is same in the dictionary of lists .
I want the output to look like this:
[
{
"id": 2,
"cluster_name": "lvs_sz01",
"cluster": [
{
"dip": "1.1.1.6",
"role": "BACKUP",
},
{
"dip": "1.1.1.5",
"role": "BACKUP",
}
],
"lb_name": "lb001",
"vip": "1.1.1.1",
"port": 80,
}
]
How should I change it? Can you help me ?
I solved it myself with the following method:
class LoadBalanceSerializer(serializers.ModelSerializer):
cluster_name = serializers.SerializerMethodField()
cluster = LvsDeployReadOnly(many=True, read_only=True)
rs = RSInfoSerializer(many=True, read_only=True)
class Meta:
model = LoadBalance
fields = '__all__'
extra_fields = ['rs','cluster','cluster_name']
def get_cluster_name(self,obj):
print(type(obj.lb_name))
lvs_cluster = obj.cluster.all()
cluster_name = [ i.cluster_name for i in lvs_cluster ][0]
print(cluster_name)
return cluster_name
Thanks!

Django Rest Framework - Group data by its parent tag

Django Rest Framework - Group data by its parent tag
I have 3 serializers, One for tactics, one for techniques, one for sub techniques, and I'll be adding the sub techniques serializer as an explicit field to the techniques serializer.
models
class Tag(models.Model):
_id = models.CharField(max_length=10)
name = models.CharField(max_length=100)
def __str__(self):
return self._id
def __unicode__(self):
return self._id
class ModelA(models.Model):
_id = models.CharField(max_length=10)
title = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
queries = ArrayField(models.TextField(),null=True, blank=True, default=list)
def __str__(self):
return self._id
def __unicode__(self):
return self._id
class ModelB(models.Model):
subtitle = models.ForeignKey(ModelA, on_delete=models.CASCADE)
def __str__(self):
return self.subtitle
serializers
# serializers
class TagSerializer(serializers.ModelSerializer):
def to_representation(self, value):
print(value)
return value.name
class Meta:
model = Tag
fields = ('name',)
class QueriesSerializer(serializers.ModelSerializer):
class Meta:
model = ModelB
fields = '__all__'
class ASerializer(serializers.ModelSerializer):
queries = QueriesSerializer(source='modelb_set', many=True)
tag = TagSerializer(many=True)
class Meta:
model = ModelA
fields = ('_id','title', 'tag','queries',)
class EndUserSerializer(serializers.ModelSerializer):
data = ??
class Meta:
model = ??
fields = '__all__'
I want to create a data visualization API where I need to use the results of ASerializer by their given tag in order to categorize each object into categories based on their associated tag if the object contains two tags, then it should be displayed in both . e.g
desired response
{
[
{
"tag": "Biologyl,
"data": [
{
"_id": str
"title": str
"tag": []
"queries": []
}
]
},
{
"tag": "Denoi2,
"data": [
{
"_id": str
"title": str
"tag": []
"queries": []
}
]
},
]
}
actual response
{
"_id": str
"title": str
"tag": []
"queries": []
}
viewset
#viewsets
class BooksViewSet(mixins.ListModelMixin,
viewsets.GenericViewSet):
queryset = ??.objects.all()
serializer_class = EndUserSerializer
You can use django-rest-multiple-models for receiving multiple querysets in the same view.
https://github.com/MattBroach/DjangoRestMultipleModels
It should look like this:
class BooksViewSet(ObjectMultipleModelAPIView):
querylist = [
{
'querylist': ModelA.objects.filter(tags__name="Biologyl"),
'serializer_class': EndUserSerializer,
'label': 'Biologyl',
},
{
'querylist': ModelA.objects.filter(tags__name="Denoi2"),
'serializer_class': EndUserSerializer,
'label': 'Denoi2',
},
....
]
You can also make it dynamic, but it seems a little bit dangerous👀.
class BooksViewSet(ObjectMultipleModelAPIView):
querylist = [
{
'querylist': ModelA.objects.filter(tags=tag),
'serializer_class': EndUserSerializer,
'label': tag.name,
}
for tag in Tag.objects.all()
]
See more https://django-rest-multiple-models.readthedocs.io/en/latest/object-options.html

django.db.utils.IntegrityError: NOT NULL constraint failed fom Postman

I am trying to create a simple model with foreign keys using Django rest framework.
This are the models:
class Route(models.Model):
place_origin = models.ForeignKey(
Place, null=False, on_delete=models.DO_NOTHING)
class Place(models.Model):
name = models.CharField(max_length=50, null=False)
This are the serializers for each model:
class PlaceSerializer(serializers.ModelSerializer):
class Meta:
model = Place
fields = ["id", "name"]
class RouteSerializer(serializers.ModelSerializer):
place_origin = PlaceSerializer(many=False, read_only=True)
class Meta:
model = Route
fields = ["id", "place_origin"]
This RouteSerializer has the place_origin property in order to show the place details(all the fields from it) when I am looking at the route detail. What I mean is for routes I want to display:
[
{
"id": 1,
"place_origin": {
"id": 1,
"name": "New york"
}
},
{
"id": 2,
"place_origin": {
"id": 2,
"name": "Boston"
}
}
]
And not just:
[
{
"id": 1,
"place_origin": 1
},
{
"id": 2,
"place_origin": 2
}
]
This is the view:
#api_view(['POST'])
def routes_new_route_view(request):
"""
Create a new route
"""
if request.method == "POST":
data = JSONParser().parse(request)
place_origin = Place.objects.get(id=data["place_origin"])
data["place_origin"] = PlaceSerializer(place_origin)
data["place_origin"] = data["place_origin"].data
serializer = RouteSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status=201)
else:
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I want to send the request from postman this way:
{
"place_origin": 3
}
But I am getting the error from the title.
Thanks for all the help!
The error is that you're trying to send data via a PlaceSerializer but this field is read_only. On the other hand, your DB expects place_origin since you precised null=False in your model. Both combined gives the error "Not NULL constraint failed".
The easiest way is to slightly modify your serializer in order to have one field for write and another for read.
class RouteSerializer(serializers.ModelSerializer):
place_origin = PlaceSerializer(many=False, read_only=True)
place = serializers.PrimaryKeyRelatedField(source="place_origin",queryset=Place.objects.all(),write_only=True)
class Meta:
model = Route
fields = ["id", "place_origin", "place"]
Here, you will use place field as a way to create the relationship with your Route instance.

Django Rest Framework: Nested serializers returning a dictionary but list is desired

I am trying to implement a basic Question Answer app. Here is my Questions model:
class Questions(models.Model):
created = models.DateTimeField(auto_now_add=True)
question = models.CharField(max_length=1000, blank=True, default='')
asked_by = models.ForeignKey(User, related_name='question_user', null=True, default=None, on_delete=models.CASCADE)
upvote_by = models.ManyToManyField(User, default=None, related_name='advicesv1_upvote_question')
Now, I want to send the usernames/first_names of the people in 'asked_by' and 'upvote_by' instead of ids which is sent by default. To implement this I used a nested serializer like this:
class QuesUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username',)
class NewQuestionSerializer(serializers.ModelSerializer):
question_id = serializers.IntegerField(source='id', required=False)
asked_by = QuesUserSerializer(read_only=True)
upvote_by = QuesUserSerializer(many=True, read_only=True)
class Meta:
model = Questions
fields = ('question_id', 'created', 'question', 'asked_by', 'upvote_by',)
due to this, my current response is like this:
{
"question_id": 182,
"created": "2017-03-07T10:53:16.241533Z",
"question": "hey what's up?",
"asked_by": {
"username": "testuser141"
},
"upvote_by": [
{
"username": "testuser"
},
{
"username": "testuser1"
}
]
},
But my desired response is:
{
"question_id": 182,
"created": "2017-03-07T10:53:16.241533Z",
"question": "hey what's up?",
"asked_by": "testuser141",
"upvote_by": ["testuser", "testuser1",....],
},
What is the best way to achieve this using django restframework serializers?
You can override the to_representation method of the nested serializer so that your QuesUserSerializer returns a string for each user, rather than an object.
class QuesUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def to_representation(self, obj):
return obj.username
try to override to representation method, like this:
class NewQuestionSerializer(serializers.ModelSerializer):
question_id = serializers.IntegerField(source='id', required=False)
asked_by = QuesUserSerializer(read_only=True)
upvote_by = QuesUserSerializer(many=True, read_only=True)
class Meta:
model = Questions
fields = ('question_id', 'created', 'question', 'asked_by', 'upvote_by',)
def to_representation(self, obj):
data = super(NewQuestionSerializer, self).to_representation(obj)
data['asked_by']= obj.asked_by.username
data['upvoted_by'] = obj.upvote_by.all().values_list('username', flat=True)
return data
this only changue the representation of object, but not the logic of save o update...
Although natbot1 answer works, a more elegant solution is to use a slugrelatedfield

django-rest-framework many-to-many relations. Create if not exists

I try to send the following data to my django application:
{
"hashtags": ["hashtag"],
"title": "title",
"message": "message"
}
and i get this response:
{
"hashtags": [
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
]
}
I have the following view defined in views.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = IsAuthorizedOwnerOrReadOnly,
the models are defined like this:
class Post(models.Model):
ambassador = models.OneToOneField("User")
publish_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
hashtags = models.ManyToManyField("PostHashtags", related_query_name="posts")
title = models.CharField(max_length=TEXT_SHORT, null=True, blank=True)
message = models.CharField(max_length=TEXT_MIDDLE, null=True, blank=True)
class PostHashtags(models.Model):
hashtag = models.CharField(max_length=TEXT_SHORT, null=False)
def __unicode__(self):
return self.hashtag
and i define the serializers like this:
class PostHashtagSerializer(serializers.ModelSerializer):
class Meta:
model = PostHashtags
fields = ("hashtag",)
class PostSerializer(serializers.ModelSerializer):
hashtags = PostHashtagSerializer(many=True)
class Meta:
model = Post
fields = ("id", "hashtags", "title", "message",)
read_only_fields = ("id", 'account_name',)
It seems like the hashtags are not created automatically using my current serialisation config. Is there a way to have my hashtags created if they do not yet exist, and if they do exist, have the Post use that same hashtag? In that case, how should my serialisers be defined?
EDIT 1:
After GwynBleidD's suggestions I now get the following error:
The `.create()` method does not support writable nestedfields by default.
Write an explicit `.create()` method for serializer PostSerializer , or set `read_only=True` on nested serializer fields.
Does anyone have a suggestion for such a create method?
EDIT 2: solved it using the following serialisers
class PostHashtagSerializer(serializers.ModelSerializer):
hashtag = serializers.CharField()
class Meta:
model = PostHashtags
fields = ("hashtag",)
class PostSerializer(serializers.ModelSerializer):
hashtags = PostHashtagSerializer(many=True,)
class Meta:
model = Post
fields = ("ambassador", "hashtags", "title", "message",)
def create(self, validated_data):
hashtags_data = validated_data.pop('hashtags')
post = Post.objects.create(**validated_data)
for hashtag in hashtags_data:
ht = PostHashtags.objects.create()
ht.hashtag = hashtag.get("hashtag")
ht.save()
post.hashtags.add(ht)
post.save()
return post
Hashtags are not string, but dict in that example. You have to submit:
{
"hashtags": [{"hashtag": "hashtag"}],
"title": "title",
"message": "message"
}

Categories