Django’s design philosophy

Django’s design philosophy


A web framework for perfectionists with deadlines

Django has the slogan “The Web framework for perfectionists with deadlines” and values the following design philosophy. Here are some more of the philosophies of Django in the table below.

PhilosophyMeaning
DRY(Don’t Repeat Yourself)The goal is to avoid code coupling and reuse. Django decouples the code, shares it, and provides each part separately.
Convention over Configuration(CoC) Django minimizes the parts that developers need to configure and instead provides general settings and structures as default. This helps developers have to make fewer decisions and develop faster.
Explicit is better than implicit Code must be clear and explicit. When code is written clearly, it is easier for other developers to understand and maintain the code.
Loose CouplingEach component should operate independently and not be strongly coupled with other parts. This increases flexibility and makes it easier to expand or modify the system.
Less CodeDjango provides many features using a concise and simple syntax. The goal is to enable you to do more with less code.
Fast DevelopmentDjango aims for rapid development, and its goal is to help develop quickly from prototype to actual product release.
Philosophies of Django

Rapid development



The key to a 21st century web framework is to make the boring parts fast. Accordingly, Django enables rapid web development. Time is often the biggest cost in project development, and reducing duplication helps you focus on service development with high productivity. However, larger companies may have more important considerations than time. These companies may consider options such as hiring hundreds or more developers, having developers work in their own areas, setting flexible development windows, and investing more to increase performance.

Loose Coupling


The fundamental goal of the Django stack is “loose coupling, tight cohesion.” Each layer of the framework must be uncoupled from each other until necessary. Additionally, there are implementations of Django Best Practice suggestions, but these are suggestions and are not mandatory. Therefore, development is possible with any architecture.

  • URLs – Mapping functions to handle requests
  • Templates – Complex string combinations such as HTML
  • File storages API – Supports various file systems with abstracted file system API (local/AWS S3, etc.)
  • Geo Spatial – Geography/Space
  • Email – Sending email
  • Views – Function that handles the request
  • Forms – Creating HTML Form tags and validating input values
  • Caching – Improve response speed by storing/utilizing calculation results (memory, Redis, files, DB, etc.)
  • l18n/l10n – Internationalization/Localization: Supports language translation and localization according to the user’s language settings.
  • Sitemaps – SiteMap
  • Models – Responsible for interface with database
  • Validators – Implement validation logic as a pure function
  • Sessions – Store arbitrary types of data on the server (logged-in users, shopping carts, etc.)
  • Logging – Logging
  • Syndication feeds(RSS/Atom)

Less Code


Reduce repetition, use as little code as possible, and avoid stereotypical code from other languages/frameworks.

  • Take full advantage of Python’s dynamic features, such as introspection (the process of examining an object’s metadata).
  • The better you know Python, the better you can use Django.

5 Endpoints Required if Developing Post REST API

  • GET – /api/v1/posts/ : Request to view list
  • POST – /api/v1/posts/ : Request to create a single item
  • GET – /api/v1/posts/pk/ : Request a single query
  • PUT/PATCH – /api/v1/posts/pk/ : Request to modify a single item
  • DELETE – /api/v1/posts/pk/ : Request to delete a single item
Python
# API implementation using django-rest-framework
# URL Patterns : Matching the request URL, mapping to Views
router = DefaultRouter()
router.register("posts", PostViewSet)

urlpatterns = [
    path("api/v1/", include(router.urls)),
]

The django-rest-framework (DRF) library is a library for web API development that faithfully follows the philosophy of Django.

Python
# Views : Handles requests directly (similar to controller layer)
class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

Using DRF’s ModelViewSet, a Web API is created that can process requests for the previous five Endpoints with only two implementations : a query set and a serializer class.

Python
# Serializers/Forms
# Data validation and response data generation
# (similar to service layer)
class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"

The serializer determines which data and structure to provide the response. This can be seen as a similar role to Spring’s PostListResponseDto, which we will see later below.

The Meta.fields = “__all__” property specified in PostSerilizer means that a response will be created according to the details of all fields of the Post model specified as model.

Python
# Models : Domain model and ORM
# Similar to storage layer
class Post(models.Model):
    title = models.CharField(max_length = 100)
    content = models.TextField()

Django models have an id field defined by default. Therefore, a response is created with the id, title, and content fields through the definition of fields = “__all__” in the preceding PostSerializer. In addition, PostSerializer intervenes in requests for post creation/editing, supporting validation and saving to the DB according to the specified field details.

Don’t repeat

Reduce duplication and aim for normalization.

  • It is sufficient for unique concepts and data to exist only once and in one place.
  • For that reason, Django allows you to create fully functional features with a stripped-down implementation.
  • ex) Class-based views (Generic Views/APIViews), ModelForm, ModelSerializer, etc.
