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"
Related
I would like to specify a custom lookup field on the action (different from the viewset default "pk"), i.e.
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field="uuid", # this does not work unfortunately
)
def get_something(self, request, uuid=None):
pass
But the router does not generate the correct urls:
router = DefaultRouter()
router.register(r"test", TestViewSet)
router.urls
yields url:
'^test/(?P<pk>[^/.]+)/something/$'
instead of
'^test/(?P<uuid>[^/.]+)/something/$'
I do not want to change the lookup field for the whole viewset though and have been unsuccessful in finding a way to do this for the action itself after debugging through the router url generation. I did notice that model viewsets have this method:
get_extra_action_url_map(self)
but am unsure how to get it to be called to generate custom urls or if it is even relevant. Any help would be great thanks!
According to their docs you could use a regex lookup field. Their example uses a CBV instead of a request based view.
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
This could work:
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
)
def get_something(self, request, uuid=None):
pass
I think it will create much confusion for your API consumers if you have 2 different resource identification on the same resource.
You can name that action query_by_uuid or just allow them to use list_view to filter by uuid if you only want to represent the object tho. (so consumers can use /test/?uuid= to retrieve data)
But if you really want to do it, you can simply override get_object method to filter for your custom action tho:
def get_object(self):
if self.action == 'do_something':
return get_object_or_404(self.get_queryset(), uuid=self.kwargs['pk'])
return super().get_object()
Here is a bit hacky solution for generate uuid in router with detail=False.
#action(detail=False, url_path=r'(?P<uuid>[^/.]+)/do_something')
def do_something(self, request, uuid=None):
pass
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.
This is a view for get all the records in the EducationalRecord model:
def all_education_resume(request):
RESUME_INFO['view'] = 'education'
educations_resume = EducationalRecord.objects.all().order_by('-created_date')
template = 'resumes/all_resume.html'
context = {'educations_resume': educations_resume, 'resume_info': RESUME_INFO}
return render(request, template, context)
Now, if I want to write exactly this view for other models (like job resumes, research resumes , etc.),
I must another view one separately.
My question is:
How can I get a view for all these requests, so first check the URL of
the request and then do the relevant query? How can I control URL
requests in my views?
My other question is exactly the same as my first question,with this difference:
control view that must render in specific template.In other words,in
second question the ratio between the template and the view is instead
of the ratio of the view to the url or how to create a template for
multiple views (for example, for a variety of database resume
resumes, I have a template) and then, depending on which view render,
the template output is different.
I have implemented these two issues as follows:
I wrote a view for each of request!
In each view, I set the value of RESUME_INFO['view'], and then I've checked it in a template page and specified the corresponding template.
What is the best solution to these two questions?
How can I get a view for all these requests, so first check the URL of the request and then do the relevant query? How can I control URL requests in my views?
You can access request.path, or you can let the url(..)s pass a parameter with kwargs that holds a reference to the model for example, but this is usually bad design. Typically if you use different models, you will likely have to order these different as well, filter these differently, render these differently, etc. If not, then this typically indicates that something is wrong with the modeling.
You can however make use of class-based views [Django-doc], to remove as much boilerplate as posssible. Your view looks like a ListView [Django-doc], by using such view, and patching where necessary, we can omit most of the "boilerplate" code:
# app/views.py
from django.views.generic.list import ListView
class MyBaseListView(ListView):
resume_info = None
template = 'resumes/all_resume.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['resume_info'] = {'view': self.resume_info}
return context
In the individual listviews, you then only need to specify the resume_info and the model or queryset to render it with the 'all_resume.html' template, for example:
# app/views.py
# ...
class EducationalResumeView(MyBaseListView):
queryset = EducationalRecord.objects.order_by('-created_date')
resume_info = 'education'
class OtherModelView(MyBaseListView):
model = OtherModel
resume_info = 'other_info'
So we can here use inheritance to define common things only once, and use it in multiple views. In case we need to change something in a specific view, we can override it at that level.
In the urls.py, you define such view with the .as_view() method [Django-doc]. For example:
# app/urls.py
from django.urls import path
from app.views import EducationalResumeView, OtherModelView
urlpatterns = [
path('education/', EducationalResumeView.as_view()),
path('other/', OtherModelView.as_view()),
]
I need to filter Haystack's SearchQuerySet based on the current user. I know I can get the current user via request.user in other views, so I imagine I need to override Haystack's default SearchView, but I'm not sure how. Below is a sample of my urls.py and views.py.
urls.py:
urlpatterns = patterns('mysite.search.views',
url(r'^mymodel/$', search_view_factory(
view_class=MySearchView,
template='mymodel_search.html',
form_class=MyModelSearchForm,
searchqueryset = SearchQuerySet().models(MyModel),
), name='mymodel_search'),
...
views.py:
class MySearchView(SearchView):
def build_form(self, form_kwargs=None):
prop = UserProfile.objects.get(user = self.request.user).property
self.searchqueryset = self.searchqueryset.filter(property = prop)
return super(MySearchView, self).build_form(form_kwargs)
This doesn't seem to be working, though. Results are limited to MyModel (as filtered in urls.py), but the filtering in MySearchView doesn't seem to do anything. Is there a different way or place I should be doing this?
It was my own mistake. In my custom MyModelSearchForm's search() method, I was resetting the search query set to SearchQuerySet().models(MyModel), so it replaced all previous filtering. The method I was attempting with MySearchView works.
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.