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.
Related
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
I'm new to django.
I'd like my users to be able to place orders. For this kind of order, the user uploads a csv. The app parses, the CSV, serializes the data, and then needs to show the user a new view with the data and a "confirm order" button. I'm not sure how to do this in django.
class UploadSampleSheetView(LoginRequiredMixin, FormView):
form_class = UploadSampleSheetForm
template_name = 'pages/upload.html'
def form_valid(self, form):
uploaded_sample_sheet = self.request.FILES['uploaded_sample_sheet']
sample = _parse_sample_sheet_to_sample_model(uploaded_sample_sheet)
sample.save()
return super().form_valid(form)
def get_success_url(self):
return reverse("orders:create") # how do I return another view populated with data here?
class CreateOrderView(LoginRequiredMixin, CreateView):
model = Order
form_class = NewOrderForm
template_name = 'pages/complete_order.html'
What I'm looking for is some way that, on success, the UploadSampleSheetView can return a CreateOrderView with sample data.
In general, I'd love to be pointed to a reference about how to build user flows like this one. How does one view defer to another? I'm seeing a lot of return HttpResponseRedirect('url') which seems a little messy. How do I pass data around views?
FormViews are supposed to return HttpResponseRedirects. They take in the data, process it, and re-route you to a place that makes sense. I think your question is, how can I get routed to the appropriate place.
The documentation isn't very clear on this, so it's a very valid question. But, you can override get_success_url to take in an argument. So, you would add this:
def get_success_url(self, sample_id):
return reverse('create_order', kwargs={"pk" : sample_id})
and also change form_valid to
def form_valid(self, form):
uploaded_sample_sheet = self.request.FILES['uploaded_sample_sheet']
sample = _parse_sample_sheet_to_sample_model(uploaded_sample_sheet)
sample.save()
return HttpResponseRedirect(self.get_success_url(sample.id))
Don't worry, you're not missing out on anything by skipping super().form_valid() -- the docs (scroll just a little down) say that all form_valid does by default is redirect to success_url.
You can use Form Wizard
Django comes with an optional “form wizard” application that splits forms across multiple Web pages. It maintains state in one of the backends so that the full server-side processing can be delayed until the submission of the final form.
You might want to use this if you have a lengthy form that would be too unwieldy for display on a single page. The first page might ask the user for core information, the second page might ask for less important information, etc.
Harder to use, but very useful and flexible.
# views.py
from formtools.wizard.views import SessionWizardView
class MyView(SessionWizardView):
template_name = 'pages/wizard_steps.html'
template_name_done = 'pages/wizard_done.html'
form_list = (
('upload', UploadSampleSheetForm),
('confirm', NewOrderForm)
)
def done(self, form_list, **kwargs):
# you can get all forms data here and process it as you want
return render(self.request, self.template_name_done, {
'form_data': [form.cleaned_data for form in form_list],
})
def process_step(self, form):
if isinstance(form, UploadSampleSheetForm):
# Example. You can put here some additional manipulations
pass
return super().process_step(form)
And template
<!-- wizard_done.html -->
{% if wizard.steps.current == 'upload' %}
{% include 'pages/wizard_upload.html' %}
{% elif wizard.steps.current == 'complete' %}
{% include 'pages/wizard_complete.html' %}
{% endif %}
I think you are looking for Django Session. This is an extensive document if you want to dive deep.
It includes the examples that you are looking for as well.
I have a website where I would like to display the product name the user purchased after he was redirected from the checkout to the thank you page.
The problem is that I don't get how I could send a data from a view to another without creating a form in the template.
here is two example of views from checkout_payment to checkout_confirmation pages:
def checkout_payment(request):
customer_id = request.user.profile.stripe_id
if request.method == 'POST':
try:
gig = Gig.objects.get(id=request.POST.get('gig_id'))
except Gig.DoesNotExist:
return redirect(reverse('purchase_error_detail'))
return redirect(reverse('purchase_confirmation_detail'))
def checkout_confirmation(request):
#how can I get the purchased gig datas ?
return render(request, 'purchase/confirmation.html', {})
models.py Gig contains : user, title, price fields.
urls.py : name='purchase_confirmation_detail'
Is there a way to get the last purchased datas avoiding using a form or the urls to get the product informations ?
Easy and fast way: Sessions
If all you need is a list of strings or a single string, you can just use sessions. You can read about them in detail in the docs. Just save the names in some key, display them and clear them.
Better, more future proof solution but slightly more complicated: Models
When you are selling something, it is desirable to keep around some record of what the user bought. This can be helpful when system fails (trust me, it will) or to keep a record of everything.
It can be something as simple as:
class Transaction(models.Model):
gig = models.ForeignKey(Gig)
user = models.ForeignKey(User)
Now you refactor your view in 2 ways:
Only the user in the transaction should be able to see the content on it.
Add the pk of the Gig to the url you redirected to.
Your source view should use a redirect like:
def checkout_payment(request):
customer_id = request.user.profile.stripe_id
if request.method == 'POST':
try:
gig = Gig.objects.get(id=request.POST.get('gig_id'))
except Gig.DoesNotExist:
return redirect(reverse('purchase_error_detail'))
new_transaction = Transaction.objects.create(user=request.user, gig=gig)
return redirect(reverse('purchase_confirmation_detail', kwargs={'pk': new_transaction.pk}))
And your destination view will be something like:
def checkout_confirmation(request, *args, **kwargs):
new_transaction = Transaction.objects.get(kwargs.get('pk'))
if request.user != new_transaction.user:
return HttpResponseForbidden() # You can raise Http404 here too to hide the resource, like github does
return render(request, 'purchase/confirmation.html', {'gig': transaction.gig})
Now you have access to everything you need to display.
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.
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