Django url validation - python

HI,
I want to validate my urls whether they are post or get with caring the proper data.So i want to validate these urls before they call to respective views.So i am willing to write the some kind of middleware between view and urls so that i can keep safe the system.I am not aware how do i pass the data through middleware code to view.In middle ware i will write the unittest code.which will validate the urls if valid then will pass to the respective view other wise happy to say 404 .So can any buddy suggest me how do i handle the case.Or may be their is another alternative best way to do this validation.
Thanks to all.

You should really be checking for request type in your views, and not in a middleware. As I mentioned in the comments above, you can't tell whether a request is a POST message from the URL alone, let alone determine what POST data it carries.
Checking the request type within a view is very straight-forward -- simple check that request.method is equal to "GET" or "POST".
If you're doing this often, a short cut would be to create a decorator which does this check for you. For example, the following decorator checks that a GET request was used to call this view, or else return an HttpResponseBadRequest object (status code 400):
# untested code, use with care
def require_GET(view_func):
def wrap(request, *args, **kwargs):
if request.method != "GET":
return HttpResponseBadRequest("Expecting GET request")
return view_func(request, *args, **kwargs)
wrap.__doc__ = view_func.__doc__
wrap.__dict__ = view_func.__dict__
wrap.__name__ = view_func.__name__
return wrap
You can then simply prepend your view function with #require_GET and the check will be done whever the view is called. E.g.
#require_GET
def your_view(request):
# ...
You can do the same for POST.
Here's an example decorator checking for POST request which takes an optional list of fields that must be provided with the POST request.
# again, untested so use with care.
def require_POST(view_func, required_fields=None):
def wrap(request, *args, **kwargs):
if request.method != "POST":
return HttpResponseBadRequest("Expecting POST request")
if required_fields:
for f in required_fields:
if f not in request.POST:
return HttpResponseBadRequest("Expecting field %s" % f)
return view_func(request, *args, **kwargs)
wrap.__doc__ = view_func.__doc__
wrap.__dict__ = view_func.__dict__
wrap.__name__ = view_func.__name__
return wrap
Use like this:
#require_POST
def another_view(request):
# ...
or:
#require_POST(required_fields=("username", "password"))
def custom_login_view(request):
# ...
Update
OK, my bad. I've just reinvented wheel.
Django already provides the #require_GET and #require_POST decorators. See django.views.decorators.http.

Like others said, you must do it in your view, or maybe you must say what you are trying to do for the best...
Anyway, you can not create a responce object in process_request , you can only add variables or change variables on the related request, like the sessionid variable used by django, or any such thing... Or update any existing request variables...
So, you must use process_view, which is triggered after process_request and just before your related view function is executed.Since you have request object at hand, you can check GET or POST data by using request.GET or request.POST.
For doing this, you must add your middle class to MIDDLEWARE_CLASSES in settings.py and write a proper middleware process_view function. For writing middlewares see middleware documentation and check existing middlewares of django. Or tell me what you are rtying to do...

Related

django object has no id, or is at least not passing any id [duplicate]

