Django practice: smaller views or less views? - django

I'm developing an app with django, and I have a view where I use 2 return render_to_response, with two different html files, depending on the status of the user.
I was wondering if it would be a better practice to split my view into two different views or if I should keep a bigger view.
What are the pros and the cons of doing so?
Sorry if my question is not clear. Thank you very much for your advices.

There's no right or wrong answer to this question so your question may not be acceptable on stackoverflow, which usually is intended for questions/problems with specific technical solutions.
That said, here's my view on this topic - I personally like to keep my view function small and if further processing is required, split them out into smaller functions.
For example:-
#permission_required('organizations.organization_add_user')
def organization_add_user(request, org_slug):
org = get_object_or_404(Organization, slug=org_slug)
form = OrganizationAddUserForm(org=org)
if request.method == 'POST':
form = OrganizationAddUserForm(request.POST or None, request.FILES or None, org=org)
if form.is_valid():
cd = form.cleaned_data
# Create the user object & send out email for activation
user = create_user_from_manual(request, data=cd)
# Add user to OrganizationUser
org_user, created = OrganizationUser.objects.get_or_create(user=user,\
organization=org)
dept = org.departments.get(name=cd['department'])
org_user.departments.add(dept)
# Add user to the appropriate roles (OrganizationGroup) and groups (django groups)
org_groups = OrganizationGroup.objects.filter(group__name__in=cd['roles'], \
organization=org)
for g in org_groups:
user.groups.add(g.group)
return HttpResponse(reverse('add_user_success'))
template = 'organizations/add_user.html'
template_vars = {'form': form}
# override request with dictionary template_vars
template_vars = FormMediaRequestContext(request=request, dict=template_vars)
return render(request, template, template_vars)
FormMediaEquestContext is a class I import from another file and has its own logic which helps me to handle javascript and css files associated with my form (OrganizationAddUserForm).
create_user_from_manual is yet another function which is encapsulated separately and deals with the reasonably convolutated logic relating to creating a new user in my system and sending an invitation email to that new user.
And of course, I serve up a different template if this is the first time a user arrives on this "add user" page as opposed to redirecting to a completely different url with its own view function and template when the add user form is successfully executed.
By keeping our view functions reasonably small, I have an easier time tracking down bugs relating to specific functionality.
In addition, it is also a good way to "reuse" my utility functions such as the create_user_from_manual method should I need this same utility function in another view function.
However, at the end of the day, organizing code and encapsulating code is a judgement call that you get to make as you progress as a developer.

Related

How to stage forms in Django?

I have form data that I gather in the views. What the user inputs will determine what the next form will be like. Should I create another view? If so, how do I pass on variables from one view to another? And should the separate view render a different html page?
Or is there a way to work in the same view that I gathered the data initially?
The view looks like this:
def admission(request):
if request.method == 'POST':
form = DeterminingForm(request.POST)
if form.is_valid():
selected_season = form.cleaned_data['season']
selected_batch = form.cleaned_data['batch']
form = DeterminingForm()
context = {
'form': form,
}
return render(request, 'admission.html', context)
I have used django-formtools Form wizard in the past for similar use cases. Subclassing out the SessionWizardView allows you to render multiple different forms using the same view and even the same template if you wish, however you can have a different template for each form. The variables for each form submission are stored in server-side sessions and the context can be extracted for each step in the process.
If the sequence of forms is different dependent on the answers in the previous form, you should be able to build up some logic within the process using the WizardView.get_form() method for each form step in the process. Hope this helps as a starter

Dynamically Chain Django Forms

In a Django 1.11 app I've created a large form for users to update a model instance, but based on what the users change there may be multiple other forms I'd like to redirect them to afterwards.
What's the best practise for dynamically chaining multiple forms (dynamic workflow)?
I can't find anything helpful in the Django docs or other questions on here.
E.g. (extremely simplified) view for model update form:
def asset_edit(request, pk):
asset = get_object_or_404(Asset, pk=pk)
current_location_concat = "{} {} {}".format(asset.location_building, asset.location_room, asset.location_area)
if request.method == "POST":
form = EditAsset(request.POST, request.FILES, instance=asset)
if form.is_valid():
asset = form.save(commit=False)
# setting some things...
asset.save()
new_location_concat = "{} {} {}".format(asset.location_building, asset.location_room, asset.location_area)
if current_location_concat != new_location_concat:
check_relatives = True # redirect to check_relatives form
if asset.equipment_id:
if Asset.objects.filter(equipment_id=asset.equipment_id).count() > 1:
duplicate_assets = True # redirect to check_duplicates form
# if check_relatives and duplicate_assets:
# redirect to check_duplicates form and then on to check_relatives form
return redirect("asset_detail", pk=asset.pk)
I know I could just add a new URL for my check_duplicates form with a "next" (or similar) parameter, pass a "next" value that the check_duplicate view maps to one or more other forms (just "check_relatives" in this instance) and redirects to this when the check_duplicates form is submitted, but is this the best practise?
Especially given that the number of forms that may need to be chained this way could get quite large and the logic complex!
I have been using formtool's WizardView for this and I have tried the 'stuff-everything-into-the-post-method' approach as well.
WizardView looks like the right fit for what you are trying to do.
For example, it allows you to skip steps in your workflow based on function conditions - removing this bit of logic from your workflow, which makes the whole view easier to read.
I have found it a pain to write tests for such views as you have to account for the WizardView's very own internal structure while testing, but overall it's definitely better than writing a 1000 lines post-method (might as well code in BASIC then).

