Form handling in class based view - python

I have problem when form is not valid (in POST method).
didn't return an HttpResponse object. It returned None instead.
I could paste this line to last line of Post method
return render(request, self.template_name, context)
But context variable is initialized in Get method. How can I pass context to post method?
class EventPage(View):
template_name = 'event.html'
def get(self, request, event_id):
event = Event.objects.get(id = event_id)
participants = Participant.objects.filter(event_id = event.id)
register_to_event_form = RegisterToEvent()
context = {
'register_to_event_form': register_to_event_form,
'title': event.title,
'description': event.description,
}
return render(request, self.template_name, context)
def post(self, request, event_id):
event = Event.objects.get(id = event_id)
if request.method == "POST":
register_to_event_form = RegisterToEvent(request.POST)
if register_to_event_form.is_valid():
participant = register_to_event_form.save(commit=False)
participant.event = event
participant.save()
return HttpResponseRedirect('/event-%s' %event_id)

You should not be doing things this way at all. The whole point of the class-based views is that they provide a series of methods for you to override which are called by the default implementations of get and post; you should not really be overriding get and post yourself.
In your case you should be using a CreateView, not a plain view. And you should be returning the events and participants in a get_context_data method. Setting the event property of the saved object should happen in the form_valid method.

I think you need an else statement in case of non valid form which return an HttpResponseRedirect

you are not returning anything if your form is invalid, so you can do like:
def post(self, request, event_id):
event = Event.objects.get(id = event_id)
register_to_event_form = RegisterToEvent(request.POST)
if register_to_event_form.is_valid():
. . .
return HttpResponseRedirect('/event-%s' %event_id)
else:
context = {'register_to_event_form': register_to_event_form}
return render(request, self.template_name, context)
and you dont need if request.method == "POST": in your post method

Related

Combined Returns in Python Function Based Views Django

I'm currently building a website with Django and I've gotten to a point where I need to print data on the screen(ListView) and update(UpdateView) data on the same page(template). From what I've found I cant do this easily with the Django generic views so I rewrote my view as a function-based view. This current piece of code updates what I need perfectly with some changes to the HTML.
def DocPostNewView(request, pk):
context = {}
obj = get_object_or_404(DocPost, id=pk)
form = GeeksForm(request.POST or None, instance=obj)
if form.is_valid():
form.save()
return HttpResponseRedirect("/" + id)
context["form"] = form
posts = DocPost.objects.all()
return render(request, "my_app/use_template.html", context)
... And this following piece of code lists objects perfectly with some changes to the HTML.
def DocPostNewView(request, pk):
context = {}
obj = get_object_or_404(DocPost, id=pk)
form = GeeksForm(request.POST or None, instance=obj)
if form.is_valid():
form.save()
return HttpResponseRedirect("/" + id)
context["form"] = form
posts = DocPost.objects.all()
return render(request, 'my_app/use_template.html', context={'posts': posts})
I just need to combine these and the only real difference is the return command at the bottom of both(the same) functions. HOW CAN I COMBINE THE RETURNS SO I CAN LIST DATA AND UPDATE DATA ON THE SAME PAGE???
Thanks
does this work for you ?
def DocPostNewView(request, pk=None):
posts = DocPost.objects.all()
obj = get_object_or_404(DocPost, id=pk)
form = GeeksForm(request.POST or None, instance=obj)
if form.is_valid():
form.save()
return HttpResponseRedirect("/" + id)
context = {"form":form, "posts":posts}
return render(request, "my_app/use_template.html", context)
in your templates you could use "posts" to list every post,
and your can use "form" to update that specific post,
the thing is for every update you make you will always be seeing the whole list of posts, if it is that what you want to achieve

Django class based View with 2 post forms returns valid unknown