In my view function I want to call another view and pass data to it :
return redirect('some-view-name', backend, form.cleaned_data)
, where backend is of registration.backends object, and form.cleaned_data is a dict of form data (but both must be either sent as *args or **kwargs to prevent raising Don't mix *args and **kwargs in call to reverse()! error). From what I've found in the docs :
def my_view(request):
...
return redirect('some-view-name', foo='bar')
It looks like I need to provide 'some-view-name' argument, but is it just the name of the view function, or the name of the url ? So I would like to make it similar to the way it's done in django-registration, where :
to, args, kwargs = backend.post_registration_redirect(request, new_user)
return redirect(to, *args, **kwargs)
def post_registration_redirect(self, request, user):
return ('registration_complete', (), {})
Ok so now, can I call directly my view function or do I need to provide a url for it ? And what more important, how my funciotn call (and a url if needed) should look like ? Both backend, and cleaned_data are just passed through this view for a later usage. I've tried this, but it's improper :
url(r'^link/$', some-view-name)
def some-view-name(request, *args):
As well as this :
return redirect('some_url', backend=backend, dataform.cleaned_data)
url(r'^link/$', some-view-name)
def some-view-name(request, backend, data):
still NoReverseMatch . But in django-registration, I've seen something like this :
url(r'^register/$',register,{'backend': 'registration.backends.default.DefaultBackend'}, name='registration_register'),
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='user/login_logout_register/registration_form.html',
extra_context=None):
urls.py:
#...
url(r'element/update/(?P<pk>\d+)/$', 'element.views.element_update', name='element_update'),
views.py:
from django.shortcuts import redirect
from .models import Element
def element_info(request):
# ...
element = Element.object.get(pk=1)
return redirect('element_update', pk=element.id)
def element_update(request, pk)
# ...
Firstly, your URL definition does not accept any parameters at all. If you want parameters to be passed from the URL into the view, you need to define them in the urlconf.
Secondly, it's not at all clear what you are expecting to happen to the cleaned_data dictionary. Don't forget you can't redirect to a POST - this is a limitation of HTTP, not Django - so your cleaned_data either needs to be a URL parameter (horrible) or, slightly better, a series of GET parameters - so the URL would be in the form:
/link/mybackend/?field1=value1&field2=value2&field3=value3
and so on. In this case, field1, field2 and field3 are not included in the URLconf definition - they are available in the view via request.GET.
So your urlconf would be:
url(r'^link/(?P<backend>\w+?)/$', my_function)
and the view would look like:
def my_function(request, backend):
data = request.GET
and the reverse would be (after importing urllib):
return "%s?%s" % (redirect('my_function', args=(backend,)),
urllib.urlencode(form.cleaned_data))
Edited after comment
The whole point of using redirect and reverse, as you have been doing, is that you go to the URL - it returns an Http code that causes the browser to redirect to the new URL, and call that.
If you simply want to call the view from within your code, just do it directly - no need to use reverse at all.
That said, if all you want to do is store the data, then just put it in the session:
request.session['temp_data'] = form.cleaned_data
I do like this in django3
redirect_url = reverse('my_function', args=(backend,))
parameters = urlencode(form.cleaned_data)
return redirect(f'{redirect_url}?{parameters}')
I am new to Django. One of my project, I used render instead of redirect to send data. That worked good. My code was like this --->
for key, value in request.POST.lists():
print(key, value)
if key.split('-')[-1] != 'csrfmiddlewaretoken':
qu_id = key.split('-')[-1]
get_answer = Answer.objects.filter(question_id=qu_id,
correct_answer__option__contains=value[0])
total_correct_answer = get_answer.count()
context = {'score': total_correct_answer}
return render(request, 'result.html', context)
context = {'questions': questions, 'total_correct_answer': total_correct_answer}
return render(request, 'test.html', context)

Django (DRF) fails to POST data to url with similar path

In my url conf I have two similar patterns:
urlpatterns = [
path('chat/', views.chat), # create chat
path('chat/message/', views.message), # create message
]
The second path works as expect, however, when I try to POST data to chat/ I get error 405 and {"detail":"Method \"POST\" not allowed."} error message. The code in the view works, if I modify chat/ to something more specific like chat/create/ then everything works fine. However, this is not what I want to do. I thought django would match the first URL that matches the request. Why is this happening? It this bug or expected behavior?
I have run into a similar issue. I created a new nested_route decorator that acts a lot like list_route and detail_route. I usually redirect to another viewset to handle the nested path. The issue was that the stream was being read too early (by the parent viewset dispatch), so I needed to ensure that the initialize_request function was only called once for a given request.
Working off of #rsalmaso's comment above, I overrode the initialize_request method in the children viewsets with the following.
def initialize_request(self, request, *args, **kwargs):
if not isinstance(request, Request):
request = super().initialize_request(request, *args, **kwargs)
return request
This works fine, but I think having some sort of attribute for either ignoring the initialize_request function within dispatch or having a global check to only run initialize_request iff isinstance(request, rest_framework.request.Request) == False. I'm happy to prepare the PR with tests if that could be acceptable.

Convert GET parameters to POST data on a Request object in Django REST Framework

