Serializer returns object instead fields - django

Using Django 2.1/Django Rest Framework.
I am receiving the Model object output from DRF instead of the actual fields.
I would like to recieve all the items for both the audio_links and release_artists tables.
Here's what I have.
Output
{
"title": "Attack The Dancefloor Volume Two",
"audiolinks": [
"AudioLinks object (55708)",
"AudioLinks object (55709)",
"AudioLinks object (55710)",
"AudioLinks object (55711)"
],
"releaseartists": [
"ReleaseArtists object (140)",
"ReleaseArtists object (141)"
]
}
models.py
class AudioLinks(models.Model):
release = models.ForeignKey('ReleasesAll', models.DO_NOTHING, db_column='release_id', related_name='audiolinks')
track_number = models.IntegerField()
class Meta:
managed = False
db_table = 'audio_links'
class ReleaseArtists(models.Model):
release = models.ForeignKey('ReleasesAll', models.DO_NOTHING, db_column='release_id', related_name='releaseartists')
artists = models.CharField(max_length=100)
class Meta:
managed = False
db_table = 'release_artists'
views.py
class ListReleaseDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = ReleasesAll.objects.all()
serializer_class = ReleasesSerializer
def get(self, request, *args, **kwargs):
try:
a_release = self.queryset.prefetch_related('releaseartists','audiolinks').get(pk=kwargs['release_id'])
return Response(ReleasesSerializer(a_release).data)
except ReleasesAll.DoesNotExist:
return Response(
data = {
"message": "{} does not exist".format(kwargs["release_id"])
},
status=status.HTTP_404_NOT_FOUND
)
serializers.py
class ReleasesSerializer(serializers.ModelSerializer):
audiolinks = serializers.StringRelatedField(many=True)
releaseartists = serializers.StringRelatedField(many=True)
class Meta:
model = ReleasesAll
fields = ('title','audiolinks','releaseartists')

serializers.stringRelatedField gives the output of the __str__ method defined on the Model.
Hence you are getting this "AudioLinks object (55708)" and ReleaseArtists object (141), which is the default __str__ representation of any model.
In order to get all fields, you must define serializers for every other model that is related to the required fields as such:
class AudioLinksSerializer(serializers.ModelSerializer):
class Meta:
model = AudioLinks
fields = ('__all__')
class ReleaseArtistsSerializer(serializers.ModelSerializer):
audiolinks = serializers.StringRelatedField(many=True)
releaseartists = serializers.StringRelatedField(many=True)
class Meta:
model = ReleaseArtists
fields = ('__all__')
and then:
class ReleasesSerializer(serializers.ModelSerializer):
audiolinks = AudioLinksSerializer(many=True)
releaseartists = ReleaseArtistsSerializer(many=True)
class Meta:
model = ReleasesAll
fields = ('title','audiolinks','releaseartists')
That would do enough for you to get all fields.
***And also as of Django convention, you need to name the models as in singular form i.e, AudioLink, ReleaseArtist. This way you see the model as a single object. And it will be easier when you try to get audiolinks as you will be thinking like "there are many AudioLink objects and I am getting few of them"

Related

Post method to pass data to multiple model serializers