Python
# ORM model and domain
class Post(models.Model):
    title = models.CharField(
                max_length = 100,
                verbose_name = "title",
                validators = [
                    RegexValidator(r"[ㄱ-힣]", 
                    message = "It must contain at least one Korean character."),
                ],
    )
    content = models.TextField(verbose_name = "내용")

Post model was defined. This is called a “domain” and the Post model stores the title and content of a single post. Validation is being performed on the Post model.

Python
# Responsibility for validating request details and storing data using the model
class PostForm(forms.Form):
    title = form.CharField(
            label = "title",
            validators = [
               RegexValidator(r"[ㄱ-힣]", 
               message = "It must contain at least one Korean character."),
            ],
    )
    content = forms.CharField(label = "content", widget = forms.TextArea)
    
    def save(self, commit = True):
        title = self.cleaned_data.get("title")
        content = self.cleaned_data.get("content")
        post = Post(title = title, content = content)
        if commit:
            post.save()
        return post

PostForm class of Django forms.Form type was defined. When trying to receive a new posting from a user or a request for modification to an existing posting, an HTML input form must be shown to the user. When such an HTML input form is shown to the user and the user enters the title and content and clicks the “Complete” button, the request is sent to the server, and the server must then check the validity of the value entered by the user.

Validation on the server is absolutely necessary. It checks whether the value entered by the user has the desired form on the server. When the server requests a value that does not fit the desired length or format, it must be able to reject this request. It can be said that Django’s Form performs these functions.

In the Post model, a title of up to 100 characters is allowed, and a rule is defined so that the label must contain “title” and at least 1 Korean character. Since the roles of models.Model and models.Form are different, PostForm requires the same rule definition as the Post model. If you look at the code where the rules are defined, you can see that it is almost similar to the model code. The code is similar enough that it feels like duplication.

However, the implementation cannot be strictly speaking duplicated, as they are in different areas. Model is related to DB, and Form is related to HTML Form.

Although the areas are different, Django provides functions that can reduce such code.

Python
# Responsibility for validating request details and storing data using the model
# ModelForm is just one option to reduce duplication.
# And Form is not legacy. Let’s use it appropriately depending on the situation with ModelForm.
class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title","content"]
        
# ModelForm automatically adds form fields with options specified in the Meta class.
# After that, it is the same as Form.

The Meta class is not intended to create an instance of a class, but simply uses class syntax for the purpose of specifying properties for the PostForm class. I did the same, but the syntax of classes within classes may be unfamiliar, but you can think of it as defining meta properties for the PostForm class.

Therefore, in the code above, the model form reads the title and content field details of the Post model and automatically adds form fields. The previous PostForm seen above and the current PostForm can be seen to operate the same way.

The conclusion is that ModelForm is just an option to reduce duplicate code, but it does not mean that ModelForm should be used unconditionally rather than Form. Depending on the situation, you can use Form and ModelForm.

Python
def video_list(request):
    qs = Video.objects.all()
    
    page_number = request.GET.get("page",1)
    paginator = Paginator(qs, 10)
    page_obj = paginator.page(page_number)
    qs = page_obj.object_list
    
    return render(request, "jack/video_list.html",
        {
            "page_obj" : page_obj,
            "is_paginated" : page_obj.has_other_pages(),
            "video_list" : qs,
        })
        
def article_list(request):
    qs = Article.objects.all()
    
    page_number = request.GET.get("page",1)
    paginator = Paginator(qs, 10)
    page_obj = paginator.page(page_number)
    qs = page_obj.object_list
    
    return render(request, "app/article_list.html",
        {
            "page_obj" : page_obj,
            "is_paginated" : page_obj.has_other_pages(),
            "article_list" : qs,
        })

The video_list view and article_list view were implemented with FBV. The video_list view is a view that provides an HTML response to a video list request, and the article_list view is a view that provides an HTML response to an article list request. As you can see from the code above, the logic is all the same, even down to paging processing. This can be seen as repeated code.

Python
# In the process of implementing multiple domains, repetitive parts are done 
# through class based views.Repetition can be reduced.

# In Class Based View, a ListView instance is created and processed every time a request is processed.

# Each view only has a different model class, and a ListView instance is created each time a class request is processed and process the request.

from django.views.generic import ListView

video_list = ListView.as_view(
    model = Video,
    paginated_by = 10,
)

article_list = ListView.as_view(
    model = Article,
    paginated_by = 10,
)

You can reduce duplication by making two overlapping codes into CBV as above. ListView is supported by default in Django, and functions for list HTML responses are already implemented in ListView.

📢 So should it ALWAYS be implemented as a class-based view?

That’s not necessarily true. It is also often implemented as a function-based view. If duplication occurs, duplication can be reduced through a class-based view, but it does not necessarily mean that it must be implemented as a class-based view.

Django vs Spring


