Implement REST API with Django - python

This is my Django model:
class M(models.Model):
a = models.IntegerField()
b = models.IntegerField()
This is the serializer:
class MSerializer(ModelSerializer):
class Meta:
model = M
fields = ['a', 'b']
I would like to be able to implement these REST APIs:
127.0.0.1:8000/m/ (GET list of all elements, POST new element)
127.0.0.1:8000/m/:id/ (GET details of element with id id)
127.0.0.1:8000/n/:a/m/ (GET all elements with a specific a field)
So far this is the view and urls that I implemented:
class MViewSet(ModelViewSet):
queryset = M.objects.all()
serializer_class = MSerializer
router = DefaultRouter()
router.register(r'm', MViewSet)
urlpatterns = [
path('', include(router.urls)),
]
However, in this way the third use case is not working. How can I modify my code to make the third case work? I'd prefer to use as few lines of code as possible (i.e., I would like to use some Django built-in functionalities).

Since it look like you want the 3rd endpoint on another root (possibly another app name n), I'll implement it is a standalone API view, and not as an action on a viewset (although both options are possible)
class FilteredMListView(ListAPIView):
serializer_class = MSerializer
def get_queryset(self):
return M.objects.filter(a=self.kwargs["a"])
Then you register it to the router using:
urlpatterns = [
path("n/<str:a>/m/", FilteredMListView.as_view())
]