I am new to Django and have a json object whose data has to be split and stored in 3 different Django models. I'm trying to figure out if I'm doing this correctly in using the view file and serializers in Django.
The json object that I receive into the views file looks like below:
[
{
"contact_entry":
{
"employee_name" : "Tom Hanks",
"employee_type" : "Full-time permanent"
},
"address" :
{
"line1": "1435 Manhattan Ave",
"line2": "Apt 123",
"city": "New York",
"state": "New York",
"country":"US",
},
"phone_number" :
{
"work_number": "9901948626"
"home_number": "9908847555"
}
}
]
I have three django models as shown below:
class ContactEntry(models.Model):
employee_name = models.CharField(max_length=128)
employee_type = models.CharField(max_length=128)
class Address(models.Model):
contact_entry = models.ForeignKey(ContactEntry, on_delete=models.CASCADE)
line1 = models.CharField(max_length=128)
line2 = models.CharField(max_length=128)
city = models.CharField(max_length=128)
state = models.CharField(max_length=128)
country = models.CharField(max_length=128)
class PhoneNumber(model.Model):
contact_entry = models.ForeignKey(ContactEntry, on_delete=models.CASCADE)
work_number = models.IntegerField(max_length=10)
home_number = models.IntegerField(max_length=10)
Each of them has a ModelSerializer as shown below:
class ContactEntrySerializer(serializers.ModelSerializer):
class Meta:
model = ContactEntry
fields = '__all__'
class AddressSerializer(serializers.ModelSerializer):
class Meta:
model = Address
fields = '__all__'
class PhoneNumberSerializer(serializers.ModelSerializer):
class Meta:
model = PhoneNumber
fields = '__all__'
I have defined one serializer (inherited from serializers.serializer) that combines the above 3 serializers as shown below:
class CombinedSerializer(serializers.Serializer):
contact_entry = ContactEntrySerializer()
phone_number = PhoneNumberSerializer()
address = AddressSerializer()
The post method in my view is as shown below:
class CombinedContactView(APIView):
def post(self, request, *args, **kwargs):
# Validate JSON data with the serializer
serializer = CombinedSerializer(data=request.data, many=True)
serializer.is_valid(raise_exception=True)
validated_data = serializer.validated_data.pop()
contact_entry_data = validated_data['contact_entry']
address_data = validated_data['address']
phone_number_data = validated_data['phone_number']
# Create model for contact book entry
contact_entry_model = ContactEntry(**contact_entry_data)
contact_entry_model.save()
# Create model for address
address_data['contact_entry'] = contact_entry_model
address_model = Address(**address_data)
address_model.save()
# Create model for phone_number
phone_number_data['contact_entry'] = contact_entry_model
phone_number_model = PhoneNumber(**phone_number_data)
phone_number_model.save()
return HttpResponse(status=201)
The above code actually runs fine. I see the contact_entry object created and the address and phone_numbers correctly created (with a foreign key relationship to the contact_entry object). However, I'm concerned that I'm doing this in a roundabout way with lots of unnecessary code in my views file. Is there a more straightforward way to do this?
if you change your serializers to these you'll be OK:
class ContactEntrySerializer(serializers.ModelSerializer):
class Meta:
model = ContactEntry
fields = '__all__'
class AddressSerializer(serializers.ModelSerializer):
contact_entry = ContactEntrySerializer(required=False)
class Meta:
model = Address
fields = '__all__'
class PhoneNumberSerializer(serializers.ModelSerializer):
contact_entry = ContactEntrySerializer(required=False)
class Meta:
model = PhoneNumber
fields = '__all__'
just notice that i changed contact_entry on AddressSerializer and PhoneNumberSerializer to required=False so you can pass contact_entry validation and these two serializers. and after it your view do the rest.

DRF Invalid pk object does not exist for foreign key on POST create

I have two classes related to each other.
One class I have made the primary key a char field so I can easily reference to it or create it to match the id of the actual object (all objects will have this unique name).
from django.db import models
class Ven(models.Model):
id = models.CharField(max_length=80, primary_key=True)
statusOn = models.BooleanField(default=True)
class Device(models.Model):
device_id = models.CharField(max_length=80)
ven_id = models.ForeignKey(VEN, related_name='devices', on_delete=models.SET_NULL, null=True)
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
class VENSerializer(serializers.ModelSerializer):
class Meta:
model = VEN
fields = ['id', 'statusOn', 'devices']
class DeviceList(generics.ListCreateAPIView):
logger.info("DeviceList: view")
# permission_classes = [permissions.IsAuthenticated]
queryset = Device.objects.all()
serializer_class = DeviceSerializer
however when I try to run my test:
class DevicesTestCase(TestCase):
def setUp(self):
self.factory = Client()
def test_create_device(self):
device = {
"ven_id": "TEST_VEN_1",
"device_id": "TEST_DEVICE_1",
"statusOn": True,
}
response = self.factory.post('/devices/', data=device)
self.assertEqual(response.status_code, 201)
my response returns 400 and states:
b'{"ven_id":["Invalid pk \\"TEST_VEN_1\\" - object does not exist."]}'
I'm trying to write a custom create in my serializer to create the ven if it does not exist but it's not being called. Data is being validated else where. My breakpoint in my view set's perform_create also does not fire.
I don't want to write a bunch of workaround code for something that should be straightforward. I know I'm missing/messing something up somewhere.
I think you need to customize the create method in the DeviceSerializer.
class DeviceSerializer(serializers.ModelSerializer):
ven_id = serializers.CharField()
status_on = serializers.BooleanField(write_only = True)
class Meta:
model = Device
fields = ['id', 'device_id', 'ven_id']
def create(self, validated_data):
ven_id = validated_data.pop('ven_id')
status_on = validated_data.pop('status_on')
try:
ven = Ven.objects.get(id = ven_id)
except Ven.DoesNotExist:
ven = Ven.objects.create(id = ven_id, statusOn = status_on)
new_device = Device.objects.create(ven_id = ven, **validated_data)
return new_device

