You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写Django命令自动生成代码?含模型配套代码生成需求

当然可以!Django不仅支持你自定义管理命令来实现这个需求,还有一些现成工具能帮你减少重复工作,下面分两部分给你详细说明:

一、现成工具推荐

如果不想从零开始写代码,这些工具可以帮你快速生成大部分 boilerplate:

  • django-extensions:这个第三方包提供了很多实用的管理命令,虽然没有直接生成你要的全部内容,但可以通过runscript扩展自己的生成逻辑,它的shell_plus等工具也能辅助模型开发;
  • django-crud-generator:主要用于生成CRUD视图和模板,但可以配置扩展生成admin类、基础服务函数和测试用例;
  • factory-boy 配套工具:社区里有一些工具可以根据模型自动生成FactoryBoy的工厂类,省去手动编写字段默认值的麻烦。

不过如果你的项目有特定的代码规范(比如服务函数的命名、admin的展示配置),自定义命令是最灵活的选择,完全贴合你的需求。

二、自行编写自定义Django命令

下面是一个完整的实现示例,能自动生成admin类、服务函数、FactoryBoy工厂类和测试用例:

1. 创建命令目录结构

在你的Django应用(比如myapp)下创建management/commands目录,同时创建空的__init__.py文件:

myapp/
├── management/
│   ├── __init__.py
│   └── commands/
│       ├── __init__.py
│       └── generate_model_boilerplate.py

2. 编写自定义命令代码

在generate_model_boilerplate.py中写入以下代码:

from django.core.management.base import BaseCommand, CommandError
from django.apps import apps
import os
from django.template import Template, Context

