Nested serializers and data representation - django

Working on a django project I am a bit stuck on data representation through APIs. In fact when designing models the data model is quite stratighforward : I have a one to many relationship A--> B
therefore I have added a FK to object B.
Object B has a boolean attribute "active".
I would like to make an API call to list all A objects having at least one assoicated object B with active = true.
The api could be like this :
/api/objectA/?ObjectB.active=True
Here is my code :
Models :
from django.contrib.postgres.fields import ArrayField
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class Startup(models.Model):
header = models.CharField("Header", max_length=255)
title = models.CharField("Title", max_length=255)
description = models.CharField("description", max_length=255)
# TODO Change this to options instead of array
tags = ArrayField(models.CharField(max_length=10, blank=True), size=5)
# TODO Images to be stored in aws only url will be in DB
card_image = models.ImageField(upload_to='media/images/cards')
logo_image = models.ImageField(upload_to='media/images/logos')
main_img = models.ImageField(upload_to='media/images/main', null=True)
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def __str__(self):
return self.title
class Investment(models.Model):
# TODO change the name of Investment to fund round in back and front
# TODO all price to be checked for max digits and decimal places
startup = models.ForeignKey(Startup, related_name='startup_investments', on_delete=models.CASCADE, default="1")
# Use the related_name as a serializer bale for investments inside startup serializer
Investment_title = models.CharField("Investment_title", max_length=255, default="Missing Title")
collected_amount = models.DecimalField(max_digits=12, decimal_places=2)
goal_percentage = models.IntegerField(default=0)
number_of_investors = models.IntegerField(default=0)
days_left = models.IntegerField()
active = models.BooleanField(default=False)
# TODO Need to update this to prevent linking to a non existing startup
createdAt = models.DateTimeField("Created At", auto_now_add=True)
def clean(self):
"""Validate that the startup does not have already an active Investment """
if self.active:
qs = Investment.objects.filter(active=True).filter(startup=self.startup)
if self.pk is not None:
qs = qs.exclude(pk=self.pk)
if qs:
raise ValidationError(message="An active investment already exists for this startup")
def __str__(self):
return self.Investment_title
Serializers :
from rest_framework import serializers
from .models import Startup, Investment
class InvestmentSerializer(serializers.ModelSerializer):
class Meta:
model = Investment
fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
'days_left', 'active')
class StartupSerializer(serializers.ModelSerializer):
startup_investments = InvestmentSerializer(many=True, read_only=True)
class Meta:
model = Startup
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
Views :
from django_filters import rest_framework as filters
from rest_framework.viewsets import ModelViewSet
from rest_framework_extensions.mixins import NestedViewSetMixin
from .serializers import *
class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
"""
Class that provides List, Retrieve, Create, Update, Partial Update and Destroy actions for startups.
It also include a filter by startup status
"""
model = Startup
queryset = Startup.objects.all()
serializer_class = StartupSerializer
class InvestmentViewSet(NestedViewSetMixin, ModelViewSet):
"""
Class that provides List, Retrieve, Create, Update, Partial Update and Destroy actions for Investments.
It also include a active and investment title
"""
model = Investment
serializer_class = InvestmentSerializer
queryset = Investment.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('active', 'Investment_title')
routers :
router = ExtendedSimpleRouter()
(
router.register(r'api/investments', views.InvestmentViewSet, basename='investment'),
router.register(r'api/startups', views.StartUpViewSet, basename='startup')
.register(r'investments', views.InvestmentViewSet, basename='startups_investment',
parents_query_lookups=['startup']),
)
Thanks for your help.

