Django POST 405 method not allowed unless rule comes first - django

My Django (2.2.5) app has the following urls.py:
from django.urls import path
from django.urls.conf import re_path
from django.views.generic.base import TemplateView
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from . import views
urlpatterns = [
re_path('^$|signin', TemplateView.as_view(template_name='signin.html'), name='signin'),
path('forgot', TemplateView.as_view(template_name='forgot.html'), name='forgot_pass'),
path('app', TemplateView.as_view(template_name='core.html'), name='core'),
path('try_signin', views.try_signin, name='try_signin'),
] + staticfiles_urlpatterns()
The first 3 rules work fine and serve up the respective HTML content. The 4th rule is for a POST request, but the request causes the following error:
Method Not Allowed (POST): /try_signin
Method Not Allowed: /try_signin
[30/Sep/2019 14:20:38] "POST /try_signin HTTP/1.1" 405 0
However if I reorder the URL rules so that the POST rule comes first, then it works fine. There's no conflict in the rules that I can see. I'm new to Django and still learning but I'd like to understand why re-ordering the rules avoids the error, or if there's something else I'm doing/not doing that caused the error.
This is my views.py:
from django.http.response import JsonResponse
from time import sleep
import logging
import json
log = logging.getLogger(__name__)
def try_signin(request):
user_email = request.POST.get('user', None)
password = request.POST.get('pass', None)
log.info("Signin attempt ==> [%s] [%s]" % (user_email, password))
sleep(2)
data = {
'success': False
}
log.info("Returning response ==> %s" % json.dumps(data))
return JsonResponse(data)
Also, adding #require_POST decorator to the try_signin function above still causes the error. As I said earlier, it does work if I reorder the rule to appear first in url_patterns.

re_path('^$|signin', ...) matches the url /try_signin. So when you POST to this URL, it goes to the TemplateView for signin.html which only accepts GET requests.

Related

DRF: AttributeError: type object 'Plan' has no attribute 'get_extra_actions' [duplicate]

Trying a simple request:
urls.py
from django.conf.urls import url
from django.urls import include, path
from rest_framework import routers
from django.http import HttpResponse
from rest_framework.urlpatterns import format_suffix_patterns
from .public_views import NavigationBar
router = routers.DefaultRouter()
router.register(r'navbar', NavigationBar, basename="NavigationBar")
urlpatterns = [
path('', include(router.urls))
]
urlpatterns = format_suffix_patterns(urlpatterns)
public_views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from rest_framework.throttling import UserRateThrottle
from rest_framework.decorators import api_view, throttle_classes
from . view_utils import *
class OncePerDayUserThrottle(UserRateThrottle):
rate = '1/day'
class NavigationBar(APIView):
"""
obtain up to date navigation bar (or side menu navigation) hierarchy.
"""
permission_classes = ([AllowAny])
def get(self, request, format=None):
"""
get user addresses
"""
return Response("this is a good response")
def get_extra_actions(cls):
return []
When I API call the /v1/navbar or /v1/navbar/ endpoints (I do have my main urls.py lead all /v1/ traffic to another dedicated urls.py), I am getting the following error:
AttributeError at /v1/navbar
type object 'NavigationBar' has no attribute 'get_extra_actions'
Request Method: GET
Request URL: http://web/v1/navbar
Django Version: 2.1
Exception Type: AttributeError
Exception Value:
type object 'NavigationBar' has no attribute 'get_extra_actions'
Exception Location: /usr/local/lib/python3.6/site-packages/rest_framework/routers.py in get_routes, line 200
Python Executable: /usr/local/bin/uwsgi
Python Version: 3.6.8
Python Path:
['.',
'',
'/usr/local/lib/python36.zip',
'/usr/local/lib/python3.6',
'/usr/local/lib/python3.6/lib-dynload',
'/usr/local/lib/python3.6/site-packages']
Server time: Tue, 2 Jul 2019 17:12:27 +0000
I would appreciate any pointers. Also, I fail to understand why the error message includes a Request URL: http://web/v1/navbar indication when web is not part of the URL I'm using. Where is web coming from??? I just use /v1/navbar/ to hit the endpoint.
There are two things wrong here. First, routers are for viewsets, not simple views. Secondly, with a class based view you need to call it via its as_view() method in the urlconf. So get rid of that router stuff and just do:
urlpatterns = [
path(r'navbar', NavigationBar.as_view(), name="NavigationBar")
]
Note, now you're not using a router, you don't need that get_extra_actions method at all.

Django Rest Framework: Can't get over strange error

