how class based views know that they need to call dispatch method - python

We know that dispatch is the first method that is called when our url hits the CBV(Class Based Views). We also know that to call these views we have to call as_view() with our CBV in our urls.py to make them callable.
views.py is shown below
class ProductListView(ListView):
template_name = "products/list.html"
model = Question #Question is a model that is defined in models.py
urls.py is shown below
urlpatterns = [
url(r'^$',ProductListhView.as_view(),name='list'),
]
Now my question is
How the CBV(ProductListView) knows that it has to call dispatch() method since we only inherited a generic views class but haven't mention anywhere to call dispatch()?

From the Django Official Doc,
When the view is called during the request/response cycle, the
HttpRequest is assigned to the view’s request attribute. Any
positional and/or keyword arguments captured from the URL pattern are
assigned to the args and kwargs attributes, respectively. Then
dispatch() is called.
Which means, Whenever matching pattern found the URLDispatecher sends the HttpRequest to the corresponding view and hence view calls its dispatch() method

Related

Accessing request object in django urls.py

The question is also inspired from documentation here.
I am using generic view (ListView) in Django in order to list out all the questions, current logged in user has asked. I was curious to do it without creating a View in views.py. So in urls.py I added a path like:
urlpatterns += [
path('myqn/', login_required(views.ListView.as_view(model=models.Question, queryset=models.Question.objects.filter(user__id=request.user.id), template_name='testapp/question_list.html', context_object_name='questions')), name='myqn'),
]
Its giving me that:
NameError: name 'request' is not defined
I know it. Since, request object is passed by the URLConf to the View class/function. So, is there a way, I can access the user.id in this scope.
PS: The code works if I replace user__id=9. It lists out all the questions asked by user-9. :)
You normally do this by overriding the get_queryset method in a subclass of the ListView. So you can create a view:
# app/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.list import ListView
from app.models import Question
class QuestionListView(LoginRequiredMixin, ListView):
model = Question
template_name='testapp/question_list.html'
context_object_name='questions'
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
user_id=self.request.user.id
)
In the urls.py you then use the QuestionListView
# app/urls.py
from django.urls import path
from app.views import QuestionListView
urlpatterns += [
path('myqn/', QuestionListView.as_view(), name='myqn'),
]
You can define a function or lambda expression with:
import inspect
def custom_queryset(*args, **kwargs):
self = inspect.currentframe().f_back.f_locals['self']
return Question.objects.filter(
user_id=self.request.user.id
)
urlpatterns += [
path('myqn/', QuestionListView.as_view(get_queryset=custom_queryset), name='myqn'),
]
This is however not a good idea. First of all, it inspects the call stack, and if later the ListView is altered, it might not work anymore. Furthermore here this listview will not check if the user has logged in for example. We can not make use of the method resolution order (MRO) to make a call to a super() method.
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
No, You can't.
The as_view() accepts any class attributes of a view class. In your case, the request object will not accessible from the class
class Foo(ListView):
queryset = Question.objects.filter(user__id=request.user.id)
The above snippet you can't reference the request and hence also in your urls.py
In these kinds of complex situations, we should override the get_queryset(), as you know.

Passing dynamic parameter to urlpatterns or DefaultRouter

