How to serialize related models in Django Rest API? - python

I have tried all the solutions. Still cannot resolve it. Here are the codes.
models.py
class Car(models.Model):
car_name = models.CharField(max_length=250)
car_description = models.CharField(max_length=250)
def __str__(self):
return self.car_name + ' - ' + str(self.pk)
class Owners(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE, default=0)
owner_name = models.CharField(max_length=250)
owner_desc = models.CharField(max_length=250)
def get_absolute_url(self):
return reverse('appname:index')
def __str__(self):
return self.owner_name + ' - ' + self.owner_desc
serializers.py
class OwnersSerializer(serializers.ModelSerializer):
class Meta:
model = Owners
fields = '__all__'
class CarSerializer(serializers.ModelSerializer):
owners = OwnersSerializer(many=True, read_only=True)
class Meta:
model = Car
fields = '__all__'
views.py
class CarList(APIView):
def get(self, request):
cars = Car.objects.all()
serializer = CarSerializer(cars, many=True)
return Response(serializer.data)
def post(self):
pass
I can't get to view all the 'Owner' objects related to a certain object of the 'Car' class.

You need to define a related name on the ForeignKey to create the reverse reference.
class Owners(models.Model):
car = models.ForeignKey(Car, on_delete=models.CASCADE, default=0, related_name='owners')

Related

drf viewset : multiple choices

