Django. Rest Framework. Nested relationships empty result - django

Need some advice. I set up seralization. There are no errors. But at the output I get empty tags. I broke my head, what am I doing wrong?
models.py:
class kv(models.Model):
title = models.CharField(max_length=200)
price = models.IntegerField()
address = models.CharField(max_length=200)
property_type = models.CharField(choices=realty_type_choices_admin, default='kv',
max_length=200, blank=True)
country = models.CharField(default='Россия', max_length=200)
region = models.CharField(max_length=200)
state = models.CharField(choices=state_choices_admin, default='DGO', max_length=200, blank=True, null=True)
locality_name = models.CharField(max_length=200, blank=True, null=True)
address_xml = models.CharField(max_length=200, blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
serializers.py
from rest_framework import serializers
from listings.models import kv
class kvSerializerLocation(serializers.ModelSerializer):
class Meta:
model = kv
fields = ['country', 'region', 'state', 'locality_name', 'address_xml', 'city']
class kvSerializer(serializers.ModelSerializer):
category = serializers.CharField(source='get_property_type_display')
url = serializers.CharField(source='get_absolute_url', read_only=True)
country = kvSerializerLocation()
class Meta:
model = kv
fields = ['title', 'price', 'address', 'category', 'url', 'country']
views.py
from listings.models import *
from rest_framework import viewsets
from rest_framework_xml.renderers import XMLRenderer
from .serializers import kvSerializer
class KvXMLRenderer(XMLRenderer):
root_tag_name = 'feed'
item_tag_name = 'offer'
def _to_xml(self, xml, data):
super()._to_xml(xml, data)
class kvViewSet(viewsets.ModelViewSet):
queryset = Kvartiry.objects.all().filter(is_published=True)
serializer_class = kvSerializer
renderer_classes = [KvXMLRenderer]
Result:
<country>
<state/>
<locality_name/>
<address_xml/>
<city/>
</country>
It’s strange. Tags are empty, there is no region tag at all
Thank!

I don't think your implemenration would work for country because its a field, and there is no way to map that country value to your kv instance which can be utilized by kvSerializerLocation. Instead use SerializerMethodField:
class kvSerializer(serializers.ModelSerializer): # please use PascalCase for defining class name
category = serializers.CharField(source='get_property_type_display')
url = serializers.CharField(source='get_absolute_url', read_only=True)
kv_country = serializers.SerializerMethodField()
class Meta:
model = kv
fields = ['title', 'price', 'address', 'category', 'url', 'kv_country']
def get_kv_country(self, obj):
return kvSerializerLocation(obj).data

Related

how to save multiple objects to the database in django rest framework views

so what i'm trying to do is add a new product to my data base using django's restapi
but a product may contain multiple categories which are related throught a third many to many
model and extra pictures which are ForeignKeyed to the product
this is my models.py
class Products(models.Model):
product_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=35, null=False, unique=True)
description = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.)
main_image = models.FileField(upload_to='shop/images')
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Category(models.Model):
category_id = models.AutoField(primary_key=True)
category = models.CharField(max_length=20, null=True, blank=True)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'Category'
class ProductsCategory(models.Model):
productscategory_id = models.AutoField(primary_key=True)
category = models.ForeignKey(to=Category, on_delete=models.CASCADE)
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'ProductsCategory'
class Pictures(models.Model):
picture_id = models.AutoField(primary_key=True)
image = models.FileField(upload_to='shop/images')
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Meta:
db_table = 'Pictures'
and heres what i've tryed:
#api_view(['POST'])
#permission_classes([IsModerator])
def create_product(request):
product_details = ProductsSerializer(request.POST, request.FILES)
pictures = PicturesSerializer(request.POST, request.FILES, many=True)
category_list = request.POST.getlist("category")
if product_details.is_valid() and validate_file_extension(request.FILES.get("main_image")):
try:
product = product_details.save()
if len(category_list) > 0:
for i in category_list:
category = Category.objects.get(category=i)
ProductsCategory.objects.create(category=category, product=product)
if pictures:
for image in request.FILES.getlist("image"):
if validate_file_extension(image):
Pictures.objects.create(image=image, product=product)
else:
error = {"error": "invalid extra pictures extension"}
return Response(error)
return Response((product_details.data, pictures.data, category_list), status=status.HTTP_201_CREATED)
except Exception as e:
return Response(e)
else:
return Response((product_details._errors, pictures._errors), status=status.HTTP_400_BAD_REQUEST)
and the output:
result
how am i supposed to use this content input?
or if you know a better for my main question of saving multiple models in the database and their relationships please leave an answer, thanks in advance
I suggest you change your models.py structure to this:
from django.db import models
class Category(models.Model):
category = models.CharField(max_length=20, null=True, blank=True)
created_on = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Categories"
class Picture(models.Model):
image = models.FileField(upload_to='shop/images')
product = models.ForeignKey(to=Products, on_delete=models.CASCADE)
created_on = models.DateTimeField(blank=True, default=datetime.now)
class Product(models.Model):
name = models.CharField(max_length=35, null=False, unique=True)
description = models.CharField(max_length=255)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0.)
main_image = models.FileField(upload_to='shop/images')
more_images = models.ManyToManyField(Pictures, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
created_on = models.DateTimeField(auto_now=True)
Then in your serializer.py add:
from rest_framework import serializers
from .models import Category, Picture, Product
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = "__all__"
class PictureSerializer(serializers.ModelSerializer):
class Meta:
model = Picture
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = "__all__"
In your views, I suggest you use ViewSets:
views.py
from .models import Category, Picture, Product
from .serializer import CategorySerializer, PictureSerializer, ProductSerializer
from rest_framework import viewsets
# import custom permissions if any
class CategoryViewSet(viewsets.ModelViewSet):
serializer_class = CategorySerializer
queryset = Category.objects.all()
class PictureViewSet(viewsets.ModelViewSet):
serializer_class = PictureSerializer
queryset = Picture.objects.all()
class ProductViewSet(viewsets.ModelViewSet):
serializer_class = ProductSerializer
queryset = Product.objects.all()
permission_classes = [IsModerator]
In your app's urls.py, add the router for your viewsets and it will create the paths for your views automatically:
from django.urls import path
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(r'category', views.CategoryViewSet, basename='category')
router.register(r'picture', views.PictureViewSet, basename='picture')
router.register(r'product', views.ProductViewSet, basename='product')
urlpatterns = [
path('', include(router.urls)),
]
Changes log:
You do not need to add an ID field to every model, Django does that for you. Unless it's a particular case.
Your database tables are named after your model by default. So no need to specify that too.
I simplified your models' structure to make it cleaner. But it still does what you want it to do.
Django adds an s to create a plural name for every model. So you can name it in singular form unless needed to specify. eg. categories.
The viewsets will reduce your work by providing you with listing and retrieval actions.
To access a specific instance of eg. a product, you will just add a /<product id> after the product listing and creation endpoint.
Note: You have to add the id without the brackets.
I also suggest you go through this DRF tutorial. It will improve your understanding of Django REST framework.

how to use RelatedField in Django rest framework?

I have problem with Django restframe work i have 2 table that one of them is a foreign key to another i have used RelatedField in serializer but i get an error:'Relational field must provide a queryset argument,
can someone help me in this case
my code is as below:
class DocTable(models.Model):
project = models.CharField(max_length=1000, null=True, blank=True)
document_no = models.CharField(max_length=1000, null=True, blank=True)
document_title = models.TextField(null=True, default='', blank=True)
class PlanTable(models.Model):
document = models.ForeignKey(DocTable, on_delete=models.CASCADE, related_name='doctable')
work_type = models.CharField(max_length=1000, null=True, blank=True)
description_work = models.TextField(null=True, default='', blank=True)
serializers.py
class DocTableSerializer(serializers.ModelSerializer):
doctable = serializers.RelatedField(many=True)
class Meta:
model = DocTable
fields = ['pk', 'project', 'document_no', 'doctable']
read_only_fields = ['pk']
class PlanTableSerializer(serializers.ModelSerializer):
class Meta:
model = PlanTable
fields = ['pk', 'document', 'work_type', 'description_work']
read_only_fields = ['pk']
views.py
class DocTableListView(generics.ListAPIView):
lookup_field = 'pk'
serializer_class = DocTableSerializer
def get_queryset(self):
return PlanTable.objects.all()
def get_object(self):
pk = self.kwargs.get('pk')
return PlanTable.objects.get(pk=pk)
You have to provide queryset in RelatedField like this.
class DocTableSerializer(serializers.ModelSerializer):
doctable = serializers.RelatedField(many=True, queryset=DocTable.objects.all())
Or if you only want to use this related field for retrieving data, you can mark it as read only
doctable = serializers.RelatedField(many=True, read_only=True)

Why my slug related field shows users object(1) instead of suggested field name in Django?

I wrote an app named as credentials.
models, serializers and views are :
models.py :
from django.db import models
class Users(models.Model):
username = models.CharField(max_length=20, blank=False)
inserted_timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('inserted_timestamp',)
class UsersDetails(models.Model):
user = models.ForeignKey(
Users,
related_name='id_user_details',
on_delete=models.DO_NOTHING,
)
user_title = models.CharField(max_length=30, blank=True)
user_first_name = models.CharField(max_length=25, blank=True)
user_last_name = models.CharField(max_length=40, blank=True)
user_birthdate = models.DateField(blank=False)
inserted_timestamp = models.DateTimeField(auto_now_add=True)
details_creator = models.ForeignKey(
Users,
related_name='dtlcreator_user_details',
on_delete=models.DO_NOTHING,
# default=1
)
class Meta:
ordering = ('user_id',)
class UsersPasswords(models.Model):
user = models.ForeignKey(
Users,
related_name='id_user_password',
on_delete=models.DO_NOTHING)
salt = models.CharField(max_length=200, blank=True)
pwdhash = models.CharField(max_length=200, blank=True)
inserted_timestamp = models.DateTimeField(auto_now_add=True)
pwd_creator = models.ForeignKey(
Users,
related_name='pwdcreator_user_details',
on_delete=models.DO_NOTHING)
class Meta:
ordering = ('user_id',)
Here is serializers.py :
from rest_framework import serializers
from credentials.models import Users
from credentials.models import UsersDetails
from credentials.models import UsersPasswords
class UsersSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Users
fields = (
'url',
'pk',
'username',
'inserted_timestamp',
)
class UsersDetailsSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.SlugRelatedField(
queryset=Users.objects.all(),
slug_field='username',
)
details_creator = serializers.SlugRelatedField(
queryset=Users.objects.all(),
slug_field='username',
)
class Meta:
model = UsersDetails
fields = (
'url',
'pk',
'user',
'user_title',
'user_first_name',
'user_last_name',
'user_birthdate',
'inserted_timestamp',
'details_creator'
)
class UsersPasswordsSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.SlugRelatedField(
queryset=Users.objects.all(),
slug_field='username'
)
pwd_creator = serializers.SlugRelatedField(
queryset=Users.objects.all(),
slug_field='username'
)
class Meta:
model = UsersPasswords
fields = (
'pk',
'user',
'salt',
'pwdhash',
'inserted_timestamp',
'pwd_creator'
)
class UsersDetailsPasswordsSerializer(serializers.HyperlinkedModelSerializer):
details = UsersDetailsSerializer(many=True, read_only=True)
passwords = UsersPasswordsSerializer(many=True, read_only=True)
class Meta:
model = Users
fields = (
'url',
'pk',
'username',
'inserted_timestamp',
'details',
'passwords',
)
views.py :
from rest_framework import generics
from rest_framework.response import Response
from rest_framework.reverse import reverse
from credentials.models import Users, UsersDetails, UsersPasswords
from credentials.serializers import UsersSerializer, UsersDetailsSerializer, UsersPasswordsSerializer
class UserList(generics.ListCreateAPIView):
queryset = Users.objects.all()
serializer_class = UsersSerializer
name = 'users-list'
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Users.objects.all()
serializer_class = UsersSerializer
name = 'users-detail'
class UserDetailsList(generics.ListCreateAPIView):
queryset = UsersDetails.objects.all()
serializer_class = UsersDetailsSerializer
name = 'usersdetails-list'
class UserDetailsDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = UsersDetails.objects.all()
serializer_class = UsersDetailsSerializer
name = 'usersdetails-detail'
class UserPasswordsList(generics.ListCreateAPIView):
queryset = UsersPasswords.objects.all()
serializer_class = UsersPasswordsSerializer
name = 'userpasswords-list'
class UserPasswordsDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = UsersPasswords.objects.all()
serializer_class = UsersPasswordsSerializer
name = 'userpasswords-detail'
class ApiRoot(generics.GenericAPIView):
name = 'api-root'
def get(self, request, *args, **kwargs):
return Response({
'user-list': reverse(UserList.name, request=request),
'user-details': reverse(UserDetailsList.name, request=request),
'user-passwords': reverse(UserPasswordsList.name, request=request),
})
When I intended to add new user details I expect to see username field values but I see users object (1) instread of username, like below picture:
What's your idea?
Set __str__ method on Users:
class Users(models.Model):
...
def __str__(self):
return self.username

Save Django Form wizard data to three models with related fields

I am working on a project that requires use of form wizard to populate three related models. The first model - Listing - has general data which has a OneToOneField relationship with the second model (Property). The Listing model also has a many to many relationships with the third model (ListingImages). In general, I am using 4 forms in the wizard. Here is the models definition
models.py
class Listing(models.Model):
listing_type_choices = [('P', 'Property'), ('V', 'Vehicle'), ('B', 'Business/Service'), ('E', 'Events')]
listing_title = models.CharField(max_length=255)
listing_type = models.CharField(choices=listing_type_choices, max_length=1, default='P')
status = models.BooleanField(default=False)
featured = models.BooleanField(default=False)
city = models.CharField(max_length=255, blank=True)
location = PlainLocationField(based_fields=['city'], zoom=7, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
expires_on = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey(User,
on_delete=models.CASCADE, editable=False, null=True, blank=True
)
listing_owner = models.ForeignKey(User,
on_delete=models.CASCADE, related_name='list_owner'
)
def __str__(self):
return self.listing_title
def get_image_filename(instance, filename):
title = instance.listing.listing_title
slug = slugify(title)
return "listings_pics/%s-%s" % (slug, filename)
class ListingImages(models.Model):
listing = models.ForeignKey(Listing, on_delete=models.CASCADE)
image_url = models.ImageField(upload_to=get_image_filename,
verbose_name='Listing Images')
main_image = models.BooleanField(default=False)
class Meta:
verbose_name_plural = "Listing Images"
def __str__(self):
return f'{self.listing.listing_title} Image'
class Property(models.Model):
sale_hire_choices = [('S', 'Sale'), ('R', 'Rent')]
fully_furnished_choices = [('Y', 'Yes'), ('N', 'No')]
listing = models.OneToOneField(Listing, on_delete=models.CASCADE)
sub_category = models.ForeignKey(PropertySubCategory, on_delete=models.CASCADE)
for_sale_rent = models.CharField(choices=sale_hire_choices, max_length=1, default=None)
bedrooms = models.PositiveIntegerField(default=0)
bathrooms = models.PositiveIntegerField(default=0)
rooms = models.PositiveIntegerField(default=0)
land_size = models.DecimalField(max_digits=10, decimal_places=2)
available_from = models.DateField()
car_spaces = models.PositiveIntegerField(default=0)
fully_furnished = models.CharField(choices=fully_furnished_choices, max_length=1, default=None)
desc = models.TextField()
property_features = models.ManyToManyField(PropertyFeatures)
price = models.DecimalField(max_digits=15, decimal_places=2)
currency = models.ForeignKey(Currency, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
Here is the forms.py
from django import forms
from .models import Listing, Property, Vehicle, Business, ListingImages
from django.forms import modelformset_factory
class ListingDetails(forms.ModelForm):
class Meta:
model = Listing
fields = ['listing_title', 'city', 'location']
class PropertyDetails1(forms.ModelForm):
class Meta:
model = Property
fields = ['sub_category', 'for_sale_rent', 'bedrooms', 'bathrooms',
'rooms', 'land_size', 'available_from', 'car_spaces', 'fully_furnished',
'desc', 'currency', 'price'
]
class PropertyDetails2(forms.ModelForm):
class Meta:
model = Property
fields = ['property_features']
class ListingImagesForm(forms.ModelForm):
image_url = forms.ImageField(label='Listing Image',
widget=forms.ClearableFileInput(attrs={'multiple': True}),
required=False
)
class Meta:
model = ListingImages
fields = ['image_url']
ImageFormSet = modelformset_factory(ListingImages, form=ListingImagesForm, extra=3)
views.py
from django.shortcuts import render, redirect
import os
from .forms import ListingDetails, PropertyDetails1, PropertyDetails2, ListingImagesForm
from .models import ListingImages, Listing, Property
from formtools.wizard.views import SessionWizardView
from django.conf import settings
from django.core.files.storage import FileSystemStorage
from django.forms import modelformset_factory
from django.contrib import messages
from django.http import HttpResponseRedirect, HttpResponse
from django.forms.models import construct_instance
class PropertyView(SessionWizardView):
# formset = ImageFormSet(queryset=Images.objects.none())
template_name = "listings/create_property.html"
form_list = [ListingDetails, PropertyDetails1, PropertyDetails2, ListingImagesForm]
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'media'))
def done(self, form_list, **kwargs):
listing_instance = Listing()
property_instance = Property()
listing_instance.created_by = self.request.user
listing_instance.listing_owner = self.request.user
listing_instance.listing_type = 'P'
for form in form_list:
listing_instance = construct_instance(form, listing_instance, form._meta.fields, form._meta.exclude)
property_instance = construct_instance(form, property_instance, form._meta.fields, form._meta.exclude)
listing = listing_instance.save()
property_instance.listing = listing
property_instance.save()
return HttpResponse('data saved successfully')
The problem that I am facing is that I am able to save the Listing model, but getting its primary id and using it to save the Property model is the problem. Again, the ListingImages model stores images related to the Listing model. How do I save these models to database considering that they are multiple?
What's wrong is that as described here, model.save() does not return the saved object, but None.
So the last few lines of the above code should be
listing_instance.save()
property_instance.listing = listing_instance
property_instance.save()
return HttpResponse('data saved successfully')
Ditto saving a set of listing_images would be something like
for li_obj in listing_image_instances:
li_obj.listing = listing_instance # saved above
li_obj.save()

Django Rest - Cant serialize items

I'm trying to serialize nested relations, but got an error during create model from request: 'MeasureUnit' object has no attribute 'unit'
What am I doing wrong? I'm just trying to create model MeasureItem, but got error in MeasureUnit somehow.
My models:
from django.db import models
from measure_unit.models import MeasureUnit
from main_user.models import MainUser
class Item(models.Model):
code = models.CharField(unique=True, max_length=15)
current_code = models.CharField(blank=True, null=True, max_length=15)
title = models.CharField(default='', max_length=100)
description = models.TextField(blank=True, null=True)
measure_units = models.ManyToManyField(MeasureUnit, through='MeasureItem', through_fields=('item', 'unit'), blank=True)
class Meta:
ordering = ('created_at',)
class MeasureItem(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE, blank=True, null=True)
unit = models.ForeignKey(MeasureUnit, on_delete=models.CASCADE, blank=True, null=True)
quantity = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('created_at',)
My serializer:
from rest_framework import serializers
from .models import Item, MeasureItem
class MeasureUnitSerializer(serializers.ModelSerializer):
class Meta:
model = MeasureItem
fields = ('id', 'unit')
class ItemAdminSerializer(serializers.ModelSerializer):
measure_units = MeasureUnitSerializer(many=True)
class Meta:
model = Item
fields = ('id', 'code', 'current_code', 'title', 'description', 'measure_units')
def create(self, validated_data):
units_data = validated_data.pop('measure_units')
item = Item.objects.create(**validated_data)
for unit_data in units_data:
try:
measure_unit = unit_data['unit']
MeasureItem.objects.create(unit=measure_unit, item=item)
except Exception as e:
print(str(e))
return item
return item
MeasureUnitSerializer is ModelSerializer for MeasureItem model, but you use it for MeasureUnit model in ItemAdminSerializer:
measure_units = MeasureUnitSerializer(many=True)
Since MeasureUnit doesn't have unit field you see error.
You could try to specify source argument of measure_units field:
measure_units = MeasureUnitSerializer(source='measureitem_set', many=True)