Populate formset with information from db - python

I have a view where the user should be able to update an instance of a model, and also update or create new instances of a model related to the first one. I tryied using formsets to do this, and it works perfeclty for creating new objects, but i'm not finding a way to show the objects that already have been created. My problem is that i don't know how to populate the formsets with the existing data, so that i can put it in the context
So this are my models:
class Order(Model):
...
class invoice(Model):
order = models.ForeignKey(Order)
...
And my view is something like this:
class OrderDetailView(UpdateView):
invoice_form_class = InvoiceForm
def get_context_data(self, **kwargs):
context = super(OrderDetailView, self).get_context_data(**kwargs)
if not 'invoice_formset' in context:
context['invoice_formset'] = formset_factory(self.invoice_form_class, extra=3, can_delete=True, formset=BaseFormSet)
return context
There's probably an easy way to do this, but i'm not finding it anywhere
EDIT:
Thanks to #mariodev, i've learned about the inline_formsetfactory, and i'm using it. Now i can fill the formsets with the existing data, and i can create and alter existing ones, but when i try to delete them, nothing happens.
So now i'm defining this formset:
InvoiceFormset = inlineformset_factory(Order, Invoice, fields=('code',), can_delete=True, extra=0)
and my view looks like:
class OrderDetailView(UpdateView):
invoice_form_class = InvoiceForm
def get_context_data(self, **kwargs):
context = super(OrderDetailView, self).get_context_data(**kwargs)
if not 'invoice_formset' in context:
context['invoice_formset'] = InvoiceFormset(instance=self.get_object())
return context
def post(self, *args, **kwargs):
data = self.request.POST
order = self.get_object()
form = self.form_class(data)
invoice_formset = InvoiceFormset(data, instance=order)
if form.is_valid() and invoice_formset.is_valid():
self.object = form.save(order)
for f in invoice_formset:
f.save(self.object)
return HttpResponseRedirect(reverse('order_detail', kwargs={'order_id': self.get_object().order_id}))
I could add some extra lines in the post() to check if i have to delete the form, but it doesn't seem right for me to do it in the view. Is there something else i'm missing?
EDIT AGAIN:
Ended up finding this link which fix exactly this last problem i was having, so now it's all good!

I think it's better use normal function based views (FBV) for this. Understand what's going on first and then gradually move to CBV if you really need to.
This will help you with FBV:
http://catherinetenajeros.blogspot.com/2013/03/inline-formset-saving-and-updating-two.html
This may help you with CBV:
django class-based views with inline model-form or formset

Related

Django: tie two redirects to a single Update button depending on where the user came from