Trying a simple request:
urls.py
from django.conf.urls import url
from django.urls import include, path
from rest_framework import routers
from django.http import HttpResponse
from rest_framework.urlpatterns import format_suffix_patterns
from .public_views import NavigationBar
router = routers.DefaultRouter()
router.register(r'navbar', NavigationBar, basename="NavigationBar")
urlpatterns = [
path('', include(router.urls))
]
urlpatterns = format_suffix_patterns(urlpatterns)
public_views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny
from rest_framework.throttling import UserRateThrottle
from rest_framework.decorators import api_view, throttle_classes
from . view_utils import *
class OncePerDayUserThrottle(UserRateThrottle):
rate = '1/day'
class NavigationBar(APIView):
"""
obtain up to date navigation bar (or side menu navigation) hierarchy.
"""
permission_classes = ([AllowAny])
def get(self, request, format=None):
"""
get user addresses
"""
return Response("this is a good response")
def get_extra_actions(cls):
return []
When I API call the /v1/navbar or /v1/navbar/ endpoints (I do have my main urls.py lead all /v1/ traffic to another dedicated urls.py), I am getting the following error:
AttributeError at /v1/navbar
type object 'NavigationBar' has no attribute 'get_extra_actions'
Request Method: GET
Request URL: http://web/v1/navbar
Django Version: 2.1
Exception Type: AttributeError
Exception Value:
type object 'NavigationBar' has no attribute 'get_extra_actions'
Exception Location: /usr/local/lib/python3.6/site-packages/rest_framework/routers.py in get_routes, line 200
Python Executable: /usr/local/bin/uwsgi
Python Version: 3.6.8
Python Path:
['.',
'',
'/usr/local/lib/python36.zip',
'/usr/local/lib/python3.6',
'/usr/local/lib/python3.6/lib-dynload',
'/usr/local/lib/python3.6/site-packages']
Server time: Tue, 2 Jul 2019 17:12:27 +0000
I would appreciate any pointers. Also, I fail to understand why the error message includes a Request URL: http://web/v1/navbar indication when web is not part of the URL I'm using. Where is web coming from??? I just use /v1/navbar/ to hit the endpoint.
There are two things wrong here. First, routers are for viewsets, not simple views. Secondly, with a class based view you need to call it via its as_view() method in the urlconf. So get rid of that router stuff and just do:
urlpatterns = [
path(r'navbar', NavigationBar.as_view(), name="NavigationBar")
]
Note, now you're not using a router, you don't need that get_extra_actions method at all.

Best way to handle legacy urls in django

I am working on a big news publishing platform. Basically rebuilding everything from ground zero with django. Now as we are almost ready for the launch I need to handle legacy url redirects. What is the best way to do it having in mind that I have to deal with tenths of thousands of legacy urls?
Logic should work like this: If none of existing urls/views where matched run that url thorough legacy Redirect urls patterns/views to see if it can provide some redirect to the new url before returning 404 error.
How do I do that?
You may want to create a fallback view that will try to handle any url not handled by your patterns. I see two options.
Just create a "default" pattern. It's important to this pattern to
be the last within your urlpatterns!
in your urls.py:
urlpatterns = patterns(
'',
# all your patterns
url(r'^.+/', 'app.views.fallback')
)
in your views.py:
from django.http.response import HttpResponseRedirect, Http404
def fallback(request):
if is_legacy(request.path):
return HttpResponseRedirect(convert(request.path))
raise Http404
Create a custom http 404 handler.
in your urls.py:
handler404 = 'app.views.fallback'
in your views.py
from django.http.response import HttpResponseRedirect
from django.views.defaults import page_not_found
def fallback(request):
if is_legacy(request.path):
return HttpResponseRedirect(convert(request.path))
return page_not_found(request)
it may seem to be a nicer solution but it will only work if you set DEBUG setting to False and provide custom 404 template.
Awesome, achieved that by using custom middleware:
from django.http import Http404
from legacy.urls import urlpatterns
class LegacyURLsMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response
for resolver in urlpatterns:
try:
match = resolver.resolve(request.path[1:])
if match:
return match.func(request, *match.args, **match.kwargs)
except Http404:
pass
return response
Simply add this middleware as a last middleware in MIDDLEWARE_CLASSES list. Then use urls.py file in your legacy app to declare legacy urls and views which will handle permanent redirects. DO NOT include your legacy urls in to main urls structure. This middleware does it for you, but in a bit different way.
Use the Jacobian's django-multiurl. There is a django ticket to address the issue someday, but for now django-multiurl works very good.
Before:
# urls.py
urlpatterns = patterns('',
url('/app/(\w+)/$', app.views.people),
url('/app/(\w+)/$', app.views.place), # <-- Never matches
)
After:
# urls.py
from multiurl import multiurl, ContinueResolving
from django.http import Http404
urlpatterns = patterns('', multiurl(
url('/app/(\w+)/$', app.views.people), # <-- Tried 1st
url('/app/(\w+)/$', app.views.place), # <-- Tried 2nd (only if 1st raised Http404)
catch=(Http404, ContinueResolving)
))

