Django REST Framework - Query models by field value - django

I have a Building model for which I have created a Serializer and ModelViewSet. Now I am able to get all Buildings with /api/buildings/ and get a Building object with /api/buildings/3 where 3 is the Building Id.
How do I query for a Building object with a particular attribute value? For example, how do I get the Building object with name == "hospital"? Something like /api/buildings?name=hospital does not work.
views.py
class APIBuildingsViewSet(viewsets.ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
models.py
class Building(models.Model):
building_id = models.AutoField('ID', auto_created=True, primary_key=True)
name = models.CharField('Name', max_length=125, null=True, blank=False, unique=False)
address = models.CharField('Address', max_length=256, null=False, blank=False)
user_id = models.ForeignKey('accounts.User', on_delete=models.CASCADE, default=1)
def __str__(self):
return self.name
serializers.py
class BuildingSerializer(serializers.ModelSerializer):
class Meta:
model = Building
fields = ('building_id', 'name', 'address', 'user_id')
urls.py
router = DefaultRouter()
router.register(r'buildings', views.APIBuildingsViewSet, base_name='buildings')
urlpatterns = [
url(r'^api/', include(router.urls)),
]

You need to use DjangoFilterBackend in your view, and specify which fields can be used to filter.
First you must install it with pip install django-filter, and add django_filters to settings.py/INSTALLED_APPS, and set your view like this:
...
from django_filters.rest_framework import DjangoFilterBackend
class APIBuildingsViewSet(viewsets.ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = (building_id, name, address, user_id)
Whit these settings, you would be able to send a post like /api/buildings/?name=hospital (be aware of the slash before the question mark)

just change in your view.py file
views.py
from django_filters.rest_framework import DjangoFilterBackend
class APIBuildingsViewSet(viewsets.ModelViewSet):
queryset = Building.objects.all()
serializer_class = BuildingSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'address']

Related

How to create seperate POST request for imagefield in DRF

I need to make a separate API for the image file. How can I achieve this?
models.py
class Organization(models.Model):
code = models.CharField(max_length=25, null=False, unique=True)
name = models.CharField(max_length=100, null=False)
location = models.ForeignKey(Location, on_delete=models.RESTRICT)
description = models.TextField()
total_of_visas = models.IntegerField(null=False, default=0)
base_currency = models.ForeignKey(Currency, on_delete=models.RESTRICT)
logo_filename = models.ImageField(upload_to='uploads/')
serializers.py
class OrganizationSerializer(serializers.ModelSerializer):
location = serializers.CharField(read_only=True, source="location.name")
base_currency = serializers.CharField(read_only=True, source="base_currency.currency_master")
location_id = serializers.IntegerField(write_only=True, source="location.id")
base_currency_id = serializers.IntegerField(write_only=True, source="base_currency.id")
class Meta:
model = Organization
fields = ["id", "name", "location", "mol_number", "corporate_id", "corporate_name",
"routing_code", "iban", "description", "total_of_visas", "base_currency", "logo_filename",
"location_id", "base_currency_id"]
def validate(self, data):
content = data.get("content", None)
request = self.context['request']
if not request.FILES and not content:
raise serializers.ValidationError("Content or an Image must be provided")
return data
def create(self, validated_data):
....
def update(self, instance, validated_data):
....
views.py
class OrganizationViewSet(viewsets.ModelViewSet):
queryset = Organization.objects.all()
serializer_class = OrganizationSerializer
lookup_field = 'id'
urls.py
router = routers.DefaultRouter()
router.register('organization', OrganizationViewSet, basename='organization')
urlpatterns = [
path('', include(router.urls)),
]+static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
I don't have idea how to make POST request for image field from postman. I've been stuck here for a long time. Any help is appreciated.
if you want to update the image field separately you just have to create a separate serializer for it
class OrganizationImageSerializer(serializers.ModelSerializer):
logo_filename = serializers.ImageField()
class Meta:
model = Organization
fields = ["logo_filename"]
view.py
class OrganizationImageView(generics.UpdateAPIView):# or `generics.RetrieveUpdateAPIView` if you also want to retriev the current one before updating it
serializer_class = OrganizationImageSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
queryset = Organization.objects.filter(id=self.kwargs['pk'])
return queryset
urls.py
from .views import OrganizationImageView
urlpatterns = [
...
path('update_org/<int:pk>', OrganizationImageView.as_view(), name='OrganizationImageUpdater'),
]

Nested serializers and data representation

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

how to get the objects of specific field

Tn the following code the User.objects returns the "url","username","email","groups"
from django.contrib.auth.models import User, Group
class UserViewSet(viewsets.ModelViewSet):
# TODO return only usernames and urls without emails for privacy purpeses
# User.objects.username
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
But I also create the following model for the user avatar
class TheUserProfiel(models.Model):
TheUser = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(blank=True, null=True)
personalityInformation = models.CharField(
max_length=9999999, null=True, blank=True)
created_date = models.DateTimeField(default=timezone.now)
So, HOW to make the UserViewSet to return all of these "url","username","email","groups" in addition to the "avatar" field in the same view
I tried before to create two views and to combine them in the frontend but it got very complicated.
You can achieve this simply by updating the UserSerializer
serializer_class = UserSerializer
In the serializers, you had define UserSerializer. In that serializer update the fields attribute of meta class to include avatar.
UserSerializer(...):
...
class Meta:
...
fields = (....., 'avatar')

Why do I get 'detail: not found' for primary key formatted as '12-345.78'?

When I try to display detail info calling e.g. /api/products/12-345.67/, I get detail: Not found. as a response. As you can see, product IDs are formatted as 12-345.67. My first suspect was the RegEx validator (listed below), but it works the same way with or without it.
Model, serializers, viewsets and URLs are defined this way:
# models.py:
class Product(models.Model):
product_id = models.CharField(max_length=9, primary_key=True)
name = models.CharField(max_length=40)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.product_id
# serializers.py:
class ProductSerializer(serializers.ModelSerializer):
product_id = serializers.RegexField(regex='^\d{2}-\d{3}\.\d{2}$', max_length=9, min_length=9, allow_blank=False)
name = serializers.CharField(min_length=6, max_length=50, allow_blank=False)
class Meta:
model = Product
fields = '__all__'
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
lookup_field = 'product_id'
# urls.py:
router = routers.DefaultRouter()
router.register(r'products', ProductViewSet, basename='products')
(...)
urlpatterns = [
path('api/', include(router.urls)),
(...)
I don't think you should be having full stops (.) in you API endpoints. Probably change the id to something else. & Zapraszamy :D

Nested router not working

I'm new to Django and am struggling to get nested routers working. Basically, I'm trying to model the endpoint /api/v1/organizers/1/events/1, where I have Event and Organizer models as follows:
class Event(models.Model):
class Meta:
db_table = 'events'
STATUS_CHOICES = (
('scheduled', 'Scheduled'),
('completed', 'Completed'),
('cancelled', 'Cancelled')
)
name = models.TextField()
purpose = models.TextField()
date_time = models.DateTimeField()
description = models.TextField()
programme = models.TextField()
entry_fee = models.DecimalField(max_digits=6, decimal_places=2)
status = models.TextField(choices=STATUS_CHOICES)
comments = models.TextField(null=True)
people_invited = models.ManyToManyField('Contact', through='EventInvitation')
organizer = models.ForeignKey('Organizer', on_delete=models.CASCADE)
class Organizer(models.Model):
STATUS_CHOICES = (
('inactive', 'Inactive'),
('active', 'Active'),
)
class Meta:
db_table = 'organizers'
first_name = models.TextField()
middle_name = models.TextField(null=True)
last_name = models.TextField(null=True)
email = models.OneToOneField('Email', on_delete=models.CASCADE)
company_name = models.TextField(null=True)
company_description = models.TextField(null=True)
password = models.TextField()
login_token = models.TextField(null=True)
registered_on = models.DateTimeField(null=True)
status = models.TextField(choices = STATUS_CHOICES, default='inactive')
I created another app called rest_api to handle the API. The models are stored in an app called shared_stuff. Anyway, here's the project-level urls.py (don't mind the front_end app for now):
from django.conf.urls import include, url
urlpatterns = [
url(r'^api/v1/', include('rest_api.urls')),
url(r'^', include('frontend.urls')),
]
And here's the urls.py from the app rest_api:
from django.conf.urls import url, include
from rest_framework_nested import routers
from .views import *
router = routers.SimpleRouter()
# /organizers/12/events/1
router.register(r'organizers', OrganizerViewSet, base_name='organizers')
organizer_router = routers.NestedSimpleRouter(router, r'organizers', lookup='organizers')
organizer_router.register(r'events', EventViewSet, base_name='organizers-events')
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^', include(organizer_router.urls)),
]
Here's the serializers.py for rest_api app:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = ['id', 'name', 'purpose', 'date_time', 'description', 'programme', 'entry_fee', 'status', 'comments']
class OrganizerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Organizer
fields = ['id', 'first_name', 'middle_name', 'last_name', 'email', 'company_name', 'company_description', 'registered_on', 'status']
events = serializers.HyperlinkedIdentityField(
view_name = 'events_list',
lookup_url_kwarg = 'organizer_pk'
)
And finally, here's the views.py from the app rest_api:
from rest_framework import viewsets
from .models import *
from .serializers import *
class EventViewSet(viewsets.ModelViewSet):
def list(self, request, organizer_pk=None, name='events_list'):
events = self.queryset.filter(organizer=organizer_pk)
serializer = EventSerializer(events, many=True)
return Response(serializer.data)
class OrganizerViewSet(viewsets.ModelViewSet):
def list(self, request, name='organizers_list'):
data = Organizer.objects.all()
serializer = OrganizerSerializer(data, many=True)
return Response(serializer.data)
I'm sure there are many things broken in my code, and that's where I need help. The problem is I'm getting the following error:
TypeError: list() got an unexpected keyword argument 'organizers_pk'
I'm not sure what's wrong, and will appreciate some help!
I got it working by changing the EventViewSet to the following:
def list(self, request, organizers_pk=None, name='events_list'):
events = self.queryset.filter(organizer=organizers_pk)
serializer = EventSerializer(events, many=True)
return Response(serializer.data)
I'm not sure why, but the expected keyword argument name is organizers_pk, whereas I had organizer_pk. I would like to know why this is so, but other than that my problem is solved for now.