I am using django class based view
class MyView(TemplateView):
def return_JSON(self, object_id):
parent = models.UserForm.objects.get(pk=object_id)
url(r'^return/(?P<object_id>\d+)/json/', views.MyView().return_JSON, name="return_json")
I get this error
return_JSON() got multiple values for keyword argument 'object_id'
You're doing something very odd here.
You're using CBVs, but passing a function as the view function. Remember, the normal signature for CBVs is to pass in MyCBV.as_view(). No CBV machinery runs without running it through as_view() or dispatch().
But if you insist, you just need to add a new argument to your function...
def return_JSON(self, request, object_id):
# ^^^^^^^ this
return http.HttpResponse("Foo!")
Related
I have created a decorator in my Django project to inject parameter values to the decorated method's parameters.
I do this by using inspect.getargspec to check which parameters are present in the method and place them in kwargs. Otherwise I get an error due to the incorrect number of parameters in the method.
While this works properly in individual view methods, it fails when it comes to Django's class based views.
I believe this might be because the decorators are applied using #method_decorator at the class level to the dispatch method instead of the individual get and post methods.
I'm a python newbie and might be overlooking something obvious here.
Is there a better way to do what I'm doing? Is it possible to get the method parameter names in a class based view?
I'm using Python 2.7 and Django 1.11
The Decorator
def need_jwt_verification(decorated_function):
#wraps(decorated_function)
def decorator(*args, **kwargs):
request = args[0]
if not isinstance(request, HttpRequest):
raise RuntimeError(
"This decorator can only work with django view methods accepting a HTTPRequest as the first parameter")
if AUTHORIZATION_HEADER_NAME not in request.META:
return HttpResponse("Missing authentication header", status=401)
jwt_token = request.META[AUTHORIZATION_HEADER_NAME].replace(BEARER_METHOD_TEXT, "")
try:
decoded_payload = jwt_service.verify_token(jwt_token)
parameter_names = inspect.getargspec(decorated_function).args
if "phone_number" in parameter_names or "phone_number" in parameter_names:
kwargs["phone_number"] = decoded_payload["phone"]
if "user_id" in parameter_names:
kwargs["user_id"] = decoded_payload["user_id"]
if "email" in parameter_names:
kwargs["email"] = decoded_payload["email"]
return decorated_function(*args, **kwargs)
except JWTError as e:
return HttpResponse("Incorrect or expired authentication header", status=401)
return decorator
A class based view
#method_decorator([csrf_exempt, need_jwt_verification], name="dispatch")
class EMController(View):
def get(self, request, phone_number, event_id):
data = get_data()
return JsonResponse(data, safe=False)
def post(self, request, phone_number, event_id):
return JsonResponse("Operation successful", safe=False)
EDIT:
The obvious solution of applying the decorator at the method level, doesn't work with Django's class based views. You need apply the decorator at the url configuration or apply the decorator to the dispatch method.
EDIT:
I've posted code that was related to a workaround I was exploring, passing the parameter names as an argument into the decorator.
I found this post: Function decorators with parameters on a class based view in Django
which may provide the answer to your problem:
If you want to pass a decorator with parameters, you only need to:
Evaluate the parameters in the decorator-creator function.
Pass the evaluated value to #method_decorator.
The above mentioned and the code provided in the linked answer taken under consideration, you should:
injectables=[inject_1, inject_2, ..., inject_n]
decorators = [csrf_exempt, need_jwt_verification(injectables)]
#method_decorator(decorators, name="dispatch")
class EMController(View):
...
Leaving my previous mistaken answer here for legacy reasons, don't try this at home (or anywhere, in django, for that matter!!)
If we observe the "decorating a class" docs, we can see the following:
Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument name:
so you have to change the name argument of your #method_decorator to match the method that will apply to:
decorators = [csrf_exempt, need_jwt_verification(injectables=[])]
#method_decorator(decorators, name='get')
#method_decorator(decorators, name='post')
class EMController(View):
Personally I prefer to place my decorators on top of the specific method they will apply to:
class EMController(View):
#method_decorator(decorators)
def get(self, request, phone_number, event_id):
...
#method_decorator(decorators)
def post(self, request, phone_number, event_id):
...
What I want seemed impossible in the current state of the libraries. So here's what I finally went with.
parameter_names = inspect.getargspec(decorated_function).args
if "phone_number" in parameter_names or "phone_number" in injectables:
kwargs["phone_number"] = decoded_payload["phone"]
if "user_id" in parameter_names:
kwargs["user_id"] = decoded_payload["user_id"]
if "email" in parameter_names:
kwargs["email"] = decoded_payload["email"]
request.__setattr__("JWT", {})
request.JWT["phone_number"] = decoded_payload["phone"]
request.JWT["user_id"] = decoded_payload["user_id"]
request.JWT["email"] = decoded_payload["email"]
This decorator will automatically populate parameters in method based views as intended.
But it will also inject an JWT attribute to the request object for the class based views to use. Like request.GET and request.POST.
I have subclassed django-haystack's FacetedSearchView, and want to have an action take place when a specific GET parameter is present in the view's URL. I know exactly how to do this if I had a function-based view, but am at a complete loss when using a subclass of a subclass.
The request variable should be available somehow. I assume I need to call it up from some kind of super() call, maybe in an __init__() override? I've tried the following:
def __init__(self, *args, **kwargs):
super(MySearch, self).__init__(*args, **kwargs)
if self.request.GET.get("date_facet", ""):
do_something()
But it tells me that the returned request object is None, though by the URL it clearly shouldn't be.
Any thoughts?
Turns out, the request variable is accessible, it just has to be through self, which means you can only access it through a function common to the parent class. __init__ wasn't working, because request isn't defined until later in the parent class. To get things to work I ended up overwriting the get_results() class (originally just return self.form.search()), in a way similar to the following:
class Foo(ParentClass):
def get_results(self):
if 'date_facet' in self.request.GET:
year = int(self.request.GET['date_facet'])
return self.form.search().filter(some_filter_function)
return self.form.search()
I'm need opportunity to add http-header(X-Accel-Expires) for each add_view.
And for add_static_view.
Ideally would be pass parameter, something like add_view(..., x_accel_expires=100), add_static_view(..., x_accel_expires=100), but the pyramid can't this.
I can do Base View, where add http-header X-Accel-Expires.
I will only need to add an attribute in each view, something like: add_headers = (('X-Accel-Expires', '100'),).
But how can add this header for add_static_view?
For the case of add_view you can use the decorator argument as documented by view configuration parameters:
A dotted Python name to a function (or the function itself) which will be used to decorate the registered view callable. The decorator function will be called with the view callable as a single argument. The view callable it is passed will accept (context, request). The decorator must return a replacement view callable which also accepts (context, request). The decorator may also be an iterable of decorators, in which case they will be applied one after the other to the view, in reverse order.
This is the example given in the documentation:
#view_config(..., decorator=(decorator2, decorator1))
def myview(request):
pass
Is similar to doing:
#view_config(...)
#decorator2
#decorator1
def myview(request):
pass
This would allow you to write the following for example:
def accel_headers_factory(expires=100):
def add_accel_headers(view):
def wrapped_view(context, request):
resp = view(context, request)
resp.headers.append(('X-Accel-Expires', expires))
return wrapped_view
return add_accel_headers
Then use:
#view_config(..., decorator=(accel_headers_factory(500),))
def myview(request):
return {}
This would then always add the X-Accel-Expires header to the response as returned from the view.
Unfortunately it doesn't look like add_static_view allows you to pass it a decorator argument.
I am working on a class based generic view that takes a model name as an argument and processes that model name to get some more parameters. I had it working fine when I hardcoded the model name into an entry in the URLconf:
url(r'^generic/', ResultCreateView.as_view(model = 'SomeTask'))
Snippets of the class based view:
class ResultCreateView(CreateView):
model = None #this is here, expecting to be overwritten, because otherwise I get an error saying I can't pass in the 'model' kwarg above because 'model' is not already an attribute of the class
def __init__(self,*args, **kwargs):
self.model = get_model_object_from_modelname(kwargs['model'])
self.form_class = my_custom_function_to_generate_a_formclass(self.model)
self.template_name = self.model.template #template_name is an attribute I set on the model class
return super(ResultCreateView,self).__init__(*args, **kwargs)
When I tried to switch to passing the model parameter in via the url, i.e.:
url(r'^tasks/(?P<model>\w+)$', ResultCreateView.as_view())
my custom init method no longer works. I get:
ResultCreateView is missing a queryset. Define ResultCreateView.model, ResultCreateView.queryset, or override ResultCreateView.get_queryset()
I can't figure out where/when the 'model' argument gets passed in from the URL pattern to the view class. Ideally, I would like to be able to make this view work in either case (hardcoded parameter in URLconf or parameter from URL pattern) but I don't know where to put the code that does the processing so that it happens at the right time. Where is the right place to put that code, or is there another approach I should be using?
EDIT: (Additional complication: I need to decorate the view with a decorator that takes 'model' as an argument.)
Those parameters will be passed to the actual request handler methods, not to the __init__ method of the view. so, in case of a GET request:
class ResultCreateView(CreateView):
model = None
def get(self, request, model_name):
self.model = get_model_object_from_modelname(model_name)
self.form_class = my_custom_function_to_generate_a_formclass(self.model)
self.template_name = self.model.template #template_name is an attribute I set on the model class
return super(ResultCreateView,self).get(request, model_name)
I'm trying to adopt the Django documentation example on using class based views with mixins in order to be able to make a simple way of downloading the objects in a list view in CSV format, but I am failing miserably and do not really see what I am doing wrong.
I have a view defined as:
class MyObjectList(CSVResponseMixin,
MultipleObjectTemplateResponseMixin,
BaseListView
):
object_type = None
def get_context_data(self, **kwargs):
object_type = self.object_type
...some code...
return context
def render_to_response(self, context, **kwargs):
if self.request.GET.get('format', '') == 'csv':
return CSVReponseMixin.render_to_response(self, context, **kwargs)
else:
return MultipleObjectTemplateResponseMixin.render_to_response(self, context, **kwargs)
the mixin is:
class CSVResponseMixin(object):
def render_to_response(self, ctx, **kwargs):
return self.HttpResponse.render_to_response(self.convert_to_csv(ctx), **kwargs)
def conver_to_csv(ctx):
return do_csv_magic(ctx)
and in urls.py the view is called as:
url(r'^list/foos/$',
MyObjectList.as_view(object_type="someobject", model=models.MyModel),
name="myobjects",
)
However when I try to access the view without the ?format=csv query, I get a TypeError
Exception Value: __init__() got an unexpected keyword argument 'request'
Exception Location: /usr/lib/python2.6/site-packages/django/views/generic/base.py in render_to_response, line 97
EDIT: I added some details to the question and ended up implementing this with a different approach, but I still want to know what I was doing wrong.
In short, you're overdoing it. I'm not sure what is your intention here, but I've learned that the best approach is to find the closest generic view to what you're trying to do and simply extend it in views.py. Examples are many, but I invite you to check my code at https://bitbucket.org/BerislavLopac/resume/src/d7cfcf9c370b/resume_app/myproject/web/views.py.
According to the docs, render_to_response only takes the following arguments: template_name, dictionary, context_instance, mimetype
Therefore within FooResponseMixin when you're calling:
self.HttpResponse.render_to_response(self.mutilate_context(ctx), **kwargs)
You're passing in extra arguments within kwargs that render_to_response doesn't accept. Either remove the **kwargs or assign only what you need from it to variables to pass in to the accepted arguments.