So, i have a rather usual "update item" page that is a class-based view which inherits UpdateView. (in views.py it looks like "class ItemUpdateView(UpdateView) and it has method get_success_url(self) defined which contains the redirect url where user will be taken after clicking "Update" button.
My problem is that in my application, there are two different pages that could lead me to this "Update item" page, and depending on the page that user comes from - i want to take the user back to either pageA or pageB upon the successful update of the item.
I wasn't able to find the best-practices of how to handle this anywhere on the web, so - would really appreciate the help.
My guess is that I need to create an additional parameter that will be a part of the url and will contain A or B depending on the pageA or pageB that user came from, i.e. the url itself would be something like '/itemUpdate/int:pk/sourcepage' => '/itemUpdate/45/A'. Does that sound like a correct aproach or is there a better way?
There is a better way that you can check Meta dictionary in request:
write in your views file:
class ItemUpdateView(UpdateView):
previous_url = ''
form_class = UpdateItem
def get(self, request, *args, **kwargs):
self.previous_url = request.META.get('HTTP_REFERER')
print(self.previous_url)
return super().get(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['success_url'] = self.previous_url
return initial
def form_valid(self, form):
self.success_url = form.cleaned_data['success_url']
print(self.success_url)
return super().form_valid(form)
# also you can use get_success_url instead of form_valid()
# def get_success_url(self):
# return super().get_form().cleaned_data['success_url']
and then write a hidden field in your form and name it success_url
class UpdateItem(forms.ModelForm):
success_url = forms.URLField(widget=forms.HiddenInput)
class Meta:
model=Item
fields=['itemName','quantity']
Note you can not use instance in order to get success_url field, because this field belong to form nor your model instance !
refer to documentions

Posting product quantity in a Django form

I'm working with an e-commerce webapp and in the product detailed-view I want to give the option to set the quantity before the checkout. I thought I could do this with a simple form:
class QuantityForm(forms.Form):
quantity = forms.IntegerField(widget=forms.NumberInput(attrs={'class':'required'}))
And then implementing it to the view:
class BagDetailView(DetailView):
context_object_name = 'ctx'
model = Bag
template_name = 'product_page.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = QuantityForm(initial={'quantity':1})
context['Jackson'] = Bag.objects.get(title = 'Jackson')
context['Anthony'] = Bag.objects.get(title = 'Anthony')
return context
But the problem is the POST request. I cannot call the render() function because I have another view to handle the checkout, so I need to call redirect(). This is the code:
def post(self, *args, **kwargs):
form = QuantityForm()
b = Bag.objects.get(slug=kwargs['slug'])
if form.is_valid:
return redirect(b)# calls get_absolute_url which redirects to checkout/<slug>
And I cannot access the data that I'm posting in the other view. I think it's bad practise to post data like this to another view, but I couldn't come up with anything but that. Is there any other way to pass that data into the other view?
Note: I'm not using a cart system because I'm only dealing with two products.
I am not sure if I have understood correctly, maybe you can do something like this.
In the template for increasing quantity, you can put this:
<i class="">INCREASE QUANTITY</i>
When the user clicks this, a function will be called:
#login_required
def add_to_checkout(request, slug):
#get the checkout for this customer which is not fulfilled
#increase the quantity
#redirect to checkout page
Another idea can be using session. You can increase the amount in the detail page and save it to the sessions, then use in the checkout page.
I hope I have understood correctly your question.

How to get instance values for form in django?

I've overridden a detail view so I can render form elements with render_field. However, when rendered they do not show the saved values. I can't set the value in the template because I can't put {{}} within {% %} syntax. How can I access and display the previously saved model instance values? (these are sliders that I want to keep as sliders, and they also have a lot of data attributes that I want to keep consistent, so I can't just write the inputs manually in the template)
In views.py:
class MyDetailEditMixin(SingleObjectMixin):
"""
Hybrid mixin to edit a detail
"""
model = MyModel
form_class = forms.MyForm
raise_execption = True
def get_context_data(self, **kwargs):
"""
expose the form
"""
kwargs.setdefault('form', forms.MyForm)
return super(MyDetailEditMixin, self).get_context_data(**kwargs)
class MyDetailView(MyDetailEditMixin, DetailView):
"""
Shows the details
"""
I feel like I need to explicitly mention the instance somehow? I'm not sure. The inputs render correctly with all their specific data attributes, just no set values.
I tried many different things, but in the end I ditched the mixin and edited MyDetailView with a get_form method (and within that I could tell it to use the instance):
class MyDetailView(DetailView):
"""
Shows the details
"""
form_class = forms.MyForm
def get_form(self):
form = self.form_class(instance=self.object)
return form
def get_context_data(self, **kwargs):
context = super(MyDetailView, self).get_context_data(**kwargs)
context.update({
'form': self.form_class(instance=self.get_object()),
})
return context
The render_field fields have their values filled with the saved instance values and all the attributes they need are there.

Generic deleteview with condition

I've been trying to create a Django generic deleteview, to delete an instance of a model.
I however have to check whether it is allowed to delete this item. This is done using a method defined in the model.
So far I've created this:
#login_required
def delete_employee(request, pk):
employee = None
try:
employee = Employee.objects.get(pk=pk)
except:
pass
if employee and not employee.empty():
return render(request, "error.html", None)
else:
# Load the generic view here.
Is this a decent way to go? And how can I load the generic view there?
I've tried things like EmployeeDelete.as_view() but those things don't work.
Or is there a way to check this in the generic view itself?
I've tried that as well, but I wasn't able to load an error page, just throw errors.
To do this with a DeleteView you can do this just by overriding the delete method on your inherited view. Here is an example based on what you have said. This is just an example of how you can accomplish it. You might need to tweak it for your exact scenario, specifically the else on can_delete
class EmployeeDeleteView(DeleteView):
success_url = reverse_lazy('index')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
can_delete = self.object.can_delete()
if can_delete:
return super(EmployeeDeleteView, self).delete(
request, *args, **kwargs)
else:
raise Http404("Object you are looking for doesn't exist")

How do I save post data using a decorator in Django

I have the following view in my django app.
def edit(request, collection_id):
collection = get_object_or_404(Collection, pk=collection_id)
form = CollectionForm(instance=collection)
if request.method == 'POST':
if 'comicrequest' in request.POST:
c = SubmissionLog(name=request.POST['newtitle'], sub_date=datetime.now())
c.save()
else:
form = CollectionForm(request.POST, instance=collection)
if form.is_valid():
update_collection = form.save()
return redirect('viewer:viewer', collection_id=update_collection.id)
return render(request, 'viewer/edit.html', {'form': form})
It displays a form that allows you to edit a collection of images. The footer of my html contains a form that allows you to request a new image source from the admin. It submits to a different data model than the CollectionForm. Since this is in the footer of every view, I want to extract lines 5-7 of the code and turn it into a decorator. Is this possible and if so how might I go about doing that?
I would make a new view to handle the post of the form. And then stick a blank form instance in a context processor or something, so you can print it out on every page.
If you do want to make a decorator, i would suggest using class based views. That way, you could easily make a base view class that handles the form, and every other view could extend that.
EDIT:
Here's the docs on class based views: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/
Note, I would still recommend having a separate view for the form POST, but here's what your solution might look like with class based views:
class SubmissionLogFormMixin(object):
def get_context_data(self, **kwargs):
context = super(SubmissionLogFormMixin, self).get_context_data(**kwargs)
# since there could be another form on the page, you need a unique prefix
context['footer_form'] = SubmissionLogForm(self.request.POST or None, prefix='footer_')
return context
def post(self, request, *args, **kwargs):
footer_form = SubmissionLogForm(request.POST, prefix='footer_')
if footer_form.is_valid():
c = footer_form.save(commit=False)
c.sub_date=datetime.now()
c.save()
return super(SubmissionLogFormMixin, self).post(request, *args, **kwargs)
class EditView(SubmissionLogFormMixin, UpdateView):
form_class = CollectionForm
model = Collection
# you can use SubmissionLogFormMixin on any other view as well.
Note, that was very rough. Not sure if it will work perfectly. But that should give you an idea.

Categories