Related
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 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()
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 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.
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.