For your 3rd case, I would use a ListAPIView, overriding the get_queryset method to filter by the passed value for a. The idea is that when get_queryset method is invoked, and with as many other filters you'd like to implement, the condition for a is always present. Since the value for a will be in the url, it is mandatory, and you always have in in the view's kwargs. Would look like this:
urls.py
router = DefaultRouter()
router.register(r'm', MViewSet)
urlpatterns = [
path('', include(router.urls)),
path('<a>/m', AValuesListApiView.as_view()
]
views.py
class AValuesListApiView(generics.ListAPIView):
queryset = M.objects.all()
serializer_class = MSerializer
def get_queryset(self):
return super().get_queryset().filter(score=self.kwargs["score"])

Related

Use URL data to fill form in Django with Class Based Views

I have 2 Models: Projects and Members, each one with a form. I was able to add to the URL the number of the project (id) this:
class PageCreate(CreateView):
model = Page
form_class = PageForm
success_url = reverse_lazy('members:create')
def get_success_url(self):
return reverse_lazy('members:create', args=[self.object.id])
When I finish of filling the Project form, it redirects the page to the Member form.
What I want to do is to extract the ID of the Project from the URL and use it in the Member form. I cannot think any other solution.
Currently I have a Selection list to select the Project in the Member form but I want the Project loaded as soon as is created.
I am using the CreateView in the models for both Projects and Members. This is the view for MemberCreate
#method_decorator(login_required, name='dispatch')
class MemberCreate(CreateView):
model = Member
form_class = MemberForm
success_url = reverse_lazy('pages:pages')
Only attempt I had to visualize the ID in the HTML was using
{{ request.get }}
To somehow get the value from the GET but I could not do it.
Url Parameters
import imp
from django.urls import path
from .views import PageListView, PageDetailView, PageCreate, PageUpdate, PageDelete, MemberCreate, MemberDelete, MemberUpdate
pages_patterns = ([
path('', PageListView.as_view(), name='pages'),
path('<int:pk>/<slug:slug>/', PageDetailView.as_view(), name='page'),
path('create/', PageCreate.as_view(), name='create'),
path('update/<int:pk>', PageUpdate.as_view(), name='update'),
path('delete/<int:pk>', PageDelete.as_view(), name='delete'),
], 'pages')
members_patterns = ([
path('create/<int:pk>', MemberCreate.as_view(), name='create'),
path('update/<int:pk>', MemberUpdate.as_view(), name='update'),
path('delete/<int:pk>', MemberDelete.as_view(), name='delete'),
], 'members')
Stuff parsed from the URL ends up in the view's self.kwargs. From
return reverse_lazy('members:create', args=[self.object.id])
you pass through an URL like
path( 'create/<int:project>', MemberCreateView.as_view(), name='create' )
and in the view, the id is now self.kwargs['project']. (note, an URL can specify multiple named variables separated by slashes, it's not limited to just one). You typically then use
project = Project.objects.get( pk = self.kwargs['project'] )
Request.GET is something different: it's where the dict encoded as a querystring goes. If your client supplies
http://server/app/foo?bar=27&baz=hello
then when you arrive in the view which handles app/foo, request.GET contains
{ 'bar':'27', 'baz':'hello' }
(actually it's a QueryDict, not a Python dict, which has some subtle diffreernces. Consult the Django documentation. The main difference is that the values attached to keys can be multi-valued, for a querystring like ?bar=27&bar=54&bar=silly

One object only in GET method in Django REST Framework

I have a list of all my objects when I use get method by api/movies in my api, and this is ok. I want also to get only one, specyfic object when use get method by api/movies/1 but now I still have a list of all my objects... What to change in my MoviesView or in urls?
My views.py:
class MoviesView(APIView):
def get(self, request):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
My appurls.py:
urlpatterns = [
url('movies', MoviesView.as_view(), name="MoviesView"),
]
And my project urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("api.urls")),
]
When I use routers everythig crushes... Could you help me?
You can simply use viewsets.ModelViewSet that natively implements list and retrieve.
You declare something like router.register('movies', my_views.MoviesViewSet) in you urls.py and
class MoviesViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
return self.queryset
def get_object(self):
movie_id = self.kwargs['pk']
return self.get_queryset().filter(id=movie_id)
def retrieve(self, request, *args, **kwargs):
try:
instance = self.get_object()
except (Movie.DoesNotExist, KeyError):
return Response({"error": "Requested Movie does not exist"}, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This approach implies that you declare a Serializer, just like:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
Django simply maps HOST/movies/ to list (multiple objects) and HOST/movies/PK/ to retrieve method (one single object).
Docs:
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
Hope it helps.
BR.
Eduardo
I would suggest you if you want to retrieve just 1 element to use a Generic View, i.e RetrieveAPIView
It would give you all you need for getting 1 element.
from rest_framework import generics
class MoviesView(generics.RetrieveAPIView):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
but you need also to change urls.py
url(r'movies/(?P<pk>[0-9]+)/$', MoviesView.as_view(), name="MoviesView"),
When you make a GET request to "api/movies/1", the url is matched to the "api/movies" path (read more in the docs), and the MoviesView's get method is called. And your get() implementation just fetches all the movies (movies = Movie.objects.all()), serializes and returns them - that's why you get the entire list.
If you want to retrieve one specific object, you need to somehow specify which object you have in mind, using its primary key (in your case, id).
1. You have to define a separate path: movies/<int:pk>/ (btw, which Django version are you using? url has been deprecated, use path instead!)
2. You have to define a detail view to handle this new case, and pass it to the path function as the second argument.
This general problem can really be solved in many ways, and depending on your app you may want to use a ViewSet instead of views. Then you don't have to define paths (urls) separately - you can use a router. You can't use routers with your view, because router needs a viewset class as its argument.
If you provide more details, I could try to suggest something more specific.
My appurls.py:
use path method
urlpatterns = [
path('movies', MoviesView.as_view(), name="MoviesView"),]
Maybe it works
Start by adding a format keyword argument to both of the views, like so
def snippet_list(request, format=None):
and
def snippet_detail(request, pk, format=None):
Now update the snippets/urls.py file slightly, to append a set of format_suffix_patterns in addition to the existing URLs
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)

DRF (django-rest-framework) action decorator not working

I have this class view, that works perfectly for creating and listing objects of SiteGroup:
But I need a method to perform several operations on a single SiteGroup object, and objects associated with them. Therefore, I have tried to create a method, decorated with #action (as suggested by the docs).
According to the docs, this will autogenerate the intermediate url. Nevertheless, it doesn't work.
When I try to access (given that 423 is an existing SiteGroup Object):
http://127.0.0.1:8000/api/site-groups/423/replace_product_id/?product_id=0x345
the url is not found.
I also tried generating myself the URL in the urls.py, with no luck.
Can someone tell me where the problem is? I've browsed through all the docs, and found no clue. Thanks a lot.
class SiteGroupDetail(generics.ListCreateAPIView):
queryset = SiteGroup.objects.all()
parser_classes = (MultiPartParser, FormParser, JSONParser)
serializer_class = SiteGroupSerializer
authentication_classes = (authentication.TokenAuthentication,)
#action(detail=True, methods=['post'], url_path='replace_product_id', permission_classes=[IsSuperUser], url_name='replace_product_id')
def replace_product_id(self, request, pk=None, device_type=None):
serializer = SiteGroupSerializer(data=request.data)
product_id = self.request.query_params.get('product_id', None)
print("replace_product", product_id, device_type, pk, flush=True)
return Response({"hello":product_id})
My urls.py
from django.conf.urls import url, include
from api import views, routers
router = routers.SimpleRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
enter code here
url(r'^site-groups/', views.SiteGroupDetail.as_view()),
url(r'^site-groups/(?P<pk>[0-9]+)/$', views.SiteGroupDetail.as_view()),
]
For one thing the router should be calling
super(OptionalSlashRouter, self).__init__()
What you have now calls __init__ of SimpleRouter's parent, skipping the logic in SimpleRouter.__init__
Change that and see if it starts working
Actually as you're using python 3 it could be just
super ().__init__()

