Need to clear a basic concept. In Django, what's the harm in redirecting to a view from form_valid() itself, instead of declaring a get_success_url()?
I.e. why is the following inferior, compared to what's below it:
class PostCreateView(CreateView):
model = Post
def form_valid(self, form):
# do something
return redirect("home")
class PostCreateView(CreateView):
model = Post
def form_valid(self, form):
# do something
return super(CreateView, self).form_valid(form)
def get_success_url(self):
return reverse("home")
Returning redirect isn't necessarily inferior. If you don't call super, then you need to save the form yourself. Duplicating two lines (save and return redirect) isn't really a problem. If super was more complicated then the duplication would be more of an issue, as there's a bigger chance of functionality being left out or errors being introduced.
On the plus side, returning the redirect response makes it obvious what form_valid will do, without having to look at what super does. Having control over how the form is saved can be useful too.
If your view might be subclassed, then you probably shouldn't return redirect, because you'll break any subclasses that override get_success_url.
Related
I am a beginner in Django, and I was reading WS Vincent's Django for beginners. In the second to last chapter, he writes the following code. He's creating a view that allows for comments that can handle GET and POST requests without mixing FormMixin and his created ArticleDetailView. I understand all of that, but what I don't understand is why it was constructed like this? Can someone explain what self.object and self.get_object are in this example? Also, why do we save twice in the second method? Thanks!:
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
def form_valid(self, form):
comment = form.save(commit=False)
comment.article = self.object
comment.save()
return super().form_valid(form)
def get_success_url(self):
article = self.get_object()
return reverse("article_detail", kwargs={"pk": article.pk})
I don't have the book, but get_object will return the object that this view displays. In your example the view is probably set to display a single Article, but the form posts details for a Comment. You use the get_object method to get access to the Article instance so that you can associate the Comment with it.
Your second question: It doesn't save twice. It may be confusing, but when you set commit to False it instantiates the object, but does not save it. You do this when you want to add extra information after instantiating the model instance. It is actually saved to the database after the comment.save() call.
Same as the title. What is the difference between the request and self in Django?
i'd try class based view and function view. and copy well-made code. but I don't know whay is the difference between self in fomr_valid and request in moneylog_delete definition.
class moneylog_update(UpdateView):
model = moneylog_models.Moneylog
form_class = forms.UpdateMoneylogForm
template_name = "moneylogs/update.html"
def form_valid(self, form):
moneylog = form.save(commit=False)
moneybook = moneybook_models.Moneybook.objects.get(
pk=self.kwargs["pk"])
moneylog.save()
form.save_m2m()
return redirect(reverse("moneybooks:detail", kwargs={'pk': moneybook.pk}))
def moneylog_delete(request, moneybook_pk, moneylog_pk):
user = request.user
try:
moneybook = moneybook_models.Moneybook.objects.get(pk=moneybook_pk)
models.Moneylog.objects.filter(pk=moneylog_pk).delete()
return redirect(reverse("moneybook:detail", kwargs={"pk": moneybook.pk}))
except models.Moneylog.DoesNotExist:
return redirect(reverse("cores:home"))
The request is an object that Django creates each time the real request was made to the view. It contains metadata about the real request and some attributes set by middleware (most commonly used is user instance).
self it's a Python way of naming the reference to the instance within the current scope. From doc's:
Often, the first argument of a method is called self. This is nothing more than a convention: the name self has absolutely no special meaning to Python. Note, however, that by not following the convention your code may be less readable to other Python programmers, and it is also conceivable that a class browser program might be written that relies upon such a convention.
Basically, these are two different things.
I'd like to write an except clause that redirects the user if there isn't something in a queryset. Any suggestions welcome. I'm a Python noob, which I get is the issue here.
Here is my current code:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
pass
return var
I want to do something like this:
def get_queryset(self):
try:
var = Model.objects.filter(user=self.request.user, done=False)
except:
redirect('add_view')
return var
A try except block in the get_queryset method isn't really appropriate. Firstly, Model.objects.filter() won't raise an exception if the queryset is empty - it just returns an empty queryset. Secondly, the get_queryset method is meant to return a queryset, not an HttpResponse, so if you try to redirect inside that method, you'll run into problems.
I think you might find it easier to write a function based view. A first attempt might look like this:
from django.shortcuts import render
def my_view(request):
"""
Display all the objects belonging to the user
that are not done, or redirect if there are not any,
"""
objects = Model.objects.filter(user=self.request.user, done=False)
if not objects:
return HttpResponseRedirect("/empty-queryset-url/")
return render(request, 'myapp/template.html', {"objects": objects})
The advantage is that the flow of your function is pretty straight forward. This doesn't have as many features as the ListView generic class based view (it's missing pagination for example), but it is pretty clear to anyone reading your code what the view is doing.
If you really want to use the class based view, you have to dig into the CBV documentation for multiple object mixins and the source code, and find a suitable method to override.
In this case, you'll find that the ListView behaviour is quite different to what you want, because it never redirects. It displays an empty page by default, or a 404 page if you set allow_empty = False. I think you would have to override the get method to look something like this (untested).
class MyView(ListView):
def get_queryset(self):
return Model.objects.filter(user=self.request.user, done=False)
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
if len(self.object_list == 0):
return HttpResponseRedirect("/empty-queryset-url/")
context = self.get_context_data(object_list=self.object_list)
return self.render_to_response(context)
This is purely supplemental to #Alasdair's answer. It should really be a comment, but couldn't be formatted properly that way. Instead of actually redefining get on the ListView, you could override simply with:
class MyView(ListView):
allow_empty = False # Causes 404 to be raised if queryset is empty
def get(self, request, *args, **kwargs):
try:
return super(MyView, self).get(request, *args, **kwargs)
except Http404:
return HttpResponseRedirect("/empty-queryset-url/")
That way, you're not responsible for the entire implementation of get. If Django changes it in the future, you're still good to go.
again, apologies for what is probably a straightforward question!
Ok, so!
my problem is i have a saveModel function, where it saves a model. If the model is all good (is_valid), it will save the model and redirect to pageA
if the model is bad, or the request is a GET, then i'd like to redirect to pageB
all well and good, but i do this several times, how annoying! I don't want to cut and paste all the time, so i came up with this:
class SaveModel(View):
def as_view(self):
if request.method == "POST":
form = SaveModel.getPostForm(self.request)
if form.is_valid():
processedForm = SaveModel.processForm(self.request)
processedForm.save()
if (self.success_template):
return render_to_response(self.success_template)
else:
return render_to_response('pageA.html')
else:
form = SaveModel.getForm()
if (self.context_object_name):
contextName = context_object_name
else:
contextName = 'form'
if (self.template_name):
return render_to_response(template_name,{contextName:form})
else :
return render_to_response('pageB.html',{contextName:form})
def getForm(self):
return None
def getPostForm(self,request):
return None
def processForm(self,form,request):
return None
THEN, i define other classes to handle particular models, like, for example, so:
class StoryModelView(SaveModel):
def getForm(self,request):
return StoryForm()
def getPostForm(self,request):
return StoryForm(request.POST)
def processForm(self,form,request):
theStory = form.save(commit=False)
theStory.user = request.user
return theStory
and then, finally, in my urls.py i will refer to (as above) the model to use like so:
url(r'^addStory/$',
StoryModelView.as_view(
context_object_name='form',
template_name='accounts/addStory.html',
success_template='accounts/addStorySuccess.html'
)
),
This doesn't seem to work though - pycharm assures me that my references to self.context_object_name and so on are invalid. I'm v. new to python and django (which is why i thought i'd build a website with them! clever andrew!), so i am sure that i've missed a whole bunch of things (abstract methods and stuff... python does that, right?)
what do i need to do to get this all working? Is this how i should be doing things?
ANSWER BY ME!
Ok, so the comments everyone has written about the CreateView are probably correct. "Probably" because i never ended up using it, because i ended up sticking with my code instead.
In case anybody is, like me, new to python and django and wants to see how the whole thing works, here we are!
class SaveModel(View):
success_template = None
context_object_name = None
template_name = None
def post(self, request):
form = self.getPostForm(self.request)
if form.is_valid():
processedForm = self.processForm(form,self.request)
processedForm.save()
if self.success_template:
return render_to_response(self.success_template)
else:
return render_to_response('accounts/addStorySuccess.html')
else:
self.renderValidations(form)
def get(self,request):
form = self.getForm()
self.renderValidations(form)
def renderValidations(self,form):
if self.context_object_name:
contextName = self.context_object_name
else:
contextName = 'form'
if self.template_name:
return render_to_response(self.template_name,{contextName:form})
else :
return render_to_response('accounts/addStory.html',{contextName:form})
def getForm(self):
return None
def getPostForm(self,request):
return None
def processForm(self,form,request):
return None
and that is the main class, then i can override it like so:
class StoryModelView(SaveModel):
def getForm(self):
return StoryForm()
def getPostForm(self,request):
return StoryForm(request.POST)
def processForm(self,form,request):
theStory = form.save(commit=False)
theStory.user = request.user
return theStory
i tripped myself up with how "self" works in python a few times. it seems to be magically sent across with all method calls, but you need it as the first arg in the method declaration (but you never need to use it when calling/using the method)
i think there's only post or get for methods when overriding the View class. i don't have a good idea of the "process" of the call, or what the order is, dispatch was mentioned as something to override, but i suspect that is only where i need to change when/how to deal with differing request types (GET, POST, HEAD etc)
oh! the urls.py!
url(r'^addStory/$',
StoryModelView.as_view(
context_object_name='form',
template_name = 'accounts/addStory.html',
success_template= 'accounts/addStorySuccess.html'
)
),
i can just chuck whatever i want into that "as_view" call, and then, as long as those parameters are defined in the overriding class it's all good.
so yay! my classes all work and women want me. use my code, and this can happen to you too!*
*results atypical and fictional. your results may differ.
The standard pattern for the view logic for Django forms is thus:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
This is fine in simple cases but quite easily descends into a complex mass of nested IF statements if your application logic gets a bit more complex.
Can anyone share their own cleaner approaches that avoid nested IF's and logic that depends on fall-through cases?
I'd be particularly interested in answers that don't rely on additional 3rd party apps.
One of the included class based views is FormView (documentation). The two main methods you'd be concerned with are form_valid and form_invalid.
from django.views.generic import FormView
from myapp.forms import MyForm
class MyView(FormView):
template_name = 'edit_something.html'
form_class = MyForm
success_url = '/success/' # you should use `reverse`, but let's stay focused.
def form_valid(self, form):
"""
This is what's called when the form is valid.
"""
return super(MyView, self).form_valid(form)
def form_invalid(self, form):
"""
This is what's called when the form is invalid.
"""
return self.render_to_response(self.get_context_data(form=form))
Alternatively you can override post, get or put methods and handle the form according to each type of request.
This is shortest approach I found:
def contact(request):
# if it's POST request it'll have data else it'll be unbound
form = ContactForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
return render_to_response('contact.html', { 'form': form, })
of course if you don't like nested if you could try reversing logic:
def contact(request):
template = 'contact.html'
if request.method != 'POST': # GET return:
return render_to_response(template, {'form': ContactForm()})
form = ContactForm(request.POST)
if not form.is_valid(): # FAIL return:
return render_to_response(template, {'form': form})
# here logic if user posted form and it's clean:
return HttpResponseRedirect('/thanks/')
For complex scenarios, you should really consider overriding various methods of your form class which are involved in the validation/sanitizing process. The BaseForm class defines a _post_clean() method with no behavior which is particularly meant to be overridden. Overriding _clean() properly is also pretty straight forward, you could do that too if you wanted to do something before clean() is called.
Reading the source of django.forms will give you a much clearer insight on how the forms work and discover the right pattern for your needs.
Here is what i tried so far to make view funcs cleaner:
Separate view functions for GET / POST
Obviously is_valid logic in form as state in prev post.
Overriding model save() method to make if form.is_valid() part cleaner ( you can hide other related objects creates inside one save() )
Move common parts of view-funcs code into separeate function.
Also if your GET part is not CPU or database intense you can do it above if req == POST..
in other words do it always..
I for example often do: ctx['form'] = ContactForm() at the top.. and then again
ContactForm(req.POST) inside if req == POST
BUT
django 1.3 Now have class based view.. And this give one a big opportunity
to make view extendable.. to make mixins.. not to write the save over and over.
I personally want to give it a shot and will try write django stuff in a new style with classes.
The main source of complexity in these cases is handling GETs and POSTs in the same view function. Separate the logic for each method into its own view and things become clearer.
The only duplication will be rendering the form html, but that particular part is concise and meaningful for both cases (i.e. not boilerplate), so that's acceptable.