I am in the process of rewriting the backend of an internal website from PHP to Django (using REST framework).
Both versions (PHP and Django) need to be deployed concurrently for a while, and we have a set of software tools that interact with the legacy website through a simple AJAX API. All requests are done with the GET method.
My approach so far to make requests work on both sites was to make a simple adapter app, routed to 'http://<site-name>/ajax.php' to simulate the call to the Ajax controller. Said app contains one simple function based view which retrieves data from the incoming request to determine which corresponding Django view to call on the incoming request (basically what the Ajax controller does on the PHP version).
It does work, but I encountered a problem. One of my API actions was a simple entry creation in a DB table. So I defined my DRF viewset using some generic mixins:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
# ...
This adds a create action routed to POST requests on the page. Exactly what I need. Except my incoming requests are using GET method... I could write my own create action and make it accept GET requests, but in the long run, our tools will adapt to the Django API and the adapter app will no longer be needed so I would rather have "clean" view sets and models. It makes more sense to use POST for such an action.
In my adapter app view, I naively tried this:
request.method = "POST"
request.POST = request.GET
Before handing the request to the create view. As expected it did not work and I got a CSRF authentication failure message, although my adapter app view has a #csrf_exempt decorator...
I know I might be trying to fit triangle in squares here, but is there a way to make this work without rewriting my own create action ?
You can define a custom create method in your ViewSet, without overriding the original one, by utilizing the #action decorator that can accept GET requests and do the creation:
class MyViewSet(MyGenericViewSet, CreateModelMixin):
...
#action(methods=['get'], detail=False, url_path='create-from-get')
def create_from_get(self, request, *args, **kwargs):
# Do your object creation here.
You will need a Router in your urls to connect the action automatically to your urls (A SimpleRouter will most likely do).
In your urls.py:
router = SimpleRouter()
router.register('something', MyViewSet, base_name='something')
urlpatterns = [
...
path('my_api/', include(router.urls)),
...
]
Now you have an action that can create a model instance from a GET request (you need to add the logic that does that creation though) and you can access it with the following url:
your_domain/my_api/something/create-from-get
When you don't need this endpoint anymore, simply delete this part of the code and the action seizes to exist (or you can keep it for legacy reasons, that is up to you)!
With the advice from all answers pointing to creating another view, this is what I ended up doing. Inside adapter/views.py:
from rest_framework.settings import api_settings
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.response import Response
from rest_framework import status
from mycoreapp.renderers import MyJSONRenderer
from myapp.views import MyViewSet
#api_view(http_method_names=["GET"])
#renderer_classes((MyJSONRenderer,))
def create_entity_from_get(request, *args, **kwargs):
"""This view exists for compatibility with the old API only.
Use 'POST' method directly to create a new entity."""
query_params_copy = request.query_params.copy()
# This is just some adjustments to make the legacy request params work with the serializer
query_params_copy["foo"] = {"name": request.query_params.get("foo", None)}
query_params_copy["bar"] = {"name": request.query_params.get("bar", None)}
serializer = MyViewSet.serializer_class(data=query_params_copy)
serializer.is_valid(raise_exception=True)
serializer.save()
try:
headers = {'Location': str(serializer.data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
headers = {}
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Of course I have obfuscated the names of everything specific to my project. Basically I reproduced almost exactly (except for a few tweaks to my query params) what happens in the create, perform_create and get_success_header methods of the DRF mixin CreateModelMixin in a single function based DRF view. Being just a standalone function it can sit in my adapter app views so that all legacy API code is sitting in one place only, which was my intent with this question.
You can write a method for your viewset (custom_get) which will be called when a GET call is made to your url, and call your create method from there.
class MyViewSet(MyGenericViewSet, CreateModelMixin):
...
def custom_get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
And in your urls.py, for your viewset, you can define that this method needs to be called on a GET call.
#urls.py
urlpatterns = [
...
url(r'^your-url/$', MyViewSet.as_view({'get': 'custom_get'}), name='url-name'),
]
As per REST architectural principles request method GET is only intended to retrieve the information. So, we should not perform a create operation with request method GET. To perform the create operation use request method POST.
Temporary Fix to your question
from rest_framework import generics, status
class CreateAPIView(generics.CreateView):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.query_params)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data,
status=status.HTTP_201_CREATED,
headers=headers)
def get(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Please refer below references for more information.
https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
https://learnbatta.com/blog/introduction-to-restful-apis-72/

Pass data from django middleware.process_view to the template context

I have a custom middleware and during its process_view I get some token. And I need to pass this token to the rendered result html.
I thought that context_processor is a good place to modify context, but looks like it's hard to pass some data from middleware into processor.
But it seems that the only way to communicate for process_view and context processor is request object. And if set any field to the request I get 'WSGIRequest' object does not support item assignment' error. Here is a pieces of code:
def process_view(self, request, view_func, view_args, view_kwargs):
...
with log(request, view_func.__name__, info) as id:
request['TOKEN_ID'] = logger.get().get_id() #here is an error
response = view_func(request, *view_args, **view_kwargs)
So, looks like I'm doing something wrong. Is there a way to communicate middleware.process_view and context_processor? Or I should change another way to pass data into html from middleware?
That error is raised when you try and use dictionary item assignment:
request['my_key'] = 'my_value'
But the request is not a dictionary, it is an object. As with all objects - like the Django models which you must be familiar with - you need to set attributes, not items.
request.my_attribute = 'my_value'
(Next time, please show the code you used and the full traceback you got.)

Django return redirect() with parameters

In my view function I want to call another view and pass data to it :
return redirect('some-view-name', backend, form.cleaned_data)
, where backend is of registration.backends object, and form.cleaned_data is a dict of form data (but both must be either sent as *args or **kwargs to prevent raising Don't mix *args and **kwargs in call to reverse()! error). From what I've found in the docs :
def my_view(request):
...
return redirect('some-view-name', foo='bar')
It looks like I need to provide 'some-view-name' argument, but is it just the name of the view function, or the name of the url ? So I would like to make it similar to the way it's done in django-registration, where :
to, args, kwargs = backend.post_registration_redirect(request, new_user)
return redirect(to, *args, **kwargs)
def post_registration_redirect(self, request, user):
return ('registration_complete', (), {})
Ok so now, can I call directly my view function or do I need to provide a url for it ? And what more important, how my funciotn call (and a url if needed) should look like ? Both backend, and cleaned_data are just passed through this view for a later usage. I've tried this, but it's improper :
url(r'^link/$', some-view-name)
def some-view-name(request, *args):
As well as this :
return redirect('some_url', backend=backend, dataform.cleaned_data)
url(r'^link/$', some-view-name)
def some-view-name(request, backend, data):
still NoReverseMatch . But in django-registration, I've seen something like this :
url(r'^register/$',register,{'backend': 'registration.backends.default.DefaultBackend'}, name='registration_register'),
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='user/login_logout_register/registration_form.html',
extra_context=None):
urls.py:
#...
url(r'element/update/(?P<pk>\d+)/$', 'element.views.element_update', name='element_update'),
views.py:
from django.shortcuts import redirect
from .models import Element
def element_info(request):
# ...
element = Element.object.get(pk=1)
return redirect('element_update', pk=element.id)
def element_update(request, pk)
# ...
Firstly, your URL definition does not accept any parameters at all. If you want parameters to be passed from the URL into the view, you need to define them in the urlconf.
Secondly, it's not at all clear what you are expecting to happen to the cleaned_data dictionary. Don't forget you can't redirect to a POST - this is a limitation of HTTP, not Django - so your cleaned_data either needs to be a URL parameter (horrible) or, slightly better, a series of GET parameters - so the URL would be in the form:
/link/mybackend/?field1=value1&field2=value2&field3=value3
and so on. In this case, field1, field2 and field3 are not included in the URLconf definition - they are available in the view via request.GET.
So your urlconf would be:
url(r'^link/(?P<backend>\w+?)/$', my_function)
and the view would look like:
def my_function(request, backend):
data = request.GET
and the reverse would be (after importing urllib):
return "%s?%s" % (redirect('my_function', args=(backend,)),
urllib.urlencode(form.cleaned_data))
Edited after comment
The whole point of using redirect and reverse, as you have been doing, is that you go to the URL - it returns an Http code that causes the browser to redirect to the new URL, and call that.
If you simply want to call the view from within your code, just do it directly - no need to use reverse at all.
That said, if all you want to do is store the data, then just put it in the session:
request.session['temp_data'] = form.cleaned_data
I do like this in django3
redirect_url = reverse('my_function', args=(backend,))
parameters = urlencode(form.cleaned_data)
return redirect(f'{redirect_url}?{parameters}')
I am new to Django. One of my project, I used render instead of redirect to send data. That worked good. My code was like this --->
for key, value in request.POST.lists():
print(key, value)
if key.split('-')[-1] != 'csrfmiddlewaretoken':
qu_id = key.split('-')[-1]
get_answer = Answer.objects.filter(question_id=qu_id,
correct_answer__option__contains=value[0])
total_correct_answer = get_answer.count()
context = {'score': total_correct_answer}
return render(request, 'result.html', context)
context = {'questions': questions, 'total_correct_answer': total_correct_answer}
return render(request, 'test.html', context)

Categories

Resources