I've got a view in django where post method has 2 different types of form:
def post(self, request):
tweet_form = TweetForm(request.POST, request.FILES)
comment_form = TweetCommentForm(request.POST)
print(request.POST['form-button'])
if request.POST['form-button'] == 'add_tweet':
print(tweet_form.is_valid)
if tweet_form.is_valid():
content = tweet_form.cleaned_data['content']
image = tweet_form.cleaned_data['image']
tweet = Tweet.objects.create(
user=request.user,
content=content,
)
if image != None:
tweet.image = image
tweet.save()
return redirect("/home")
if request.POST['form-button'] == 'add_comment':
print(comment_form.is_valid)
if comment_form.is_valid():
content = comment_form.cleaned_data['body']
return redirect("/home")
ctx = {"tweet_form": tweet_form, "comment_form": comment_form}
return render(request, "home.html", ctx)
The both forms have different name, value, and id.
When i print (tweet_form.is_valid()) or (comment_form.is_valid()) i receive sth like:
<bound method BaseForm.is_valid of <TweetForm bound=True, valid=Unknown, fields=(content;image)>>
Respectively for both forms.
Can i somehow reorganize view to deal with both of forms or i have to have another View?
Regards
You are trying to print the method instead of evaluating the method.
Try:
print(comment_form.is_valid())
print(tweet_form.is_valid())

Django - how to reuse code in function-based view with decorators

I've got some code that I have to re-use for several views, so I would like to create a decorator so that I don't have to copy and paste many lines of code.
So the code I need to re-use in different views is:
#login_required
def add_custom_word_song(request, target_word, source_word, pk):
"""
Add new word
"""
if request.method == 'POST':
form = WordForm(request.POST)
if form.is_valid():
deck_name = form.cleaned_data['deck_name']
source_word = form.cleaned_data['source_word']
target_word = form.cleaned_data['target_word']
fluency = form.cleaned_data['fluency']
user = request.user
Word.objects.create(user=user, target_word=target_word,
source_word=source_word, deck_name=deck_name, fluency=fluency)
return HttpResponseRedirect(reverse('vocab:list'))
if request.method =="GET":
user = request.user
request.session['pk'] = pk
form = CustomWordForm(initial={'user': user, 'target_word': target_word, 'source_word': source_word, 'deck_name': 'My Words', 'fluency': 0})
return render(request, 'vocab/add_custom_initial_song.html', {'form': form, 'pk': pk})
And the only part of the code that will change for the other views is the template part in the last line. So I tried putting everything except the last line in the decorator, but then I get:
TypeError: add_custom_word() missing 3 required positional arguments: 'target_word', 'source_word', and 'pk'
I tried different variations of this, but still get the same error.
If I understood correct, you would like to write some code like:
def add_custom_word(request, target_word, second_word, pk):
...
#add_custom_word
def add_custom_word_song(request, target_word, second_word, pk):
return render(request, 'vocab/add_custom_initial_song.html', {'form': form, 'pk': pk})
In this case, if you called add_custom_word_song it means:
add_custom_word_song = add_custom_word(add_custom_word_song)
add_custom_word_song()
Python while first pass add_custom_word_song as the first argument to add_custom_word when module initialing, they call the new add_custom_word_song.
So you will get the error as you said: add_custom_word() missing 3 required positional arguments: 'target_word', 'source_word', and 'pk'
If you really want to use decorator, you need wrap it again:
def add_custom_word(func):
def wrapper(request, target_word, second_word, pk):
...
return func(request, target_word, second_word, pk)
return wrapper
#decorator
def add_custom_word_song(request, target_word, second_word, pk):
return render(request, 'vocab/add_custom_initial_song.html', {'form': form, 'pk': pk})
But if you only want to change the template file, consider use a registry to manage the template content!
Edit:
A simplest registry could like a dict:
registry = {
"song": "vocab/add_custom_initial_song.html",
"image": "vocab/add_custom_initial_image.html",
...
}
You could define some types as keys, then define the template files as values. So you can return it like:
def add_custom_word(...):
...
return render(request, registry[return_type], {'form': form, 'pk': pk})
If you have some complex conditions, you could have a custom registry class, and it would always have register and match methods.
class Registry(object):
def __init__(self):
self.registry_list = []
def register(self, conditions, template_file):
self.registry_list.append([conditions, template_file])
def match(self, condition):
for conditions, template_file in self.registry_list:
if match(condition, conditions):
return template_file
registry = Registry()
Then you could use this registry to get template files:
def add_custom_word(...):
...
return render(request, registry.match(condition), {'form': form, 'pk': pk})

Own method in CreateView

