编码风格

编写用于包含在 Django 中的代码时,请遵循以下编码标准。

提交前检查

pre-commit 是一个用于管理提交前钩子的框架。这些钩子有助于在提交代码以供审查之前识别简单的问题。通过在代码审查之前检查这些问题,审阅者可以专注于更改本身,并且还可以帮助减少 CI 运行的次数。

要使用该工具,首先安装pre-commit,然后安装 git 钩子

$ python -m pip install pre-commit
$ pre-commit install
...\> py -m pip install pre-commit
...\> pre-commit install

在第一次提交时,pre-commit 将安装钩子,这些钩子安装在其自己的环境中,并且在第一次运行时需要一段时间才能安装。随后的检查将显著加快。如果发现错误,将显示相应的错误消息。如果错误与blackisort有关,则该工具将为您修复它们。如果您对更改感到满意,请查看更改并重新暂存以进行提交。

Python 风格

  • 所有文件都应使用 black 自动格式化程序进行格式化。如果配置了pre-commit,它将运行此程序。

  • 项目存储库包含一个.editorconfig文件。我们建议使用具有EditorConfig支持的文本编辑器,以避免缩进和空格问题。Python 文件使用 4 个空格进行缩进,HTML 文件使用 2 个空格。

  • 除非另有说明,否则请遵循PEP 8

    使用 flake8 检查这方面的潜在问题。请注意,我们的.flake8文件包含一些排除的文件(我们不关心清理的已弃用模块和 Django 供应商的一些第三方代码),以及一些我们不认为是严重违规的排除错误。请记住PEP 8只是一个指南,因此请将周围代码的风格作为首要目标。

    PEP 8的一个例外是关于行长的规则。如果这意味着代码看起来难看得多或难以阅读,则不要将代码行限制为 79 个字符。我们允许最多 88 个字符,因为这是black使用的行长。运行flake8时会包含此检查。文档、注释和文档字符串应在 79 个字符处换行,即使PEP 8建议 72 个字符。

  • 字符串变量插值可以使用%-格式化f-字符串str.format(),目标是最大限度地提高代码可读性。

    最终的可读性判断权留给合并者的自由裁量权。作为指导,f-字符串应仅使用简单的变量和属性访问,对于更复杂的情况,应先进行局部变量赋值。

    # Allowed
    f"hello {user}"
    f"hello {user.name}"
    f"hello {self.user.name}"
    
    # Disallowed
    f"hello {get_user()}"
    f"you are {user.age * 365.25} days old"
    
    # Allowed with local variable assignment
    user = get_user()
    f"hello {user}"
    user_days_old = user.age * 365.25
    f"you are {user_days_old} days old"
    

    对于可能需要翻译的任何字符串(包括错误和日志消息),都不应使用 f-字符串。一般来说,format() 更冗长,因此更倾向于使用其他格式化方法。

    不要浪费时间对现有代码进行无关的重构以调整格式化方法。

  • 避免在注释中使用“我们”,例如使用“遍历”而不是“我们遍历”。

  • 对于变量、函数和方法名称,使用下划线而不是 camelCase(即poll.get_unique_voters(),而不是poll.getUniqueVoters())。

  • 对于类名(或返回类的工厂函数),使用InitialCaps

  • 在文档字符串中,遵循现有文档字符串和PEP 257的风格。

  • 在测试中,使用assertRaisesMessage()assertWarnsMessage()代替assertRaises()assertWarns(),以便您可以检查异常或警告消息。仅当您需要正则表达式匹配时,才使用assertRaisesRegex()assertWarnsRegex()

    对于测试布尔值,使用assertIs(…, True/False),而不是assertTrue()assertFalse(),以便您可以检查实际的布尔值,而不是表达式的真假性。

  • 在测试文档字符串中,说明每个测试演示的预期行为。不要包含诸如“测试”或“确保”之类的序言。

    将工单引用保留用于难以在文档字符串或注释中轻松描述的模糊问题,其中工单包含其他详细信息。在句子的末尾包含工单号,例如:

    def test_foo():
        """
        A test docstring looks like this (#123456).
        """
        ...
    