I would try something like this:
class StartUpViewSet(NestedViewSetMixin, ModelViewSet):
model = Startup
#queryset = Startup.objects.all()
serializer_class = StartupSerializer
def get_queryset(self):
Startup.objects.annotate(active_investments=Count('startup_investments', filter=Q(startup_investments__active=True)).filter(active_investments__gt=0)

Hello I am posting this answer hoping it will help others as I have spent two days to make this work!!
class ActiveStartupSerializer(serializers.ListSerializer):
def to_representation(self, data):
"""List all startups with one active investment"""
data = data.filter(startup_investments__active=True)
return super(ActiveStartupSerializer, self).to_representation(data)
class Meta:
model = Startup
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')
class InvestmentSerializer(serializers.ModelSerializer):
class Meta:
model = Investment
fields = ('id', 'Investment_title', 'collected_amount', 'goal_percentage', 'number_of_investors',
'days_left', 'active')
class StartupSerializer(serializers.ModelSerializer):
startup_investments = InvestmentSerializer(many=True, read_only=True)
class Meta:
model = Startup
list_serializer_class = ActiveStartupSerializer
fields = ('id', 'header', 'title', 'description', 'tags', 'card_image',
'logo_image', 'main_img', 'startup_investments')

Related

Django Rest API issue with retrieving correct information from urls. It is a Grandparent - Parent - Child relationship. How do I correctly map them?

I have been stuck on this problem for about 9 hours and am having trouble understanding the Grandparent - Parent - Child relationship with a One to Many Relationship(foreign key) in Django.
I am currently creating an E-Commerce website and am trying to display the information in 4 different ways to fetch from my React frontend.
I have 3 models setup:
Category
Subcategory (Foreign key to Category)
Products (Foreign Key to Subcategory)
This is the result I am attempting to create in my urls/views/serializer files:
For some reason I can successfully retrieve at a granular level for a single product but as soon as I start going up nested models I am running into the following errors:
Desired Result
I have api/ included in the core urls folder already.
I want JSON to display the information like this.
# URLSearch api/Category/
# Category -
# |
# Subcategory 1 -
# |
# Product 1
# Product 2
# Subcategory 2 -
# |
# Product 1
# Proudct 2
I also want JSON to display the information like this.
# URLSearch api/Category/Subcategory/
# Category -
# |
# Subcategory -
# |
# Product 1
# Product 2
Errors
Error 1:
Got AttributeError when attempting to get a value for field slug on serializer SubcategoryListSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the QuerySet instance.
Original exception text was: 'QuerySet' object has no attribute 'slug'.
Error 2:
Returns Wrong Object piggies back off issue above - I deleted my code to clean it up file as I have been attempting this for 10+ hours so there is currently a place holder route there.
Urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from api import views
urlpatterns = [
path('search/<slug:category_slug>/<slug:subcategory_slug>/',
views.SubcategoryListView.as_view()), #Error 1
path('search/<slug:category_slug>/', views.AllProductsListView.as_view()), # Error 2
path('all-products/', views.AllProductsListView.as_view()), # Good
path('search/<slug:category_slug>/<slug:subcategory_slug>/<slug:product_slug>',
views.ProductDetail.as_view()), # WORKS
# path('all-products/', views.AllProductsListView.as_view())
]
Models.py
from django.db import models
import uuid
# Create your models here.
class Category(models.Model):
slug = models.SlugField(
primary_key=True, max_length=50, null=False, unique=True)
class Meta:
ordering = ('slug',)
def __str__(self):
return self.slug
def get_absolute_url(self):
return f'/{self.slug}/'
class Subcategory(models.Model):
slug = models.SlugField(
primary_key=True, max_length=50, null=False, unique=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
class Meta:
ordering = ('category',)
def __str__(self):
return self.slug
def get_absolute_url(self):
return f'/{self.category.slug}/{self.slug}/'
class Products(models.Model):
subcategory = models.ForeignKey(Subcategory, on_delete=models.CASCADE)
newAdd = models.BooleanField(default=False)
name = models.CharField(max_length=200)
slug = models.SlugField(
primary_key=True, max_length=50, null=False, unique=True)
imageOne = models.CharField(max_length=100)
imageTwo = models.CharField(max_length=100, null=True)
imageThree = models.CharField(max_length=100, null=True)
colors = models.CharField(max_length=300) # This is going to be a list
price = models.FloatField()
dateCreated = models.DateField(auto_now_add=True)
class Meta:
ordering = ('-dateCreated',)
def __str__(self):
return self.slug
def get_absolute_url(self):
return f'/{self.subcategory.category.slug}/{self.subcategory.slug}/{self.slug}'
Views
from django.shortcuts import render
from .serializers import AllProductsSerializer, CategorySerializer, SubcategoryListSerializer, ProductsSerializer, SubcategorySerializer
from rest_framework import viewsets, generics, views, response
from products.models import Products, Subcategory, Category
# Create your views here.
class ProductDetail(views.APIView):
def get_object(self, category_slug, subcategory_slug, product_slug):
obj = Products.objects.filter(subcategory__category__slug=category_slug).filter(
subcategory=subcategory_slug).get(slug=product_slug)
return obj
def get(self, request, category_slug, subcategory_slug, product_slug):
print(request)
product = self.get_object(
category_slug, subcategory_slug, product_slug)
serializer = ProductsSerializer(product)
return response.Response(serializer.data)
class SubcategoryListView(views.APIView):
def get_object(self, category_slug, subcategory_slug):
obj = Products.objects.filter(
subcategory__category__slug=category_slug)
return obj
def get(self, request, category_slug, subcategory_slug):
products = self.get_object(category_slug, subcategory_slug)
serializer = SubcategoryListSerializer(products)
return response.Response(serializer.data)
class AllProductsListView(generics.ListAPIView):
serializer_class = AllProductsSerializer
queryset = Products.objects.all()
class CategoryListView(generics.ListCreateAPIView):
queryset = Products.objects.all()
serializer_class = Category
Serializers
from rest_framework import serializers
from products.models import Products, Category, Subcategory
class ProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = (
'newAdd',
'name',
'slug',
'imageOne',
'price',
'dateCreated',
'get_absolute_url',
)
class SubcategoryListSerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('__all__')
class SubcategorySerializer(serializers.ModelSerializer):
items = SubcategoryListSerializer(many=True, read_only=True)
class Meta:
model = Subcategory
fields = (
'slug',
'items'
'get_absolute_url',
)
class AllProductsSerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('__all__')
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Products
fields = ('__all__')

NOT NULL constraint failed: shipping_ship.user_id Django

So I'm working on a shipping website with the django rest framework. The website brings two to four people together so they can easily ship their goods together at the same time. But I'm facing a major stumbling block on the views where user book a shipping the code is below.
models.py
from django.db import models
from django.contrib import get_user_model
User = get_user_model()
class Container(models.Model):
container_type = models.Charfield(max_length = 30, blank=False, null = False)
max_users = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places =2, default=0, blank=True, null=True)
users = models.ManyToManyField(User)
class Ship(models.Model):
container = models.ForeignKey(Container, related_name='cont', on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name='shipper', on_delete=models.CASCADE)
location = (
('France', 'France'),
)
from_location = models.CharField(max_length=30, choices=location, blank=False, null=False)
to_location = (
('Lagos', 'Lagos'),
('Abuja', 'Abuja'),
('Abeokuta', 'Abeokuta'),
('Osun', 'Osun'),
)
to_location = models.CharField(max_length=30, choices=to_location, blank=False, null=False)
date_leaving = models.DateField(auto_now=False)
price = models.DecimalField(max_digits=10, decimal_places=2, default=0, blank=True, null=True)
def __str__(self):
return self.user
then my serializer.py file
from rest_framework import serializers
from .models import Container, Ship
class ContainerSerializer(serializers.ModelSerializer):
class Meta:
model = Container
fields = '__all__'
class MiniContainerSerializer(serializers.ModelSerializer):
class Meta:
model = Container
fields =['container_type', 'price']
class ShipSerializer(serializers.ModelSerializer):
class Meta:
model = Ship
fields = '__all__'
read_only_fields = ('user', 'price')
class MiniShipSerializer(serializers.ModelSerializer):
class Meta:
model = Ship
fields = ['container', 'from_location', 'to_location']
and now my views.py file which I have issues with
from django.shortcuts import render
from django.shortcuts import get_object_or_404
from rest_framework.generics import ListCreateAPIView, CreateAPIView, ListAPIView, RetrieveUpdateDestroyAPIView, RetrieveAPIView
from .serializers import ContainerSerializer, MiniContainerSerializer, ShipSerializer, MiniShipSerializer
from rest_framework import permissions, status
from rest_framework.response import Response
from .models import Container, Ship
class ShipAPI(ListCreateAPIView):
serializer_class = ShipSerializer
def get_queryset(self):
user = self.request.user
queryset = Ship.objects.filter(user=user)
return queryset
def Book_shipping(self, request, *args, **kwargs):
user = request.user
container = get_object_or_404(Container, pk=request.data['container'])
if container.users.count() >= container.max_users:
return Response('container already full')# here i'm trying to set limits so the users joining each container won't surpass the max users.
cont = container(users=user)
cont.save()
from_location = (request.data['from_location'])
to_location = (request.data['to_location'])
date_leaving = int(request.data['date_leaving'])
price = container.price / container.max_users
cart = Ship(container=container, user=user, from_location=from_location, to_location=to_location, date_leaving=date_leaving, price=price)
cart.save()
serializer = ShipSerializer(cart)
data ={'message': 'shipping successfully created',
'data':serializer.data}
return Response(data=data, status=status.HTTP_201_CREATED)
and then after testing the endpoint it returns this error:
IntegrityError at /Shipping/Ship/
NOT NULL constraint failed: shipping_ship.user_id
I've tried debugging and looking at it over and over again can someone please help me? Thanks in advance. And yes I've tried deleting migrations and the database.
As your Container model have a ManyToMany relationship with the User model.
So it may not work like cont = container(users=user)
For me it worked like this:
cont = container.users.add(user)
cont.save()

django class based CreateApiView author_id is not null?

I have also authenticate it with token but when I create a new post error is alert IntegrityError at /api/create/ NOT NULL constraint failed: core_article.author_id how can I valid data with request user in serializer?
model.py
from django.contrib.auth.models import User
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=255, help_text="Short title")
content = models.TextField(blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
status = models.BooleanField(default=True)
def __str__(self):
return self.title
serializer.py
from rest_framework import serializers
from django.contrib.auth.models import User
from core.models import Article
class NewsSerializer(serializers.ModelSerializer):
author = serializers.SlugRelatedField(
slug_field=User.USERNAME_FIELD, read_only=True, required=False)
class Meta:
model = Article
fields = [
'id',
'author',
'title',
'content',
'status',
]
views.py
class ArticleCreate(CreateAPIView):
queryset = Article.status_objects.all()
serializer_class = NewsSerializer
permission_classes = (permissions.IsAuthenticated, )
I don't know if this is what you're looking for, but you can pass the user as author object to your serializer and in your serializer use that author to create your Article object (Note: I assume that you have used correct authentication_class in your view and have access to user from your request object).
First you need to override the perform create of your view:
class ArticleCreate(CreateAPIView):
queryset = Article.status_objects.all()
serializer_class = NewsSerializer
permission_classes = (permissions.IsAuthenticated, )
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Which will send an user instance to your serializer validated data. Then you should change your serializer to:
class NewsSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = [
'id',
'title',
'content',
'status',
]
Note that the line author = serializers.SlugRelatedField(slug_field=User.USERNAME_FIELD, read_only=True, required=False) and author are removed from your serializer. But if you need to serialize the id of that author (read only purpose) you can change your serializer to:
class NewsSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = [
'id',
'title',
'author',
'content',
'status',
]
read_only_fields = ('author',)
author = serializers.SlugRelatedField(
slug_field=User.USERNAME_FIELD, read_only=True,required=True)
Please change required=False to required=True . If you set True then don't add Null value. So you avoid this errror.

How to add count of pending tasks to List Json

I am creating a todo list API(backend) in django rest framework. I have two models List and Task
#models.py
from django.db import models
# Create your models here.
class List(models.Model):
list_name = models.CharField(max_length=200)
def __str__(self):
return self.list_name
class Meta:
db_table = 'list'
class Task(models.Model):
todo_list = models.ForeignKey(List, on_delete=models.CASCADE)
task_name = models.CharField(max_length=500)
due_date = models.DateField()
done = models.BooleanField(default=False)
def __str__(self):
return self.task_name
class Meta:
db_table = 'task'
The serializers file,
#serializers.py
from rest_framework import serializers
from rest_framework_serializer_extensions.serializers import SerializerExtensionsMixin
from .models import List, Task
class ListSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
class Meta:
model = List
fields = "__all__"
class TaskSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
class Meta:
model = Task
fields = "__all__"
expandable_fields = dict(
todolist=ListSerializer
)
My todo list app will have multiple lists and each list have multiple tasks. Each task will have a due date and can be marked as done. I am trying to add the number of pending tasks to my List json. How do I go about doing that?
You can add a new field using SerializerMethodField:
class ListSerializer(SerializerExtensionsMixin, serializers.ModelSerializer):
pending_count = serializers.SerializerMethodField()
class Meta:
model = List
fields = "__all__"
def get_pending_count(self, obj):
return obj.task_set.filter(done=False).count()

Django REST Framework (DRF): Set current user id as field value

I have model NewsModel and 2 serializers for him:
models.py
class NewsModel(models.Model):
title = models.CharField('Заголовок', max_length=255, help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс', help_text='Краткий анонс новости')
author = models.ForeignKey(settings.AUTH_USER_MODEL, help_text='Автор новости', related_name='news')
full_text = models.TextField('Полный текст новости', help_text='Полный текст новости')
pub_date = models.DateTimeField('Дата публикации', auto_now_add=True, default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serilizers.py:
from rest_framework import serializers
from .models import NewsModel
from extuser.serializers import UserMiniSerializer
class NewsReadSerializer(serializers.ModelSerializer):
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author_name')
def get_author_full_name(self, obj):
return obj.get_author_full_name()
class NewsWriteSerializer(serializers.ModelSerializer):
def validate_author(self, value):
value = self.request.user.id
return value
class Meta:
model = NewsModel
I select serializers in the api.py:
class NewsList(ListCreateAPIView):
queryset = NewsModel.objects.order_by('-pub_date')
def get_serializer_class(self, *args, **kwargs):
if self.request.method == 'GET':
return NewsReadSerializer
return NewsWriteSerializer
class Meta:
model = NewsModel
But when I will create NewsModel item, I see Error 400: Bad request [{'author': 'This field is required'}]
How I can set current user id as NewsItem.author value on creating new item?
I don't think you're using the serializer properly. A better practice to set request related data is to override perform_create in your view:
def perform_create(self, serializer):
serializer.save(author=self.request.user)
def perform_update(self, serializer):
serializer.save(author=self.request.user)
and then set your author serializer to read-only:
author = UserMiniSerializer(read_only=True)
this way you can simply use one single NewsSerializer for both read and write actions.
In new DRF you can write
owner = serializers.HiddenField(
default=serializers.CurrentUserDefault()
)
See http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault
In DRF version prior 3 field must be declader with allow_null=True and default=None. DRF don't run checking fields without this params. Result code:
class NewsReadSerializer(serializers.ModelSerializer):
"""
Serializer only for reading.
author field serialized with other custom serializer
"""
author = UserMiniSerializer()
class Meta:
model = NewsModel
fields = ('id', 'title', 'announce', 'comments_count', 'reviews', 'author', 'pub_date',)
class NewsWriteSerializer(serializers.ModelSerializer):
"""
Serializer for creating and updating records.
author here is the instance of PrimaryKeyRelatedField, linked to all users
"""
author = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), # Or User.objects.filter(active=True)
required=False,
allow_null=True,
default=None
)
# Get the current user from request context
def validate_author(self, value):
return self.context['request'].user
class Meta:
model = NewsModel
fields = ('title', 'announce', 'full_text', 'author',)
I would try something like this:
your models.py
class NewsModel(models.Model):
title = models.CharField(
'Заголовок', max_length=255,
help_text='Максимальная длина - 255 символов')
announce = models.TextField('Анонс',
help_text='Краткий анонс новости')
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
help_text='Автор новости', related_name='news')
full_text = models.TextField(
'Полный текст новости',
help_text='Полный текст новости')
pub_date = models.DateTimeField(
'Дата публикации', auto_now_add=True,
default=timezone.now, help_text='Дата публикации')
def comments_count(self):
return NewsComment.objects.filter(news=self.id).count()
def get_author_full_name(self):
return self.author.get_full_name()
class Meta:
db_table = 'news'
ordering = ('-pub_date',)
serializers.py
(ref.: http://www.django-rest-framework.org/api-guide/validators/#currentuserdefault)
from <yourapp>.models import NewsModel
from rest_framework import serializers
class NewsModelSerializer(serializers.ModelSerializer):
author = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = NewsModel
Also you should set settings.py to something like this:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',)
}