Cleaning is probably not your favorite pastime activity, and it’s certainly not mine. Whether it’s cleaning your room or cleaning your code, it’s probably not going to be a good time. But both of them have something in common. It’s very important.
1. Some of Clean Code’s Principles
As you may have known already, clean code is not about computers or compilers, it’s about humans. For computers, how you write your code doesn’t matter one bit as long as it’s syntax is right and it’s logically right. But humans are not computers. Your fellow co-workers and other programmers may not necessarily be able to read your code, especially if you didn’t write clean code.
Some of the most well known Clean Code principles are:
KISS (Keep It Simple (and) Stupid)
DRY (Don’t Repeat Yourself)
YAGNI (You Aren’t Gonna Need It)
In the following parts, I’ll try to show examples of these principles in Django.
2. KISS in Django
KISS (Keep It Simple (and) Stupid) is basically telling you to keep your code short, simple, and easy to read by others.
Use Django Forms, Serializers, Permissions, and Decorators Rather Than Manual Alternatives
When learning Django, you’ve probably learned Django forms, serializers, and many other things, as I did a long time ago.
But sometimes, I find myself questioning the benefit of using these things. Couldn’t you just write a few lines of HTML codes for forms? Or write more lines of code in the views instead of making serializers or permissions?
Yes, you can do just that to do the same thing. But because you can do it, doesn’t mean you SHOULD do it.
First thing is, manual HTML Forms is not safe! By using Django forms, your forms will be safer because it uses a CSRF Token as Cross-Site Request Forgery protection. You can search this on your own, but from clean code’s perspective, it’s also not good for manual HTML forms. Look at the comparison here:
Without Django Forms:
from django.shortcuts import render, redirect
from app.models import Object
def object_create(request):
if request.POST:
if request.POST.get("object_name") is None:
# Handle bad data
if request.POST.get("about_object") is None:
# Handle bad data
object = Object.objects.create(
object_name = request.POST.get("object_name"),
about_object = request.POST.get("about_object"),
)
return redirect("object:object_create_success", object.id)
return render(request, 'objects/object_create.html', {
'form' : form,
})
app/views.py
With Django Forms:
from django import forms
from app.models import Object
class ObjectCreationForm(forms.Form):
object_name = forms.CharField(
widget=forms.TextInput(attrs={'type' : 'text', 'placeholder' : 'Name of Object'})
)
about_object = forms.CharField(
widget=forms.TextInput(attrs={'type' : 'text', 'placeholder' : 'About Object'})
)
def save(self, user):
data = self.cleaned_data
return Object.objects.create(
object_name = data["object_name"],
about_object = data["about_object"],
)
app/views2.py
from django.shortcuts import render, redirect
from app.forms import ObjectCreationForm
def object_create(request):
form = ObjectCreationForm(request.POSTorNone)
if request.POSTandform.is_valid():
object = form.save(request.user)
return redirect("object:object_create_success", object.id)
return render(request, 'objects/object_create.html', {
'form' : form,
})
app/views2.py
You may think, “wait a minute, isn’t the code with Django forms longer?”. Yes, it’s longer if you include the forms.py file. But if you compare only the views.py file, you’ll see that the one with Django forms will be much simpler and easy to read than without Django forms. This is because even when the code (combined) is longer on the first snippet, it’s separated neatly. This means that you can infer what is supposed to do more easily.
This is the essence of KISS principle. You need to make the code simple and easy to read for others, even if you end up writing more code or have to think about it more. The most important thing to take away from this is make simple and readable code for others.
Use Class-Based Views Rather Than Function-Based Views
Use Class-Based Views instead of Function-Based Views when necessary. This is because even though Function-Based views is more flexible than its counterpart, Class-Based Views will be more readable on long and redundant functions/views.
I won’t go far in explaining this as most people probably have known about Class Based Views, but you can check out my other story about refactoring in Django here. One of the sections there goes a little bit further on Class-Based Views in Django REST Framework.
3. DRY in Django
DRY or Don’t Repeat Yourself is a principle that explains itself. You shouldn’t repeat yourself in your code. Some things that you can do in Django to reduce redundancies are:
Use Decorators for Restricting Access to Views
If you haven’t used decorators before, you’ve probably written this in your views before:
if request.user.is_authenticated:
And if you have multiple views in one file, it might look just like this:
from django.http import HttpResponseRedirect
from django.urls import reverse
def a_view(request):
if request.is_authenticated():
# Do stuff
else:
return HttpResponseRedirect(reverse('users:login'))
def b_view(request):
if request.is_authenticated():
# Do stuff
else:
return HttpResponseRedirect(reverse('users:login'))
app2/views.py
Well, what if I tell you there’s a better way that doesn’t involve copy-pasting and repeating yourself over and over again? Here’s how with decorators:
from django.http import HttpResponseRedirect
from django.urls import reverse
def login_required(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
# If user is authenticated, let go to the view
return view_func(request, *args, **kwargs)
else:
# If user is not authenticated, redirect to login page
return HttpResponseRedirect(reverse('users:login'))
return wrapper_func
app2/decorators.py
from app2.decorators import login_required
@login_required
def a_view(request):
# Do stuff
@login_required
def b_view(request):
# Do stuff
app2/views2.py
Wow! Cool right? You can also see that in the decorator, you could write any custom code you want. So it’s not only for restricting access to views or redirecting you somewhere. You can save things to user’s session like referer URL paths before they go to the view or many other things you can imagine. The sky is the limit!
Put Frequently Used Codes to Separate Helper Functions and in a Separate File
This is another one where it’s really easy to implement for all of us! Extract frequently used code to a separate helper function and put it in utils.py file.
Example of sending emails without helper functions:
from django.shortcuts import render
from django.core.mail import send_mail
from django.conf import settings
def send_to_requester_views(request):
email_to_list = [request.user.email]
if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
send_mail(
"Subject of Email", # subject
"Hello there!", # message
settings.EMAIL_HOST_USER, # from
email_to_list, # to
)
return render(request, 'main/home.html')
def send_to_all_views(request):
email_to_list = User.objects.values_list('email', flat=True)
if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD:
send_mail(
"Subject of Email Wohooo", # subject
"Hello ALL!", # message
settings.EMAIL_HOST_USER, # from
email_to_list, # to
)
return render(request, 'main/home.html')
app3/views.py
Example of sending emails with helper functions:
from django.core.mail import send_mail
from django.conf import settings
def send_email(email_subject, email_message, email_to_list):
# send email only if host's email and password is set
if settings.EMAIL_HOST_USER and settings.EMAIL_HOST_PASSWORD and len(email_to_list) >0:
send_mail(
email_subject, # subject
email_message, # message
settings.EMAIL_HOST_USER, # from
email_to_list, # to
)
app3/utils.py
from django.shortcuts import render
from app3.utils import send_email
def send_to_requester_views(request):
email_to_list= [request.user.email]
send_email("Subject of Email", "Hello there!", email_to_list)
return render(request, 'main/home.html')
def send_to_all_views(request):
email_to_list=User.objects.values_list('email', flat=True)
send_email("Subject of Email Wohooo", "Hello ALL!", email_to_list)
return render(request, 'main/home.html')
app3/views2.py
The difference is quite stark, to be honest and it would be much more needed for more complicated and complex functions.
4. YAGNI in Django
YAGNI or You Aren’t Gonna Need It is a principle that tells us to not get ahead of ourselves. Sometimes I and maybe some of you would write code for the ‘future’. Maybe we’ll need this or that later on, so might as well write it now right? Nope! It’s really a bad habit for clean code because most often than not, you aren’t gonna need that code.
For example, I’ll go back to the decorators that we’ve discussed before. Maybe after learning that we can make @login_required decorators, we would like to make another one for users who are qualified.
Well, we’ve not really implemented this qualified thing, but maybe we’ll write it now for the ‘future’.
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.http import HttpResponseForbidden
def login_required(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
# If user is authenticated, let go to the view
return view_func(request, *args, **kwargs)
else:
# If user is not authenticated, redirect to login page
return HttpResponseRedirect(reverse('users:login'))
return wrapper_func
# NEW DECORATER FOR THE 'FUTURE'
def qualified_required(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_qualified:
# If user is qualified, let go to the view
return view_func(request, *args, **kwargs)
else:
# If user is not qualified, return HTTP status 403 FORBIDDEN
return HttpResponseForbidden()
return wrapper_func
app4/decorators.py
Source: Medium - Rico Tadjudin
The Tech Platform
Comments