Django rest framework; how do you use the ID of a foreign key to create an instance through the serializer?

I have two serializers, one for Country and one for my model Foo, I want to save the object by using the foreign key for this model but it errors out whenever I try to validate.
I have this
class Actor(TLPWrappedModel, CommentableModel):
label = models.CharField(max_length=56, unique=True)
country_of_origin = models.ForeignKey(Country, on_delete=models.CASCADE)
class FooSerializer(serializers.ModelSerializer):
country_of_origin = CountrySerializer()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
class Country(models.Model):
label = models.CharField(max_length=56, unique=True)
iso_code = models.CharField(max_length=3, unique=True)
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = [
'iso_code',
'label',
]
And this is what I'm trying to do
serializers = FooSerializer(data={'label': 'Foobar',
'country_of_origin': self.country.id})
serializers.is_valid()
print(serializers.errors)
print(serializers.validated_data)
serializers.save()
But I get this error {'country_of_origin': {'non_field_errors': [ErrorDetail(string='Invalid data. Expected a dictionary, but got int.', code='invalid')]}}
is it possible to use the ID of a foreign key to validate and create the object using the serializer?
We can update the to_represent of the FooSerializer to get the desired output
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
def to_representation(self, instance):
data = super().to_representation(instance)
data['country_of_origin'] = CountrySerializer(instance.country_of_origin)
return data
serializers = FooSerializer(data={'label': 'Foobar', 'country_of_origin': self.country})
serializers.is_valid(raise_expection=True)
serializers.save()
In this I have updated the code to assign the self.country as country_of_origin. Also, I am using the raise_expection in the is_valid method. This method will return the errors as 400 response.
Try
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]
You can safely drop defining the 'country of origin` in the FooSerializer
contry_of_origin would be an object, and you are passing an id for it.
Do you need a nested serializer? : country_of_origin = CountrySerializer()
For the example that you have given, I would suggest you to change it to PrimaryKeyRelatedField()
Your serializer would look like:
class FooSerializer(serializers.ModelSerializer):
country_of_origin = serializers.PrimaryKeyRelatedField()
class Meta:
model = Actor
fields = [
'id',
'country_of_origin',
'label',
]

How to get fields from Serializer into ListView for filtering