class Command(BaseCommand):
    help = 'Generates admin, services, factory and test boilerplate for a Django model'

    def add_arguments(self, parser):
        parser.add_argument('app_name', type=str, help='Name of the Django app containing the model')
        parser.add_argument('model_name', type=str, help='Name of the model to generate boilerplate for')

    def handle(self, *args, **options):
        app_name = options['app_name']
        model_name = options['model_name']

        # 动态获取模型类
        try:
            model = apps.get_model(app_name, model_name)
        except LookupError:
            raise CommandError(f"Model {model_name} not found in app {app_name}")

        app_dir = os.path.join(os.getcwd(), app_name)
        self.generate_admin(app_name, model_name, app_dir)
        self.generate_services(app_name, model_name, app_dir)
        self.generate_factory(app_name, model_name, model, app_dir)
        self.generate_tests(app_name, model_name, model, app_dir)

        self.stdout.write(self.style.SUCCESS(f'Successfully generated boilerplate for {model_name} in {app_name}'))

    def generate_admin(self, app_name, model_name, app_dir):
        admin_path = os.path.join(app_dir, 'admin.py')
        template = Template("""
from django.contrib import admin
from {{ app_name }}.models import {{ model_name }}

@admin.register({{ model_name }})
class {{ model_name }}Admin(admin.ModelAdmin):
    list_display = [field.name for field in {{ model_name }}._meta.fields]
    search_fields = [field.name for field in {{ model_name }}._meta.fields if field.get_internal_type() in ('CharField', 'TextField')]
""")
        context = Context({'app_name': app_name, 'model_name': model_name})
        content = template.render(context).strip()

        # 处理文件:不存在则创建,存在则检查是否已有该类,无则追加
        if os.path.exists(admin_path):
            with open(admin_path, 'r') as f:
                existing_content = f.read()
            if f'class {model_name}Admin' not in existing_content:
                with open(admin_path, 'a') as f:
                    f.write('\n\n' + content)
                self.stdout.write(f'Added {model_name}Admin to {admin_path}')
            else:
                self.stdout.write(f'{model_name}Admin already exists in {admin_path}')
        else:
            with open(admin_path, 'w') as f:
                f.write(content)
            self.stdout.write(f'Created {admin_path} with {model_name}Admin')

    def generate_services(self, app_name, model_name, app_dir):
        services_path = os.path.join(app_dir, 'services.py')
        model_lower = model_name.lower()
        template = Template("""
from {{ app_name }}.models import {{ model_name }}
from django.core.exceptions import ObjectDoesNotExist

def get_{{ model_lower }}(pk):
    try:
        return {{ model_name }}.objects.get(pk=pk)
    except {{ model_name }}.DoesNotExist:
        return None

def create_{{ model_lower }}(**kwargs):
    return {{ model_name }}.objects.create(**kwargs)

def update_{{ model_lower }}(instance, **kwargs):
    for key, value in kwargs.items():
        if hasattr(instance, key):
            setattr(instance, key, value)
    instance.save()
    return instance

def delete_{{ model_lower }}(instance):
    instance.delete()
""")
        context = Context({'app_name': app_name, 'model_name': model_name, 'model_lower': model_lower})
        content = template.render(context).strip()

        if os.path.exists(services_path):
            with open(services_path, 'r') as f:
                existing_content = f.read()
            if f'def get_{model_lower}' not in existing_content:
                with open(services_path, 'a') as f:
                    f.write('\n\n' + content)
                self.stdout.write(f'Added {model_name} services to {services_path}')
            else:
                self.stdout.write(f'{model_name} services already exist in {services_path}')
        else:
            with open(services_path, 'w') as f:
                f.write(content)
            self.stdout.write(f'Created {services_path} with {model_name} services')

    def generate_factory(self, app_name, model_name, model, app_dir):
        factory_path = os.path.join(app_dir, 'factories.py')
        fields = [f for f in model._meta.fields if not f.primary_key]
        template = Template("""
import factory
from {{ app_name }}.models import {{ model_name }}

class {{ model_name }}Factory(factory.django.DjangoModelFactory):
    class Meta:
        model = {{ model_name }}

{% for field in fields %}
{% if field.get_internal_type == 'CharField' and field.name != 'slug' %}
    {{ field.name }} = factory.Faker('word')
{% elif field.get_internal_type == 'TextField' %}
    {{ field.name }} = factory.Faker('text')
{% elif field.get_internal_type == 'IntegerField' %}
    {{ field.name }} = factory.Faker('random_int', min=1, max=100)
{% elif field.get_internal_type == 'FloatField' %}
    {{ field.name }} = factory.Faker('pyfloat', positive=True)
{% elif field.get_internal_type == 'BooleanField' %}
    {{ field.name }} = factory.Faker('boolean')
{% elif field.get_internal_type == 'DateTimeField' %}
    {{ field.name }} = factory.Faker('date_time_this_year')
{% elif field.get_internal_type == 'DateField' %}
    {{ field.name }} = factory.Faker('date_this_year')
{% endif %}
{% endfor %}
""")
        context = Context({'app_name': app_name, 'model_name': model_name, 'fields': fields})
        content = template.render(context).strip()

        if os.path.exists(factory_path):
            with open(factory_path, 'r') as f:
                existing_content = f.read()
            if f'class {model_name}Factory' not in existing_content:
                with open(factory_path, 'a') as f:
                    f.write('\n\n' + content)
                self.stdout.write(f'Added {model_name}Factory to {factory_path}')
            else:
                self.stdout.write(f'{model_name}Factory already exists in {factory_path}')
        else:
            with open(factory_path, 'w') as f:
                f.write(content)
            self.stdout.write(f'Created {factory_path} with {model_name}Factory')

    def generate_tests(self, app_name, model_name, model, app_dir):
        test_dir = os.path.join(app_dir, 'tests')
        if not os.path.exists(test_dir):
            os.makedirs(test_dir)
            with open(os.path.join(test_dir, '__init__.py'), 'w') as f:
                f.write('')
        
        model_lower = model_name.lower()
        test_path = os.path.join(test_dir, f'test_{model_lower}.py')
        model_fields = [f for f in model._meta.fields if not f.primary_key]
        template = Template("""
from django.test import TestCase
from {{ app_name }}.models import {{ model_name }}
from {{ app_name }}.factories import {{ model_name }}Factory
from {{ app_name }}.services import get_{{ model_lower }}, create_{{ model_lower }}, update_{{ model_lower }}, delete_{{ model_lower }}

class {{ model_name }}ModelTests(TestCase):
    def test_create_{{ model_lower }}(self):
        obj = {{ model_name }}Factory()
        self.assertTrue(isinstance(obj, {{ model_name }}))
        self.assertIsNotNone(obj.pk)

    def test_get_{{ model_lower }}(self):
        obj = {{ model_name }}Factory()
        retrieved_obj = get_{{ model_lower }}(obj.pk)
        self.assertEqual(obj, retrieved_obj)

    def test_get_nonexistent_{{ model_lower }}(self):
        retrieved_obj = get_{{ model_lower }}(999)
        self.assertIsNone(retrieved_obj)

    def test_update_{{ model_lower }}(self):
        obj = {{ model_name }}Factory()
        update_data = {}
        {% for field in model_fields %}
        {% if field.get_internal_type == 'CharField' and field.name != 'slug' %}
        update_data['{{ field.name }}'] = 'Updated Test Value'
        {% elif field.get_internal_type == 'IntegerField' %}
        update_data['{{ field.name }}'] = 200
        {% endif %}
        {% endfor %}
        
        if update_data:
            updated_obj = update_{{ model_lower }}(obj, **update_data)
            for key, value in update_data.items():
                self.assertEqual(getattr(updated_obj, key), value)

    def test_delete_{{ model_lower }}(self):
        obj = {{ model_name }}Factory()
        obj_pk = obj.pk
        delete_{{ model_lower }}(obj)
        with self.assertRaises({{ model_name }}.DoesNotExist):
            {{ model_name }}.objects.get(pk=obj_pk)
""")
        context = Context({'app_name': app_name, 'model_name': model_name, 'model_lower': model_lower, 'model_fields': model_fields})
        content = template.render(context).strip()

        if os.path.exists(test_path):
            self.stdout.write(f'Test file {test_path} already exists, skipping creation')
        else:
            with open(test_path, 'w') as f:
                f.write(content)
            self.stdout.write(f'Created test file {test_path} for {model_name}')

3. 使用命令

在项目根目录下运行:

python manage.py generate_model_boilerplate your_app_name YourModelName

4. 扩展和调整

你可以根据自己的项目规范修改模板内容:

  • 比如在admin类中添加list_filter、readonly_fields;
  • 调整服务函数的逻辑(比如添加事务、权限校验);
  • 自定义FactoryBoy的字段生成规则(比如针对特定字段使用自定义Faker provider);
  • 扩展测试用例,覆盖更多场景(比如字段验证、关联模型测试)。

内容的提问来源于stack exchange,提问作者user2937998

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:13:26