I am new to django and I am trying to understand class views.
In urls.py (main) I have:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^', include('webapp.urls')),
]
in webapp folder I have:
urls.py (webapp):
from django.conf.urls import url
from webapp.views import Firstapp
urlpatterns = [
url(r'^whatever$', Firstapp.as_view()),
]
views.py (webapp):
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
class Firstapp(View):
def something(self):
return HttpResponse('Yes it works!')
As I have said, I am trying to use class views and I would appreciate if you could help me understand why class returns 405 error. Thank you. CMD returns 0 problems.
Because you are subclassing View and the only method you define is called something.
View expects you to define a method for each valid http verb. (GET, POST, HEAD etc). Since Firstapp has no such method, View.dispatch will return a response with http status 405 (Method not allowed).
dispatch(request, *args, **kwargs)
The view part of the view – the
method that accepts a request argument plus arguments, and returns a HTTP response.
The default implementation will inspect the HTTP method and attempt to delegate to a method that matches the HTTP method; a GET will be delegated to get(), a POST to post(), and so on.
By default, a HEAD request will be delegated to get(). If you need to handle HEAD requests in a different way than GET, you can override the head() method. See Supporting other HTTP methods for an example.
To fix this, change your something method:
def get(self, request):
return HttpResponse('Yes it works!')
Related
greetings dear django experts,
i am having an issue with my website i am using the defualt login view in django from
from django.contrib.auth import views as auth_views
the code is working
like this inside the urls.py file :
from django.contrib.auth import views as auth_views
path('login/',auth_views.LoginView.as_view(template_name='website/login.html'), name='login-page'),
but using this methode i don't have a view inside my views.py file. the problem is i need to define a view inside my viwes.py to record logs for any one accessing this page the problem is when i tried the following code i get the error "'function' object has no attribute 'get'"
the code that gives me the error is as the following:
views.py
from django.contrib.auth import views as auth_views
def login(request):
ip = get_client_ip(request)
logger.info(f'user access: {request.user} via ip:{ip}')
return auth_views.LoginView.as_view(template_name='website/login.html')
urls.py
path('login/',views.login, name='login-page'),
.LoginView.as_view() does not process the request, it simply returns a function that will dispatch the request, you thus need to call the function that is the result of .as_view(…) with the request as parameter:
from django.contrib.auth import views as auth_views
def login(request):
ip = get_client_ip(request)
logger.info(f'user access: {request.user} via ip:{ip}')
return auth_views.LoginView.as_view(template_name='website/login.html')(request)
That being said, it looks a bit odd to do this. Why not just subclass the LoginView and register that view?
You thus can do this with:
# appname/views.py
from django.contrib.auth.views import LoginView
class MyLoginView(LoginView):
template_name='website/login.html'
def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
ip = get_client_ip(request)
logger.info(f'user access: {request.user} via ip:{ip}')
and then register your MyLoginView instead:
from appname.views import MyLoginView
path('login/', MyLoginView.as_view(), name='login-page'),
I have defined a custom action for a ViewSet
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
And the viewset is registered to url in the conventional way
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet, base_name='myuser')
urlpatterns = [
url(r'^', include(router.urls)),
]
The URL /api/users/gender/ works. But I don't know how to get it using reverse in unit test. (I can surely hard code this URL, but it'll be nice to get it from code)
According to the django documentation, the following code should work
reverse('admin:app_list', kwargs={'app_label': 'auth'})
# '/admin/auth/'
But I tried the following and they don't work
reverse('myuser-list', kwargs={'app_label':'gender'})
# errors out
reverse('myuser-list', args=('gender',))
# '/api/users.gender'
In the django-restframework documentation, there is a function called reverse_action. However, my attempts didn't work
from api.views import UserViewSet
a = UserViewSet()
a.reverse_action('gender') # error out
from django.http import HttpRequest
req = HttpRequest()
req.method = 'GET'
a.reverse_action('gender', request=req) # still error out
What is the proper way to reverse the URL of that action?
You can use reverse just add to viewset's basename action:
reverse('myuser-gender')
See related part of docs.
You can print all reversible url names of a given router at startup with the get_urls() method
In urls.py after the last item is registered to the router = DefaultRouter() variable, you can add:
#router = routers.DefaultRouter()
#router.register(r'users', UserViewSet)
#...
import pprint
pprint.pprint(router.get_urls())
The next time your app is initialized it will print to stdout something like
[<RegexURLPattern user-list ^users/$>,
<RegexURLPattern user-list ^users\.(?P<format>[a-z0-9]+)/?$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)/$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,
#...
]
where 'user-list' and 'user-detail' are the names that can be given to reverse(...)
Based on DRF Doc.
from rest_framework import routers
router = routers.DefaultRouter()
view = UserViewSet()
view.basename = router.get_default_basename(UserViewSet)
view.request = None
or you can set request if you want.
view.request = req
In the end, you can get a reverse action URL and use it.
url = view.reverse_action('gender', args=[])
If you want to use UserViewset().reverse_action() specifically, you can do this by assigning a basename and request = None to your ViewSet:
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
basename = 'user'
request = None
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
and in urls.py:
router.register('user', UserViewSet, basename=UserViewSet.basename)
Then calling url = UserViewset().reverse_action('gender') or url = UserViewset().reverse_action(UserViewSet().gender.url_name) will return the correct url.
Edit:
Above method only works when calling reverse_action() only once because the ViewSetMixin.as_view() method overrides basename on instantiation. This can be solved by adding a custom subclass of GenericViewSet (or ModelViewSet if preferred) like so:
from django.utils.decorators import classonlymethod
from rest_framework.viewsets import GenericViewSet
class ReversibleViewSet(GenericViewSet):
basename = None
request = None
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
basename = cls.basename
view = super().as_view(actions, **initkwargs)
cls.basename = basename
return view
and subclassing this class for the specific ViewSets, setting basename as desired.
The answer is reverse('myuser-gender').
Note! But remember that DRF will replace _ in the action name with -. That means if the action name is my_pretty_action in the reverse you should use reverse(app-my-pretty-action).
According to your code you would like to get the list of users you use
reverse("myuser-list")
details
reverse("myuser-detail",kwargs={"id":1})
In here you might notice i use list or detail by adding it to the reverse so if you add appname you can just input it thats because we are using viewsets
I have a django website with many urls and views. Now I have asked to redirect all non-authenticated users to a certain landing page. So, all views must check if user.is_authenticated() and return to a new set of landing pages.
Can it be done in a pretty way, instead of messing with my views.py/urls.py that much?
There is a simpler way to do this, just add the "login_url" parameter to #login_required and if the user is not login he will be redirected to the login page. You can find it here
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
def my_view(request):
...
You can use Middleware.
Something like this will check user auth every request:
class AuthRequiredMiddleware(object):
def process_request(self, request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('landing_page')) # or http response
return None
Docs: process_request
Also, don't forget to enable it in settings.py
MIDDLEWARE_CLASSES = (
...
'path.to.your.AuthRequiredMiddleware',
)
see the docs for login required decorator
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
another option is to add it to your urls.py patterns, see this answer
urlpatterns = patterns('',
(r'^foo/$', login_required(direct_to_template), {'template': 'foo_index.html'}),
)
As of Django 1.10, the custom middleware classes must implement the new style syntax. You can use the following class to verify that the user is logged in while trying to access any views.
from django.shortcuts import HttpResponseRedirect
class AuthRequiredMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
if not request.user.is_authenticated: # in Django > 3 this is a boolean
return HttpResponseRedirect('login')
# Code to be executed for each request/response after
# the view is called.
return response
You can avoid specifying login_url by setting LOGIN_URL.
Therefore, in settings.py add:
LOGIN_URL = '<some_url>'
And in your views.py annotate relevant functions with only #login_required:
#login_required
def some_view_function(request):
If you need to redirect within a view function, you can do so with:
return redirect_to_login(request.get_full_path())
This can be done with middleware.
I've found a really nifty djangosnippet that does exactly what you are asking for. You can find it here, and it looks like:
from django.http import HttpResponseRedirect
from django.conf import settings
from re import compile
EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware:
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which
you can copy from your urls.py).
Requires authentication middleware and template context processors to be
loaded. You'll get an error if they aren't.
"""
def process_request(self, request):
assert hasattr(request, 'user'), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\
'django.contrib.auth.middlware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'."
if not request.user.is_authenticated():
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return HttpResponseRedirect(settings.LOGIN_URL)
All you have to do is to save the file as middleware.py and include the class in you're settings.py, i.e.
MIDDLEWARE_CLASSES += ('projectname.common.middleware.RequireLoginMiddleware',)
You can also define a LOGIN_URL in settings.py, so that you'll be redirected to your custom login page. The default LOGIN_URL is '/accounts/login/'.
Maybe too late but in django 1.9+ it's too easy.
Django introduced Login Required mixin for generic classes and this a great example
here by William S. Vincent
simply in your view add LoginRequiredMixin as parent class
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogUpdateView(LoginRequiredMixin, UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
Also you can use login_required decorator for method request
from django.contrib.auth.decorators import login_required
#login_required(login_url='/login/')
def home(request):
return render(request, "home.html")
It's showing like this: http://127.0.0.1:1235/login/?next=/home/
I'm using class based views and I couldn't get any of these solutions to work for what I needed so I'll post my working solution here.
class ManagementPageView(TemplateView):
# Make a dispatch method to handle authentication
def dispatch(self, *args, **kwargs):
# Check if user is authenticated
if not self.request.user.is_authenticated:
# Redirect them to the home page if not
return redirect('home')
# Render the template if they are
return render(self.request, 'management.html')
Here is my code. I was just trying to implement this but could not.I am in a stage of leaning Django
class BCD(View):
def start(self):
return HttpResponse("Huray Finally I called")
urls.py
urlpatterns = patterns('',
url(r'^login1/$',BCD.as_view(),)
And even i tried
urlpatterns = patterns('',
url(r'^login1/$',BCD.start.as_view(),)
It throws error.I have read the docs of django methods such as get and post ,head can be written they will be called directly according to the request made.But what I is need to make my own methods in class based views
Thanks in Advance
You need to call methods in get(), post() or whatever the HTTP request is. There isn't a start HTTP request so Django won't call that.
You can do the following in your view:
def get(self, request, *args, **kwargs):
return self.start()
This return the result of self.start() whenever you visit your view with a HTTP GET request.
I want a way to automatically route GET and POST requests to subsequent methods in a centralized way.
I want to create my handler in the following way.
class MyHandler(BaseHandler):
def get(self):
#handle get requests
def post(self):
#handle post requests
This is what webapp2 does and I very much like the style, is it possible to do in Django?
I also want the view in Class-method style. What kind of BaseHandler and router should I write.
HINT: Use django generic views.
This is supported in Django as class based views. You can extend the generic class View and add methods like get(), post(), put() etc. E.g. -
from django.http import HttpResponse
from django.views.generic import View
class MyView(View):
def get(self, request, *args, **kwargs):
return HttpResponse('This is GET request')
def post(self, request, *args, **kwargs):
return HttpResponse('This is POST request')
The dispatch() method from View class handles this-
dispatch(request, *args, **kwargs)
The view part of the view – the
method that accepts a request argument plus arguments, and returns a
HTTP response.
The default implementation will inspect the HTTP method and attempt to
delegate to a method that matches the HTTP method; a GET will be
delegated to get(), a POST to post(), and so on.
By default, a HEAD request will be delegated to get(). If you need to
handle HEAD requests in a different way than GET, you can override the
head() method. See Supporting other HTTP methods for an example.
The default implementation also sets request, args and kwargs as
instance variables, so any method on the view can know the full
details of the request that was made to invoke the view.
Then you can use it in urls.py -
from django.conf.urls import patterns, url
from myapp.views import MyView
urlpatterns = patterns('',
url(r'^mine/$', MyView.as_view(), name='my-view'),
)