I have an app with class based views. Each view inherits from the base view defined earlier.
Said view (and all of its descendants) implements get_context_data method which can throw exceptions under some conditions.
I would like to catch these exceptions and render specific template.
Basically views.py looks like this
class BaseView(TemplateView):
def get_context_data(**ctx):
context = super(BaseView, self).get_context_data(**ctx)
if condition:
raise CustomException('Condition was met')
# code that adds values into context dictionary
# like context['somevar'] = somevalue
...
return context
class SpecificView1(BaseView):
def get_context_data(**ctx):
context = super(SpecificView1, self).get_context_data(**ctx)
# code that updates values in context dictionary
...
return context
I've tried to insert a new class into views hierarchy (renaming BaseView into OriginalBaseView)
class BaseView(OriginalBaseView):
def get_context_data(**ctx):
context = {}
try:
context = super(BaseView, self).get_context_data(**ctx)
except RepoException:
self.template_name = 'specific_template.html'
return context
But using this approach would mean that I will have to update all descendant get_context_data methods with checks that keys they update are in context.
Is there a way to better handle this?
You can write your own middleware which will handle RepoException and render specific_template.html
https://docs.djangoproject.com/en/3.0/topics/http/middleware/#exception-middleware
This way you can throw RepoException anywhere in view and your template will be rendered automatically.
batiskaf answer seems to be right. You basically need to put a middleware in place which processes your view exceptions. This middleware can either send None in which case the exception gets handled, or a HTTP Response , which can have your template.html rendered.
def process_exception(exception, *args, **kwargs):
error_code = getattr(exception, 'code', 500) #you can use codes if you want
if not isinstance(exception, MyBaseException):
request = kwargs.get('request') #you can use request to load the template
return {
'status': 0,
'message': exception.message,
'error_code': error_code
} #or send an HttpResponse with your template.
Hope this helps :)
Related
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)
I am generating an object from a submitted form. I want to pass along the generated AutoField to the next form as a hidden form element. However, I cannot figure out how to pass a variable from post() to get_context_data(). I know that post() is called first, followed by get_context_data(), but adding the variable to self does not yield the results I expect (the attribute does not exist on self).
Here is an example of what I am experiencing:
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
print self.hello
return context
def post(self, request, *args, **kwargs):
self.hello = "hello"
return HttpResponseRedirect(request.path)
self.hello is not valid when called in get_context_data. I feel as though I may be mistaken with the HttpResponseRedirect(request.path) call, but I'm not sure how else to render the template.
Is it a simple mistake, or should I be approaching this an entirely different way?
By defining post yourself, you've overridden the default behaviour of the view. You can see that there is no call to get_context_data, or any of the other class methods, so naturally they won't be called.
Generally you should not be overriding the specific get or post methods. You haven't shown the full view so it's not clear what behaviour you are trying to achieve, but for example in a form view you would want to define success_url to set the place the form redirects to after submission.
You have to find a way to pass the object ID to the next page. The options that come to mind are to put it into the URL or as solarissmoke has suggested save it in the session. If you are doing it in the url you can also put the page sequence there (meaning 1 for the forst form, 2 for the second...).
The nice thing about this approach is, that you can cover all functonailty in one view: depending on the page set the respective fields in the get_object methods (self.fields=[....]) and the template names in the get_template_names method.
So using an Updateview, it would look like this:
urls.py:
....
url(r'^mysite/(?P<object_no>\d+)/(?P<form_no>\d+)$', BaseView.as_view()),
views.py:
class BaseView(UpdateView):
def get_object(self):
obj=MyModel.objects.get(id=self.kwargs['object_no'])
form_no = self.kwargs['form_no']
if form_no=="1":
self_fields=["field1","field2"...]
.....
def get_object(self):
obj=MyModel.objects.get(id=self.kwargs['object_no'])
form_no = self.kwargs['form_no']
if form_no=="1":
self_fields=["field1","field2"...]
.....
return obj
def get_template_names(self):
from_no = self.kwargs['form_no']
if form_no=="1":
return ["template1.html"]
....
You have to make sure that all your fields can be null.
I'm trying to write an app for Django. I want my users to be able to collect certain types of data, for instance samples, videos, etc... The app is called collector and for each type of item there is a class and a form that goes along with it.
Example Class:
class CreateTextView(CreateItemView):
form_class = TextForm
model = Text
def get_context_data(self, **kwargs):
context = super(CreateTextView, self).get_context_data(**kwargs)
context['item_type'] = 'text'
return context
Example Form:
class TextForm(forms.ModelForm):
class Meta:
model = Text
fields = COMMON_FIELDS + ('text',)
As you can see, the actual view is inheriting from CreateItemView. I want as much of the functionality to be defined for CreateItemView so that I don't have to do it individually for all item classes. That has been working for the most part, but it gets a bit tricky when I try to process forms with data.
def post(self, request, *args, **kwargs):
form = TextForm(request.POST) # line 2
form = getattr(TextForm, '__init__')(data=request.POST) # line 3
if form.is_valid():
# Add owner information.
item = form.save(commit=False)
item.owner = request.user
item.save()
return HttpResponseRedirect(reverse('collector:index'))
return render(request, self.template_name, {'form': form})
In Line 2 you can see how I would handle the form if there was only one type of form. Line 3 is what I'm trying to do. I want to be able to use the context['item_type'] to dynamically choose the right form and instantiate it with the given data.
Now the problem lies with the __init__-method which I have never defined anywhere. When I pass only POST.request to __init__, it complains about not having a self. When I pass the additional self, it complains about how CreateTextView has no _meta-attribute and so on. I just can't find the right combination of argumentes to satisfy the __init__-method. I can't look up it's definition either, because I didn't define it. I then followed the definition of the parent classes in the django framework which led me to a couple of complex functions that looked like factories. That didn't really help me...
Now I know how to use the TextForm()-initiation. Is there a way to fetch this method dynamically with getattr()? That would save me the hassle with __init__. If not, how do I provide __init__ with the correct self-instance?
As mentioned below, I have changed my classes a little bit. I no longer use context to store the item_type, instead I use a class variable to have easy acces to the item_type within a view. My post method is defined in the mother class CreateItemView and looks like this now:
def post(self, request, *args, **kwargs):
try:
form_cls = ITEM_TYPE_MAP[self.item_type]
except KeyError:
# invalid item_type. raise a meaningful error here
raise Exception('Invalid item type.')
form = form_cls(request.POST)
if form.is_valid():
# Add owner information.
item = form.save(commit=False)
item.owner = request.user
item.save()
return HttpResponseRedirect(reverse('collector:index'))
return render(request, self.template_name, {'form': form})
A clean and quite simple solution to look for is using a dictionary to map the item_type values to actual form classes:
ITEM_TYPE_MAP = {
"foo": TextForm,
"bar": SomeOtherForm,
}
You’d put that dictionary at some global place and use it from within the controller like this:
item_type = context['item_type']
try:
form_cls = ITEM_TYPE_MAP[item_type]
except KeyError:
# invalid item_type. raise a meaningful error here
raise
form = form_cls(request.POST)
You cannot directly call __init__ usually, because there’s more than that to instanciate an object. Python will also call __new__ on the class of the object, so the only way to be sure is to go through the actual constructor, which is calling the type.
This is what happens above, by first fetching the type into form_cls and then calling the type (i.e. the constructor).
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...
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)