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.)
Related
I'm using a GET parameter in my Django app to pass in information, that I want to be preserved in future links using Django's {% url %} template tag.
In my case, this is to allow view-only access within the app.
Example URL:
https://my.app/entry/123?key=abcdef
An example link on that page is already created like this:
View more details
Desired result:
I would like Django's URL template tag to automatically preserve any GET parameters named key throughout the app. In this case, it would generate new URLs with that same parameter applied. For example:
https://my.app/entry/123/details?key=abcdef
Workarounds
This blog post and this gist solve the problem by creating a new template tag and using that instead of Django's url template tag.
Is that really the best solution? I'd end up having to replace every instance of {% url %} throughout my app with my own tag. It also wouldn't fix the use of reverse().
Django middlewares may work for what you want. You could check when you receive the key parameter and store in session, then if there is not key parameter in the url append it :
class DetectKeyParameter:
def __init__(self, get_response):
self.get_response = get_response
#staticmethod
def process_view(request, view_func, view_args, view_kwargs):
return view_func(request, *view_args, **view_kwargs)
def __call__(self, request):
if 'key' in request.GET:
#If alerady is on the url just store it on session
request.session['key'] = request.GET.get('key')
else:
if 'key' in request.session:
if not request.GET._mutable:
#You need to change this property to add parameters to GET
request.GET._mutable = True
# now you can edit it
request.GET['key'] = request.session['key']
request.GET._mutable = False
response = self.get_response(request)
return response
This way, you'll have the key parameter on every request, until you delete the session variable. Perhaps the code doesn't work 100%, but I hope you get the idea.
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.
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/
I am trying to write something elegant where I am not relying on Request object in my code. All the examples are using:
(r'^hello/(?P.*)$', 'foobar.views.hello')
but it doesn't seem like you can post to a URL like that very easily with a form. Is there a way to make that URL respond to ..../hello?name=smith
Absolutely. If your url is mapped to a function, in this case foobar.views.hello, then that function might look like this for a GET request:
def hello(request):
if request.method == "GET":
name_detail = request.GET.get("name", None)
if name_detail:
# got details
else:
# error handling if required.
Data in encoded forms, i.e. POST parameters, is available if you HTTP POST from request.POST.
You can also construct these yourself if you want, say, query parameters on a POST request. Just do this:
PARAMS = dict()
raw_qs = request.META.get('QUERY_STRING', '') # this will be the raw query string
if raw_qs:
for line in raw_qs.split("&"):
key,arg = line.split("=")
PARAMS[key] = arg
And likewise for form-encoded parameters in non POST requests, do this:
FORM_PARAMS = QueryDict(request.raw_post_data)
However, if you're trying to use forms with Django, you should definitely look at django.forms. The whole forms library will just generally make your life easier; I've never written a html form by hand using Django because this part of Django takes all the work out of it. As a quick summary, you do this:
forms.py:
class FooForm(forms.Form):
name = fields.CharField(max_length=200)
# other properties
or even this:
class FooForm(forms.ModelForm):
class Meta:
model = model_name
Then in your request, you can pass a form out to the template:
def pagewithforminit(request):
myform = FooForm()
return render_to_response('sometemplate.html', {'nameintemplate': myform},
context_instance=RequestContext(request))
And in the view that receives it:
def pagepostingto(request):
myform = FooForm(request.POST)
if myform.is_valid(): # check the fields for you:
# do something with results. if a model form, this:
myform.save()
# creates a model for you.
See also model forms. In short, I strongly recommend django.forms.
You can't catch GET parameters in a URL pattern. As you can see in django.core.handlers.base.BaseHandler.get_response, only the part of the URL that ends up in request.path_info is used to resolve an URL:
callback, callback_args, callback_kwargs = resolver.resolve(
request.path_info)
request.path_info does not contain the GET parameters. For handling those, see Ninefingers answer.
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...