I have one-to-many relation Developer and Constructions
I want to serialize all Developer field when I serialize Construction object
For request
{
"developer": 1,
"name": "as2",
"address": "ZZZZZZZZZZZZZsdf",
"deadline": "2020-05-13 14:26:58",
"coordinates": { "latitude": 49.8782482189424, "longitude": 24.452545489 }
}
I have an error:
{
"developer_data": [
"This field is required."
]
}
It's strange for me because developer_data marked as read_only
How can I fix the error? I thin that problem deals with serializer
models.py
class Developer(models.Model):
name = models.CharField(max_length=100)
...
def name_image(instance, filename):
return '/'.join(['images', str(instance.name), filename])
class Construction(models.Model):
developer = models.ForeignKey(
Developer, related_name="constructions", on_delete=models.CASCADE
)
name = models.CharField(max_length=100)
image = models.ImageField(upload_to=name_image, blank=True, null=True)
...
serializers.py (UPDATED)
class DeveloperSerializer(serializers.ModelSerializer):
constructions_number = serializers.SerializerMethodField()
workers_number = serializers.SerializerMethodField()
machines_number = serializers.SerializerMethodField()
class Meta:
model = Developer
fields = ('id', 'name', 'constructions_number', 'workers_number', 'machines_number')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
def get_constructions_number(self, obj):
return obj.constructions.count()
def get_workers_number(self, obj):
res = obj.constructions.aggregate(Sum('workers_number'))['workers_number__sum']
if res:
return res
return 0
def get_machines_number(self, obj):
res = obj.constructions.aggregate(Sum('machines_number'))[ "machines_number__sum"]
if res:
return res
return 0
class ConstructionSerializer(serializers.ModelSerializer):
coordinates = PointField()
deadline = serializers.DateTimeField(format=TIME_FORMAT)
cameras_number = serializers.SerializerMethodField()
developer = DeveloperSerializer()
class Meta:
model = Construction
fields = (
'id', 'developer', 'name', 'image', 'address', 'coordinates', 'deadline',
'workers_number', 'machines_number', 'cameras_number',
)
read_only_fields = ('workers_number', 'machines_number', 'cameras_number')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
def get_cameras_number(self, obj):
return obj.cameras.count()
I use standard ModelViewSet for the models in views.py and i think that problem in serializers.py
You are using the developer serializer wrong, change the ConstructionSerializer to this
class ConstructionSerializer(serializers.ModelSerializer):
coordinates = PointField()
deadline = serializers.DateTimeField(format=TIME_FORMAT)
cameras_number = serializers.SerializerMethodField()
developer = DeveloperSerializer()
class Meta:
model = Construction
fields = (
'id', 'developer', 'name', 'image', 'address', 'coordinates', 'deadline',
'workers_number', 'machines_number', 'cameras_number',
)
read_only_fields = ('workers_number', 'machines_number', 'cameras_number', 'developer') # if you don't want developer to be read only then remove it from there
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
def get_cameras_number(self, obj):
return obj.cameras.count()
Related
I set my default value of publish to True in the model, but after creating a new entry in the EntrySerializer it saves as False. I can manually correct this by overriding the create() method in the serializer, but I'm wondering it there's a more elegant way.
class EntrySerializer(serializers.ModelSerializer):
class Meta:
model = Entry
fields = '__all__'
read_only_fields = ['author', 'slug', 'created', 'modified']
My model
class Entry(models.Model):
slug = models.SlugField(max_length=255)
title = models.CharField(max_length=200)
description = models.TextField()
publish = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
if not self.id:
slug_str = "%s %s" % ( uuid4().hex[:6].upper(), self.title)
self.slug = slugify(slug_str)
super(Entry, self).save(*args, **kwargs)
Results of post request
{
"id": 9,
"slug": "f4eabc-new-test",
"title": "new test",
"description": "dsfsdfs",
"publish": false,
"created": "2022-02-22T03:12:52.479158Z",
"modified": "2022-02-22T03:12:52.479190Z",
}
I found that I needed to add 'publish' to read_only_fields
class EntrySerializer(serializers.ModelSerializer):
class Meta:
model = Entry
fields = '__all__'
read_only_fields = ['author', 'publish', 'slug', 'created', 'modified']
I have a OneToMany relation. One Construction and many Cameras.
I want to return all Building object fields in CameraSerializer
Problem
When I perform POST request (create new Camera object)
{
"name": "CameraName",
"url": "CameraUrl",
"building": 2
}
I have an error
{
"building": {
"nonFieldErrors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
Reason of error -- Django expects FULL Construction object, but I want to set only ID
How can I fix the error?
models.py
class Construction(models.Model):
""" Объект строительства"""
developer = models.ForeignKey(
Developer, related_name="constructions", on_delete=models.CASCADE
)
name = models.CharField(max_length=100)
plan_image = models.ImageField(upload_to=name_image, blank=True, null=True)
...
def __str__(self):
return self.name
class Camera(models.Model):
building = models.ForeignKey(
Construction, related_name="cameras", on_delete=models.CASCADE
)
name = models.CharField(max_length=100)
url = models.CharField(max_length=100)
...
def __str__(self):
return self.name
serializers.py
class ConstructionSerializer(serializers.ModelSerializer):
coordinates = MyPointField()
deadline = serializers.DateTimeField(format=TIME_FORMAT)
cameras_number = serializers.SerializerMethodField()
developer_name = serializers.SerializerMethodField()
events = serializers.SerializerMethodField()
class Meta:
model = Construction
fields = (
'id', 'developer', 'developer_name', 'name', 'plan_image', 'address', 'coordinates', 'deadline',
'workers_number', 'machines_number', 'cameras_number', 'events'
)
read_only_fields = ('workers_number', 'machines_number', 'cameras_number', 'events')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
return instance
class CameraSerializer(serializers.ModelSerializer):
frames = FrameSerializer(many=True, read_only=True)
building = ConstructionSerializer()
class Meta:
model = Camera
fields = (
'id', 'building', 'name', 'url', 'zone_id_x', 'zone_id_y',
'proc_id', 'path_frames', 'frames'
)
read_only_fields = ('proc_id', 'path_frames', 'frames')
def create(self, validated_data):
instance = super().create(validated_data=validated_data)
instance.set_proc_id()
instance.set_path_frame()
return instance
views.py
class CameraView(viewsets.ModelViewSet):
serializer_class = CameraSerializer
queryset = Camera.objects.all()
def get_camera_create_serializer(self, *args, **kwargs):
kwargs["context"] = self.get_serializer_context()
return self.serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
serializer = self.get_camera_create_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = {"result": serializer.data}
return Response(
response, status=status.HTTP_201_CREATED, headers=headers
)
I have model and serializer, there is ArrayField(postgres) in that model.
Now I wanted to create a serializer field that will receive list [1,2] and save it to object, but for a list and detail in serializer to show a list of JSON objects.
Model:
class User(models.Model):
email = models.EmailField('Email', unique=True, blank=False)
full_name = models.CharField(
'Full name', max_length=150, blank=True, null=True)
roles = ArrayField(
models.PositiveSmallIntegerField(),
default=list,
blank=True
)
Serializer:
class ArraySerializerField(ListField):
def __init__(self, queryset, serializer_class):
super(ArraySerializerField, self).__init__()
self.queryset = queryset
self.serializer_class = serializer_class
def to_representation(self, value):
if value:
qs = self.queryset.filter(pk__in=value)
return self.serializer_class(qs, many=True).data
return []
def to_internal_value(self, value):
super(ArraySerializerField, self).to_internal_value(value)
print(value) # [1, 2]
return value
class UserSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
roles = ArraySerializerField(queryset=Role.objects.all(), serializer_class=RoleSerializer)
class Meta:
model = User
fields = ('id', 'email', 'full_name', 'roles')
def create(self, validated_data):
print(validated_data)
# {'email': 'test#test.com', 'full_name': 'Test', 'roles': []}
user = super(UserSerializer, self).create(validated_data)
return user
Now when I do list or detail request, everything is ok, I get a list of roles as JSON.
But when I try to POST data and send with this data:
{
"email": "test#test.com",
"full_name": "Test",
"roles": [1, 2]
}
validated_data in create method shows roles always as [] and object is saved without roles, but print from to_internal_value shows [1, 2].
What am I doing wrong? It should save sent data because to_internal_value works fine.
EDIT:
GET and LIST response gives me right format:
{
"id": 1,
"email": "test#test.com",
"full_name": "Test",
"roles": [
{
"id": 1,
"name": "Role 1"
},
{
"id": 2,
"name": "Role 2"
}
]
}
Have you tried this?
class UserSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
roles = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, required=False)
class Meta:
model = User
fields = ('id', 'email', 'full_name', 'roles')
def create(self, validated_data):
# check validated_data here
...
Note
I'm not sure about the nature of SerializerExtensionsMixin class here. Also I'm not sure the intention behind the queryset and serializer_class arguments of your custom ListField
Django Shell Output
In [7]: from rest_framework import serializers
In [8]: class UserSerializer(serializers.Serializer): # Created a simple serializer without model
...: roles = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, required=False)
...: email = serializers.EmailField()
...: full_name = serializers.CharField()
...:
In [9]: data = { # your data
...: "email": "test#test.com",
...: "full_name": "Test",
...: "roles": [1, 2]
...: }
In [10]: u = UserSerializer(data=data)
In [11]: u.is_valid()
Out[11]: True
In [12]: u.data # got valid data
Out[12]: {'roles': [1, 2], 'email': 'test#test.com', 'full_name': 'Test'}
In [13]: data["roles"] = [] # change data to accept empty list
In [14]: u = UserSerializer(data=data)
In [15]: u.is_valid()
Out[15]: True
In [16]: u.data # got validated data with empty list
Out[16]: {'roles': [], 'email': 'test#test.com', 'full_name': 'Test'}
In [17]: data["roles"] = ["foo","bar"] #added string to the list
In [18]: u = UserSerializer(data=data)
In [19]: u.is_valid() # validation failed
Out[19]: False
In [20]: u.errors
Out[20]: {'roles': {0: [ErrorDetail(string='A valid integer is required.', code='invalid')], 1: [ErrorDetail(string='A valid integer is required.', code='invalid')]}}
UPDATE-1
Create a RoleSerializer and use it in the UserSerializer
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
fields = ('id', 'name')
class UserSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
roles = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, required=False)
class Meta:
model = User
fields = ('id', 'email', 'full_name', 'roles')
def create(self, validated_data):
# check validated_data here
...
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['roles'] = RoleSerializer(Role.objects.filter(id__in=rep['roles']), many=True).data
return rep
UPDATE-2
Using a custom array field
class ArrayField(serializers.ListField):
def __init__(self, *args, **kwargs):
self.queryset = kwargs.pop('queryset', None)
self.serializer_class = kwargs.pop('serializer_class', None)
super().__init__(*args, **kwargs)
def to_representation(self, data):
qs = self.queryset.filter(id__in=data)
serializer = self.serializer_class(qs,many=True)
return serializer.data
class UserSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
roles = ArrayField(queryset=Role.objects.all(), serializer_class=RoleSerializer)
class Meta:
model = User
fields = ('id', 'email', 'full_name', 'roles')
def create(self, validated_data):
# check validated_data here
...
Try switching to PrimaryKeyRelatedField. Though you'll need to change your user model to use an actual relation. Which is generally a good idea because it'll help enforce data integrity with your project.
class User(models.Model):
email = models.EmailField('Email', unique=True, blank=False)
full_name = models.CharField(
'Full name', max_length=150, blank=True, null=True)
roles = models.ManyToManyField(Role, blank=True)
class UserSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
roles = serializers.PrimaryKeyRelatedField(
many=True,
queryset=Role.objects.all(),
)
class Meta:
model = User
fields = ('id', 'email', 'full_name', 'roles')
def create(self, validated_data):
print(validated_data)
# {'email': 'test#test.com', 'full_name': 'Test', 'roles': []}
user = super(UserSerializer, self).create(validated_data)
return user
I'm working on ModelSerializer I'm facing these below issues.
1) Unable to validate .validate(self, data) method as I want to return a custom validator message which is not working.
model.py
class BlogModel(models.Model):
BLOG_STATUS = (
('PUBLISH', 'Publish'),
('DRAFT', 'Draft'),
)
blog_id = models.AutoField(primary_key=True)
user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='blogs')
title = models.CharField(max_length=255)
content = models.TextField(blank=True, null=True)
status = models.CharField(max_length=7, choices=BLOG_STATUS)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta():
db_table = 'blogs'
verbose_name = 'Blog'
verbose_name_plural = 'Blogs'
def __str__(self):
return self.title
serializers.py
import datetime
from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer,Serializer
from blogs.models import BlogModel, BlogFilesModel
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = '__all__'
# exclude = ['password']
class BlogFilesSerializer(ModelSerializer):
count_files = serializers.IntegerField(required=False)
class Meta:
model = BlogFilesModel
fields = ('blog_files_id', 'blog', 'path',
'created_at', 'updated_at', 'count_files')
def get_path(self, obj):
formatted_date = obj.created_at.strftime("%d-%m-%Y")
return formatted_date
class BlogSerializer(ModelSerializer):
blog_files = serializers.SerializerMethodField()
def get_blog_files(self, obj):
info = BlogFilesSerializer(BlogFilesModel.objects.filter(
blog=obj).order_by('-pk'), many=True)
if info.data:
for i in info.data:
user_detail = User.objects.get(pk=obj.user.id)
i.__setitem__('user_detail', UserSerializer(user_detail).data)
if i.get('user_detail'):
try:
del i['user_detail']['password']
except expression as identifier:
pass
return info.data
blog_created_at=serializers.SerializerMethodField()
def get_blog_created_at(self, obj):
formatted_date=obj.created_at.strftime("%d-%m-%Y")
return formatted_date
def validate(self, data):
if data.get('title') == "":
#this below error message not showing
raise serializers.ValidationError("Title field must not be left blank.")
return data
def validate_title(self, value):
if value != "Hello":
raise serializers.ValidationError("Title field must be 'Hello' ")
return value
def validate_content(self, value):
if len(value) < 2:
raise serializers.ValidationError("Content must have at least 2 letters")
return value
class Meta:
model=BlogModel
fields=('blog_id', 'user', 'title', 'content',
'status', 'blog_files', 'blog_created_at')
views.py
class BlogViewSet(viewsets.ModelViewSet):
queryset = BlogModel.objects.all()
lookup_field = 'blog_id'
serializer_class = BlogSerializer
below is the screen shot of my output
Hope is have explained my problem well.
Thank you.
You can define extra_kwargs in the class Meta of your serializer to customize error messages.
https://www.django-rest-framework.org/api-guide/serializers/#additional-keyword-arguments
In your case, you would add this to BlogSerializer:
extra_kwargs = {
'title': {
'error_messages': {
'blank': 'my custom error message for title'
}
}
}
The tricky part might be figuring out the key you need to override. Getting the errors raised by the validators in the serializer can be done in the django shell. For example.
serializer = BlogSerializer(data={'title':''})
serializer.is_valid()
serializer._errors
{'status': [ErrorDetail(string=u'This field is required.', code=u'required')]
'title': [ErrorDetail(string=u'This field may not be blank.', code=u'blank')
'user': [ErrorDetail(string=u'This field is required.', code=u'required')]}
ErrorDetail.code is the key for the error_messages dictionary. In this case, it is 'blank'.
I have two models that are related to each other with user_id, now I want to have a get request in which I will have fields from both the tables. How to make this possible? I guess it would be possible with foreign key, but how do I implement it.
Two models look like:
model1
class Account(AbstractBaseUser):
fullname = models.CharField(max_length=100, blank=True)
username = models.CharField(unique=True, max_length=50)
email = models.EmailField(unique=True)
phonenumber = models.IntegerField(null=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
is_admin = models.BooleanField(default=False)
objects = AccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
models2
class Profile(models.Model):
User = get_user_model()
branch = models.CharField(max_length=20, null=True)
year = models.IntegerField(null=True)
image = models.ImageField(upload_to="accounts/images/", null=True, blank=True)
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
primary_key=False,
null=True
)
I want to display particular user details based on who is logged in at the moment.
My get request looks something like this:
def get(self, request, format=None):
current_user = request.user
acc = Account.objects.filter(pk=current_user.pk)
serializer = AccountSerializer(acc, many=True)
return Response(serializer.data)
But this will show only data as of Account model, I want data of Profile model too. How do I do it?
Update
serializers
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
class Meta:
model = Account
fields = (
'id', 'email', 'username', 'date_created', 'date_modified',
'fullname', 'password','phonenumber' ,'token' )
read_only_fields = ('date_created', 'date_modified')
def create(self, validated_data):
return Account.objects.create_user(**validated_data)
def update(self, instance, validated_data):
instance.email = validated_data.get('email', instance.email)
instance.username = validated_data.get('username',
instance.username)
instance.fullname = validated_data.get('fullname',
instance.fullname)
password = validated_data.get('password', None)
instance.save()
return instance
Update2
After having a get requst I want something like:
{
"username": "Steve"
"fullname": "Steve Smith"
"phonenumber": "1234567890"
"email": "st#gmail.com"
"profile":[ {
"branch": "CS"
"year": 4
"image": path/to/folder
}]
}
class ProfileSerializer(ModelSerializer):
user = AccountSerializer(read_only=True)
class Meta:
model = Profile
fields = '__all__'
def get(self, request, format=None):
try:
profile= Profile.objects.get(user=request.user)
except Profile.DoseNotExist:
return Response('404')
serializer = ProfileSerializer(profile)
return Response(serializer.data)
this will return data like:
{
"your profile fields": "xxx"
...
"user": {
"your user fields": "xxx"
...
}
}
if you want your user info include profile info:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
profile = serializers.SerializerMethodField()
class Meta:
model = Account
fields = (
'id', 'email', 'username', 'date_created', 'date_modified',
'fullname', 'password','phonenumber' ,'token' )
read_only_fields = ('date_created', 'date_modified')
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
def get_profile(self, instance):
try:
profile= Profile.objects.get(user=instance)
return ProfileSerializer(profile).data
except Profile.DoseNotExist:
return ''
or:
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
profile = serializers.ProfileSerializer(read_only=True)
class Meta:
model = Account
fields = (
'id', 'email', 'username', 'date_created', 'date_modified',
'fullname', 'password','phonenumber' ,'token' )
read_only_fields = ('date_created', 'date_modified')
def create(self, validated_data):
...
def update(self, instance, validated_data):
...
this will return:
{
"username": "Steve"
"fullname": "Steve Smith"
"phonenumber": "1234567890"
"email": "st#gmail.com"
"profile":{
"branch": "CS"
"year": 4
"image": path/to/folder
}
}
as your profile is onetoone to user,so profile is JsonObject not JsonArray
Configure your AccountSerializer somewhat like this,
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = [f.name for f in model._meta.fields]
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
token = serializers.CharField(max_length=500, read_only=True)
profile = ProfileSerializer()
class Meta:
model = Account
fields = [f.name for f in model._meta.fields]
You may also override your create and update methods of your serializer accordingly.