serialize self-referential foreign key - python

Lets say I want to create an article that links via slug to one or multiple other articles (for now one).
A post request comes in giving me some article-info and the related slug.
I want to
validate the data and the related slug
create an article in the db
return the article (with the slug)
models.py
class Article(models.Model):
heading = models.CharField(max_length=2550)
slug = models.SlugField(null=True, default=None, unique=True, max_length=255)
article_previous = models.ForeignKey('self', to_field='slug', blank=True, null=True,
related_name='subcategories', on_delete=models.DO_NOTHING)
serializers.py
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = (
"heading",
"slug",
"article_previous",
def create(self, validated_data):
try:
article_previous = Article.objects.get(slug=d['value'])
article_previous_slug = article_previous.slug
except Article.DoesNotExist:
raise serializers.ValidationError(f'No Article with the slug {d["value"]}')
article=Article.objects.create(**validated_data,article_previous_id=article_previous_slug)
return validated_data
This solution gives:
article_previous = serializers.CharField()
ValueError: Cannot assign "'test_1'": "Article.article_previous" must be a "Article" instance.
This solution gives:
article_previous = serializers.CharField('article.article_previous')
TypeError: Article() got an unexpected keyword argument 'article'
If I use serializer.validated_data in views.py:
return Response(serializer.validated_data, status=status.HTTP_201_CREATED, headers=headers)
response = serializer.save()
return Response(response, status=status.HTTP_201_CREATED, headers=headers)
TypeError: Object of type Article is not JSON serializable.

You can serialize the slug by working with the article_previous_id field:
class ArticleSerializer(serializers.ModelSerializer):
article_previous = serializers.CharField(source='article_previous_id')
class Meta:
model = Article
fields = ('heading', 'slug', 'article_previous')
# …

class ArticleSerializer(serializers.ModelSerializer):
article_previous=serializers.SlugRelatedField(queryset=Article.objects.all(), slug_field="slug")
class Meta:
model = Article
fields = ('heading', 'slug', 'article_previous')
def create(self, validated_data):
article_previous=validated_data.pop("article_previous")
article=Article.objects.create(**validated_data, article_previous=article_previous)
validated_data.update( {"article_previous" : article.article_previous.slug} )
return validated_data
I think this solution has the advantage that the naming stays article_previous and is not changed to article_previous_id.

Related

Django 'model' object is not iterable when response

I have 2 model. And the two models are connected to the ManyToManyField.
models.py
class PostModel(models.Model):
id = models.AutoField(primary_key=True, null=False)
title = models.TextField()
comments = models.ManyToManyField('CommentModel')
class CommentModel(models.Model):
id = models.AutoField(primary_key=True, null=False)
post_id = models.ForeignKey(Post, on_delete=models.CASCADE)
body = models.TextField()
and serializers.py
class CommentModel_serializer(serializers.ModelSerializer):
class Meta:
model = MainCommentModel
fields = '__all__'
class PostModel_serializer(serializers.ModelSerializer):
comment = MainCommentModel_serializer(many=True, allow_null=True, read_only=True)
class Meta:
model = MainModel
fields = '__all__'
and views.py
#api_view(['GET'])
def getPost(request, pk):
post = PostModel.objects.filter(id=pk).first()
comment_list = CommentModel.objects.filter(post_id=post.id)
for i in comments_list:
post.comments.add(i)
serializer = PostModel_serializer(post, many=True)
return Response(serializer.data)
There is an error when I make a request.
'PostModel' object is not iterable
and The trackback points here.
return Response(serializer.data)
I tried to modify a lot of code but I can't find solutions.
Please tell me where and how it went wrong.
Referring from this thread, you should remove many=True in PostModel_serializer.
Also it should be comment_list not comments_list.
#api_view(['GET'])
def getPost(request, pk):
post = PostModel.objects.filter(id=pk).first()
comment_list = CommentModel.objects.filter(post_id=post.id)
for i in comment_list:
post.comments.add(i)
serializer = PostModel_serializer(post)
return Response(serializer.data)
I think you did wrong while creating ManyToManyField().
Instead of this:
comments = models.ManyToManyField('CommentModel') #Here you made mistake. you should not add single quote to CommentModel. I think that's why you got that error
Try this:
comments = models.ManyToManyField(CommentModel)

Django writable nested serializers, create method not working

I have a nested serializer, the create method does not work. I have searched over the internet and no method worked for me. The most popular one is the one listed below.
models.py
class Displays(models.Model):
id = models.CharField(primary_key=True, max_length=32,
default=generate_uuid)
name = models.CharField(max_length=45, blank=True, null=True)
class OrdersDj(models.Model):
id = models.CharField(primary_key=True, max_length=32,
default=generate_uuid)
class AdsDj(models.Model):
id = models.CharField(primary_key=True, max_length=32,
default=generate_uuid)
order = models.ForeignKey(
OrdersDj, on_delete=models.CASCADE, blank=False, null=True)
display = models.ForeignKey(
Displays, on_delete=models.CASCADE, blank=True, null=True)
serializers.py
class DisplaySerializer(serializers.ModelSerializer):
class Meta:
model = Displays
fields = "__all__"
class AdsSerializer(serializers.ModelSerializer):
display = DisplaySerializer()
def create(self, validated_data):
print("validated_data", validated_data)
display_id = validated_data.pop('display')
display = Displays.objects.get(id=display_id)
ad = Displays.objects.create(display=display, **validated_data)
return ad
class Meta:
model = Ads
fields = "__all__"
class OrderSerializer(serializers.ModelSerializer):
ads = AdsSerializer(source="adsdj_set", many=True)
def create(self, validated_data):
validated_data.pop('adsdj_set')
order = Orders.objects.create(**validated_data)
return order
class Meta:
model = Orders
fields = "__all__"
views.py
class AdsCreate(APIView):
def put(self, request):
print('request.data', request.data)
serializer = serializers.AdsSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
print('serializer.data > serializer.valid', serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED)
print('serializer.errors > serializer.invalid', serializer.errors)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
print outs
request.data <QueryDict: {'order': ['18bb2225cf6e407a943f2941072d06de'], 'display': ['91'], 'firstday': ['2021-12-4'], 'lastday': ['2021-12-21'], 'duration': ['17'], 'filetype': ['image/png'], 'originalname': ['Picture1.png'], 'price': ['2550'], 'status': ['0'], 'filehash': ['not available yet'], 'orderByNum': ['-1'], 'imgWidth': ['1061'], 'imgHeight': ['708'], 'image': [<InMemoryUploadedFile: Picture1.png (image/png)>]}>
serializer.errors > serializer.invalid {'display': [ErrorDetail(string='This field is required.', code='required')]}
I am giving the display field to the serializer that is the id of the display. In the create method I find that display and reference it in the new ad object. The display field is present in the request.data and passed to the serializer, the serializer complains that the display field is not present.
One version of sending json data
{"order":"18bb2225cf6e407a943f2941072d06de","display":{"id":91},"firstday":"2021-12-5","lastday":"2021-12-15","duration":11,"image":{},"filetype":"image/png","originalname":"Picture1.png","price":1650,"status":0,"filehash":"not available yet","orderByNum":-1,"imgWidth":1061,"imgHeight":708}
Try using different serializers for creation and representation.
class AdsCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Ads
fields = "__all__"
class AdsSerializer(serializers.ModelSerializer):
display = DisplaySerializer()
class Meta:
model = Ads
fields = "__all__"
For display field, AdsSerializer expects the following structure;
"display": {"id": 91}
whereas you send this;
"display": 91

Django - Serializer not setting ManyToManyField

For some reason the following code isn't setting the hash_tags attribute under Post. The way I checked was I put a breakpoint at the return Response line in view.py and I checked the newly created Post object and the hash_tags attribute just returned an empty list. Also when I read the serializer.data, hash_tags is an empty list as well. Even though the HashTag table clearly created the hash tag found in the body. What's going on?
model.py
class Post(AbstractBaseModel):
creator = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="post_creator")
join_goal = models.ForeignKey(JoinGoal, on_delete=models.CASCADE)
body = models.CharField(max_length=511, validators=[MinLengthValidator(5)])
hash_tags = models.ManyToManyField(HashTag)
type = models.CharField(
choices=PostType.choices,
max_length=50,
)
class HashTag(models.Model):
hash_tag = models.CharField(max_length=140, primary_key=True, validators=[
MinLengthValidator(1)])
Serializer.py
class HashTagSerializer(serializers.ModelSerializer):
class Meta:
model = HashTag
fields = ['hash_tag']
class PostSerializer(serializers.ModelSerializer):
hash_tags = HashTagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('creator', 'join_goal', 'body', 'uuid', 'created', 'type', 'updated_at', 'hash_tags')
view.py
#api_view(['POST'])
def post_create_update_post(request):
user_uuid = str(request.user.uuid)
request.data['creator'] = user_uuid
request.data['type'] = PostType.UPDATE
post_text = request.data['body']
hash_tags_list = extract_hashtags(post_text)
hash_tags = [HashTag.objects.get_or_create(hash_tag=ht)[0].hash_tag for ht in hash_tags_list]
request.data['hash_tags'] = hash_tags
try:
with transaction.atomic():
serializer = PostSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
post_obj = serializer.save()
except Exception as e:
return Response(dict(error=str(e),
user_message=error_message_generic),
status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)
I also tried not setting read_only=True and get this error
this leads to ValidationError({'hash_tags': [ErrorDetail(string='This field is required.', code='required')]}) with request.data['hash_tags']=['Test']
You set read_only=True This is why hash_tags value not saving on the database.
hash_tags = HashTagSerializer(many=True, read_only=True)
Declare it without read_only like this:
hash_tags = HashTagSerializer(many=True)
I believe that you have to override the create and update method for the nested serializer in serializer.py:
class HashTagSerializer(serializers.ModelSerializer):
class Meta:
model = HashTag
fields = ['hash_tag']
class PostSerializer(serializers.ModelSerializer):
hash_tags = HashTagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('creator', 'join_goal', 'body', 'uuid', 'created', 'type', 'updated_at', 'hash_tags')
def create(self, validated_data):
hash_tags_data = validated_data.pop('hash_tags')
post = Post.objects.create(**validated_data)
for data in hash_tags_data:
hash_tag = HashTag.objects.get_or_create(**data)
post.hash_tags.add(hash_tag)
post.save()
return post

