When I declare a relationship with SQL-Alchemy, I don't found all the child's data when Marshmallow dumps the object. When Marshmallow deserialize the object, i would like to get my Member object to json not just the Ids.
My models :
class Company(db.Model):
__table_args__ = {"schema": "public"}
id_company = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
members = db.relationship('Member', back_populates="company", lazy='joined')
class Member(db.Model):
__table_args__ = {"schema": "public"}
id_member = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), nullable=False)
age = db.Column(db.Integer)
company_id = db.Column(db.Integer, db.ForeignKey('public.company.id_company'), nullable=False)
company = db.relationship("Company", back_populates="members",lazy='joined')
class MemberSchema(ma.ModelSchema):
class Meta:
model = Member
class CompanySchema(ma.ModelSchema):
class Meta:
model = Company
class CompanyIdInputSchema(Schema):
id_company = fields.Integer(required=True)
My route :
company_schema = CompanySchema()
pagination_schema = PaginationInputSchema()
company_id_schema = CompanyIdInputSchema()
class CompanyById(Resource):
#swag_from('swag_docs/swag_company_get.yml')
def get(self, id_company):
company_id_args = {"id_company": id_company}
errors = company_id_schema.validate(company_id_args)
if errors:
return {"error": str(errors)}, 500
else:
company = Company.query.filter_by(id_company=id_company)
company = companies_schema.dump(company)
if len(company)>=1:
return company, 201
else:
return 204
The result when a call the route is :
{
"members": [
1
],
"id_company": 1,
"name": "Total"
}
What i expect :
{
"members": [
{'id_member':1,'name':'Laurent','age':32,'company_id'=1}
],
"id_company": 1,
"name": "Total"
}
If you want the serializer to serialize nested field as a list of serialized objects instead of ids you need to add Nested field with many=True.
class CompanySchema(ma.ModelSchema):
members = ma.fields.Nested(MemberSchema, many=True)
class Meta:
model = Company
Related
I`m trying to post this json object
{
"name": "Country Name",
"description": "Description here...",
"generalInfo": {
"capital": "Capital",
"population": "Population",
"highestPeak": "HighestPeak",
"area": "Area"
},
"timelineData": [
{
"name": "Name 1",
"ruler": "Ruler",
"dateStart": "dateStart",
"dateEnd": "dateEnd",
"description": "description"
},
{
"name": "Name 2",
"ruler": "Ruler",
"dateStart": "dateStart",
"dateEnd": "dateEnd",
"description": "description"
},
{
"name": "Name 3",
"ruler": "Ruler",
"dateStart": "dateStart",
"dateEnd": "dateEnd",
"description": "description"
}
]
}
But I`m receiving this error
ValueError at /countries/
Cannot assign "OrderedDict([('capital', 'Capital'), ('population', 'Population'), ('highestPeak', 'HighestPeak'), ('area', 'Area')])": "Country.generalInfo" must be a "GeneralInfo" instance.
Request Method: POST
Request URL: http://127.0.0.1:8000/countries/
Django Version: 4.0.3
Here are my models.py:
class GeneralInfo(models.Model):
capital = models.CharField(max_length=200)
population = models.CharField(max_length=200)
highestPeak = models.CharField(max_length=200)
area = models.CharField(max_length=200)
class TimelineData(models.Model):
name = models.CharField(max_length=200)
ruler = models.CharField(max_length=200)
dateStart = models.CharField(max_length=200)
dateEnd = models.CharField(max_length=200)
description = models.TextField(default='Description here...')
class Country(models.Model):
name = models.CharField(max_length=200, db_index=True)
description = models.TextField(default='Description here...')
generalInfo = models.ForeignKey(GeneralInfo, on_delete=models.CASCADE)
timelineData = models.ManyToManyField(TimelineData)
I have also overridden the create method for CountrySerializer and here are
my serializers.py:
class TimelineDataSerializer(serializers.ModelSerializer):
class Meta:
model = TimelineData
fields= '__all__'
class GeneralInfoSerializer(serializers.ModelSerializer):
class Meta:
model= GeneralInfo
fields = '__all__'
class CountrySerializer(serializers.ModelSerializer):
timelineData = TimelineDataSerializer(many=True)
generalInfo = GeneralInfoSerializer()
class Meta:
model= Country
fields = '__all__'
def create(self, validated_data):
timelineData = validated_data.pop('timelineData')
country = Country.objects.create(**validated_data)
for timelineItem in timelineData:
TimelineData.objects.create(country=country,**timelineItem)
return country
and views.py
class CountryList(viewsets.ModelViewSet):
serializer_class = CountrySerializer
queryset = Country.objects.all()
I am new to Django and Python so maybe I missed something. I've been stuck on this for a while now and wasn't able to find any solution myself
The error is because that OrderedDict needs to be an actual GeneralInfo Object..
I have not used serializers much (or at all tbh), but this would be my general guess from what I've seen from similar problems:
class CountrySerializer(serializers.ModelSerializer):
timelineData = TimelineDataSerializer(many=True)
generalInfo = GeneralInfoSerializer()
class Meta:
model= Country
fields = '__all__'
def create(self, validated_data):
timelineData = validated_data.pop('timelineData')
# pop out data (so it's not in final create), filter for object
generalInfoObj = GeneralInfo.objects.filter(**validated_data.pop('generalInfo')).first()
# ^ get or None (no crash, my preference)
# could also be:
# generalInfoObj = GeneralInfo.objects.get( ... )
# ^ - get or crash
# generalInfoObj, created = GeneralInfo.objects.get_or_create( ... )
# ^ get or create it
# debugger
print('generalInfoObj', type(generalInfoObj), generalInfoObj)
country = Country.objects.create(**validated_data, GeneralInfo=generalInfoObj)
for timelineItem in timelineData:
TimelineData.objects.create(country=country,**timelineItem)
return country
If generalInfo shows up like an array of tuples, you'd do something like this with List Comprehension
(adding this just in case)
generalInfoObj = GeneralInfo.objects.filter(**{i[0]:i[1] for i in validated_data.pop('generalInfo')}).first()
## breakdown:
# generalinfo = [('key0', val0'), ('key1', val1')]
dict = {}
for i in generalinfo:
# i = ('key', 'value')
dict[i[0]] = i[1]
# dict = {
# 'key0': 'val0',
# 'key1': 'val1',
# }
Models:
class Person(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveSmallIntegerField()
# More Person fields
class Student(models.Model):
person = models.OneToOneField(
Person, on_delete=models.PROTECT, primary_key=True)
year_of_study = models.PositiveSmallIntegerField()
# More Student fields
Serializers:
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
class StudentSerializer(serializers.ModelSerializer):
person = PersonSerializer()
class Meta:
model = Student
fields = '__all__'
Views:
class StudentView(viewsets.ReadOnlyModelViewSet):
renderer_classes = [JSONRenderer]
parser_classes = [JSONParser]
queryset = Student.objects.all()
serializer_class = StudentSerializer
Requesting single Student:
{
"person": {
"id": 1,
"name": "Example Name",
"age": 20
},
"year_of_study": 3
}
But I need to work with plain structure like:
{
"id": 1,
"name": "Example Name",
"age": 20,
"year_of_study": 3
}
Where (in serializer or in view or somewhere else) and how should I do it?
I only need GET requests (using ReadOnlyModelViewSet because of it). But if would also be nice to know how to create/update/delete such structure as well.
you can create serializer like below
class StudentSerializer(serializers.ModelSerializer):
name = serilizer.CharFiled(source="person.name")
person_id = serilizer.IntegerFiled(source="person.id")
age = serilizer.IntegerFiled(source="person.age")
class Meta:
model = Student
exclude = ('person',)
You have person = PersonSerializer() which outputs the person's data. Remove this line and it'll display just person_id.
Now if you want a combination of GETs returning person data but POSTs using the person_id, you can do the following:
person = PersonSerializer(read_only=True)
person_id = serializers.PrimaryKeyRelatedField(
queryset=Person.objects.all(),
source='person',
write_only=True,
)
i have applied join on two tables with following query,
VIEWS.PY
class performance(viewsets.ModelViewSet):
queryset = Leads.objects.select_related('channelId'
).values("channelId__channelName").annotate(tcount=Count('channelId'))
serializer_class = teamwise_lead_performance_serializer
but i am unable to catch response using this serializers,
SERIALIZER.PY
class channel_serializer(serializers.ModelSerializer):
class Meta:
model = Channels
fields = ['channelName']
class performance_serializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channel = channel_serializer(many=True, read_only=True)
class Meta:
model = Leads
fields = ['tcount', 'channel']
actual results:
[
{
"tcount": 88
},
{
"tcount": 25
},
{
"tcount": 31
},
...
]
expected results:
[
{
"channelName": "abc",
"tcount": 88
},
{
"channelName": "def",
"tcount": 25
},
{
"channelName": "ghi",
"tcount": 31
},
...
]
i have tried the following:
How to join two models in django-rest-framework
Models.py
class Channels(models.Model):
id = models.IntegerField(primary_key=True)
channelName = models.CharField(max_length=20, default=None)
class Meta:
db_table = "table1"
class Leads(models.Model):
id = models.IntegerField(primary_key=True)
channelId = models.ForeignKey(Channels, on_delete=models.CASCADE, db_column='channelId')
class Meta:
db_table = "table2"
why is it not getting the channelName in response?
what am i doing wrong here?
Thank you for your suggestions
Edit
When I try Mehren's answer, I get the following error:
KeyError when attempting to get a value for field channelName on serializer performance_serializer. The serializer field might be named incorrectly and not match any attribute or key on the dict instance.Original exception text was: 'channelId'.
I managed to get it working with the following:
class performance_serializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channelName = serializers.CharField(source='channelId__channelName')
class Meta:
model = Leads
fields = ['tcount', 'channelName']
class performance(viewsets.ModelViewSet):
queryset = Leads.objects.select_related('channelId'
).values("channelId__channelName").annotate(tcount=Count('channelId'))
serializer_class = performance_serializer
That being said, I would strongly encourage you to follow both PEP and Django naming conventions.
Here is what your code would look like following said conventions:
class Channel(models.Model):
id = models.IntegerField(primary_key=True)
channel_name = models.CharField(max_length=20, default=None)
class Lead(models.Model):
id = models.IntegerField(primary_key=True)
channel = models.ForeignKey(Channel, on_delete=models.CASCADE)
class PerformanceSerializer(serializers.ModelSerializer):
channel_count = serializers.IntegerField()
channel_name = serializers.CharField(source='channel__channel_name')
class Meta:
model = Lead
fields = ['channel_count', 'channel_name']
class PerformanceViewSet(viewsets.ModelViewSet):
queryset = Lead.objects.select_related('channel'
).values("channel__channel_name").annotate(channel_count=Count('channel'))
serializer_class = PerformanceSerializer
The main takeaway from this is to not change the default name of your ForeignKey columns! It makes working with related models much more confusing, and is possibly the reason for your problem in the first place (although I couldn't prove it).
If you want to only get the channelName, then it's better to use
channelName = serializers.CharField(source='channelId.channelName')
Also, please fix your syntax. You are not following the pep8 standards.
EDIT
class PerformanceSerializer(serializers.ModelSerializer):
tcount = serializers.IntegerField()
channelName = serializers.CharField(source='channelId.channelName')
class Meta:
model = Leads
fields = ['tcount', 'channelName']
EDIT
queryset = Leads.objects.select_related('channelId').values("channelId__channelName").annotate(tcount=Count('channelId'))
Remove the .values("channelId__channelName") part from your view
I have one to many relationships between tables in a database and I want to get the data using an API.
I think that ma.Nested does not work because I don't get all the fields
can anyone help me?
I get only this :
[
{
"IsRef": false,
"commands": [
"299d7f0b-721c-484b-9448-072716a5fd70",
"382c5d9f-99a1-4aa2-ac30-96084e202fad",
"299d7f0b-721c-484b-9448-072716a5fd75",
"382c5d9f-99a1-4aa2-ac30-96084e202fak"
],
"filename": "uiyg",
"version": 8
}
]
this is the database model :
class File(db.Model):
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), unique=True)
version = db.Column(db.Integer, unique=True)
IsRef = db.Column(db.Boolean)
commands = db.relationship('Command', backref='log', lazy='joined')
def __init__(self, filename, version, IsRef):
self.filename = filename
self.version = version
self.IsRef = IsRef
class Command(db.Model):
id_command = db.Column(db.String(255), primary_key=True, autoincrement=False)
name = db.Column(db.String(255))
log_id = db.Column(db.Integer, db.ForeignKey('file.id'))
status = db.Column(db.Boolean)
events = db.relationship('Event', backref='com', lazy='joined')
def __init__(self, id_command, name, log_id, status):
self.id_command = id_command
self.name = name
self.log = log_id
self.status = status
class Event(db.Model):
id_event = db.Column(db.Integer, primary_key=True)
event_name = db.Column(db.String(255))
seq_number = db.Column(db.Integer)
com_id = db.Column(db.Integer, db.ForeignKey('command.id_command'))
def __init__(self, event_name, seq_number, com_id):
self.event_name = event_name
self.seq_number = seq_number
self.com = com_id
this is the schema :
class EventSchema(ma.ModelSchema):
class Meta:
model = Event
fields = ('event_name', 'seq_number')
class CommandSchema(ma.ModelSchema):
class Meta:
model = Command
events = ma.Nested(EventSchema)
fields = ('id_command', 'name', 'status', 'events')
class LogSchema(ma.ModelSchema):
class Meta:
model = File
commands = ma.Nested(CommandSchema)
fields = ('filename', 'version', 'IsRef', 'commands')
Log_schema = LogSchema(many=True)
this is the API :
#app.route("/getall/<version>", methods=["GET"])
def get_all(version):
alle = File.query.filter_by(version=version)
return Log_schema.jsonify(alle)
this question is solved. I've changed 'lazy ='joined'' to 'lazy='dynamic'' and I've put ma.Nested out of the Meta class
My models:
class ContentHotel(models.Model):
hotel_id = models.IntegerField(unique=True, blank=True, primary_key=True)
class Meta:
managed = False
db_table = 'content_hotels'
ordering = ('hotel_id',)
def __str__(self):
return str(self.hotel_id)
class RateHotel(models.Model):
rate_hotel_id = models.IntegerField(blank=True, primary_key=True, unique=True)
content_hotel = models.ForeignKey(ContentHotel, on_delete=models.CASCADE, related_name='rate_hotel')
class Meta:
managed = False
db_table = 'rate_hotels'
ordering = ('rate_hotel_id',)
def __str__(self):
return str(self.rate_hotel_id)
My Serializers:
class RateHotelSerializer(serializers.ModelSerializer):
class Meta:
model = RateHotel
fields = __all__
class ContentHotelSerializer(serializers.ModelSerializer):
rate_hotel = RateHotelSerializer(many=True)
class Meta:
model = ContentHotel
fields = ('hotel_id', 'rate_hotel')
def create(self, validated_data):
rate_hotels = validated_data.pop('rate_hotel')
content_hotel = ContentHotel.objects.create(**validated_data)
for rate_hotel in rate_hotels:
RateHotel.objects.create(content_hotel=content_hotel, **rate_hotel)
return content_hotel
JSON:
{
"hotel_id": -1,
"rate_hotel": [{"content_hotel": -1, "rate_hotel_id": 1}]
}
Above JSON input gives me error like:
{
"rate_hotel": [
{
"content_hotel": [
"Invalid pk \"1\" - object does not exist."
]
}
],
"status_code": 400
}
REFERENCE: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
I referenced the link above, anyone knows how to address this? But f I create the two objects separately, it works correctly, like this:
{
"hotel_id": -1,
}
{
"content_hotel": -1,
"rate_hotel_id": 1
}
The validation has been done before the serializer create function and because you haven't create the contenthotel with that pk yet, the pk is invalid for that field(content_hotel). make content_hotel readonly in RateHotelSerializer and the problem will be fixed, change the serializer to this:
class RateHotelSerializer(serializers.ModelSerializer):
class Meta:
model = RateHotel
fields = __all__
read_only_fields = ('content_hotel', )
and also now you don't need to add content_hotel in objects of the list for rate_hotel, use a json like this:
{
"hotel_id": -1,
"rate_hotel": [{"rate_hotel_id": 1}]
}