Django - URL not found using generic views

I am writing a basic Events app which contains of two modules(apps) so far : users and events.
I am using Django 2.1 with Python 3.6 on Ubuntu 16.04
So far, I've been able to handle users, but on events, I can't use Update, Detail and Delete generic views. All of them return 404.
My views.py:
class EventListView(ListView):
model = EventModel
template_name = 'event_list.html'
queryset = EventModel.objects.order_by('start_date_time')
class EventUpdateView(UpdateView):
model = EventModel
fields = ['event_type','start_date_time'
]
template_name = 'event_update.html'
class EventDeleteView(DeleteView):
model = EventModel
template_name = 'event_delete.html'
success_url = reverse_lazy('event_list')
class EventDetailView(DetailView):
model = EventModel
template_name = 'event_detail.html'
My urls.py (in project folder):
urlpatterns = [
path('', include('pages.urls')),
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('users/', include('django.contrib.auth.urls')),
path('events/', include('events.urls')),
]
My urls.py (in events app):
urlpatterns = [
path('', views.EventListView.as_view(), name='event_list'),
path('<int:id>', views.EventDetailView.as_view(), name='event_detail'),
path('<int:id>/edit/', views.EventUpdateView.as_view(), name='event_update'),
path('<int:id>/delete/', views.EventDeleteView.as_view(), name='event_delete'),
]
What am I doing wrong? I've been searching the whole day and still have no idea how this might be wrong.
Note that the first line works (EventListView) but the other lines don't. By the way, I am using the book Django for Beginners. Most of the code here is identical to the code in the book.
Update
I don't use namespace in this application, the rest of urls.py is only some basic imports :
from django.urls import path
from . import views
The urls.py for the Project is like above, except it has include and admin as well.
The examples of URLs giving 404 error:
http://127.0.0.1:8000/events/1/
http://127.0.0.1:8000/events/1/edit/
PS I thought edit and delete give me 404, but actually the error is :
ImproperlyConfigured at /events/1/edit/
EventUpdateView is missing a QuerySet. Define EventUpdateView.model, EventUpdateView.queryset, or override EventUpdateView.get_queryset().)
In short: you defined a models (with s) attribute, but it should be model (without s).
Well the error actually already explains the problem:
ImproperlyConfigured at /events/1/edit/ EventUpdateView is missing a QuerySet.
Define EventUpdateView.model, EventUpdateView.queryset,
or override EventUpdateView.get_queryset().)
In your EventUpdateView you did not specify a model attribute, you wrote models, and for Django that is an entirely different attribute. So you should rename it to:
class EventListView(ListView):
model = EventModel
template_name = 'event_list.html'
queryset = EventModel.objects.order_by('start_date_time')
class EventUpdateView(UpdateView):
model = EventModel
fields = ['event_type','start_date_time'
]
template_name = 'event_update.html'
class EventDeleteView(DeleteView):
model = EventModel
template_name = 'event_delete.html'
success_url = reverse_lazy('event_list')
class EventDetailView(DetailView):
model = EventModel
template_name = 'event_detail.html'
For the EventListView, that did not matter, since you also defined a queryset attribute, and so Django took that one, but I would update it anyway.
Furthermore in the urls.py, you need to specify a pk parameter by default:
urlpatterns = [
path('', views.EventListView.as_view(), name='event_list'),
path('<int:pk>', views.EventDetailView.as_view(), name='event_detail'),
path('<int:pk>/edit/', views.EventUpdateView.as_view(), name='event_update'),
path('<int:pk>/delete/', views.EventDeleteView.as_view(), name='event_delete'),
]
Finally in the template you wrote something like:
{% url 'event_update' event.id %}
But apparently there was no event identifier, as a result the event.id is the string_if_invalid (by default the empty string), which is not an integer (well at least not if you did not specify that), and hence it can not find a relevant URL. After some discussion, it turned out that the correct identifier was object, so the correct url is something like:
{% url 'event_update' pk=object.id %}
The same of course should happen with other {% url ... %} calls.