Sending form to another view django

I am building a website and I want various views that will ask the user to request a quote from our page. I want to keep the code as DRY as possible so I am writing a view quote which will receive the quote requests from various views and, if there is a validation error redirect back to the page that made the request. I managed to solve this using the super bad practice 'global variables'. I need a better solution, I would like redirecting to respective view with the current form so I can iterate through the form.errors. Here is my code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
global session_form
session_form = form
return redirect('Main:' + quote_for) # Here I would like to send form instead of storing in global variable`
You can use the HttpResponseRedirect function, and pass as argument the page that made the request.
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
All the META data is store on a dictionary, if you want to learn more check the documentation.
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
If you redirect to the referrer, form.errors will be empty, as redirection is always a GET request.
I can think of two solutions to your problem:
Submit forms asynchronously using JavaScript and so populate the errors
Make all the views containing the form support POST - one way to do this would be to create a base class that inherits from FormView
The second option is a typical way of handling forms in Django - you process both POST and GET inside the same view.
After two days of searching I finally found the answer. Instead of saving form in request.session I just save request.POST and then redirect. Here is the code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
request.session['invalid_form'] = request.POST
return redirect('Main:endview')
def endview(request):
session_form = request.session.pop('invalid_form', False)
if session_form:
form = Quote(session_form)
# render template again with invalid form ;)
Now I can repeat this with all the views I want and just change the what_for input of each form to match the respective view (Like I intended).

Django: Returning a customized view function

I'm developing a Django application that will be used by people in various companies. Some companies require highly customized views, which means I'll change the default template depending on their needs. It could also require some extra context variables.
My idea is to create separate view functions as required, since trying to put it all in one view function will get messy quite quickly. For example, let's say I have a dashboard view:
def dashboard(request):
var1 = some_func()
context = {'var1': var1 }
return render(request, 'normal_template.html', context)
Now company x wants a custom dashboard:
def dashboard_for_company_x(request):
var1, var2 = some_func_x()
context = {'var1': var1, 'var2': var2 }
return render(request, 'template_for_x.html', context)
So when a user from company x goes to a certain page, dashboard_for_company_x should be used. All other user should use the original dashboard function.
The two options I can think of is either:
1. adding the company's name to the url, so that the URL resolver will use the correct view function.
2. Using middleware to intercept the request and redirect them to the appropriate view function if required.
1 seems like the better option, but it would mean I would to change all the url tags in my template to include the company name, so that is not really an option. I think 2 will work, but seems like a bit of a hack.
Is it fine to use number 2, or are there better options available?
Note that only a small percentage of views will require customization.
you could do the routing inside your view function, e.g.:
def dashboard(request):
if request.user.company == 'foo':
return dashboard_foo(request)
elif request.user.company == 'bar':
return dashboard_bar(request)
else:
return dashboard_standard(request)
Depending on the complexity of your view it might make sense to use a class based view, so you only need to swap out the specific logic (e.g.: templates, context vars, etc.) in the specific methods, and let the main logic be the same across all users (i.e.: reduce code duplication).

Django URL configuration

I have a purchase page, it can take an optional argument as a gift, if it is a gift, the view passes a gift form to the template and if not, a regular purchase form.
my old regular url, which redirects to two seperate views:
(r'^(?P<item>[-\w]+)/purchase/$', 'purchase_view'),
(r'^(?P<item>[-\w]+)/purchase/gift$', 'gift_view'),
and the views was like this:
def purchase_view(request,item):
....use purchase form
def gift_view(request,item):
....use giftform
It is a bad design indeed, as both views having are almost everything same but the forms used.
I have also thougt about using GET and giving gift as a GET param however it wasnt a good idea as I am using POST method for these pages, especially would cause issue after validation.
How can I make this a single url and a single view?
Thanks
urls.py
url(r'^(?P<item>[-\w]+)/purchase/$', 'purchase_view', name='purchase_view'),
url(r'^(?P<item>[-\w]+)/purchase/(?P<gift>gift)/$', 'purchase_view', name='gift_view'),
views.py
def purchase_view(request, item, gift=False):
if gift:
form = GiftForm
else:
form = PurchaseForm
...