I have several categories that I want to create distinct views for in rest_framework. But all the categories pull from the same model.
It strikes me that these categories could be passed to urlpatterns as a keyword (or accessed through the DefaultRouter). Then you can use the keyword to filter the model as required.
Here's my view:
class CategoryRankedViewSet(ModelViewSet):
serializer_class = CategoriesSerializer
def get_queryset(self):
return Categories.objects.all().order_by(self.kwargs['category'])
One way I was able to make this work was:
urlpatterns = [path('<' + category + '>/', CategoryRankedViewSet.as_view({'get': 'list'}), name=category) for category in CATEGORIES]
But it's not perfect because the key for the parameter is set to the value of first item in CATEGORIES, rather than a more generic term like category.
I was considering DefaultRouter but it is not obvious if parameters can be passed to DefaultRouter.
I was also looking for ways that the literal url could be accessed and accessing the category that way. Does not appear this is possible in a ViewSet in DRF.
Also, base_name for DefaultRouter could be set dynamically, but I could not find how base_name can be accessed from a ViewSet.
Has anyone ever tried this and is there a more effective method?
UPDATE
As it turns out, path() has kwargs attribute that passes the keywords without them being included as url parameters. So don't pass the category as a url parameter. Just use the category variable in the url path and pass category as a keyword.
urlpatterns = [path(category + '/', CategoryRankedViewSet.as_view({'get': 'list'}), kwargs={'category': category}, name=category) for category in CATEGORIES]
Is there any way to replicate this using DefaultRouter???
A viewset may mark extra actions for routing by decorating a method with the #action decorator.
You can use action decorator for your problem
from rest_framework.decorators import action
class CategoryRankedViewSet(ModelViewSet):
serializer_class = CategoriesSerializer
def get_queryset(self):
return Categories.objects.all().order_by(self.kwargs['category'])
#action(methods=['get'],detail=False,url_path=r'list/(?P<category>[\w-]+)',url_name='categorylist')
def get_category(self,request,category=None):
return Categories.objects.all().order_by(category)
so, now you can provide any category in url and access it using category variable.
your url will be like rooturl/list/{categoryhere}.This url will call get_category method.
Remember the url_path uses only regular expression, so you cannot use slug there.
you can see the code of action decorator in this link and you can see the example of #action decorator in djagorest.
I hope this will solve your problem.

How to implement method in Django REST?

Have the next Django REST question.
I have the view.
class MessageViewSet(viewsets.ModelViewSet):
serializer_class = MessageSerializer
queryset = Message.objects.filter(isread = False)
def mark_read():
queryset = Message.objects.update(isread=True)
return Response({'read':queryset})
And router in urls.py
router = SimpleRouter() router.register(r'api/get_messages', MessageViewSet)
urlpatterns = [
url(r'^$', MainView.as_view(), name='main'),
url(r'^', include(router.urls)) ]
Now i have 'get_messages' page which shows all list.
How can i implement a method which would change 'isread' value of model instanse from False to True, when I visit a 'mark_read' page?
As you can see, i tried to write method in the class. But when i'm trying to call it in urls in this way:
router.register(r'api/mark_read', MessageViewSet.mark_read),
Here comes an error.
assert queryset is not None, 'base_name argument not specified, and could ' \
AssertionError: base_name argument not specified, and could not automatically determine the name from the viewset, as it does not have a .queryset attribute.
Maybe i shouldnt use router, and rewrite view and urls in other way. If u know how to solve this problem, please answer. Thanks.
You can use detail_route or list_route decorators.
from rest_framework.decorators import list_route
class MessageViewSet(viewsets.ModelViewSet):
#list_route()
def mark_read(self, request):
queryset = Message.objects.update(isread=True)
return Response({'read':queryset})
With that mark_read method will be available at api/get_messages/mark_read. And you don't need to create separate router, just use one you created for MessageViewSet
docs reference
Since you are using a model viewset you can directly use put or patch rest method to send the desired value for the desired field as the data.
Ideally in rest get should not change model values. If you really want a different end point put the list_route or detail_route decorator on your mark_read method, and make them a valid call for only a put and/or patch call
from rest_framework.decorators import list_route
class MessageViewSet(viewsets.ModelViewSet):
#list_route(methods=['Patch', 'PUT'])
def mark_read(self, request):
queryset = Message.objects.update(isread=True)
return Response({'read':queryset})
Thanks to #ivan-semochkin and #Shaumux for replies. Advices were really helpful.
That is my route. I used detail_route instead of list_route.
#detail_route(methods=['get','put'], url_name='mark_read/')
def mark_read(self, request, pk=None):
queryset = Message.objects.filter(pk=pk).update(isread=True)
return Response({'read':queryset})
Now 'isread' value is changing wnen i visit 'mark_read' page.
Link: "api/get_messages/pk/mark_read"
Does anyone know, is it posslible to make links looking the next way:
"api/get_messages" - list, "api/mark_read/pk" - changing isread value.
Is it possible to create something like this? "api/mark_read?=pk"

decorate as_view() with extra context