I want to use my own method, which will return JsonResponse, but this method isn't called in this View by default. So maybe i need to rewrite another method or?
views.py:
class CreatePostJS(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
response_data = {}
response_data['text'] = model.post_message
response_data['posted'] = model.posted_at
response_data['user'] = model.user
def create_post(request):
if request.method == 'POST':
return JsonResponse(serializers.serialize('json', response_data), safe = False)
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
https://docs.djangoproject.com/en/3.1/topics/class-based-views/intro/
Basically if you want to execute the method on POST requests, you can override the post method with your own. It will look more or less like this:
def post(self, request, *args, **kwargs):
my_json_response = self.create_post(request)
return super().post(request, *args, **kwargs)
That way there is no need to check for request.method in create_post, unless you plan to use it elsewhere.
When you work with generic views and want to override the default request handler, you need to overwrite the default method handler for the request method. In your case, it's the post() method from CreateView class.
I strongly recommend OOP way, so it would look like:
def get_request_data(self):
return {
'text': self.model.post_message,
'posted': self.model.posted_at,
'user': self.model.user
}
def create_post(request):
return JsonResponse(serializers.serialize('json', self.get_response_data()), safe = False)
def post(self, request, *args, **kwargs):
return self.create_post(request)
Note the if request.method == 'POST': is not needed, as post() method will only execute when POST request is being made in the current view.
You tried to initialize response_data outside a method, so as in my code you'd need to create another method for this - which I included in my example code.

django render and validate formset in class based-view (ListView)

I have the following class based view that I want to use to render a formset and validate it when it gets submitted through a post method:
The formset renders perfectly. When I submit the form I can read the formset and check it for errors. in the post method of this class -> errors = backorder_formset.errors
If I find any errors in the formset, I would like to render the view, but this time with the formset instance, that I read from POST.
When I call ctx = self.get_context_data() form within the post method of the class the following error gets raised from the call super(MissingProductsListView, self).get_context_data(*args, **kwargs):
'MissingProductsListView' object has no attribute 'object_list'
It seems like the superclass of Listview performs this call:queryset = kwargs.pop('object_list', self.object_list)
My question is why am I running in this error? and how could I render this formset with its errors messages to display it in the template after it was posted? I am using Django 1.9.9
class MissingProductsListView(generic.ListView):
template_name = 'dashboard/purchaseorder/missing_products.html'
context_object_name = 'backorders'
model = BackOrder
def post(self, request, *args, **kwargs):
backorder_formset = BackOrderFormset(request.POST)
errors = backorder_formset.errors
if backorder_formset.is_valid():
# <process form cleaned data>
return HttpResponseRedirect('/success/')
else:
ctx = self.get_context_data()
return self.render_to_response(ctx)
def accumulate_identical_products_from_backorders(self, back_order_list):
... some code
return sorted_accumulated_dict.values()
def get_context_data(self, *args, **kwargs):
ctx = super(MissingProductsListView, self).get_context_data(*args, **kwargs)
ctx['title'] = _("Missing Products")
if self.request.POST:
ctx['back_order_formset'] = BackOrderFormset(self.request.POST)
else:
accumulated_backorders_per_product = self.accumulate_identical_products_from_backorders(BackOrder.objects.all())
back_orders = BackOrderFormset(initial=[{'product_id': backorder_dict['product_id'],
'product': backorder_dict['title'],
'quantity': backorder_dict['quantity']} for backorder_dict in
accumulated_backorders_per_product])
ctx['back_order_formset'] = back_orders
return ctx
def get_queryset(self):
.. some code
return backorder_list
Look here:
class BaseListView(MultipleObjectMixin, View):
"""
A base view for displaying a list of objects.
"""
def get(self, request, *args, **kwargs):
self.object_list = self.get_queryset()
allow_empty = self.get_allow_empty()
if not allow_empty:
# When pagination is enabled and object_list is a queryset,
# it's better to do a cheap query than to load the unpaginated
# queryset in memory.
if self.get_paginate_by(self.object_list) is not None and hasattr(self.object_list, 'exists'):
is_empty = not self.object_list.exists()
else:
is_empty = len(self.object_list) == 0
if is_empty:
raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") % {
'class_name': self.__class__.__name__,
})
context = self.get_context_data()
return self.render_to_response(context)
Basically - you missed this part in the POST handler:
self.object_list = self.get_queryset()
And to be honest - I am not quite sure if this is a good idea to add post to the generic ListView in django. It looks more like FormView - but I can be wrong here.

Categories