Own method in CreateView - python

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.

Related

Success message not displayed

Don't see any SuccessMessage. Anyone can tell me why I don't get any success message once I successfully created the database entry?
class TicketCreate(AdminPermissionRequiredMixin, SuccessMessageMixin, FormValidationMixin, BaseTicketView, TemplateView):
template_name = 'tickets/admin/create.html'
success_message = _("Ticket has been successfully created.")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ticket_form'] = self.ticket_form
context['tax_form'] = self.tax_form
return context
#cached_property
def ticket_form(self):
return TicketForm(data=self.request.POST or None, event=self.request.event)
#cached_property
def tax_form(self):
return TicketTaxForm(prefix='tax', data=self.request.POST or None)
#transaction.atomic
def post(self, request, *args, **kwargs):
if self.ticket_form.is_valid() and self.tax_form.is_valid():
tax_instance = self.tax_form.save(commit=False)
tax_choice = self.tax_form.cleaned_data.get('tax_choice')
new_tax = (tax_choice == TicketTaxChoice.NEW_TAX and
tax_instance.name and tax_instance.percentage)
# save the tax instance
if new_tax:
tax_instance.event = self.request.event
tax_instance.save()
# save the ticket instance
ticket_instance = self.ticket_form.save(commit=False)
ticket_instance.event = self.request.event
if new_tax:
ticket_instance.tax = tax_instance
self.ticket_form.save()
return redirect(
'tickets:admin:detail',
self.request.organizer.slug,
self.request.event.slug,
ticket_instance.pk
)
return super().get(request, *args, **kwargs)
From the official docs:
Adds a success message attribute to FormView based classes
I can't see your other classes, but from what I see your class is based on TemplateView, which is not derived from FormView.
Looking the source you can see that message is shown when form_valid is called, so your class should at least specify form_class or model field. But you are calling is_valid() manually, so form_valid is not called and message is not shown.

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.

Django KeyError kwargs.pop('pk')

I'm using CBV in Django 1.9 and in CreateView when I try to pass an additional parameter ('pk') to my form using self.kwargs.pop('pk') i got "Key Error" but if I get the parameter by index it works, here is my code:
def get_form(self, form_class=None, **kwargs):
self.project_version_pk = self.kwargs.pop('pk')
form = super(HRCreateView, self).get_form(form_class)
form.fields['project_version'].queryset = form.fields['project_version'].queryset.filter(pk=self.project_version_pk)
form.fields['project_version'].initial = self.project_version_pk
return form
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class, pk=self.kwargs['pk'])
return self.render_to_response(
self.get_context_data(form=form)
And I get this error:
builtins.KeyError
KeyError: 'pk'
File "RelationView.py", line 65, in get_form
self.project_version_pk = self.kwargs.pop('pk')
KeyError: 'pk'
But if i read the key this way it works:
def get_form(self, form_class=None, **kwargs):
self.project_version_pk = self.kwargs['pk']
form = super(HRCreateView, self).get_form(form_class)
form.fields['project_version'].queryset = form.fields['project_version'].queryset.filter(pk=self.project_version_pk)
form.fields['project_version'].initial = self.project_version_pk
return form
I don't really understand why the parameter is missing on pop() or which is the best practice for this.
Firstly, you shouldn't be overriding get. In a CreateView, Django already calls get_form for you - inside get_context_data. This is the cause of the issue you are having; you call get_form and pop the pk so that it is no longer in kwargs; but Django calls it again in get_context_data, but this second time it can't find the pk because you removed it the first time.
So don't use pop; but, as I said, don't do this at all. The only thing you actually need to override is get_form.

Persist Django FormView Data

I have a FormView that generates a review of an object (which it is generically related to) and then links it to the object and saves it when the form is completed.
The issue I'm having is that I have no way to hold onto the data of the object I want to connect to. This means that I need to 'look it up' for context (template rendering) for valid processing (to do the linking) and for the success (to generate an appropriately reversed url.
Is there a better way to be binding the review to the object? Or better yet, is there a way to persist form data that I'm missing?
EDIT: Sorry the login decorator was on dispatch. I removed that method because SO was complaining about too much code and I didn't think it was relevant... I must have missed the decoratot
class ReviewCreate(FormView):
template_name = 'food/item_add_review.html'
form_class = ReviewForm
review_item = None
def get_context_data(self, **kwargs):
context = super(ReviewCreate, self).get_context_data(**kwargs)
item_modelname = self.kwargs.get('model')
item_model = apps.get_model('food',item_modelname)
review_item = get_object_or_404(item_model,pk=self.kwargs.get('pk'))
context['item'] = review_item
return context
def form_valid(self, form):
item_modelname = self.kwargs.get('model')
item_model = apps.get_model('food',item_modelname)
review_item = get_object_or_404(item_model,pk=self.kwargs.get('pk'))
r = form.save(commit=False)
r.content_object=review_item
r.save()
return super(ReviewCreate, self).form_valid(form)
def get_success_url(self):
item_modelname = self.kwargs.get('model')
item_model = apps.get_model('food',item_modelname)
review_item = get_object_or_404(item_model,pk=self.kwargs.get('pk'))
return reverse( 'pkitem', kwargs = {'pk': review_item.id, 'model':item_modelname},)
The view is an object right, so you just assign your values to instance variables, i e "to self" (this is thread-safe). Like this:
class ReviewCreate(FormView):
template_name = 'food/item_add_review.html'
form_class = ReviewForm
#method_decorator(login_required) # Use a class level mixin instead
def get_context_data(self, **kwargs):
return super(
ReviewCreate,
self
).get_context_data(
item=self.review_item,
**kwargs
)
def lookup_review_item(self):
self.item_modelname = self.kwargs.get('model')
item_model = apps.get_model('food', self.item_modelname)
self.review_item = get_object_or_404(
item_model,
pk=self.kwargs.get('pk')
)
def dispatch(self, request, *args, **kwargs):
# lookup performed here to be set for both GET and POST
self.lookup_review_item()
return super(ReviewCreate, self).dispatch(request, *args, **kwargs)
def form_valid(self, form):
r = form.save(commit=False)
r.content_object=self.review_item
r.save()
return super(ReviewCreate, self).form_valid(form)
def get_success_url(self):
return reverse(
'pkitem',
kwargs = {
'pk': self.review_item.id,
'model': self.item_modelname
},
)
The default form_valid() method for FormView redirects to the success url and reinitializes the form. You can make the form data persist by overriding form_valid():
def form_valid(self, form):
return super(YourFormView, self).get(form)
This will redirect to your success url with a (bounded) form having the posted data. The form is added to the context so you can use the data in your template or in your view as you wish.
(Django version 1.11.7)
The get_context_data should always return the context dictionary. It doesn't make sense to use the login_required decorator with it, because that means it might return a redirect response instead.
It would be better decorate the dispatch method instead. In your dispatch, you can set attributes on the instance.
class ReviewCreate(FormView):
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
self.item_modelname = self.kwargs.get('model')
self.item_model = apps.get_model('food',item_modelname)
self.review_item = get_object_or_404(item_model,pk=self.kwargs.get('pk'))
return super(ReviewCreate, self).dispatch(request, *args, **kwargs)
Then, in your other methods, you can access the attributes, for example:
def get_context_data(self, **kwargs):
context = super(ReviewCreate, self).get_context_data(**kwargs)
context['item'] = self.review_item
return context

Form handling in class based view

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

Categories