导入

  • 使用isort 使用以下准则来自动执行导入排序。

    快速入门

    $ python -m pip install "isort >= 5.1.0"
    $ isort .
    
    ...\> py -m pip install "isort >= 5.1.0"
    ...\> isort .
    

    这将从您的当前目录递归运行isort,修改任何不符合准则的文件。如果您需要按非标准顺序导入(例如,为了避免循环导入),请使用如下注释:

    import module  # isort:skip
    
  • 将导入分为以下几组:future、标准库、第三方库、其他 Django 组件、本地 Django 组件、try/excepts。按每个组中完整模块名称的字母顺序对行进行排序。将所有import module语句放在每个部分的from module import objects之前。对其他 Django 组件使用绝对导入,对本地组件使用相对导入。

  • 在每一行中,按字母顺序排列项目,并将大写字母项目分组在大写字母项目之前。

  • 使用括号换行,并将续行缩进 4 个空格。在最后一个导入后包含一个尾随逗号,并将右括号放在它自己的行上。

    在最后一个导入和任何模块级代码之间使用单行空行,并在第一个函数或类上方使用两行空行。

    例如(注释仅用于解释目的):

    django/contrib/admin/example.py
    # future
    from __future__ import unicode_literals
    
    # standard library
    import json
    from itertools import chain
    
    # third-party
    import bcrypt
    
    # Django
    from django.http import Http404
    from django.http.response import (
        Http404,
        HttpResponse,
        HttpResponseNotAllowed,
        StreamingHttpResponse,
        cookie,
    )
    
    # local Django
    from .models import LogEntry
    
    # try/except
    try:
        import yaml
    except ImportError:
        yaml = None
    
    CONSTANT = "foo"
    
    
    class Example: ...
    
  • 尽可能使用便捷导入。例如,执行以下操作:

    from django.views import View
    

    而不是:

    from django.views.generic.base import View
    

模板风格

