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.
Related
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'),
]
I am making a Django application API, where an authorized user can post a messages, and other users can browse the message and like/dislike/comment on the messages (twitter style).
I have implemented oAuth2 already and tested it.
The API works fine from the admin panel, but when I access it through url, it is giving error:
enter image description here
My models.py class is:
class Posts(models.Model):
def expirationTimeCalculation(self):
EXPIRATION_DURATION = 86400 #time in seconds
expirationTime = self.creationTimestamp + timedelta(seconds = EXPIRATION_DURATION)
return expirationTime
def postLiveStatus(self):
return (self.expirationTimestamp > timezone.now)
def postLiveTimeRemaining(self):
if (self.expirationTimestamp > timezone.now):
return (self.expirationTimestamp - timezone.now).total_seconds()
else:
return 0
postIdentifier = models.IntegerField()
title = models.CharField(max_length=100)
message = models.TextField()
topic = models.ManyToManyField('Topics')
creationTimestamp = models.DateTimeField(default=timezone.now)
expirationTimestamp = property(expirationTimeCalculation)
post_owner = models.ForeignKey(AppUser, related_name='post_owner', on_delete=models.CASCADE)
isLive = property(postLiveStatus)
post_likes = models.ForeignKey('Likes', related_name="post_likes", on_delete=models.CASCADE)
post_dislikes = models.ForeignKey('Dislikes', related_name="post_dislikes", on_delete=models.CASCADE)
post_comments = models.ForeignKey('Comments', related_name="post_comments", on_delete=models.CASCADE)
def __str__(self):
return self.title
class Topics(models.Model):
TOPICS = [('P','Politics'),('H','Health'),('S','Sports'),('T','Tech')]
topicName = models.CharField(max_length=2, choices=TOPICS, blank=False)
class Likes(models.Model):
isLiked = models.BooleanField(null=True)
likes_owner = models.ForeignKey(AppUser, related_name='likes_owner', on_delete=models.CASCADE)
post = models.ForeignKey(Posts, related_name='likes', on_delete = models.CASCADE)
class Dislikes(models.Model):
isDisliked = models.BooleanField(null=True)
dislikes_owner = models.ForeignKey(AppUser, related_name='dislikes_owner', on_delete=models.CASCADE)
post = models.ForeignKey(Posts, related_name='dislikes', on_delete = models.CASCADE)
class Comments(models.Model):
comment = models.TextField(null=True)
comments_owner = models.ForeignKey(AppUser, related_name='comments_owner', on_delete=models.CASCADE)
post = models.ForeignKey(Posts, related_name='comments', on_delete = models.CASCADE)
The serializer class is:
class CommentsSerializer(serializers.ModelSerializer):
comments_owner = CreateUserSerializer()
class Meta:
model = Comments
fields = ('comment', 'comments_owner')
class DislikesSerializer(serializers.ModelSerializer):
dislikes_owner = CreateUserSerializer()
class Meta:
model = Dislikes
fields = ('isDisliked', 'dislikes_owner')
class LikesSerializer(serializers.ModelSerializer):
likes_owner = CreateUserSerializer()
class Meta:
model = Likes
fields = ('isLiked', 'likes_owner')
class TopicsSerializer(serializers.ModelSerializer):
class Meta:
model = Topics
fields = ('topicName')
class PostsSerializer(serializers.ModelSerializer):
class Meta:
model = Posts
fields = ('postIdentifier', 'title', 'message', 'topic', 'creationTimestamp', 'expirationTimestamp', 'isLive', 'post_owner', 'post_likes', 'post_dislikes', 'post_comments')
Views class is:
from rest_framework.generics import ListAPIView
from .models import Posts
from .serializers import PostsSerializer
class PostsView(ListAPIView):
queryset = Posts.objects.all()
serilaizer_class = PostsSerializer
And the main URLS class is:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
import posts_backend.views
urlpatterns = [
path('admin/', admin.site.urls),
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')), #Adding oAuth2 privider URLS to the url path
path('authentication/', include('accounts.urls')), #Creating authentication endpoints http://10.61.64.150:8000/authentication/ for performing authentication
path('v1/posts/', posts_backend.views.PostsView.as_view(),), # Version 1 of the API
]
Can anyone kindly let me know why I am getting this error and how to resolve this?
try renaming the attribute from serilaizer_class to serializer_class
I am pretty new to Django Rest Framework and am trying to build an API that has various viewsets, and almost all are working properly except one.
In my viewset I have 3 ModelViewSet's: one to list all Inspections, one to show all completed (done) inspections and the last one to show all the undone inspections. The problem is that it is returning the list of all inspections correctly, but the other 2 return "detail: not found" even though I have instances of Inspections in my database.
Here is my Inspection Viewset:
from rest_framework.viewsets import ModelViewSet
from inspections.models import Inspection
from .serializers import InspectionSerializer
from rest_framework import generics
class InspectionViewSet(ModelViewSet):
queryset = Inspection.objects.all()
serializer_class = InspectionSerializer
class DoneInspectionsViewSet(ModelViewSet):
serializer_class = InspectionSerializer
def get_queryset(self):
queryset = Inspection.objects.all()
return queryset
class UndoneInspectionsViewSet(ModelViewSet):
serializer_class = InspectionSerializer
def get_queryset(self):
queryset = Inspection.objects.filter(done=False)
return queryset
Here's my Inspection Serializer:
from rest_framework.serializers import ModelSerializer
from inspections.models import Inspection
from properties.api.serializers import PropertySerializer
class InspectionSerializer(ModelSerializer):
property = PropertySerializer(many=False)
class Meta:
model = Inspection
fields = ('id', 'connected_check', 'category', 'property', 'date',
'done_date', 'done_by', 'staff', 'notes', 'done')
Here's the Inspection model:
from django.db import models
from check.models import Check
from properties.models import Property
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils import timezone
class Inspection(models.Model):
connected_check = models.ForeignKey(Check, on_delete=models.DO_NOTHING, null=True, blank=True, related_name='inspection_check')
category = models.ForeignKey(InspectionCategory, on_delete=models.DO_NOTHING)
property = models.ForeignKey(Property, on_delete=models.DO_NOTHING, related_name='inspection_property')
date = models.DateField()
done_date = models.DateField(blank=True, null=True)
done_by = models.ForeignKey(User, max_length=25, blank=True, null=True, related_name='inspection_done_by', on_delete=models.DO_NOTHING)
staff = models.ForeignKey(User, max_length=25, null=True, blank=True, related_name='inspection_staff', on_delete=models.DO_NOTHING)
notes = models.TextField(max_length=500, blank=True)
done = models.BooleanField(default=False)
def __str__(self):
return self.category.name
def get_absolute_url(self):
return reverse("inspections:details",kwargs={'pk':self.pk})
def get_category_name(self):
return self.category
def is_past_due(self):
return timezone.now() > self.date
def is_done(self):
self.done = True
self.done_date = timezone.now()
self.save()
And here are the urlpatterns and router:
router = routers.DefaultRouter(trailing_slash=False)
router.register(r'inspection', InspectionViewSet)
router.register(r'inspection/done', DoneInspectionsViewSet, basename='done-inspection')
router.register(r'inspection/undone', UndoneInspectionsViewSet, basename='undone-inspection')
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^admin/', admin.site.urls),
url(r'^inspections/', include(('inspections.urls','inspections'), namespace='inspections')),
]
urlpatterns += [
path('api/', include(router.urls)),
path('api/schema/', get_schema_view(
title = "DAPIMS",
description = "API for DAPIMS",
version = "1.0.0"
), name='openapi-schema'),
]
I have already tried to change the queryset and noticed that even if I just want to return all Inspection objects it still returns "detail: not found" even though if I run the interactive shell and execute the same queryset it returns the correct objects.
Here's an example of the return from the interactive shell
And this is what my Browsable API is returning
Thanks in advance!
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']
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