the model in question:
class CustomerPrices(models.Model):
url = models.OneToOneField('CustomerProductUrls', models.DO_NOTHING, db_column="url", primary_key=True)
written = models.DateTimeField()
reseller = models.CharField(max_length=250)
price = models.FloatField(blank=True, null=True)
class Meta:
managed = False
db_table = 'customer_prices'
unique_together = (('url', 'written', 'reseller'),)
the serializer (and one related serializer) in question:
class CustomerProductUrlsSerializer(serializers.ModelSerializer):
ean = CustomerProductsSerializer()
url = serializers.CharField(max_length=255, required=False)
class Meta:
model = CustomerProductUrls
fields = '__all__'
class CustomerPricesSerializer(serializers.ModelSerializer):
written = serializers.DateTimeField(format='%Y-%m-%d %H:%M:00', required=False)
reseller = serializers.CharField(max_length=250, required=False)
url = CustomerProductUrlsSerializer()
name = serializers.SerializerMethodField('get_name')
ean = serializers.SerializerMethodField('get_ean')
url = serializers.SerializerMethodField('get_url')
price = serializers.FloatField()
class Meta:
model = CustomerPrices
fields = '__all__'
def get_name(self, obj):
return obj.url.ean.name
def get_ean(self, obj):
return obj.url.ean.ean
def get_url(self, obj):
return obj.url.url
and the ListAPIView for the CustomerPrices class:
class CustomerPricesListView(generics.ListAPIView):
serializer_class = CustomerPricesSerializer
filter_backends = (DjangoFilterBackend, OrderingFilter)
fields = ('written', 'url', 'price')
filter_fields = fields
search_fields = fields
def get_queryset(self):
"""
This view should return a list of all the products where price >= 70
"""
return CustomerPrices.objects.filter(price__gte=70)
Inside my CustomerPricesSerializer I've got a field named ean as well as name the values for these two come through the related CustomerProductUrlsSerializer (and corresponding model). The code is working so far, I get a response looking like this:
"results": [
{
"url": "https://www.example.com/p/12345678",
"written": "2020-04-05 12:00:00",
"reseller": "ABC123",
"name": "Product 12345678",
"ean": "1234567890123",
"price": 98.3
}, ...
]
I'm using the DjangoFilterBackend and I would like to filter on the ean as well as the name, which is available in my response. But as soon as I add ean to my fields tupel inside the Serializer I get the Meta.fields contains fields that are not defined on this FilterSet: ean. I do understand that my queryset is returning the fields from CustomerPrices Model inside my ListAPIView, but how do I get get the ean as well as the name to be a part of the queryset and therefore a part of the available fields
The field ean does not belong to the CustomerPrices model but somewhere else. The DRF expects the filter tuples are directly related to the model (here CustomerPrices) and hence the error.
To resolve this issue, you have to provide the actual relationship to the ean field.
class CustomerPricesListView(generics.ListAPIView):
fields = ('written', 'url', 'price', 'url__ean__ean', 'url__ean__name')
filter_fields = fields
# rest of the code

How can I make a writable ManyToManyField with a Through Model in Django Rest Framework?

I have a Product class that has "source products"; I use a many-to-many field with a through model to represent it (the database tables already exist, and that's the only way I found to configure the models):
class Product(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
product_name = models.TextField()
source_products = models.ManyToManyField('self', symmetrical=False, related_name='derived_products', through='Link', through_fields=('product', 'source'),)
class Meta:
db_table = 'product'
managed = False
class Link(models.Model):
product = models.ForeignKey('Product', models.CASCADE, db_column='uuid', related_name='source_links')
source = models.ForeignKey('Product', models.CASCADE, db_column='source_uuid', related_name='+')
class Meta:
db_table = 'link'
unique_together = (('product', 'source'),)
managed = False
My serializer is dead simple:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ('uuid', 'product_name', 'source_products', )
A GET request returns:
{
"product_name": "dummy.fra",
"source_products": [
"17b021e7-3d6b-4d29-a80b-895d62710080"
],
"uuid": "48c5a344-877e-4e3f-9a4b-2daa136b68fe"
}
The since ManyToManyFields with a Through Model are read-only, how do I go about to create a product including the link between products? I'd like to send a POST request that follows the same format as the GET response (i.e., a source_products field that lists UUIDs of existing products).
You can use PrimaryKeyRelatedField but you have to write custom create method.
class ProductSerializer(serializers.ModelSerializer):
source_products = serializers.PrimaryKeyRelatedField(many=True, queryset=Products.objects.all())
class Meta:
model = Product
fields = ('uuid', 'product_name', 'source_products', )
def create(self, validated_data):
source_products = validated_data.pop('source_products', [])
product = Product.objects.create(**validated_data)
for source in source_products:
product.source_products.add(source)
return product
Your data will be like this
{
"product_name": "dummy.fra",
"source_products": [
"17b021e7-3d6b-4d29-a80b-895d62710080"
],
"uuid": "48c5a344-877e-4e3f-9a4b-2daa136b68fe"
}
The serializer has to override create() and access the unvalidated GET parameters (self.context['request'].data) directly:
class ProductSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# extract sources data
unvalidated_data = self.context['request'].data
sources_data = unvalidated_data.get('source_products', [])
# save objects
instance = Product.objects.create(**validated_data)
for uuid in sources_data:
source = Product.objects.get(pk=uuid)
instance.source_links.create(source=source)
return instance
class Meta:
model = Product
fields = ('uuid', 'product_name', 'source_products', )