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.
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 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.
I recently learned that you should override the get method when you specifically want to do something other than what the default view does:
class ExampleView(generic.ListView):
template_name = 'ppm/ppm.html'
def get(self, request):
manager = request.GET.get('manager', None)
if manager:
profiles_set = EmployeeProfile.objects.filter(manager=manager)
else:
profiles_set = EmployeeProfile.objects.all()
context = {
'profiles_set': profiles_set,
'title': 'Employee Profiles'
}
That's simple enough, but when should I use get_queryset or get_context_data over get? To me it seems like they basically do the same thing or am I just missing something? Can I use them together? This is a major source of confusion for me.
So to reiterate: In what cases would I use get over get_queryset or get_context_data and vise versa?
They indeed do different things.
get()
This is a top-level method, and there's one for each HTTP verb - get(), post(), patch(), etc. You would override it when you want to do something before a request is processed by the view, or after. But this is only called when a form view is loaded for the first time, not when the form is submitted. Basic example in the documentation. By default it will just render the configured template and return the HTML.
class MyView(TemplateView):
# ... other methods
def get(self, *args, **kwargs):
print('Processing GET request')
resp = super().get(*args, **kwargs)
print('Finished processing GET request')
return resp
get_queryset()
Used by ListViews - it determines the list of objects that you want to display. By default, it will just give you all for the model you specify. By overriding this method you can extend or completely replace this logic. Django documentation on the subject.
class FilteredAuthorView(ListView):
template_name = 'authors.html'
model = Author
def get_queryset(self):
# original qs
qs = super().get_queryset()
# filter by a variable captured from url, for example
return qs.filter(name__startswith=self.kwargs['name'])
get_context_data()
This method is used to populate a dictionary to use as the template context. For example, ListViews will populate the result from get_queryset() as author_list in the above example. You will probably be overriding this method most often to add things to display in your templates.
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['page_title'] = 'Authors'
return data
And then in your template, you can reference these variables.
<h1>{{ page_title }}</h1>
<ul>
{% for author in author_list %}
<li>{{ author.name }}</li>
{% endfor %}
</ul>
Now to answer your main question, the reason you have so many methods is to let you easily stick your custom logic with pin-point accuracy. It not only allows your code to be more readable and modular, but also more testable.
The documentation should explain everything. If still not enough, you may find the sources helpful as well. You'll see how everything is implemented with mixins which are only possible because everything is compartmentalized.
Let's look at the default implementation of ListView's get method:
https://github.com/django/django/blob/92053acbb9160862c3e743a99ed8ccff8d4f8fd6/django/views/generic/list.py#L158
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)
You will notice that get_queryset gets called in the first line. You can simply overwrite that if you just want to return your model's queryset after applying some filtering/ordering etc.
You don't need to overwrite the whole get method for that because you will be missing on all this provided functionality i.e. pagination, 404 checks etc.
get_context_data merges the resulting queryset together with context data like querystring parameters for pagination etc.
What I would recommend would be to check with django's source every once in a while and try to understand it a little bit so that you can recognize the most appropriate method you can overwrite/replace.
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