I want to call class-based generic view with extra context from my method (view). Error that I get is as_view() takes exactly 1 argument (4 given) . I'm using django-userena.
Code that executes this is:
return userena_views.ProfileListView.as_view(request,template_name='userena/profil.html', extra_context=projekti)
In urls.py I have this line:
url(r'^accounts/(?P<username>[\.\w-]+)', userena_views.ProfileListView.as_view(template_name='userena/profil.html', extra_context=Projekat.objects.all), name='userena_profile_list'),
Why are these two different? What am I doing wrong?
this is due to how url functions. you can use kwargs to pass the parameters, and define a url pattern as follows:
url(r'^accounts/(?P<username>[\.\w-]+)', userena_views.ProfileListView.as_view(), name='userena_profile_list', kwargs={'template_name':'userena/profil.html', 'extra_context':Projekat.objects.all}),
EDIT
I misunderstood your question, sorry.
Then, trying to answer your question correctly... your code should be like this:
your_callable_view = userena_views.ProfileListView.as_view()
return your_callable_view(request, template_name='userena/profil.html', extra_context=projekti)
the reason is ProfileListView.as_view() returns a function that have to be called with parameters. url() do this for you, this is why it works in your ulrpatterns and not in your code. The only parameter as_view() is requiring is self.
Class based views can provide extra context data via a get_context_data method.
In the example below from the Django documentation, book_list is added to the DetailView context. The same method works for FormViews as well.
from django.views.generic import DetailView
from books.models import Book, Publisher
class PublisherDetailView(DetailView):
model = Publisher
def get_context_data(self, **kwargs):
# Call the base implementation first to get context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['book_list'] = Book.objects.all()
return context

How to use current logged in user as PK for Django DetailView?

When defining URL patterns, I am supposed to use a regular expression to acquire a PK from the URL.
What if I want a URL that has no PK, and if it's not provided, it will use the currently logged in user? Examples:
visiting /user will get a DetailView of the currently logged in user
/user/edit will show an UpdateView for the currently logged in user
I tried hard-coding the pk= in the Detail.as_view() call but it reports invalid keyword.
How do I specify that in the URL conf?
My sample code that shows PK required error when visiting /user URL:
urlpatterns = patterns('',
url(r'user/$',
DetailView.as_view(
model=Account,
template_name='user/detail.html')),
)`
An alternative approach would be overriding the get_object method of the DetailView subclass, something along the line of:
class CurrentUserDetailView(UserDetailView):
def get_object(self):
return self.request.user
Much cleaner, simpler and more in the spirit of the class-based views than the mixin approach.
EDIT: To clarify, I believe that two different URL patterns (i.e. one with a pk and the other without) should be defined separately in the urlconf. Therefore they could be served by two different views as well, especially as this makes the code cleaner. In this case the urlconf might look something like:
urlpatterns = patterns('',
url(r"^users/(?P<pk>\d+)/$", UserDetailView.as_view(), name="user_detail"),
url(r"^users/current/$", CurrentUserDetailView.as_view(), name="current_user_detail"),
url(r"^users/$", UserListView.as_view(), name="user_list"),
)
And I've updated my example above to note that it inherits the UserDetailView, which makes it even cleaner, and makes it clear what it really is: a special case of the parent view.
As far as I know, you can't define that on the URL definition, since you don't have access to that information.
However, what you can do is create your own mixin and use it to build views that behave like you want.
Your mixin would look something like this:
class CurrentUserMixin(object):
model = Account
def get_object(self, *args, **kwargs):
try:
obj = super(CurrentUserMixin, self).get_object(*args, **kwargs)
except AttributeError:
# SingleObjectMixin throws an AttributeError when no pk or slug
# is present on the url. In those cases, we use the current user
obj = self.request.user.account
return obj
and then, make your custom views:
class UserDetailView(CurrentUserMixin, DetailView):
pass
class UserUpdateView(CurrentUserMixin, UpdateView):
pass
Generic views uses always RequestContext. And this paragraph in the Django Documentation says that when using RequestContext with auth app, the template gets passed an user variable that represents current user logged in. So, go ahead, and feel free to reference user in your templates.
You can get the details of the current user from the request object. If you'd like to see a different user's details, you can pass the url as parameter. The url would be encoded like:
url(r'user/(?P<user_id>.*)$', 'views.user_details', name='user-details'),
views.user_details 2nd parameter would be user_id which is a string (you can change the regex in the url to restrict integer values, but the parameter would still of type string). Here's a list of other examples for url patterns from the Django documentation.

Categories

Resources