Passing Pk or Slug to Generic DetailView in Django?

I am new to Django Class based views. I am trying to make a simple view to get details of a post.
My views.py:
from django.views.generic import ListView, View, DetailView
class GenreDetail(DetailView):
model = Post
template_name = "post.html"
My urls.py:
urlpatterns = [
url(r'(?P<post_id>[^/]+)', GenreDetail.as_view(), name = 'post'),
url(r'(?P<post_id>[^/]+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
]
Error that I get:
AttributeError at /2/memoirs-of-a-geisha-by-arthur-golden
Generic detail view GenreDetail must be called with either an object pk or a slug.
So the pk or slug is not passed to the Generic Detailview. How do I pass that ? I assume from url it can pick up but it's not.
url patterns are checked in the order you define them
so here:
urlpatterns = [
url(r'(?P<post_id>[^/]+)', GenreDetail.as_view(), name = 'post'),
url(r'(?P<post_id>[^/]+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
]
...the first pattern is getting matched (because it does not end with $ so the extra segment is just ignored)
...and that pattern only passes a single keyword arg
Generally it is a bad idea to have multiple url patterns pointing to the same view. If possible you should try and make a single regex (eg using optional groups) which handles the various cases of the url for a particular view. It's more explicit that way.
On the other hand, simply reversing the order of your patterns to put the more explicit one first would also work and be correct (this is the Django rule of urlpatterns!)
urlpatterns = [
url(r'(?P<post_id>[^/]+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
url(r'(?P<post_id>[^/]+)', GenreDetail.as_view(), name = 'post'),
]
As #ozgur mentions you also need to tell the view to use post_id instead of pk by setting pk_url_kwarg
If you want to fetch details using either post_id or slug then your urls should be like this
url(r'post/(?P<post_id>\d+)/$', GenreDetail.as_view(), name = 'post_detail'),
url(r'post/(?P<slug>[-\w]+)/$', GenreDetail.as_view(), name = 'post_detail_slug'),
And your view should be like this
from django.views.generic import DetailView
class GenreDetail(DetailView):
model = Post
template_name = "post.html"
pk_url_kwarg = "post_id"
slug_url_kwarg = 'slug'
query_pk_and_slug = True
For more details please read the docs.
The problem is that you have to tell DetailView that it should use post_id keyword in the URL instead of default ones pk or slug in order to get the object that will be displayed.
This can be done by setting pk_url_kwarg attribute:
(Your url definition is also wrong, always end your url definitions with $. Below is the corrected version)
url(r'(?P<post_id>\d+)$', GenreDetail.as_view(), name = 'post'),
url(r'(?P<post_id>\d+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
The following urls will match given the url patterns above:
/2
/2/memoirs-of-a-geisha-by-arthur-golden
from django.views.generic import DetailView
class GenreDetail(DetailView):
model = Post
template_name = "post.html"
pk_url_kwarg = "post_id"
Alternatively, you can just change post_id to pk in your url so you don't have to touch anything in your view:
url(r'(?P<pk>\d+)$', GenreDetail.as_view(), name = 'post'),
url(r'(?P<pk>\d+)/(?P<slug>[-\w]+)$', GenreDetail.as_view()),
Using path:
from django.urls import path
from . import views
urlpatterns = [
path('<pk>/', views.GenreDetail.as_view(), name="post")]
For slug:
path('<slug:slug>/', views.GenreDetail.as_view(), name="post")

Categories