在 Django 模板代码中遵循以下规则。

  • {% extends %} 应是非注释的第一行。

    执行以下操作:

    {% extends "base.html" %}
    
    {% block content %}
      <h1 class="font-semibold text-xl">
        {{ pages.title }}
      </h1>
    {% endblock content %}
    

    或者:

    {# This is a comment #}
    {% extends "base.html" %}
    
    {% block content %}
      <h1 class="font-semibold text-xl">
        {{ pages.title }}
      </h1>
    {% endblock content %}
    

    不要执行以下操作:

    {% load i18n %}
    {% extends "base.html" %}
    
    {% block content %}
      <h1 class="font-semibold text-xl">
        {{ pages.title }}
      </h1>
    {% endblock content %}
    
  • {{、变量内容和}}之间放置恰好一个空格。

    执行以下操作:

    {{ user }}
    

    不要执行以下操作:

    {{user}}
    
  • {% load ... %}中,按字母顺序列出库。

    执行以下操作:

    {% load i18n l10 tz %}
    

    不要执行以下操作:

    {% load l10 i18n tz %}
    
  • {%、标签内容和%}之间放置恰好一个空格。

    执行以下操作:

    {% load humanize %}
    

    不要执行以下操作:

    {%load humanize%}
    
  • 如果{% block %}标签名称不在同一行,则将其放在{% endblock %}标签中。

    执行以下操作:

    {% block header %}
    
      Code goes here
    
    {% endblock header %}
    

    不要执行以下操作:

    {% block header %}
    
      Code goes here
    
    {% endblock %}
    
  • 在大括号内,用单个空格分隔标记,除了属性访问的.和过滤器|周围。

    执行以下操作:

    {% if user.name|lower == "admin" %}
    

    不要执行以下操作:

    {% if user . name | lower  ==  "admin" %}
    
    {{ user.name | upper }}
    
  • 在使用{% extends %}的模板中,避免缩进顶层{% block %}标签。

    执行以下操作:

    {% extends "base.html" %}
    
    {% block content %}
    

    不要执行以下操作:

    {% extends "base.html" %}
    
      {% block content %}
      ...
    

视图风格

  • 在 Django 视图中,视图函数的第一个参数应称为request

    执行以下操作:

    def my_view(request, foo): ...
    

    不要执行以下操作:

    def my_view(req, foo): ...
    

模型风格

  • 字段名称应全部小写,使用下划线而不是 camelCase。

    执行以下操作:

    class Person(models.Model):
        first_name = models.CharField(max_length=20)
        last_name = models.CharField(max_length=40)
    

    不要执行以下操作:

    class Person(models.Model):
        FirstName = models.CharField(max_length=20)
        Last_Name = models.CharField(max_length=40)
    
  • class Meta应出现在字段定义*之后*,字段和类定义之间用单行空行分隔。

    执行以下操作:

    class Person(models.Model):
        first_name = models.CharField(max_length=20)
        last_name = models.CharField(max_length=40)
    
        class Meta:
            verbose_name_plural = "people"
    

    不要执行以下操作:

    class Person(models.Model):
        class Meta:
            verbose_name_plural = "people"
    
        first_name = models.CharField(max_length=20)
        last_name = models.CharField(max_length=40)
    
  • 模型内部类和标准方法的顺序应如下所示(注意,并非所有这些都是必需的):

    • 所有数据库字段

    • 自定义管理器属性

    • Meta

    • def __str__()

    • def save()

    • def get_absolute_url()

    • 任何自定义方法

  • 如果为给定的模型字段定义了choices,请将每个选项定义为映射,并使用全大写名称作为模型上的类属性。示例

    class MyModel(models.Model):
        DIRECTION_UP = "U"
        DIRECTION_DOWN = "D"
        DIRECTION_CHOICES = {
            DIRECTION_UP: "Up",
            DIRECTION_DOWN: "Down",
        }
    

    或者,可以考虑使用枚举类型

    class MyModel(models.Model):
        class Direction(models.TextChoices):
            UP = "U", "Up"
            DOWN = "D", "Down"
    

使用django.conf.settings

模块通常不应在顶层使用存储在django.conf.settings中的设置(即在导入模块时计算)。解释如下:

允许并可以按如下方式手动配置设置(即不依赖于DJANGO_SETTINGS_MODULE环境变量):

from django.conf import settings

settings.configure({}, SOME_SETTING="foo")

但是,如果在settings.configure行之前访问任何设置,则此方法将不起作用。(在内部,settings是一个LazyObject,如果尚未配置,则在访问设置时会自动配置自身)。

因此,如果有一个模块包含如下代码:

from django.conf import settings
from django.urls import get_callable

default_foo_view = get_callable(settings.FOO_VIEW)

…那么导入此模块将导致配置设置对象。这意味着第三方在顶层导入模块的能力与手动配置设置对象的能力不兼容,或者在某些情况下会使其变得非常困难。

不要使用上述代码,而应使用一层惰性或间接性,例如django.utils.functional.LazyObjectdjango.utils.functional.lazy()lambda

其他

  • 标记所有需要国际化的字符串;详情请参见国际化文档

  • 更改代码时,请移除不再使用的import语句。flake8 将为您识别这些导入。如果由于向后兼容性需要保留未使用的导入,请在末尾标记为# NOQA以消除 flake8 警告。

  • 系统地移除代码中所有尾随空格,因为这些空格会增加不必要的字节,增加补丁的可视混乱,并且有时还会导致不必要的合并冲突。某些 IDE 可以配置为自动移除它们,大多数 VCS 工具可以设置为突出显示差异输出中的它们。

  • 请不要在您贡献的代码中添加您的姓名。我们的策略是将贡献者的姓名保留在 Django 附带的AUTHORS文件中,而不是分散在代码库本身中。如果您进行了不止一次微不足道的更改,请随时在您的补丁中包含对AUTHORS文件的更改。

JavaScript 风格

有关 Django 使用的 JavaScript 代码风格的详细信息,请参见JavaScript 代码

返回顶部