2

I have several function based views in a django project, and I noticed that they have some repeated code, in fact, they all do the same thing:

def my_view(request):
   form = MyForm(request.POST or None)
   if requqest.POST:
      if form.is_valid():
         do_somethin()
         and_something_else()
         return redirect('another:page')
   return render(request, 'my/tempalate.html', {'form': form})

The only thing that is different is the url where the user is redirected to in case of a succcesful form validation, the template, and maybe the form in the future.

Is it a good idea to use something like this to avoid that repetition?:

def a_view(request, success_redirect_url, template):
    form = MyForm(request.POST or None)
    if request.POST:
        if form.is_valid():
           do_something()
           and_something_else()
           return redirect(success_redirect_url)
    return render(request, template, {'form': form})

and then reuse it in other views that have the repeated code? like:

def my_view1(request, url='another_page', template='my/template.html'):
    return a_view(request, url, template)
3
  • 1
    Typically class-based views are used for this. What you describe looks close to a FormView. Commented Sep 8, 2021 at 21:18
  • 1
    Yes, factoring out a reusable function is perfectly fine. As Willem says, there are also other ways to do this with class-based views. Commented Sep 8, 2021 at 21:20
  • Ok, but the form is already written, I have to use it. And I have to use function based views. Commented Sep 8, 2021 at 21:22

2 Answers 2

4

Typically such patterns are defined in class-based views where one can use inheritance to override only some parts of the code flow.

Class-based views

Your view for example looks quite similar to a FormView [Django-doc]. Indeed, we can define a view with:

# app_name/views.py

from django.urls import reverse_lazy
from django.views.generic import FormView

class MyView(FormView):
    template_name = 'my/template.html'
    success_url = reverse_lazy('another_page')
    form_class = MyForm

    def form_valid(self, form):
        do_somethin()
        and_something_else()
        return super().form_valid(form)

Here the class-based view has implemented a handler for a POST request that will first construct the form, then check if that form is valid, and if it is invalid rerender the template with the form that now contains the errors. It thus minimizes the amount of code that one has to write to handle a simple form.

Such views also explain clearly what they are doing. A CreateView for example explains by its name that it is used to create new objects, it will thus trigger the .save() method of the form it is operating on. A DeleteView on the other hand makes it clear that a POST request to that view will delete an object.

You can also override the attributes in the urls.py by specifying a value in the .as_view(…) [Django-doc] call:

# app_name/urls.py

from app_name.views import MyView
from django.urls import path

urlpatterns = [
    # ⋮,
    path('some/path/', MyView(template_name='other/template.html'), name='some-name'),
    # ⋮
]

A class-based function thus acts as a template method pattern

Decorators

Another way to do this is to work with decorators that implement small pieces of logic that run before/after a call to the function-based view, and can alter the request/response, or decide to raise an error instead of calling the view.

For example the @require_http_methods(…) decorator [Django-doc] [GitHub] is implemented to first check if the method is one of the listed ones. This is implemented as:

def require_http_methods(request_method_list):
    # ⋮
    def decorator(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            if request.method not in request_method_list:
                response = HttpResponseNotAllowed(request_method_list)
                log_response(
                    'Method Not Allowed (%s): %s', request.method, request.path,
                    response=response,
                    request=request,
                )
                return response
            return func(request, *args, **kwargs)
        return inner
    return decorator

here the decorator thus checks if the request.method is a member of the request_method_list. If that is not the case, it will return a HTTP 405 response, and specify that that method is not allowed.

While Django offers a lot of decorators, most decorators have a mixin counterpart for a class-based views, and some are implemented already in the View class. For example if the View does not contains a get method, then it will return a HTTP 405 response, so here the required_http_method is not needed as a mixin/decorator for a class-based view.

Skeleton functions

You can implement a view function and use parameters instead to pass values. Usually however this will not be as flexible as a class-based view: it is rather easy to pass some parameters, but it is less useful to specify behavior in a pattern: in that case you need to pass a reference to a function, but then the question arises what parameters should be passed to that.

For example if we want to make a function that renders the template, we can work with a view that looks like:

from django.shortcuts import render

def render_some_template(request, parameter, context_generator, template='our/template.html'):
    context = context_generator()
    return render(request, template, context)

but perhaps the context_generator function should be called together with the parameter? or perhaps know what template will be rendered. This is one of the reasons why altering code flow is usually done with a class-based view, and less with a function-based view.

While a function-based view can work with a skeleton function, it is usually less exendible than the class-based counterpart. Django's builtin apps (the ones defined in the django.contrib module) are moving mainly towards class-based views, since it is easier to extend these.

Sign up to request clarification or add additional context in comments.

8 Comments

Thank you, but, the thing is that I'm am not allowed to use class based views.
@DavidEmanuelSandoval: well almost all views in Django's builtin apps are moving towards class-based views. The login view was marked as deprecated in Django-1.11 and has been removed in Django-2.1 if I recall correctly: docs.djangoproject.com/en/2.0/topics/auth/default/…
@DavidEmanuelSandoval: and in fact likely class-based views are more secure, since it will implement a lot of boilerplate logic that one then never has to write again.
@DavidEmanuelSandoval: one can use both class-based views and function-based views. Usually class-based views are used to avoid repitition of a view: so one defines a class-based view that encapsulates the code flow, and then subclasses. Function-based views on the other hand are often used if the codeflow is "special", and thus does not fit into a certain "pattern".
@DavidEmanuelSandoval There is something to be said about establishing conventions within a team. At the same time, good teams allow some flexibility in their conventions as they discover new and better ways of doing things. I suggest discussing class-based views with your team and weighing the pros and cons of using them.
|
0

You can do a lot better if just give a name to any button wich lunch this function:

<button name='lukaku'>
<button name='ronaldo'>

then into the view check for the name

if form.get('lukaku'):
    do something
if form.get('ronaldo'):
    do other thing

and so on. this can be limitless

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.