Related
In the docs there is the example of methods with custom url:
http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers
class SnippetViewSet(viewsets.ModelViewSet):
...
#link(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
This example add following route:
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'),
It is possible to add an url without pk param, like this?
r'^snippets/highlight/$'
The ViewSets docs mention using action decorator:
from rest_framework.decorators import action
class SnippetViewSet(viewsets.ModelViewSet):
...
#action(detail=False, methods=['GET'], name='Get Highlight')
def highlight(self, request, *args, **kwargs):
queryset = models.Highlight.objects.all()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Then just update your queryset to do whatever it needs to do.
The advantage of doing it this way is that your serialisation is preserved.
If your urls.py looks like this:
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
from snippets import viewsets
router = routers.DefaultRouter()
router.register('snippets', viewsets.SnippetViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('snippets/', include(router.urls)),
]
Then it is reachable via http://localhost:8000/snippets/highlights
To see usage for a POST, or how to change routing, see docs for routers.
Yes, you can do that. Just add your method in the viewset with the list_route decorator.
from rest_framework.decorators import list_route
class SnippetViewSet(viewsets.ModelViewSet):
...
#list_route(renderer_classes=[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
...
It will add a url without the pk param like :
r'^snippets/highlight/$'
You can even specify the methods it supports using the methods argument in your decorator.
http://www.django-rest-framework.org/api-guide/routers/#usage
Since this question still turns up on first Google Page, here is up-to-date (for the late march of 2020) snippet (pun intended) to start working on your custom ModelViewSet route for single object:
from rest_framework.decorators import action
class SnippetViewSet(viewsets.ModelViewSet):
...
#action(detail=True, methods=['POST'], name='Attach meta items ids')
def custom_action(self, request, pk=None):
"""Does something on single item."""
queryset = Snippet.objects.get(pk=pk)
serializer = self.get_serializer(queryset, many=False)
return Response(serializer.data)
Having default routers from the DRF tutorial will allow you to access this route with: http://localhost:8000/snippets/<int:pk>/custom_action/
I was referring to this page of django documentation to write views. Can someone explain what I did wrong ? And what could be the solution
self.object_list = self.get_queryset()
File "/vagrant/projects/kodeworms/course/views.py", line 23, in get_queryset
self.Course = get_object_or_404(Course, name=self.args[0])
IndexError: tuple index out of range
My views.py file
# Create your views here.
from django.views.generic import ListView, DetailView
from django.shortcuts import get_object_or_404
from .models import Course, Content
class PublishedCourseMixin(object):
def get_queryset(self):
queryset = super(PublishedCourseMixin, self).get_queryset()
return queryset.filter(published_course=True)
class CourseListView(PublishedCourseMixin, ListView):
model = Course
template_name = 'course/course_list.html'
class CourseContentListView(ListView):
model = Content
template_name = 'course/content_list.html'
def get_queryset(self):
self.Course = get_object_or_404(Course, name=self.args[0])
return Content.objects.filter(course=self.course, published=True)
My urls.py file
from django.conf.urls import patterns, url
from . import views
urlpatterns = patterns('',
url(r"^$", views.CourseListView.as_view(), name="list" ),
url(r"^(?P<slug_topic_name>[\w-]+)/$", views.CourseContentListView.as_view(), name="list"),
)
You are using self.args[0] which is for positional arguments, but you are passing in a keyword argument to your view.
As you have no positional arguments self.args is a zero-length tuple which is why you get that exception.
You should be using self.kwargs['slug_topic_name'] since you have a keyword argument in your url.
if you are going to this url
url(r"^$", views.CourseListView.as_view(), name="list" ),
there is no self.args, you should check it
I suppose, it will work if you will go to this url
url(r"^(?P<slug_topic_name>[\w-]+)/$", views.CourseContentListView.as_view(), name="list"),
I have lots of class-based views in my app. Most of them should be accessible only by authentificated staff users. How can I easylly add user check for lot of class-based views?
For standart function views I added decorator like this:
def only_staff_allowed(fn):
'''decorator'''
def wrapped(request, *args, **kwargs):
if request.user.is_staff:
return fn(request, *args, **kwargs)
else:
return HttpResponseRedirect(reverse('moderator:login'))
return wrapped
#only_staff_allowed
def dashboard(request):
''' now accessible only by staff users '''
return render(request, 'moderator/dashboard.html', {})
How can I do somthing similar to class-based views like this?
class AddressesAddList(ListView):
template_name = 'moderator/addresses/add_list.html'
queryset = Address.objects.filter(need_moderating=True)
paginate_by = 100
Should I add some mixins or override some methods? Or can I decorate something?
Actually, there are at least three ways to avoid decorating the dispatch method of each and every view class you want to require login for.
If you only have a few such views, you can either use that decorator in the URLconf, like this:
url(r"^protected/$", login_required(ProtectedView.as_view()), name="protected_view"),
Alternatively, and better if you have a bit more views to protect, is to use the LoginRequiredMixin from django-braces:
from braces.views import LoginRequiredMixin
class ProtectedView(LoginRequiredMixin, TemplateView):
template_name = 'secret.html'
And, if you have a lot of views to protect, you should use a middleware to cover a bunch of views in one fell swoop; something along the lines of:
class RequireLoginMiddleware(object):
"""Requires login for URLs defined in REQUIRED_URLS setting."""
def __init__(self):
self.urls = tuple([re.compile(url) for url in REQUIRED_URLS])
self.require_login_path = getattr(settings, 'LOGIN_URL', '/accounts/login/')
def process_request(self, request):
if not request.user.is_authenticated() and request.path != self.require_login_path:
for url in self.urls:
if url.match(request.path):
return HttpResponseRedirect(u"{0}?next={1}".format(self.require_login_path, request.path))
You should decorate the dispatch method of the class-based view. See below.
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
class ProtectedView(TemplateView):
template_name = 'secret.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ProtectedView, self).dispatch(*args, **kwargs)
See the docs here.
You can use LoginRequiredMixin. This will redirect unauthenticated users to the page set.
from braces.views import LoginRequiredMixin
class DashboardIndex(LoginRequiredMixin, TemplateView):
template_name = 'dashboard/index.html'
login_url = 'action:login' #Where you must set the page else will use default.
raise_exception = False
https://django-braces.readthedocs.org/en/latest/access.html#loginrequiredmixin
Does anyboy know how do I set the desired mimetype when using TemplateView, as in:
urlpatterns = patterns('',
url(r'^test\.txt$', TemplateView.as_view(template_name='staticpages/test.html')),
In this case, I want to set the mimtype as "text/plain"
For Django >= 1.5
TemplateView accepts a content_type argument.
Coping example from #Meilo
urlpatterns = patterns('',
url(r'^test\.txt$', TemplateView.as_view(template_name='staticpages/test.html', content_type='text/plain')),
For Django < 1.5
I think that just calling TemplateView.as_view() is not posible but maybe i missed it (from the source),
but you can do your own class
class TextTemplateView(TemplateView):
def render_to_response(self, context, **response_kwargs):
response_kwargs['content_type'] = 'text/plain'
return super(TemplateView, self).render_to_response(context, **response_kwargs)
You can take a look to:
django.template.response => TemplateResponse
django.views.generic.base => TemplateView
And if you need something more dynamic:
from django.utils.decorators import classonlymethod
class ContentTypeTemplateView(TemplateView):
#classonlymethod
def as_view(cls, content_type='text/plain', **initargs):
setattr(cls, 'content_type', content_type)
return super(ContentTypeTemplateView, cls).as_view(**initargs)
def render_to_response(self, context, **response_kwargs):
response_kwargs['content_type'] = self.content_type
return super(ContentTypeTemplateView, self).render_to_response(context, **response_kwargs)
urlpatterns = patterns('',
url(r'^$', ContentTypeTemplateView.as_view(content_type='text/plain',
template_name='staticpages/test.html'),
name='index'),
)
Using a Mixin
from django.core.exceptions import ImproperlyConfigured
class ContentTypeMixin(object):
content_type = None
def render_to_response(self, context, **response_kwargs):
if not self.content_type:
raise ImproperlyConfigured(
"MimeTypeMixin rquires a definition of content_type")
response_kwargs['content_type'] = self.content_type
return super(ContentTypeMixin, self).render_to_response(context,
**response_kwargs)
class MyTxtView(ContentTypeMixin, TemplateView):
content_type = 'text/plain'
....
In Django 1.5 the content_type argument in the TemplateView adds the same functionality that was in the function-based view before. That makes it easier to set the desired mimetype:
urlpatterns = patterns('',
url(r'^test\.txt$', TemplateView.as_view(template_name='staticpages/test.html', content_type='text/plain')),
I know that you ask for setting a content type with TemplateView, but I will give you different answer which I think that will be more clean and can be used in Django versions lower than 1.5.
url(r'^robots\.txt$', 'django.shortcuts.render', kwargs={
'template_name': 'robots.txt',
'content_type': 'text/plain',
})
With this approach you don't need to import anything or to subclass TemplateView and make ugly overwrites of some methods. You can simply use the old technique with function based views.
If you don't want to extend the TemplateView, you can extend the TemplateResponse to set the mimetype:
from django.template.response import TemplateResponse
class TextResponse(TemplateResponse):
def __init__(self, *args, **kwargs):
kwargs['mimetype'] = 'text/plain'
return super(TextResponse, self).__init__(*args, **kwargs)
Then pass it as the template_class to the TemplateView
urlpatterns = patterns('django.views.generic.simple',
(r'^robots\.txt$', TemplateView.as_view(template_name='robots.txt', response_class=TextResponse)),
)
The best way to do it is to subclass TemplateView and override the render_to_response() method:
class StaticPagesTest(TemplateView):
template_name = 'staticpages/test.html'
def render_to_response(self, context, **kwargs):
return super(StaticPagesTest, self).render_to_response(context,
mimetype='text/plain', **kwargs)
I know this is solved for 1.5, but the application I am working in is 1.4.
I had an issue with two url patterns in a row using sacabuche's answer:
url(r'^playlist1\.m3u$', ContentTypeTemplateView.as_view(template_name='playlist1.m3u', content_type='audio/x-mpegurl')),
url(r'^playlist2\.pls$', ContentTypeTemplateView.as_view(template_name='playlist2.pls', content_type='audio/x-scpls'))
I found playlist1 would return the correct template, but with playlist2's content type! Playlist2 was ok. Adding a 3rd url pattern with a content-type of 'foo' would cause all playlist views to return with content-type 'foo'.
I ended up using the render method instead with good results:
urls:
url(r'^playlist1\.m3u$', 'content_type_to_template', {'template_name': 'playlist1.m3u', 'content_type': 'audio/x-mpegurl'}),
url(r'^playlist2\.pls$', 'content_type_to_template', {'template_name': 'playlist2.pls', 'content_type':'audio/x-scpls'})
views:
from django.shortcuts import render
def content_type_to_template(request, template_name='', content_type='text/plain'):
return render(request, template_name, content_type=content_type)
A simple example of how change content type of a TemplateView:
#views.py
from django.views.generic import TemplateView
class HomeView(TemplateView):
template_name = "home/index.html"
content_type = 'text/html'
# urls.py
url(r'^home/$', HomeView.as_view(), name='home_page'),
url(r'^test/(?P<template>.*)', lambda request, template: TemplateView.as_view(template_name=template)(request)),
I'm having a bit of trouble understanding how the new CBVs work. My question is this, I need to require login in all the views, and in some of them, specific permissions. In function-based views I do that with #permission_required() and the login_required attribute in the view, but I don't know how to do this on the new views. Is there some section in the django docs explaining this? I didn't found anything. What is wrong in my code?
I tried to use the #method_decorator but it replies "TypeError at /spaces/prueba/ _wrapped_view() takes at least 1 argument (0 given)"
Here is the code (GPL):
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required, permission_required
class ViewSpaceIndex(DetailView):
"""
Show the index page of a space. Get various extra contexts to get the
information for that space.
The get_object method searches in the user 'spaces' field if the current
space is allowed, if not, he is redirected to a 'nor allowed' page.
"""
context_object_name = 'get_place'
template_name = 'spaces/space_index.html'
#method_decorator(login_required)
def get_object(self):
space_name = self.kwargs['space_name']
for i in self.request.user.profile.spaces.all():
if i.url == space_name:
return get_object_or_404(Space, url = space_name)
self.template_name = 'not_allowed.html'
return get_object_or_404(Space, url = space_name)
# Get extra context data
def get_context_data(self, **kwargs):
context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
place = get_object_or_404(Space, url=self.kwargs['space_name'])
context['entities'] = Entity.objects.filter(space=place.id)
context['documents'] = Document.objects.filter(space=place.id)
context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
return context
There are a few strategies listed in the CBV docs:
Decorate the view when you instantiate it in your urls.py (docs)
from django.contrib.auth.decorators import login_required
urlpatterns = [
path('view/',login_required(ViewSpaceIndex.as_view(..)),
...
]
The decorator is applied on a per-instance basis, so you can add it or remove it in different urls.py routes as needed.
Decorate your class so every instance of your view is wrapped (docs)
There's two ways to do this:
Apply method_decorator to your CBV dispatch method e.g.,
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
#method_decorator(login_required, name='dispatch')
class ViewSpaceIndex(TemplateView):
template_name = 'secret.html'
If you're using Django < 1.9 (which you shouldn't, it's no longer supported) you can't use method_decorator on the class, so you have to override the dispatch method manually:
from django.contrib.auth.decorators import login_required
class ViewSpaceIndex(TemplateView):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
Use a mixin like django.contrib.auth.mixins.LoginRequiredMixin outlined well in the other answers here:
from django.contrib.auth.mixins import LoginRequiredMixin
class MyView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
Make sure you place the mixin class first in the inheritance list (so Python's Method Resolution Order algorithm picks the Right Thing).
The reason you're getting a TypeError is explained in the docs:
Note:
method_decorator passes *args and **kwargs as parameters to the decorated method on the class. If your method does not accept a compatible set of parameters it will raise a TypeError exception.
Here is my approach, I create a mixin that is protected (this is kept in my mixin library):
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
class LoginRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
Whenever you want a view to be protected you just add the appropriate mixin:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
Just make sure that your mixin is first.
Update: I posted this in way back in 2011, starting with version 1.9 Django now includes this and other useful mixins (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) as standard!
Here's an alternative using class based decorators:
from django.utils.decorators import method_decorator
def class_view_decorator(function_decorator):
"""Convert a function based decorator into a class based decorator usable
on class based Views.
Can't subclass the `View` as it breaks inheritance (super in particular),
so we monkey-patch instead.
"""
def simple_decorator(View):
View.dispatch = method_decorator(function_decorator)(View.dispatch)
return View
return simple_decorator
This can then be used simply like this:
#class_view_decorator(login_required)
class MyView(View):
# this view now decorated
For those of you who use Django >= 1.9, it's already included in django.contrib.auth.mixins as AccessMixin, LoginRequiredMixin, PermissionRequiredMixin and UserPassesTestMixin.
So to apply LoginRequired to CBV(e.g. DetailView):
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView
class ViewSpaceIndex(LoginRequiredMixin, DetailView):
model = Space
template_name = 'spaces/space_index.html'
login_url = '/login/'
redirect_field_name = 'redirect_to'
It's also good to keep in mind GCBV Mixin order: Mixins must go on the left side, and the base view class must go in the right side. If the order is different you can get broken and unpredictable results.
I realise this thread is a bit dated, but here's my two cents anyway.
with the following code:
from django.utils.decorators import method_decorator
from inspect import isfunction
class _cbv_decorate(object):
def __init__(self, dec):
self.dec = method_decorator(dec)
def __call__(self, obj):
obj.dispatch = self.dec(obj.dispatch)
return obj
def patch_view_decorator(dec):
def _conditional(view):
if isfunction(view):
return dec(view)
return _cbv_decorate(dec)(view)
return _conditional
we now have a way to patch a decorator, so it will become multifunctional. This effectively means that when applied to a regular view decorator, like so:
login_required = patch_view_decorator(login_required)
this decorator will still work when used the way it was originally intended:
#login_required
def foo(request):
return HttpResponse('bar')
but will also work properly when used like so:
#login_required
class FooView(DetailView):
model = Foo
This seems to work fine in several cases i've recently come across, including this real-world example:
#patch_view_decorator
def ajax_view(view):
def _inner(request, *args, **kwargs):
if request.is_ajax():
return view(request, *args, **kwargs)
else:
raise Http404
return _inner
The ajax_view function is written to modify a (function based) view, so that it raises a 404 error whenever this view is visited by a non ajax call. By simply applying the patch function as a decorator, this decorator is all set to work in class based views as well
Use Django Braces. It provides a lot of useful mixins that is easily available.
It has beautiful docs. Try it out.
You can even create your custom mixins.
http://django-braces.readthedocs.org/en/v1.4.0/
Example Code:
from django.views.generic import TemplateView
from braces.views import LoginRequiredMixin
class SomeSecretView(LoginRequiredMixin, TemplateView):
template_name = "path/to/template.html"
#optional
login_url = "/signup/"
redirect_field_name = "hollaback"
raise_exception = True
def get(self, request):
return self.render_to_response({})
In my code I have written this adapter to adapt member functions to a non-member function:
from functools import wraps
def method_decorator_adaptor(adapt_to, *decorator_args, **decorator_kwargs):
def decorator_outer(func):
#wraps(func)
def decorator(self, *args, **kwargs):
#adapt_to(*decorator_args, **decorator_kwargs)
def adaptor(*args, **kwargs):
return func(self, *args, **kwargs)
return adaptor(*args, **kwargs)
return decorator
return decorator_outer
You can simply use it like this:
from django.http import HttpResponse
from django.views.generic import View
from django.contrib.auth.decorators import permission_required
from some.where import method_decorator_adaptor
class MyView(View):
#method_decorator_adaptor(permission_required, 'someapp.somepermission')
def get(self, request):
# <view logic>
return HttpResponse('result')
If it's a site where the majority of pages requires the user to be logged in, you can use a middleware to force login on all views except some who are especially marked.
Pre Django 1.10 middleware.py:
from django.contrib.auth.decorators import login_required
from django.conf import settings
EXEMPT_URL_PREFIXES = getattr(settings, 'LOGIN_EXEMPT_URL_PREFIXES', ())
class LoginRequiredMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
path = request.path
for exempt_url_prefix in EXEMPT_URL_PREFIXES:
if path.startswith(exempt_url_prefix):
return None
is_login_required = getattr(view_func, 'login_required', True)
if not is_login_required:
return None
return login_required(view_func)(request, *view_args, **view_kwargs)
views.py:
def public(request, *args, **kwargs):
...
public.login_required = False
class PublicView(View):
...
public_view = PublicView.as_view()
public_view.login_required = False
Third-party views you don't want to wrap can be made excempt in the settings:
settings.py:
LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
It has been a while now and now Django has changed so much.
Check here for how to decorate a class-based view.
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class
The documentation did not include an example of "decorators that takes any argument". But decorators that take arguments are like this:
def mydec(arg1):
def decorator(func):
def decorated(*args, **kwargs):
return func(*args, **kwargs) + arg1
return decorated
return deocrator
so if we are to use mydec as a "normal" decorator without arguments, we can do this:
mydecorator = mydec(10)
#mydecorator
def myfunc():
return 5
So similarly, to use permission_required with method_decorator
we can do:
#method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
def get(self, request):
# ...
I've made that fix based on Josh's solution
class LoginRequiredMixin(object):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
Sample usage:
class EventsListView(LoginRequiredMixin, ListView):
template_name = "events/list_events.html"
model = Event
This is super easy with django > 1.9 coming with support for PermissionRequiredMixin and LoginRequiredMixin
Just import from the auth
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class YourListView(LoginRequiredMixin, Views):
pass
For more details read Authorization in django
If you are doing a project which requires variety of permission tests, you can inherit this class.
from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.views.generic import View
from django.utils.decorators import method_decorator
class UserPassesTest(View):
'''
Abstract base class for all views which require permission check.
'''
requires_login = True
requires_superuser = False
login_url = '/login/'
permission_checker = None
# Pass your custom decorator to the 'permission_checker'
# If you have a custom permission test
#method_decorator(self.get_permission())
def dispatch(self, *args, **kwargs):
return super(UserPassesTest, self).dispatch(*args, **kwargs)
def get_permission(self):
'''
Returns the decorator for permission check
'''
if self.permission_checker:
return self.permission_checker
if requires_superuser and not self.requires_login:
raise RuntimeError((
'You have assigned requires_login as False'
'and requires_superuser as True.'
" Don't do that!"
))
elif requires_login and not requires_superuser:
return login_required(login_url=self.login_url)
elif requires_superuser:
return user_passes_test(lambda u:u.is_superuser,
login_url=self.login_url)
else:
return user_passes_test(lambda u:True)
Here the solution for permission_required decorator:
class CustomerDetailView(generics.GenericAPIView):
#method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
def post(self, request):
# code...
return True