Rest Framework cant save serializer with foreign key

I have next serializers:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
class Meta:
model = Post
fields = ['id', 'title', 'text', 'date', 'category']
And here is my view:
#api_view(['POST'])
def create_post(request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors)
return Response(serializer.data)
I want to create new Post object, but when I pass an category id at form it does not work, it is not saving my object. I tried to replace create method at my PostSerializer, to this:
def create(self, validated_data):
category_id = validated_data.pop('category')
post = Post.objects.create(**validated_data, category=category_id)
return post
but this dont work. Using postman formdata it is saying, that category field is required despite I filled it.
Here is my models:
class Category(models.Model):
name = models.CharField(max_length=512)
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=512)
text = models.TextField()
date = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category')
You need a category object not just an id, so try this
#api_view(['POST'])
def create_post(request):
category_id = request.data['category'] # or however you are sending the id
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
category = Category.objects.get(id=category_id)
serializer.save(category=category)
else:
return Response(serializer.errors)
return Response(serializer.data)
or you can do something similar in the create method of the serializer

Django REST - Create object with foreign key using serializers

I'm a little new to Django and Django-REST so please bear with me. Perhaps the answer is in the documentation, so if I missed it, apologies in advance.
Goal: I would like to create an EquipmentInfo object whose attributes include pre-existing foreign keys (EquipmentType and EquipmentManufacturer).
models.py
class EquipmentType(models.Model):
equipment_type = models.CharField(verbose_name="Equipment Type", max_length=50, unique=True)
def __unicode__(self):
return self.equipment_type
class EquipmentManufacturer(models.Model):
manufacturer_name = models.CharField(verbose_name="Manufacturer Name", max_length=50, unique=True)
def __unicode__(self):
return self.manufacturer_name
class EquipmentInfo(models.Model):
equipment_type = models.ForeignKey(EquipmentType, verbose_name="Equipment Type")
part_identifier = models.CharField(verbose_name="Machine ID (alias)", max_length=25)
manufacturer_name = models.ForeignKey(EquipmentManufacturer, verbose_name="Manufacturer Name")
serial_number = models.CharField(verbose_name="Serial Number", max_length=25)
date_of_manufacture = models.DateField(verbose_name="Date of Manufacture", default=date.today)
is_active = models.BooleanField(verbose_name="Is Active", default=True)
def __unicode__(self):
return self.part_identifier
serializers.py
class EquipmentTypeSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentType
fields = ('id', 'equipment_type',)
class EquipmentManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentManufacturer
fields = ('id', 'manufacturer_name',)
class EquipmentInfoSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentInfo
fields = ('id', 'equipment_type', 'part_identifier', 'manufacturer_name','serial_number', 'date_of_manufacture', 'is_active')
equipment_type = EquipmentTypeSerializer(many=False)
manufacturer_name = EquipmentManufacturerSerializer(many=False)
def create(self, validated_data):
equipment_type = validated_data.pop('equipment_type')
manufacturer_name = validated_data.pop('manufacturer_name')
equipment_info = EquipmentInfo.objects.create(**validated_data)
return equipment_info
Assuming I already have relevant EquipmentType and EquipmentManufacturer objects created, I would like to add another EquipmentInfo object. What is the appropriate way to set up my EquipmentInfo serializer so that I can pass in information such as
{
"equipment_type":{
"equipment_type":"already_created",
},
"part_identifier":"something_new",
"manufacturer_name":{
"manufacturer_name":"already_created"
},
"serial_number":"WBA1",
"date_of_manufacture": "1900-01-01",
"is_active":true
}
or even better:
{
"equipment_type":"already_created",
"part_identifier":"something_new",
"manufacturer_name":"already_created",
"serial_number":"WBA1",
"date_of_manufacture": "1900-01-01",
"is_active":true
}
Any help is appreciated.
I have also faced the problem ,and have solved it ,the following is my step ,hope it will be helpful
1.company Model and contact model as follows:
class Company(models.Model):
Company_Name = models.CharField(u'Company Name',max_length=255, default="")
Modified_By = models.CharField(u'Modified By',max_length=255, default="")
class Company_Contact(models.Model):
Company = models.ForeignKey(Company)
Last_Name = models.CharField(u'Last Name',max_length=255, default="")
First_Name = models.CharField(u'First Name',max_length=255, default="")
2.I create A New Serializer Named CompanyReferenceSerializer,and company_contact
class CompanyReferenceSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ['id', 'Company_Name', 'Company_Name_SC']
class CompanyContactSerializer(serializers.ModelSerializer):
Company = CompanyReferenceSerializer()
class Meta:
model = Company_Contact
fields = ['Company', 'Last_Name','First_Name']
extra_kwargs = {
'Company': {'allow_null': True, 'required': False},
'Last_Name': {'allow_null': True, 'allow_blank': True, 'required': False},
'First_Name': {'allow_null': True, 'required': False, 'allow_blank': True},
}
3.Viewset as follows,in the backend,I get the object Namedcompany_instanceaccording to the 'company_id'
class CompanyContactViewSet(viewsets.ModelViewSet):
serializer_class = CompanyContactSerializer
def create(self, validated_data):
serializer = self.get_serializer(data=self.request.data)
company_id_for_contact = self.request.data.pop('Company_id')
company_instance = Company.objects.filter(id=company_id_for_contact).first()
if not serializer.is_valid():
print serializer.errors
data = serializer.validated_data
serializer.save(Company=company_instance)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
and I success insert one record in the company_contact ,Hope it can help you !
Using nested serializers makes it really hard for posts (if it even works, as it didn't used to work), and given your simple models, I would recommend just removing them.
I will recommend you add APIs for
/api/v1/type
/api/v1/manufacturer
/api/v1/info
(or whatever names you want to use). The type and manufacturer ones should be vanilla views and using your existing serializers.
For info, remove the two nested serializers:
class EquipmentInfoSerializer(serializers.ModelSerializer):
class Meta:
model = EquipmentInfo
fields = ('id', 'equipment_type', 'part_identifier', 'manufacturer_name','serial_number', 'date_of_manufacture', 'is_active')
After that, you should be able to do post using:
data = {
"equipment_type": 5, # The ID of the equipment type record
"part_identifier":"something_new",
"manufacturer_name": 10 # The ID of the manufacturer record
"serial_number":"WBA1",
"date_of_manufacture": "1900-01-01",
"is_active":true
}
In my case, I do like making it the GETs more convenient so I add read-only fields to return a name (or even the whole serialized record):
class EquipmentInfoSerializer(serializers.ModelSerializer):
type_name = serializers.SerializerMethodField(read_only=True)
class Meta:
model = EquipmentInfo
fields = ('id', 'equipment_type', 'part_identifier', 'manufacturer_name','serial_number', 'date_of_manufacture', 'is_active')
def get_type_name(self, obj):
return obj.equipment_type.equipment_type
Hope this helps.

Categories

Resources