Let’s look at how each framework implements posting list searches and see how the two differ. If you don’t know about Spring and Django yet, it would be best to just think “Oh, that’s right” and move on.

First, let’s take a look at how to simply implement a posting list query in Spring.

📢 It is just an example to see the overall structure, so you do not need to understand the details by just looking at the flow.

Spring Controller

Java
// Controller : Receives external requests, performs logic through services, and responds
@RequiredArgsConstructor
@Controller
public class PostController{
    private final PostService postService;
    
    @GetMapping("/")
    public String index(Model model){
        model.addAttribute("postList", postService.findAllDesc());
        return "index";
    }
}

It is a Spring controller, and when a request comes in to the top address “/”, the index instance function of the PostController class is called and processes the request. The controller’s index function calls the postService service to delegate request processing. The return value of postService.findAllDesc() is added as a new property of the model with the name “postList”, and the added property will be used when rendering the template.

Spring Service

Java
// Service : Processes actual business logic
@RequiredArgsConstructor
@Service
public class PostService{
    private final PostRepository postRepository;
    
    public List<PostListResponseDto> findAllDesc(){
        return postRepository
               .findAllDesc()
               .stream()
               .map(PostListResponseDto::new)
               .collect(Collectors.toList());
    }
}

In PostService, the return value of postRepository.findAllDesc() is converted to a stream again, converted to a PostListResponseDto object list, and returned.

Spring Entities

Java
// Entity
@Getter
@NoArgsConstructor
@Entity
public class Post extends BaseTimeEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String author;
    
    @Column(length = 500, nullable = false)
    private String title;
    
    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;
}

The Post entity is mapped to a database table, and matches table column details and class details.

Java
// Storage
public interface PostRepository extends JpaRepository<Post, Long>{
    @Query("SELECT p FROM Post p ORDER BY p.id DESC")
    List<Post> findAllDesc();
}

Finally, PostRepository queries the database and returns a response. When findAllDesc is called, the query stored in @Query is executed and a Post list is returned as a return value.

Spring DTO

Java
// DTO (Data Transfer Object): Defines the response format
@Getter
public class PostListResponseDto{
    private Long id;
    private String title;
    private String author;
    
    public PostListResponseDto(Post entity){
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.author = entity.getAuthor();
    }
}

The response is originally PostList, but since it goes through the process of converting to PostListResponseDto format, the PostListResponseDto class is also defined.

Template

HTML
// index template
<table>
    <thead>
    <tr>
        <th>ID</th>
        <th>TITLE</th>
        <th>AUTHOR</th>
    </tr>
    </thead>
    <tbody>
    {{#postList}}
        <tr>
            <td>{{ id }}</td>
            <td>{{ title }}</td>
            <td>{{ author }}</td>
        </tr>
    {{/postList}}    
    </tbody>
</table>

The “index” string returned from PostController’s index renders an HTML response using a template with the index name.

We looked at a really simple example code that creates an HTML response through DB in Spring. Now, let’s take a look at how the same function is implemented in


Django URL Patterns

Python
# URL Patterns
# Django defines view definitions and URL mapping separately.
from django.urls import path
from . import views

urlpatterns = [
    path("", views.post_list, name = "post_list"),
]

Define a URL to call the post_list function when a certain URL comes in.

Django view

📢 There are two ways to create a view in Django: FBV (Function Based View) and CBV (Class Based View). However, in the current example, let’s look at Function Based View (FBV).

Python
# View (view created with a function)
# It also acts as the controller of the preceding spring and as part of the service.
from django.shortcuts import render
from blog.models import Post, Article

def post_list(request):
    qs = Post.objects.all().order_by("-id")
    
    return render(request, "index.html",{
        "post_list" : qs,
    })

When the post_list function is called, it creates a query set object that queries the DB through the Post model. The render function references a query set object named qs with the name “post_list” when creating an HTML response through index.html.

Django model

Python
# Model
# Similar to previous Spring <entity>
from django.conf import settings
from django.db import models

class Post(models.Model):
    author = models.ForeignKey(
                 settings.AUTH_USER_MODEL,
                 on_delete = models.CASCADE)
    title = models.CharField(max_length = 100)
    content = models.TextField()

Django’s Model is similar to Spring’s Entity and provides more functions.

Django Templates

HTML
{# Template : A template for complex string combinations #}
<table>
    <thead>
        <tr>
            <th>ID</th>
            <th>TITLE</th>
            <th>AUTHOR</th>
        </tr>
    </thead>
    <tbody>
        {% for post in post_list %}
            <tr>
                <td>{{ post.id }}</td>
                <td>{{ post.title }}</td>
                <td>{{ post.author }}</td>
            </tr>
        {% endfor %}
    </tbody>
</table>

If you only define the template index.html, it performs the same operation as the implementation in Spring seen above. You can see that the code is much more concise.

Leave a Reply

Your email address will not be published. Required fields are marked *