I tried to make method(location_item) that shows item by region.
And I want to implement the 'gu, dong' field(in location model) with multiple choices.
so, wrote this method code. but filtering doesn't work..
This shows all item objects, not just the objects I want.
TypeError: Field 'id' expected a number but got <Item: 애니원모어 원피스입니다!>.
[13/Aug/2022 15:01:07] "GET /item_post/location/location_item/ HTTP/1.1" 500 152955
I don't know what sholud i do.
please help me...
models.py
class Item(models.Model):
user_id = models.ForeignKey(User, related_name='item_sets', on_delete=models.CASCADE)
category_id = models.ForeignKey(Category, related_name='item_sets', on_delete=models.DO_NOTHING)
description = models.TextField()
feature = models.TextField()
product_defect = models.TextField()
size = models.CharField(max_length=6)
wear_count = models.IntegerField()
price = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.description
class Location(models.Model):
city = models.CharField(max_length=10)
gu = models.CharField(max_length=10)
dong = models.CharField(max_length=10)
def __str__(self):
return self.city+" "+self.gu+" "+self.dong
class LocationSet(models.Model):
item_id = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='location_sets')
location_id = models.ForeignKey(Location, on_delete=models.CASCADE, related_name='location_sets')
serializers.py
class ItemSerializer(serializers.ModelSerializer):
photos = PhotoSerializer(source='photo_sets', many=True, read_only=True)
style_photos = StylePhotoSerializer(source='style_photo_sets', many=True, read_only=True)
class Meta:
model = Item
fields = '__all__'
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = '__all__'
class LocationSetSerializer(serializers.ModelSerializer):
class Meta:
model = LocationSet
fields = '__all__'
views.py
class ItemViewSet(ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['description'] # ?search=
ordering_fields = ['-created_at'] # ?ordering=
ordering = ['-created_at']
# here
#action(detail=False, methods=['GET'])
def location_item(self, request):
locations = Location.objects.all()
city = request.GET.get('city', None)
gu = request.GET.getlist('gu', None) # multiple choices
dong = request.GET.getlist('dong', None) # multiple choices
print(city, " ", gu, " ", dong) # > None [] [] (upper codes not work..)
if city:
locations = locations.filter(city=city)
if gu:
locations = locations.filter(gu__in=gu).distinct()
if dong:
locations = locations.filter(dong__in=dong).distinct()
location_ids = []
for i in locations:
location_ids.append(i.id)
locationsets = LocationSet.objects.filter(location_id__in=location_ids)
item_ids = []
for i in locationsets:
item_ids.append(i.item_id)
serializer = self.get_serializer(item_ids, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
To create method that shows items by region.
what sholud i do?
I think the last lines of code are wrong.
...
#action(detail=False, methods=['GET'])
def location_item(self, request):
...
# here I changed the last four lines
item_ids = [x.item_id for x in locationsets]
serializer = self.get_serializer(item_id__id__in = item_ids, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)

How can I implement the same widget that Django uses to ManyToMany fields in the admin page?

My models:
class Ingredient(models.Model):
BASE_UNIT_CHOICES = [("g", "Grams"), ("ml", "Mililiters")]
CURRENCY_CHOICES = [("USD", "US Dollars"), ("EUR", "Euro")]
ingredient_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
base_unit = models.CharField(max_length=2, choices=BASE_UNIT_CHOICES)
cost_per_base_unit = models.FloatField()
currency = models.CharField(
max_length=3, choices=CURRENCY_CHOICES, default="EUR")
def __str__(self):
return self.name
class RecipeIngredient(models.Model):
quantity = models.FloatField()
ingredient_id = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
def __str__(self):
return f"{self.quantity} / {self.ingredient_id}"
class Recipe(models.Model):
recipe_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=200)
ingredients = models.ManyToManyField(RecipeIngredient)
date_created = models.DateTimeField('Date Created')
def __str__(self):
return f"{self.name}, {self.ingredients}"
When I use the admin page, it has this + button that allows me to create new ingredient/quantity combinations
like this
But when I try to use it from a form in my code it looks like
this
Here is my form code:
class AddRecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ['name', 'ingredients', 'date_created']
You should write the 'widgets' for each field in you Form that need configuration.
Check the documentation 'Widgets in forms', or even, you can define your own Widgets.

How to deal with nested serializer fields in Django Rest Framework?

I have nested serializer (AmountSerializer). I need a field meal_name in one ViewSet. But when this field is nested, I don't need it to be seen in endpoint(in MealSerializer). How to exclude field from nested serializer when is it actually nested?
models.py:
class MealType(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Ingredient(models.Model):
name = models.TextField()
def __str__(self):
return self.name
class Meal(models.Model):
name = models.TextField()
type = models.ForeignKey(MealType, on_delete=models.CASCADE, default=None)
recipe = models.TextField()
photo = models.ImageField(null=True, height_field=None, width_field=None, max_length=None,upload_to='media/')
ingredients = models.ManyToManyField(Ingredient)
def __str__(self):
return self.name
class IngredientAmount(models.Model):
ingredient_name = models.ForeignKey(Ingredient, on_delete=models.CASCADE, default=None)
amount = models.FloatField(default=None)
meal = models.ForeignKey(Meal, on_delete=models.CASCADE, default=None, related_name='meal_id')
class Meta:
ordering = ['meal']
def __str__(self):
return self.ingredient_name
serializers.py:
class AmountSerializer(serializers.ModelSerializer):
ingredient_name= serializers.ReadOnlyField(source='ingredient_name.name')
-->#meal_name = serializers.ReadOnlyField(source='meal.name')
#I CAN'T use ReadOnlyField( #with write_only=True)
#i trired use PrimaryKeyRelatedField
# butgot AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.
class Meta:
model = IngredientAmount
fields = ('ingredient_name','amount','meal_name')
class MealSerializer(serializers.ModelSerializer):
type_name= serializers.ReadOnlyField(source='type.name')
ingredients = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
amount = AmountSerializer(read_only=True, many=True,source='meal_id')
class Meta:
model = Meal
fields = ('id', 'name', 'type_name', 'recipe', 'photo', 'ingredients','amount')
I'd rather use a trick to exclude some of the fields that are not needed in certain situations. You can inherit your serializer from ExcludeFieldsModelSerializer, and exclude any fields that you want so that the serializer will not serialize that field.
class ExcludeFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `exclude_fields` argument that
controls which fields should be excluded from the serializer.
Plagiarised from https://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
"""
def __init__(self, *args, **kwargs):
# Don't pass the 'exclude_fields' arg up to the superclass
exclude_fields = kwargs.pop('exclude_fields', None)
# Instantiate the superclass normally
super(ExcludeFieldsModelSerializer, self).__init__(*args, **kwargs)
if exclude_fields is not None:
# Drop any fields that are specified in the `exclude_fields` argument.
drop = set(exclude_fields)
for field_name in drop:
self.fields.pop(field_name)
class AmountSerializer(ExcludeFieldsModelSerializer):
ingredient_name= serializers.ReadOnlyField(source='ingredient_name.name')
meal_name = serializers.CharField(read_only=True, source='meal.name')
class Meta:
model = IngredientAmount
fields = ('ingredient_name','amount','meal_name')
class MealSerializer(serializers.ModelSerializer):
type_name= serializers.ReadOnlyField(source='type.name')
ingredients = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
amount = AmountSerializer(read_only=True, many=True, source='meal_id', exclude_fields={'meal_name'})
class Meta:
model = Meal
fields = ('id', 'name', 'type_name', 'recipe', 'photo', 'ingredients','amount')

Order post by date

I've created a list of post and now I want order this list by date of publishing. If I use order_by(-post_publishing_date) in the view the shell show me this error:
NameError: name 'post_publishing_date' is not defined
models.py
class PostModel(models.Model):
post_title = models.CharField(max_length=70)
post_short_description = models.TextField(max_length=200)
post_contents = models.TextField()
post_publishing_date = models.DateTimeField(auto_now=False, auto_now_add=True)
post_author = models.ForeignKey(AuthorModel, on_delete=models.CASCADE, related_name="connected_author")
post_keyconcept = models.ManyToManyField(KeyConceptModel, related_name="connected_keyconcept")
slug = models.SlugField(verbose_name="Slug", unique="True")
post_highlighted = models.BooleanField(default=False)
def __str__(self):
return self.post_title
def get_absolute_url(self):
return reverse("singlepostManuscriptusView", kwargs={"slug": self.slug})
class Meta:
verbose_name = "Articolo"
verbose_name_plural = "Articoli"
views.py
class SinglePostGDV(DetailView):
model = PostModel
template_name = "manuscriptus_post_detail.html"
class ListPostGDV(ListView):
model = PostModel
template_name = "manuscriptus_home.html"
queryset = PostModel.objects.filter().order_by(-post_publishing_date)
urls.py
urlpatterns = [
path("it/blog/", ListPostGDV.as_view(), name="homeManuscriptusView"),
path("it/blog/<slug:slug>/", SinglePostGDV.as_view(), name="singlepostManuscriptusView"),
]
What I did wrong?
Ad hoc ordering
Well Python is correct. There is no identifier post_publishing_date, you pass the name of the column through a string, so:
class ListPostGDV(ListView):
model = PostModel
template_name = "manuscriptus_home.html"
queryset = PostModel.objects.filter().order_by('-post_publishing_date')
Define an inherent ordering on the model
Note that you can also give a model an "inherent" ordering in the Meta class:
class PostModel(models.Model):
post_title = models.CharField(max_length=70)
post_short_description = models.TextField(max_length=200)
post_contents = models.TextField()
post_publishing_date = models.DateTimeField(auto_now=False, auto_now_add=True)
post_author = models.ForeignKey(AuthorModel, on_delete=models.CASCADE, related_name="connected_author")
post_keyconcept = models.ManyToManyField(KeyConceptModel, related_name="connected_keyconcept")
slug = models.SlugField(verbose_name="Slug", unique="True")
post_highlighted = models.BooleanField(default=False)
def __str__(self):
return self.post_title
def get_absolute_url(self):
return reverse("singlepostManuscriptusView", kwargs={"slug": self.slug})
class Meta:
ordering = ['-post_publishing_date']
verbose_name = "Articolo"
verbose_name_plural = "Articoli"
If you do this, all queries to this model will implicitly be ordered by -post_publishing_date. So this means that you can not "forget" to order the objects properly.
So then you do not have to order it in the views. You can of course only define one such "inherent" ordering, and it is not clear if you want to use one here.
order_by argument should be string:
queryset = PostModel.objects.filter().order_by('-post_publishing_date')

Object of type 'ListSerializer' is not JSON serializable

I want to get all customer data and responses and also remarks.
This is model.py
class Customer(models.Model):
name = models.CharField(max_length=200)
email_address = models.CharField(max_length=200)
phone_number = models.CharField(max_length=20)
age = models.SmallIntegerField(default=14)
remarks = models.ManyToManyField(Remark,null=True,blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.id)
class Response(models.Model):
question = models.ForeignKey(Question)
customer = models.ForeignKey(Customer)
response_text = models.CharField(max_length=100, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField()
def __str__(self):
return str(self.id)
This is serializers.py
class ResponseSerializer(ModelSerializer):
class Meta:
model = Response
fields = '__all__'
class RemarksSerializer(ModelSerializer):
class Meta:
model = Remark
fields = '__all__'
class CustomerInformationSerializer(ModelSerializer):
remarks = RemarksSerializer(many=True)
responses = serializers.SerializerMethodField()
def get_responses(self, obj):
responses = Response.objects.filter(customer=obj)
return ResponseSerializer(responses, many=True)
class Meta:
model = Customer
fields = ('name', 'email_address', 'phone_number', 'age', 'remarks', 'responses')
This is services.py
def customer_information(company_id=1):
cus = Customer.objects.filter(remarks__company_id=company_id)
return CustomerInformationSerializer(cus, many=True).data
This is views.py
class CustomerInformationView(APIView):
def get(self, request):
company_id = request.GET.get('company_id', 1)
resp = {'data': customer_information(company_id)}
return Response(data=resp, status=status.HTTP_200_OK)
This is url.py
url(r'^customer/$', CustomerInformationView.as_view()),
I'm having this problem. How can I solve this. Kindly guide me.
get function in your view should return responses.data, insted of responsed.
SIDE NOTE
First, let me point you to a resource that I think is GREAT for anything dealing with Django REST Framework:
Classy Django REST Framework. It is a fantastic resource because you can easily dig right into the source code to see how you may or may not need to override default operations.
MY ANSWER
What I suggest is that instead of using the APIView, you use ListAPIView.
It would look something like this:
from rest_framework.generics import ListAPIView
class Customer(models.Model):
name = models.CharField(max_length=200)
email_address = models.CharField(max_length=200)
phone_number = models.CharField(max_length=20)
age = models.SmallIntegerField(default=14)
remarks = models.ManyToManyField(Remark,null=True,blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.id)
class Response(models.Model):
question = models.ForeignKey(Question)
customer = models.ForeignKey(Customer, related_name='responses')
response_text = models.CharField(max_length=100, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField()
def __str__(self):
return str(self.id)
class ResponseSerializer(ModelSerializer):
class Meta:
model = Response
fields = '__all__'
class RemarksSerializer(ModelSerializer):
class Meta:
model = Remark
fields = '__all__'
class CustomerInformationSerializer(ModelSerializer):
remarks = RemarksSerializer(many=True)
responses = ResponseSerializer(many=True)
class Meta:
model = Customer
fields = ('name', 'email_address', 'phone_number', 'age', 'remarks', 'responses')
class CustomerInformationView(ListAPIView):
queryset = Customer.objects.all()
serializer_class = CustomerInformationSerializer
lookup_field = 'remarks__company'
Note the change that I made by adding related_name to the customer field on your Response model. See Django documentation for more information on related_name. In short, it adds responses as a field name on your Customer model so that you can travel backwards through that relationship.
This is not tested, but this should be a better strategy to do what you want without having to have a get_responses method, or a services.py.
Some there might be error because of missing "/" at the end of path like "event-api"=incorrect and "event-api/" correct. That worked for me. Hope you also have same problem.
Incorrect: path('event-api',views.event_view,name="event-view")
Correct: path('event-api/',views.event_view,name="event-view")

Categories