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.
Related
I am trying to handle Django form in a more gentle way and actually, I have no idea how to push further this topic. I have a View which is responsible for displaying a Form, in If form.is_valid() condition I am looking for free parking spots, if I found nothing then I would like to show an Error to the user or pass to context additional data. Fist part I've covered easily but I have no idea how to show an error or something similar.
class BookParkingSpot(FormMixin, generic.ListView):
template_name = 'parking/book_spot.html'
form_class = BookParkingSpotForm
model = ParkingBooking
def post(self, request, *args, **kwargs):
form = BookParkingSpotForm(self.request.POST)
if form.is_valid():
parking_spot_list = ParkingSpot.objects.all().exclude(
parkingbooking__booking_time_start__lte=form.instance.booking_time_end,
parkingbooking__booking_time_end__gte=form.instance.booking_time_start
)
if parking_spot_list.exists():
random_spot = random.choice(parking_spot_list)
reservation = ParkingBooking.objects.create(
car=form.instance.car,
booking_owner=self.request.user,
spot=random_spot,
booking_time_end=form.instance.booking_time_start,
booking_time_start=form.instance.booking_time_end,
)
return redirect(reservation.get_absolute_url())
else: # this part doesn't work
context = self.get_context_data(**kwargs)
return render(self.request, self.template_name, context)
Any idea how to improve it?
Best write your validations in the form methods. Read about in: https://docs.djangoproject.com/en/4.0/ref/forms/validation/#validating-fields-with-clean
from django.core.exceptions import ValidationError
class BookParkingSpotForm(forms.Form):
# ...
def clean(self):
cleaned_data = super().clean()
p_list = ParkingSpot.objects.all().exclude(
parkingbooking__booking_time_start__lte=cleaned_data.booking_time_end,
parkingbooking__booking_time_end__gte=cleaned_data.booking_time_start
)
if p_list.count() == 0:
raise ValidationError('Your error message')
And to access to the form data use form.cleaned_data['field_name'] and not form.instance.field_name.
Sorry everyone for the inconvenient but again after posting on StackOverflow I figured out how to resolve this issue and it was very simple, I am still learning Django ;)
So the answer to my question lies in the last part of my code, if I didn't find any free parking spots then I can render the same page with additional data like on below example and pass form object to context:
return render(self.request, 'parking/book_spot.html',
{'errors': "There are no free parking spots in selected time",
'form': form})
I've been working with Django for about 3 months now and feel I'm getting a bit better, working my way up to class based views. On the surface they seem cleaner and easier to understand and in some cases they are. In others, not so much. I am trying to use a simple drop down view via ModelChoiceField and a form. I can get it to work with a function based view as shown below in my views.py file:
def book_by_name(request):
form = BookByName(request.POST or None)
if request.method == 'POST':
if form.is_valid():
book_byname = form.cleaned_data['dropdown']
return HttpResponseRedirect(book_byname.get_absolute_url1())
return render(request,'library/book_list.html',{'form':form})
Here is my form in forms.py:
class BookByName(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Book.objects.none())
def __init__(self, *args, **kwargs):
super(BookByName, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Book.objects.order_by('publisher')
This code works. When I have tried to convert to a Class Based View, that's when the trouble begins. I tried to do something like this in views.py:
class BookByNameView(FormView, View):
form_class = BookByName
initial = { 'Book' : Book }
template_name = 'library/book_list.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def get_success_url(self, *args):
return reverse_lazy('library:book_detail', args = (self.object.id,))
When using this with the same form, I receive an attribute error,
'BookByNameView' object has no attribute 'object'.
I've tried ListView as well and received several other errors along the way. The get_success_url also needs to take in a primary key and I can't figure out how to get that passed in as well. Again, I'm a 3 month Django newbie so please be gentle and thanks in advance for your thoughts and suggestions! I feel like I'm in the ballpark...just can't find my seat! I'm very open to doing this differently, if there's a cleaner/better way to do this!
Based on the latest feedback, it would appear the Class Based View should look like:
class BookNameView(FormView):
form_class = BookName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
Is this correct? I ran a test version of this and in response to your question as to why I am using self.object.id at all, I am trying to get the pk from the modelchoicefield that I am using to return the view I am trying to get. This may be where I am getting a bit lost. I am trying to get the detail view from the modelchoicefield dropdown, and return the book that is selected. However, I can't seem to pass the pk to this view successfully.
I updated my code to...
class BookByNameView(FormView, ListView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def get_success_url(self, *args):
return reverse_lazy('library:book_detail')
But now it says error...Reverse for 'book_detail' with no arguments not found.
Why are you using self.object there at all? You used form.cleaned_data in the original view, that's what you should use in the class based version too. Note that the form is passed to form_valid.
Note that you've done lots of other weird things too. Your getmethod is pointless, as is your definition of the initial dict; you should delete them both. Also, FormView already inherits from View, there's no need to have View in your declaration explicitly.
You can override the form_valid() function in FormView to achieve what you want. If the form is valid then it is passed to the form_valid() function.
Try this:
class BookByNameView(FormView):
model = Book
form_class = BookByName
template_name = 'library/book_list.html'
def form_valid(self, form):
bookbyname = form.cleaned_data['dropdown']
return HttpResponseRedirect(bookbyname.get_absolute_url())
Let's say I have the following models:
class Post(model):
...
class BlogPost(Post):
...
class OtherPost(Post):
...
Assume my url schema to edit a post is something like,
/site/post/\d+/edit
In other words, I don't have separate url paths for editing OtherPosts vs. BlogPost.
When using UpdateView, I need to set the model -- but of course, the actual model is a subclass of Post.
class Update(generics.UpdateView):
model = Post
What is the Djangoey/DRY way to handle this?
At the moment, looking over the UpdateView code, it looks like I could leave Update.model undefined, and override get_queryset, which would need to return a query with the right submodel. I would also need to override get_form to return the right form.
I'll post my solution when I get it working, but am looking for possibly better (DRYer) integrations.
It looks like the following method is working, which seems fairly minimal.
class Update(generic.edit.UpdateView):
model = Post
def get_form_class(self):
try:
if self.object.blogpost:
return BlogPostForm
except Post.DoesNotExist:
pass
try:
if self.object.otherpost:
return OtherPostForm
except Post.DoesNotExist:
pass
def get_object(self, queryset=None):
object = super(Update, self).get_object(queryset)
try:
return object.blogpost
except Post.DoesNotExist:
pass
try:
return object.otherpost
except Post.DoesNotExist:
pass
Or, if using a polymorphic mixin like InheritanceManager, then something like this:
class Update(generic.edit.UpdateView):
model = Post
form_class = {
BlogPost: BlogPostForm,
OtherPost: OtherPostForm,
}
def get_form_class(self):
return self.form_class[self.object.__class__]
def get_queryset(self):
return self.model.objects.select_subclasses()
I have the following views which are working fine:
class FriendView(View):
message_success = None
message_error = None
def get_pending_received(self):
return models.FriendRequest.objects.filter(recipient_user_id=self.request.user.id)
def get_existing_friends(self):
return models.Friendship.objects.filter(source_user_id=self.request.user.id)
def get_pending_sent(self):
return models.FriendRequest.objects.filter(sender_user_id=self.request.user.id)
def get(self, request):
return render(request, 'template/friends.html',
{'pending_received_requests': self.get_pending_received(),
'existing_friends': self.get_existing_friends(),
'pending_sent_requests': self.get_pending_sent(),
'message_success': self.message_success,
'message_error': self.message_error})
class DeleteFriendView(FriendView):
def get(self, request, friendship_id):
try:
friendship = models.Friendship.objects.get(id=friendship_id)
except models.Friendship.DoesNotExist:
raise Http404
if not ((friendship.source_user_id == request.user.id) or (friendship.dest_user_id == request.user.id)):
return HttpResponseForbidden("Forbidden")
friendship.delete()
message_success = "Friend has been deleted"
return render(request, 'template/friends.html',
{'pending_received_requests': self.get_pending_received(),
'existing_friends': self.get_existing_friends(),
'pending_sent_requests': self.get_pending_sent(),
'message_success': 'Friend has been Deleted'})
My question is, is there a way to put the logic in the DeleteFriendView that is execute before get() without overriding get()? This would be cleaner and would reduce duplicate code but after reading the ClassView documentation I can't seem to figure out the best way to do this, since I can't access self outside of a method in the class view?
I would imagine something like this:
class DeleteFriendView(FriendView):
def before_get(self):
try:
friendship = models.Friendship.objects.get(id=friendship_id)
except models.Friendship.DoesNotExist:
raise Http404
if not ((friendship.source_user_id == request.user.id) or (friendship.dest_user_id == request.user.id)):
return HttpResponseForbidden("Forbidden")
friendship.delete()
self.message_success = "Friend has been deleted"
This way the get() method could be reused.
Thanks,
Mark
Why don't you want to override get()? You can put your logic there, then simply call the superclass method.
class DeleteFriendView(FriendView):
def get(self, request):
# delete-specific logic here
# now call FriendView get()
return super(DeleteFriendView, self).get(request)
Note by the way that it's very poor practice to put data-modifying actions in a GET request. They're far too easy to trigger by accident, or via malicious links. You should always put things like deletion in a POST request.
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.