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()
Related
I have a few different models with a fact table of scenarios and 6 dimension tables that relate to it below as examples:
class fScenario(models.Model):
#Variables
scenarioId = models.IntegerField(primary_key=True)
description = models.CharField(max_length=100)
def __str__(self):
return str(self.scenarioId)
def get_absolute_url(self):
return reverse('scenario-detail', args=[str(self.scenarioId)])
class Meta:
ordering = ['scenarioId']
class dADA(models.Model):
#Variables
scenarioId = models.ForeignKey(fScenario, on_delete=models.CASCADE)
dateTimeId = models.DateTimeField('ADA Time/Date')
latitutde = models.FloatField(default=0)
longitude = models.FloatField(default=0)
instanceType = models.CharField(max_length=50, default='ADA')
def __str__(self):
return f'{self.scenarioId}, {self.instanceType}'
class Meta:
ordering = ['dateTimeId']
serializers.py
class fScenarioSerializer(serializers.ModelSerializer):
class Meta:
model = fScenario
fields = ['scenarioId', 'description']
class dADASerializer(serializers.ModelSerializer):
scenarioId = fScenarioSerializer(read_only=True)
class Meta:
model = dADA
fields = ['scenarioId', 'dateTimeId', 'latitutde', 'longitude', 'instanceType']
urls.py
router = routers.DefaultRouter()
router.register(r'fScenario', views.fScenarioViewSet)
router.register(r'dADA', views.dADAViewSet)
urlpatterns = [
path('', include(router.urls)),
views.py
class fScenarioViewSet(viewsets.ModelViewSet):
queryset = fScenario.objects.all()
serializer_class = fScenarioSerializer
class dADAViewSet(viewsets.ModelViewSet):
queryset = dADA.objects.all()
serializer_class = dADASerializer
I'm using rest framework view sets and I currently can see in my API separate fScenario and dADA views but cant figure out how to link the dADA to the fScenario view on one page.
You can use SerializerMethodField and Django many-to-one backward relation to retrieve related objects:
serializers.py
class fScenarioSerializer(serializers.ModelSerializer):
dADA_list = serializers.SerializerMethodField()
class Meta:
model = fScenario
fields = ['scenarioId', 'description', 'dADA_list']
def get_dADA_list(self, obj):
return obj.dada_set.all().values()
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')
I am a newbie in Django. I have 3 models: Continent, Country, Region
Here is the code:
from django.db import models
# Create your models here.
class Continent(models.Model):
continent = models.CharField(max_length=50, unique=True)
class Meta:
ordering = ['continent']
def __str__(self):
return self.continent
class Country(models.Model):
country = models.CharField(max_length=50, unique=True)
continent = models.ForeignKey(Continent)
class Meta:
ordering = ['country']
verbose_name_plural = 'Countries'
def __str__(self):
return self.country
class Region(models.Model):
country = models.ForeignKey(Country)
region = models.CharField(max_length=50)
class Meta:
ordering = ['region']
def __str__(self):
return self.region
def get_continent(self):
return self.get_continent()
my admin.py looks like this:
from django.contrib import admin
from location.models import Continent, Country, Region
# Register your models here.
class MyAdmin1(admin.ModelAdmin):
list_display = ['continent']
#list_display_links = None
#actions = None
class MyAdmin2(admin.ModelAdmin):
list_display = ['country', 'continent']
class MyAdmin3(admin.ModelAdmin):
model = Region
list_display = ['region', 'country', 'get_continent']
admin.site.register(Continent, MyAdmin1)
admin.site.register(Country, MyAdmin2)
admin.site.register(Region, MyAdmin3)
But in admin panel when I click on table regions it doesn't show 3 attributes in 3 columns. Please, help.
You get a infinite recursion in the Region.get_continent() method:
class Region(models.Model):
...
def get_continent(self):
return self.get_continent()
Change it to:
def get_continent(self):
return self.country.continent
I have a ModelSerializer, but by default it serializes all the objects in my model. I would like to limit this queryset to only the most recent 500 (as opposed to all 50 million). How do I do this?
What I have currently is the following:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
The reason I don't think I can just specify the queryset in my viewset is that this is in fact the nested portion of another serializer.
models.py
class Container(models.Model):
size = models.CharField(max_length=20)
shape = models.CharField(max_length=20)
class Item(models.Model):
container = models.ForeignKey(Container, related_name='items')
name = models.CharField(max_length=20)
color = models.CharField(max_length=20)
views.py
class ContainerViewSet(viewsets.ModelViewSet):
queryset = Container.objects.all() # only a handful of containers
serializer_class = ContainerSerializer
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('name', 'color')
class ContainerSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True) # millions of items per container
class Meta:
model = Container
fields = ('size', 'shape', 'items')
In your View Set you may specify the queryset like follows:
from rest_framework import serializers, viewsets
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
class MyModelViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()[:500]
serializer_class = MyModelSerializer
I think what you are looking for is the SerializerMethodField.
So your code would look as follows:
class ContainerSerializer(serializers.ModelSerializer):
items = SerializerMethodField('get_items')
class Meta:
model = Container
fields = ('size', 'shape', 'items')
def get_items(self, container):
items = Item.objects.filter(container=container)[:500] # Whatever your query may be
serializer = ItemSerializer(instance=items, many=True)
return serializer.data
The one catch is that the SerializerMethodField is read only.
You may use source parameter
class Container(models.Model):
...
def get_items(self):
return self.items[:500]
and in serializer
items = ItemSerializer(many=True, source='get_items', )
In my model, I have two tables named: Vtable and Vdata. Each virtual table (in Vtable) has entries of virtual data stored in Vdata.
I'm trying to make a view that would show the list of Vdata corresponding to each Vtable
My serializer isn't working and I think it's because I'm doing it backwards.
I think the problem is in this line:
table_id = serializers.RelatedField(many=True)
For reference, I get this error: 'Vtable' object is not iterable
Here is my models.py:
from django.db import models
from django.contrib.auth.models import User
class Vtable(models.Model):
user = models.ForeignKey(User)
table_name = models.CharField(max_length=200)
added_date = models.DateTimeField('date added')
def __unicode__(self):
return self.table_name
class Vdata(models.Model):
table_id = models.ForeignKey(Vtable)
table_pk = models.IntegerField()
column_1 = models.CharField(max_length=200)
column_2 = models.CharField(max_length=200)
added_date = models.DateTimeField('date added')
def __unicode__(self):
return str(self.added_date)
Here is my serializers.py:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from vtables.models import Vtable, Vdata
class TableSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.Field(source='user.username')
class Meta:
model = Vtable
fields = ('table_name', 'added_date', 'user')
class EntrySerializer(serializers.HyperlinkedModelSerializer):
table_id = serializers.RelatedField(many=True)
class Meta:
model = Vdata
fields = ('table_id', 'table_pk', 'column_1', 'column_2', 'added_date')
Here is the view that calls it:
class EntryList(APIView):
def get(self, request, format=None):
entries = Vdata.objects.all()
serializer = EntrySerializer(entries, many=True)
return Response(serializer.data
class Meta:
model = Vdata
fields = ('table_id', 'table_pk', 'column_1', 'column_2', 'added_date')
Here is an example of how you might do this:
class TableSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.Field(source='user.username')
entries = EntrySerializer(many=True)
class Meta:
model = Vtable
fields = ('table_name', 'added_date', 'user', 'entries')
class EntrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Vdata
fields = ('table_id', 'table_pk', 'column_1', 'column_2', 'added_date')
And for the view:
class EntryList(GenericAPIView):
queryset = Vtable.objects.all()
serializer_class = TableSerializer
Do not forget about:
setting related_name='entries' in your model foreign key field definition.