像专业人士一样设置 Django 项目 wemake-django-template

2025-05-24

像专业人士一样设置 Django 项目

wemake-django-模板

在本文中,我将向您展示如何布局一个典型的中型到复杂的django项目,该项目使用git进行源代码控制,使用pyenvpipenv来处理包,使用celeryredis来运行任务,使用pytest进行测试,使用flake8jsbeautify进行代码检查,使用sentry来管理日志,使用webpack来编译静态文件,使用 docker compose来本地提供服务,使用postgresql来存储数据,所有这些都尝试遵循十二因素应用程序配置方法。 

如果您希望直接得出结论,请查看此 repo,我已经为您准备好了一切,您可以克隆并立即开始编写新项目的代码!

注意:我并不声称对这方面拥有最终决定权,这是一个不断发展的主题,所以如果您认为我可以做得更好,请告诉我:)

Python 版本

每次发布 Python 新版本,都会添加、弃用或删除一些功能,所以你需要确保自己清楚自己正在使用哪个版本的 Python。最好的方法是使用pyenv,它是一个 Python 版本管理器,可以让你为每个项目安装和指定不同的 Python 版本。要指定一个版本,请在项目根目录下运行

pyenv local 3.7.2
Enter fullscreen mode Exit fullscreen mode

并将创建名为 的文件, 其中.python-version包含文本。现在,当您键入 时,您将获得,如果您修改文件并写入,则同一命令将输出3.7.2python --versionPython 3.7.23.6.8Python 3.6.8

包管理器和虚拟环境

对解释器的版本有信心后,我们就可以放心地考虑如何下载库以及将它们放在何处。如果是几年前,我会谈论pipvirtualenv甚至标准库的模块venv,但现在最酷的是pipenv,至少pypa是这么说的。Pipenv 可以同时处理虚拟环境和包管理。只需运行

pipenv install
Enter fullscreen mode Exit fullscreen mode

pipenv 将创建一个虚拟环境 (vm) 和两个文件:PipfilePipfile.lock。如果您使用 JavaScript,它与package.json和非常相似package-lock.json。要查看 vm 的创建位置,请输入pipenv --venv和 。这将为您提供有关 pipenv 工作原理的线索:它会自动将项目目录映射到其特定的 vm。

因为我们正在创建一个 Django 项目,所以我们将下载软件包并使用命令 将其添加到我们的虚拟机中pipenv install django。要访问虚拟机的库,您必须pipenv run在运行的每个命令前面添加此命令。例如,要检查已安装的 Django 版本,请输入pipenv run python -c "import django; print(django.__version__)"

项目布局

下图展示了我喜欢如何布局我的目录结构。

我不喜欢默认的 Django 布局,它提示我们输入一个像awesome这样的项目名称(这恰好也是我们仓库的名称),最后我们输入了像 这样的内容~/code/awesome/awesome/settings.py,这简直糟透了。我觉得把所有配置文件放在一个名为 的目录中更合理conf。你的项目名称已经是根目录的名称了。所以,让我们通过

pipenv run django-admin.py startproject conf .
mkdir {apps,assets,logs,static,media,templates,tests}
Enter fullscreen mode Exit fullscreen mode

输入 后,开发服务器应该可以正常工作了pipenv run python manage.py runserver,事实也确实如此,但有些地方不对劲,这个命令写起来非常痛苦。幸运的是,我们可以Pipfile像这样在 config.json 文件中为 pipenv 创建别名。

[scripts]
server = "python manage.py runserver"
Enter fullscreen mode Exit fullscreen mode

再试一次,这次用pipenv run server,好多了,对吧?

源代码控制

现在我们已经准备好了应用程序的骨架,它是开始跟踪我们的更改的好地方,确保我们不会跟踪秘密代码、开发媒体和日志文件等。创建一个名为的文件,.gitignore其中包含以下内容,您就可以安全地初始化您的 repo git init,并使用创建您的第一个提交git commit -am "Initialize project"

# Python bytecode
__pycache__

# Django dynamic directories
logs
media

# Environment variables
.env
.env.*

# Static files
node_modules

# Webpack
assets/webpack-bundle.dev.json
assets/bundles/style-dev-main.css
assets/bundles/style-dev-main.css.map
assets/bundles/script-dev-main.js
assets/bundles/script-dev-main.js.map
Enter fullscreen mode Exit fullscreen mode

