How to retrieve foreign field name in Django Rest Framework - django

I have 2 models one for a user and another for associated files for the user
Models.py
class IndividualUser(models.Model):
membership_id = models.CharField(primary_key=True, max_length=100, default=1)
profile_image = models.ImageField(blank=True, upload_to ="individual_member_profile/", null=True)
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
class MemberCatalogue(models.Model):
membership_id = models.ForeignKey(IndividualUser, default=None,on_delete=models.CASCADE, related_name="member_catalogue")
files = models.FileField(upload_to="individualmembercatalogue/")
Currently Iam getting absolute path for files. I need a filename uploaded by user for which Iam trying to get "files" field and and then split it with "/". But Iam struggling to get the files field from MemberCatalogue.
My serializers look like this at the moment:
class MemberCatalogueSerializer(serializers.ModelSerializer):
files = serializers.FileField()
class Meta:
model = MemberCatalogue
fields = ['id','membership_id', 'files']
class IndividualMembersSerializer(serializers.ModelSerializer):
member_catalogue = MemberCatalogueSerializer(many=True)
filename = serializers.SerializerMethodField('get_filename')
def get_filename(self, obj):
return obj.individualmember.files
class Meta:
model = IndividualMembers
fields = "__all__"
But I cant get the desired output.
Expected output is like
{
"membership_id": "142369ca1b1484d8d9d6d87fdc8543db",
"member_catalogue": [
{
"id": 156,
"membership_id": "142369ca1b1484d8d9d6d87fdc8543db",
"files": "http://127.0.0.1:8000/individualmembercatalogue/some_file.pdf"
"filename" : "some_file.pdf"
}
],
"profile_image": null,
"firstname": "John",
"lastname": "Doe",
}

You are using the wrong model in your IndividualMembersSerializer.
I think you are trying to edit the way filename is displayed in the wrong serializer, since files is a field on MemberCatalogue and not on IndividualUser.
Here is how I would do it:
class MemberCatalogueSerializer(serializers.ModelSerializer):
filename = serializers.SerializerMethodField()
def get_filename(self, obj):
return str(obj.files).split('/')[1]
class Meta:
model = MemberCatalogue
fields = ['id','membership_id', 'files', 'filename']
class IndividualMembersSerializer(serializers.ModelSerializer):
member_catalogue = MemberCatalogueSerializer(many=True)
class Meta:
model = IndividualUser
fields = "__all__"

class MemberCatalogueSerializer(serializers.ModelSerializer):
filename = serializers.SerializerMethodField()
class Meta:
model = MemberCatalogue
fields = ['membership_id', 'files','filename']
def get_filename(self, obj):
return obj.files.url.split('/')[-1]

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.

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

Rest Framework deserialize one field and serialize model object

Hi I want to deserialize only using 1 field. However, I want to serialize it as an object depending on the model.
Suppose I have:
#models.py
class Product(models.Model):
name = models.CharField()
amount = models.IntegerField()
description = models.TextField()
class Query(models.Model):
name = models.CharField()
product = models.ForeignKey(Product)
...
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class QuerySerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = Query
fields = '__all__'
I want to POST/deserialize something like this on the QuerySerializer:
{
"name": "Query 1",
"product": "Banana",
...
}
and I want something like this in return for serializer:
{
"name": "Query 1",
"product": {
"name": "Banana",
"amount": 1,
"description": "Banana description"
}
...
}
I know a way is overriding to_internal_value but I do not like it since it messes up with ValidationErrrors.
I also get this as a result:
{'product': {'non_field_errors':
['Invalid data. Expected a dictionary, but got str.']}}
First of all, make the name field of Product as unique to avoid unnecessary complications.
class Product(models.Model):
name = models.CharField(unique=True)
amount = models.IntegerField()
description = models.TextField()
and change your serializer as,
class QuerySerializer(serializers.ModelSerializer):
product = serializers.CharField(write_only=True)
class Meta:
model = Query
fields = '__all__'
def create(self, validated_data):
product_name = validated_data.pop('product')
product_instance = Product.objects.get(name=product_name)
return Query.objects.create(product=product_instance, **validated_data)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['product'] = ProductSerializer(instance.product).data
return rep
Reference: DRF: Simple foreign key assignment with nested serializers?

Django Rest Framework related fields

I'm new to Django I want to get profile image from another model topic
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
file = models.ImageField(upload_to='profile_image', blank=True)
def __unicode__(self):
return u'%s' % self.user
class Topics(models.Model):
user = models.ForeignKey(User)
title = models.charField(max_length = 55)
serializers.py
User = get_user_model()
class pic(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['file']
class UserInfo(serializers.ModelSerializer):
username = pic(read_only=True)
class Meta:
model = User
fields = ['username','first_name',]
class TopicSerializer(serializers.ModelSerializer):
user= UserInfo(read_only=True)
class Meta:
model=Topics
fields = ('user','title',)
I'm getting like this:
"user": {
"username": {},
"first_name": ""
},
"title": "Django the title",
Now I need every file field related to Topics field with the first name and email of the user.
I want to like this:
"user": {
"username": "akash",
"first_name": "Akash DK"
"file":"static/imag.png"
},
"title": "Django the title",
Thanks in advance
You could use a SerializerMethodField in your UserInfo serializer to lookup the profile image and add it to the output data.
class UserInfo(serializers.ModelSerializer):
file = serializers.SerializerMethodField()
def get_file(self, user):
return UserProfile.objects.get(user=user).file.url
class Meta:
model = User
fields = ['username','first_name', 'file']

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', )