返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
DjangoStarter

About the currently developing DjangoStarter v3 version

This recent project heavily utilized Python and its associated ecosystem, and thus I naturally chose my DjangoStarter as the backend framework.

曦远 (DealiAxy)约 29 分钟
目录13 个章节

Preface

This project I've been working on recently heavily uses Python and its related ecosystem, so naturally I chose my DjangoStarter as the backend framework.

The previous v2 version used RestFramework for the API, but after I tried django-ninja once, I fell in love with this FastAPI-like way of building APIs.

As the saying goes, "DRF has been a pain point for many" (天下苦 drf 久矣). In the new v3 version of the framework, I decided to replace the entire RestFramework with ninja.

Most of the functionality is now complete, and the code is in the v3-alpha branch of the main project. Once development is finished, I will merge it into master (it's basically usable now).

Additionally, many new features and improvements have been made, which I will introduce next.

About DjangoStarter

This development scaffold was initially called DjangoRails, with the name inspired by Ruby on Rails.

It was later renamed to DjangoStarter after a refactoring.

This scaffold originated from a project started in 2020. At the time, Django + RestFramework was used as the backend framework. To meet the requirements of the security department (安全部门), extensive customizations were made, along with other configurations. Over time, these accumulated into DjangoStarter~

Here’s a project introduction~

DjangoStarter v3 is a next-generation rapid development template for Django projects, designed to enhance development efficiency and performance.

Combining Django's rich features with the performance, flexibility, and simplicity of Django-Ninja, the v3 version aims to provide developers with a more powerful, streamlined, and fast development experience.

With this new framework version, developers can quickly set up a project infrastructure that meets modern web application standards.

Core Features

  • Django Ninja Integration: Uses Django Ninja instead of the traditional Django Rest Framework, bringing performance optimization and a more concise coding experience for API development. By leveraging Python type hints, it automatically generates interactive API documentation, eliminating the need for the cumbersome manual documentation configurations of drf-yasg, while improving code readability and maintainability.
  • Enhanced Security: Built-in security features include, but are not limited to, admin login CAPTCHA (验证码) and IP restrictions (IP 限制), ensuring application security.
  • Code Auto-Generation: The v3 version further optimizes the code generator. By shedding the DRF dependency, it only requires defining models to generate schemas and RESTful APIs, and can also automatically create test cases based on definitions, significantly boosting development efficiency.
  • Random Seed Data Generation: The v3 version includes a built-in seed module (seed 模块) that supports automatically populating fake data for existing models, facilitating development and testing.
  • Modular Project Structure: Introduces a more modular project structure design (模块化项目结构设计), allowing developers to add or remove functional modules as needed, making project maintenance simpler.
  • Modern Frontend Integration: Provides integration with modern frontend technologies and uses NPM and gulp to manage frontend resources, helping developers build richly interactive user interfaces.
  • Containerization Support: Includes built-in Dockerfile and docker-compose.yml configurations, simplifying the containerization deployment process and supporting one-click deployment to any Docker-supported environment.
  • Comprehensive Documentation & Community Ecosystem: Offers complete documentation and guides covering every step from project startup to deployment. Simultaneously, leveraging the active Django open-source community, developers can easily access support and feedback.

About Version 3

Okay, now let's get back on track.

The primary reason for refactoring to v3 is to replace RestFramework with ninja.

Additionally, several new features have been implemented:

  • New automatic code generation feature
  • Improved unit tests and integration tests; combined with code generation, it can automatically generate CRUD test cases for each application.
  • Random seed data; currently using faker for generating fake data. Planning to further implement a seed data mechanism similar to EFCore to make the data more natural.
  • New login endpoint
  • Integration with multiple third-party login services (currently WeChat, Mini Programs, and WeCom)
  • Using tailwindcss to replace bootstrap for the frontend implementation (only for some simple admin displays, the focus remains on the API)
  • Split settings configuration, supporting multiple environment configurations like in AspNetCore
  • Switched the package manager

This is roughly the current state for now.

As new needs arise, they will be integrated step by step in the future.

Code Generation

I haven't properly introduced the specific implementation of the DjangoStarter framework yet.

The code generation part is actually not that complex. The package name is django_starter.contrib.code_generator.

It mainly consists of two parts:

  • Analyzer - src/django_starter/contrib/code_generator/analyzer.py
  • Generator - src/django_starter/contrib/code_generator/generator.py

The analyzer uses get_app_config and get_models provided by django.apps.apps to scan all registered apps.

It then uses reflection (or in Python, it should be called introspection via the inspect module) to get information about each field, and saves the collected information to a few objects I defined.

Then, in the generator section, based on specific rules, rendering is performed using jinja2 templates~

Remember to extract properties like primary_key and is_relation of the fields, as they will be useful later. For relational fields (like foreign keys), target_field also needs to be extracted.

This is the general idea. There are many details involved, but due to the limited length of this article, I will write another article to explain them later.

API

Using ninja to write APIs

Unlike the previous RestFramework, ninja uses decorators to define routes, which is very friendly for people who don't like Django's configuration-based routing

I've also made some adjustments to the project structure

Using the demo app as an example

Each model has a separate package created under apis, each with its own apis.py and schemas.py files, so that all the code logic isn't mixed together

PS: If I later switch to using FastApi, I can also use this approach to organize the project

 demo
 ├─ tests
 │  ├─ __init__.py
 │  ├─ test_music_album.py
 │  ├─ test_music.py
 │  ├─ test_movie.py
 │  └─ test_actor.py
 ├─ migrations
 │  ├─ __init__.py
 │  └─ 0001_initial.py
 ├─ apis
 │  ├─ music_album
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  ├─ music
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  ├─ movie
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  ├─ actor
 │  │  ├─ __init__.py
 │  │  ├─ schemas.py
 │  │  └─ apis.py
 │  └─ __init__.py
 ├─ __init__.py
 ├─ views.py
 ├─ models.py
 ├─ apps.py
 └─ admin.py

Pitfalls

To be honest, for a relatively new library like Ninja, there are still a few potential pitfalls:

  • Some documentation is not detailed enough
  • The URL reverse functionality is not very convenient. You can only configure urls_namespace in the NinjaAPI object, and the routers at various lower levels do not allow setting urls_namespace. I can only name the sub-endpoints using formats like url_name='demo/movie/list'.
  • ModelSchema has limited support for foreign keys. For input schemas, you cannot configure the foreign key field in Meta.fields; you need to define it separately. This can be somewhat inconvenient for auto-generated code, but I have already resolved it.

Seed Data / Mock Data

This can be called seed data or mock data.

It's very useful in development and testing, as it eliminates the need to manually add various types of data.

The package name is django_starter.contrib.seed.

Initially, I found a library called django-seed that could generate seed data.

However, this package has been unmaintained for years and hasn't been updated in a long time.

When I tried it, it actually relied on the PostgreSQL library to run?!

That's absurd—shouldn't it be database-agnostic?...

Anyway, I decided to write it myself. It's not that hard.

The Python ecosystem is great; the Faker library is excellent for generating random mock data.

The main code is in the src/django_starter/contrib/seed/seeder.py file.

It works by using different fake data generation methods based on different field types.

Pitfall: Foreign Key

The foreign key field can be particularly tricky and requires some special handling.

related_model = field.related_model

Ensure there is at least one instance of the related model

related_instance = related_model.objects.order_by('?').first()
if not related_instance:
  related_instance = related_model.objects.create(**self.seed(related_model))

The content you provided is already written in English (both the comment and the code), so no translation was needed. I've preserved the formatting exactly as provided.

Set the foreign key ID field

fake_data[field.attname] = related_instance.pk

## Settings Split

I've been unhappy with Django's `settings.py` configuration file for a long time.

When the project grows, this file becomes messy, lengthy, and unpleasant to work with.

It also doesn't support multiple environment switching, requiring you to write a bunch of logic to handle different environments.

In previous versions, I would split the main configurations into different files and then reference them in `settings.py`.

Now I'm using the `django-split-settings` package, and it feels instantly comfortable.

Let's take a look at the current `config` directory:

```css
 config
 ├─ settings
 │  ├─ environments
 │  │  ├─ __init__.py
 │  │  ├─ testing.py
 │  │  ├─ production.py
 │  │  ├─ local.py.template
 │  │  └─ development.py
 │  ├─ components
 │  │  ├─ __init__.py
 │  │  ├─ simpleui.py
 │  │  ├─ rq.py
 │  │  ├─ ninja.py
 │  │  ├─ logging.py
 │  │  ├─ django_starter.py
 │  │  ├─ database.py
 │  │  ├─ csp.py
 │  │  ├─ cors.py
 │  │  ├─ common.py
 │  │  ├─ captcha.py
 │  │  └─ caches.py
 │  └─ __init__.py
 ├─ __init__.py
 ├─ wsgi.py
 ├─ urls_root.py
 ├─ urls.py
 ├─ env_init.py
 ├─ asgi.py
 └─ apis.py

As you can see, settings has now become a package.

Various configurations are split out and distributed under components.

Then, different environments are placed under environments, allowing them to override the previously defined configurations.

This effectively resolves the previous pain points.

Switching Package Managers

Originally, I used pip directly, managing dependencies with requirements.txt.

I won't elaborate on the pros and cons of this approach.

This time, I switched to pdm, which finally feels like a more modern package manager.

PS: I actually tried poetry before, but I occasionally encountered weird issues, so I gave up on it~

Summary

That's about it. I'll update this if I come up with any new ideas later.

Also, I might write separate articles to give more detailed introductions to some of the key updates~