设置和环境变量

你读过《十二要素应用》吗?那就读读吧。太懒了?我来帮你​​总结一下,至少是关于配置的部分:你应该将连接信息存储在你的环境中,例如数据库、外部存储、API 和凭证等外部服务。这样可以很容易地在代码运行的不同环境(例如开发、预发布、持续集成、测试和生产)之间切换。

有一个很棒的包叫django-environ,可以帮到我们。安装它并修改pipenv install django-environconf/settings.py以便从环境中读取所有外部服务配置和机密值。

import environ
env = environ.Env()
root_path = environ.Path(__file__) - 2
ENV = env('DJANGO_ENV')
DEBUG = env.bool('DEBUG', default=False)
SECRET_KEY = env('SECRET_KEY')
DATABASES = {'default': env.db('DATABASE_URL')}
...
Enter fullscreen mode Exit fullscreen mode

如果像在这个例子中,我们决定使用postgresql,我们需要确保安装一个像psycopg2这样的适配器pipenv install pyscopg2-binary

日志记录

日志记录是项目中很少被关注的事情之一,但如果它做得好并且存在,那就真的很棒了。让我们从正确的方向开始,修改我们的conf/settings.py文件

LOGS_ROOT = env('LOGS_ROOT', default=root_path('logs'))
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'console_format': {
            'format': '%(name)-12s %(levelname)-8s %(message)s'
        },
        'file_format': {
            'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
        }
    },
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'console_format'
        },
        'file': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(LOGS_ROOT, 'django.log'),
            'maxBytes': 1024 * 1024 * 15,  # 15MB
            'backupCount': 10,
            'formatter': 'file_format',
        },
    },
    'loggers': {
        'django': {
            'level': 'INFO',
            'handlers': ['console', 'file'],
            'propagate': False,
        },
        'apps': {
            'level': 'DEBUG',
            'handlers': ['console', 'file'],
            'propagate': False,
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

我们现在需要做的就是将所需的环境变量添加到我们的.env文件中

...
LOGS_ROOT=./logs
USE_SENTRY=on
SENTRY_DSN=https://<project-key>@sentry.io/<project-id>
Enter fullscreen mode Exit fullscreen mode

请注意,如果我们决定使用 来打开哨兵USE_SENTRY=on,我们首先需要在sentry.io上创建一个新项目pipenv install sentry-sdk并获取我们的秘密网址。

测试

测试代码是个好主意。发现了 bug?那就写一个失败的测试,修复代码,通过测试并提交。这样,你就不用担心这个 bug 会再次出现,即使其他人(很可能就是未来的你)触碰了完全不相关的部分,通过类似戈德堡过程的影响,导致 bug 再次出现。只要你写一些测试,就不会出现这种情况。

你可以使用 DjangoTestCase或其他框架,但我喜欢Pytest,它我最喜欢的功能之一是参数化测试。我们继续吧

pipenv install pytest pytest-django --dev
Enter fullscreen mode Exit fullscreen mode

请注意--dev,这告诉 pipenv 仅将某些依赖项作为开发进行跟踪。

我们可以配置 pytest 的文件有很多名称,但我更喜欢使用一个名为 的文件.setup.cfg,因为这个文件名与其他工具共享,这有助于减少文件数量。以下是一个可能的配置

[tool:pytest]
testpaths = tests
addopts = -p no:warnings
Enter fullscreen mode Exit fullscreen mode

tests现在您可以在目录中创建测试并使用以下方式运行它们

pipenv run pytest
Enter fullscreen mode Exit fullscreen mode

代码检查

代码 linting 是指运行某种软件来分析你的代码。我只会关注代码风格一致性工具,但你应该知道还有很多其他工具可以考虑,比如微软的pyright

我要提到的第一个工具是你的 IDE 本身。无论是 VIM、VSCode、Sublime 还是其他,都有一个叫做EditorConfig的项目,它是一个标准规范,可以告诉你的 IDE 缩进应该有多大,你喜欢用哪种字符串引号等等。只需在项目根目录下添加一个名为 EditorConfig 的文件,内容.editorconfig如下:

root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.py]
indent_size = 4
combine_as_imports = true
max_line_length = 79
multi_line_output = 4
quote_type = single

[*.js]
indent_size = 2

[*.{sass,scss,less}]
indent_size = 2

[*.yml]
indent_size = 2

[*.html]
indent_size = 2
Enter fullscreen mode Exit fullscreen mode

接下来,我们将添加flake8来强制执行 PEP8 规则,并添加 isort来获得对导入进行排序的标准方法。

pipenv install flake8 isort --dev
Enter fullscreen mode Exit fullscreen mode

两者都可以在.setup.cfg我们为 pytest 创建的同一个文件中进行配置。这是我喜欢的方式,但你也可以选择按照自己的意愿进行配置。

[flake8]
exclude = static,assets,logs,media,tests,node_modules,templates,*/migrations/*.py,urls.py,settings.py
max-line-length = 79
ignore =
    E1101  # Instance has not member
    C0111  # Missing class/method docsting
    E1136  # Value is unsubscriptable
    W0703  # Catching too general exception
    C0103  # Variable name doesnt conform to snake_case naming style
    C0330  # Wrong hanging indentation
    W504   # Too few public methods

[isort]
skip = static,assets,logs,media,tests,node_modules,templates,docs,migrations,node_modules
not_skip = __init__.py
multi_line_output = 4
Enter fullscreen mode Exit fullscreen mode

pipenv run flake8我们可以分别使用和运行这两个程序pipenv run isort

我们将要使用的最后一个工具是Js Beautifier,它将帮助我们维护 html、js 和样式表的有序性。对于这个工具,你可以选择为你的 IDEpipenv install jbbeautifier安装一个插件,让它显示错误信息(我就是这样做的)。要配置它,请在项目根目录中创建一个文件,内容如下(由于篇幅过大,本篇文章无法完整展示)。.jsbeautifyrc

静态文件

我们所有的 JavaScript、样式表、图片、字体和其他静态文件都位于此assets目录中。我们将使用 Webpack 将 SASS 样式表编译成 CSS,并将 ES6 编译成浏览器 JS(ES5?)。Webpack 会提取assets/index.js这些文件并将其用作所有静态文件的入口点。我们将从此文件导入所有 JavaScript 和样式表,然后 Webpack 会编译、最小化并将其打包成一个合适的文件包。我的典型assets/index.js示例如下:

import './sass/main.sass'
import './js/main.js'
Enter fullscreen mode Exit fullscreen mode

当然,我们至少需要安装 webpack、babel 和 sass 编译器。我们需要创建一个名为 的文件,package.json内容如下:

{  
  "scripts": {
    "dev": "webpack --mode development --watch",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "@babel/core": "^7.4.4",
    "@babel/preset-env": "^7.4.4",
    "babel-loader": "^8.0.6",
    "css-loader": "^2.1.1",
    "file-loader": "^3.0.1",
    "mini-css-extract-plugin": "^0.6.0",
    "node-sass": "^4.12.0",
    "sass-loader": "^7.1.0",
    "webpack": "^4.32.0",
    "webpack-bundle-tracker": "^0.4.2-beta",
    "webpack-cli": "^3.3.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

然后运行npm install,它将下载中指定的所有依赖项package.json到名为的目录中node_modules

现在该配置 Webpack 了。这是我使用的配置链接(篇幅过长,不适合这篇文章),长话短说,它定义了一系列规则,当你看到这类文件时,应该执行这些操作;当你看到其他文件时,应该执行这些操作。请将此配置放入webpack.config.js目录根目录下名为 的文件中。

现在是时候使用我发现的最好的工具django-webpack-loader将 webpack 的输出连接到 django 模板了。你可能已经习惯了,我们需要pipenv install django-webpack-loader安装它,然后修改conf/settings.py

INSTALLED_APPS = [
    ...
    'webpack_loader',
]
filename = f'webpack-bundle.{ENV}.json'
stats_file = os.path.join(root_path('assets/'), filename)
WEBPACK_LOADER = {
    'DEFAULT': {
        'CACHE': not DEBUG,
        'BUNDLE_DIR_NAME': 'bundles/',  # must end with slash
        'STATS_FILE': stats_file,
        'POLL_INTERVAL': 0.1,
        'TIMEOUT': None,
        'IGNORE': ['.+\.hot-update.js', '.+\.map']

Enter fullscreen mode Exit fullscreen mode

请注意,我们正在做的是读取一个文件,该文件包含有关在哪里找到 webpack 的编译文件(包)的信息。

我们现在可以使用模板标签在模板中添加 webpack 编译的脚本和样式表

{% load render_bundle from webpack_loader %}
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>{% block page_title %}{% endblock %}</title>
    <meta name="description" content="{% block page_description %}{% endblock %}">
    {% block page_extra_meta %}{% endblock %}
    {% render_bundle 'main' 'css' %}
  </head>
  <body>
    {% block body %}{% endblock %}
    {% render_bundle 'main' 'js' %}
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

芹菜

Celery 是一个任务队列,它非常方便地卸载那些不应该阻塞用户请求(例如发送电子邮件)的工作。使用以下命令安装 Celerypipenv install celery并添加以下代码

conf/celery.py

import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'conf.settings')
app = Celery('conf')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
Enter fullscreen mode Exit fullscreen mode

conf/settings.py

...
CELERY_BROKER_URL = env('CELERY_BROKER_URL')
Enter fullscreen mode Exit fullscreen mode

.env

...
CELERY_BROKER_URL=redis://localhost:6379/0
Enter fullscreen mode Exit fullscreen mode

现在你可以开始运行异步代码了pipenv run celery,它应该会运行成功,或者提示你 Redis 不可用。如果提示不可用也不用担心,反正我们也不会使用你系统的 Redis。

Docker compose

要使所有项目组件上线,我们必须启动 Web 服务器、Celery、Redis、Postgres 和 Webpack。打开多个终端并输入所有必要的命令可能相当麻烦。为了解决这个问题,我们将使用 Docker Compose,这是一个Docker 容器编排工具。

简而言之,docker 的工作原理是将你的代码及其所有依赖项“编译”成所谓的镜像。然后,为了运行我们的应用程序,我们可以将此镜像实例化为一个容器,容器是镜像的运行版本。我们还会将代码挂载到容器中,这样我们就可以在本地机器上进行更改,并在容器中实时查看更改,而无需重新构建镜像。

为了构建我们需要的图像,我们将创建一个名为的文件Dockerfile,它将作为构建 Web 和工作图像的脚本。

Dockerfile

# Pull base image
FROM python:3.7.2-slim

# Instal system dependencies
RUN apt-get update
RUN apt-get install git -y

# Set environment varibles
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Create dynamic directories
RUN mkdir /logs /uploads

# Set work directory
WORKDIR /code

# Install pipenv
RUN pip install --upgrade pip
RUN pip install pipenv

# Install project dependencies
COPY Pipfile Pipfile.lock ./
RUN pipenv install --dev --ignore-pipfile --system
Enter fullscreen mode Exit fullscreen mode

现在我们将创建一个名为的文件,docker-compose.yml我们将在其中设置项目使用的所有服务

version: '3'
services:
  web:
    image: dev_server
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/code
      - ./logs/web:/logs
      - ./media:/uploads
    command: python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    env_file:
      - .env
      - .env.docker
    environment:
      - BROKER_URL=redis://cache
      - MEDIA_ROOT=/uploads
      - LOGS_ROOT=/logs    
    links:
      - redis:cache
  worker:
    image: dev_worker
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/code
      - ./logs/worker:/logs
      - ./media:/uploads
    command: python manage.py celery
    env_file:
      - .env
      - .env.docker
    environment:
      - BROKER_URL=redis://cache
      - MEDIA_ROOT=/uploads
      - LOGS_ROOT=/logs
    links:
      - redis:cache
  redis:
    image: redis
    expose:
      - 6379
  webpack:
    image: dev_webpack
    build:
      context: .
      dockerfile: Dockerfile-webpack
    volumes:
      - ./assets:/code/assets
    command: npm run dev
Enter fullscreen mode Exit fullscreen mode

关于这个配置文件的几点

  • 请注意,我们将三个环境变量文件传递给 web 和 worker 服务。我们这样创建是为了在 Docker 中运行时覆盖某些设置。具体来说,我们需要将数据库地址更改为指向主机(你的机器),而不是 localhost(Docker 容器内)。为此,请创建.env.docker并添加以下行
DATABASE_URL=postgres://<user>@host.docker.internal:4321/<db-name>
Enter fullscreen mode Exit fullscreen mode
  • 我们将这些文件挂载./media./logs您的容器内部,以便您轻松读取日志并在本地机器上检查已上传的文件。不用担心,它们会被 git 忽略。

我们一直在努力构建的时刻……tum tum tum

docker-compose up
Enter fullscreen mode Exit fullscreen mode

耶!现在一切都应该上线了,虽然还没到喝啤酒的时间,但你可以开始考虑了。

当我们在机器上更改代码时,服务器将像 Django 的开发服务器一样自动重新加载。但是,当我们使用 添加新库时pipenv install,我们需要使用 重新构建镜像。

docker-compose build
Enter fullscreen mode Exit fullscreen mode

最后一件事是,我们将通过在安装代码时忽略一些文件来使我们的图像更轻量,文件名称.dockerignore如下

.env
.env.*
.git
.gitignore
.dockerignore
.editorconfig
.gitignore
.vscode
Dockerfile*
docker-compose*
node_modules
logs
media
static
README.md
Enter fullscreen mode Exit fullscreen mode

额外:自定义用户模型

我还没有做过一个不需要修改 Django 内置身份验证应用的项目,无论是添加/修改字段、添加自定义行为还是重命名 URL。为了让所有操作都触手可及,而不是深陷于 Django 依赖的泥潭,我们将创建第一个名为 的应用users,并添加一个自定义用户模型。

应用程序/用户/模型.py

from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models
from django.utils import timezone
from apps.users.managers import UserManager

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True, null=True, db_index=True)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    date_joined = models.DateTimeField(default=timezone.now)

    REQUIRED_FIELDS = []
    USERNAME_FIELD = 'email'

    objects = UserManager()
Enter fullscreen mode Exit fullscreen mode

应用程序/用户/managers.py

from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError('The Email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user
    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_active', True)
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')
        return self.create_user(email, password, **extra_fields)
Enter fullscreen mode Exit fullscreen mode

应用程序/用户/urls.py

from django.contrib.auth import views as auth_views
from django.urls import path
urlpatterns = [
    path('login/',
         auth_views.LoginView.as_view(),
         name='login'),
    path('logout/',
         auth_views.LogoutView.as_view(),
         name='logout'),
    path('password-change/',
         auth_views.PasswordChangeView.as_view(),
         name='password_change'),
    path('password-change/done/',
         auth_views.PasswordChangeDoneView.as_view(),
         name='password_change_done'),
    path('password-reset/',
         auth_views.PasswordResetView.as_view(),
         name='password_reset'),
    path('password-reset/done/',
         auth_views.PasswordResetDoneView.as_view(),
         name='password_reset_done'),
    path('reset/<uidb64>/<token>/',
         auth_views.PasswordResetConfirmView.as_view(),
         name='password_reset_confirm'),
    path('reset/done/',
         auth_views.PasswordResetCompleteView.as_view(),
         name='password_reset_complete'),
]
Enter fullscreen mode Exit fullscreen mode

conf/settings.py

AUTH_USER_MODEL = 'users.User'
INSTALLED_APPS = [
    ...
    'apps.users',    
]
Enter fullscreen mode Exit fullscreen mode

conf/urls.py

from django.contrib import admin
from django.urls import include, path
urlpatterns = [
    path('', include('apps.users.urls')),
    path('admin/', admin.site.urls),
]
Enter fullscreen mode Exit fullscreen mode

一切就绪后,我们现在可以完全控制用户的身份验证流程了。如果您想将注册和自定义模板添加到我的代码库中,请查看我的代码库,我已经在其中设置好了所有与django-registration-redux相关的设置。

最后的想法

我希望本指南能像我撰写指南一样,对你的阅读有所帮助。如果你觉得有任何可以改进的地方,我鼓励你克隆我的 GitHub 仓库并提交拉取请求。

祝您编码愉快!

这篇文章最初发表在medium上。

文章来源:https://dev.to/fceruti/setting-up-a-django-project-like-a-pro-353
PREV
我创建了自己的纯 CSS 微框架,这是一个故事🙃
NEXT
VS Code 的 10 个基本扩展