Django 404 pages return 200 status code

I'm going through the Django tutorial and am on part 5: Testing. I run into the problem where I'm using the DetailView and ListView "shortcut" views to factor out code (as suggested by the tutorial), but when a 404 page is displayed, a 200 status code is returned instead. Am I doing something wrong? The tutorial says the status code should be 404.
Thanks!
You need to define the Http header to have a 404 status.
return HttpResponse(content=template.render(context), content_type='text/html; charset=utf-8', status=404)
It is important to inform the search engines that the current page is a 404. Spammers sometimes creates lots of urls that could seem that would lead you to some place, but then serves you another content. They frequently make lots of different addresses serve you almost the exact same content. And because it is not user friendly, most SEO guide lines penalize that. So if you have lots of addresses showing the same pseudo-404 content, it could not look good to the crawling systems from the search websites. Because of that you want to make sure that the page you are serving as a custom 404 has a 404 status.
If you are trying to make a custom 404 page, here it is a good way to go:
Into your application's urls.py add:
# Imports
from django.conf.urls.static import static
from django.conf.urls import handler404
from django.conf.urls import patterns, include, url
from yourapplication import views
##
# Handles the URLS calls
urlpatterns = patterns('',
# url(r'^$', include('app.homepage.urls')),
)
handler404 = views.error404
Into your application's views.py add:
# Imports
from django.shortcuts import render
from django.http import HttpResponse
from django.template import Context, loader
##
# Handle 404 Errors
# #param request WSGIRequest list with all HTTP Request
def error404(request):
# 1. Load models for this view
#from idgsupply.models import My404Method
# 2. Generate Content for this view
template = loader.get_template('404.htm')
context = Context({
'message': 'All: %s' % request,
})
# 3. Return Template for this view + Data
return HttpResponse(content=template.render(context), content_type='text/html; charset=utf-8', status=404)
The secret is in the last line: status=404
Hope it helped!
I look forward to see the community inputs to this approach. =)
You can
return HttpResponseNotFound(render_to_string('404.html'))
instead.

Custom Django 404 error

I have a 404.html page, but in some cases I want to be able to send a json error message (for 404 and 500, etc.). I read the following page:
https://docs.djangoproject.com/en/dev/topics/http/views/#the-404-page-not-found-view
Is there any sort of example that shows the implementation? I have it in my urls.py but it's not being picked up in the event of an error.
This worked for me:
from django.conf.urls import patterns, include, url
from django.views.static import *
from django.conf import settings
from django.conf.urls.defaults import handler404, handler500
from app.views import error
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'app.views.home', name='home'),
)
handler404 = error.error_handler
handler500 = error.error_handler
You can make it do anything as you wish when going to that controller.
In addition to the previous answer, it is important to say that the views.py should return a HttpResponse with a 404 status in the http header. It is important to inform the search engines that the current page is a 404. Spammers sometimes creates lots of urls that could seem that would lead you to some place, but then serves you another content. They frequently make lots of different addresses serve you almost the exact same content. And because it is not user friendly, most SEO guide lines penalize that. So if you have lots of addresses showing the same pseudo-404 content, it could not look good to the crawling systems from the search websites. Because of that you want to make sure that the page you are serving as a custom 404 has a 404 status. So here it is a good way to go:
Into your application's urls.py add:
# Imports
from django.conf.urls.static import static
from django.conf.urls import handler404
from django.conf.urls import patterns, include, url
from yourapplication import views
##
# Handles the URLS calls
urlpatterns = patterns('',
# url(r'^$', include('app.homepage.urls')),
)
handler404 = views.error404
Into your application's views.py add:
# Imports
from django.shortcuts import render
from django.http import HttpResponse
from django.template import Context, loader
##
# Handle 404 Errors
# #param request WSGIRequest list with all HTTP Request
def error404(request):
# 1. Load models for this view
#from idgsupply.models import My404Method
# 2. Generate Content for this view
template = loader.get_template('404.htm')
context = Context({
'message': 'All: %s' % request,
})
# 3. Return Template for this view + Data
return HttpResponse(content=template.render(context), content_type='text/html; charset=utf-8', status=404)
The secret is in the last line: status=404
Hope it helped!
I look forward to see the community inputs to this approach. =)
Basics:
To define custom view for handling 404 errors, define in the URL config, a view for handler404, like handler404 = 'views.error404'
Apart from the basics, some things to note about (custom 404 views):
It will be enabled only in Debug=False mode.
And more ignored one, across most answers (and this this stuck my brains out).
The 404 view defaults to
django.views.defaults.page_not_found(request, exception, template_name='404.html')
Notice the parameter exception
This was causing a 404 to 500 redirect from within def get_exception_response(self, request, resolver, status_code, exception) function defined in core.handlers.base since it could not find the parameter exception