跳到主要内容

11 篇博文 含有标签「django」

查看所有标签

vue和django项目的开发/生产环境配置管理

· 阅读需 5 分钟
Jason Lee
The owner of this blog site

前言

虽然教程作者提到了一些分开管理两种环境的做法,但是我这边没有形成完整可复制的做法。借助 chatgpt,将收获整理出来。

vuejs

在项目的根目录下,分别创建两个文件:.env.development and .env.production。最基础的设置是 api 地址:

# .env.development
VITE_API_URL=http://dev-api.example.com

# .env.production
VITE_API_URL=http://api.example.com

任何设置都有以 VITE_开头。为了区别使用配置,在vite.config.js中,有如下:

export default defineConfig({
// 开发环境使用 .env.development 文件
envDir: './',
build: {
// 生产环境使用 .env.production 文件
envFile: '.env.production',
},
});

django

相对于 vue, django 的设置就要复杂一些了。这里把 gpt 关于生产和开发环境的不同列出来,虽然有很多在这个项目没有实现,但是可以为以后更复杂的真实项目做参考。

在 Django 项目中,开发环境和生产环境通常需要进行不同的设置。下面是一些常见的设置差异:

调试模式(Debug Mode):

开发环境:设置 DEBUG = True,以便在出现错误时显示详细的错误信息。 生产环境:设置 DEBUG = False,以避免显示敏感信息和详细的错误信息。 数据库设置:

开发环境:通常使用本地数据库(如 SQLite)进行开发和测试。 生产环境:使用更稳定和可靠的数据库服务器(如 PostgreSQL、MySQL、Oracle)。 静态文件设置:

开发环境:通常将静态文件存储在本地,并通过 Django 的 staticfiles 应用程序来提供静态文件服务。 生产环境:可以使用 CDN(内容分发网络)来提供静态文件,以减轻服务器负载并提高速度。 日志设置:

开发环境:可以设置较详细的日志记录级别,以便更容易调试和跟踪问题。 生产环境:通常使用更严格的日志记录级别,并将日志信息定向到适当的日志文件,以便进行监视和故障排除。 密钥和敏感信息:

开发环境:可以在本地设置中直接存储密钥和敏感信息。 生产环境:建议使用环境变量或其他安全的方式来存储密钥和敏感信息,以保护生产环境的安全性。 域名设置:

开发环境:可以使用本地的开发服务器(如 localhost)进行开发和测试。 生产环境:设置正确的域名,并配置 Web 服务器(如 Nginx 或 Apache)以将请求转发到 Django 应用程序。 缓存设置:

开发环境:可以禁用缓存或使用简单的缓存配置,以方便开发和调试。 生产环境:使用更高效的缓存后端(如 Memcached 或 Redis)来提高性能。 这些只是一些常见的设置差异,实际上还可以根据项目需求和部署环境的要求进行更多的设置和调整。

本项目中,在根目录下创建了三个文件,settings_common.py, settings_dev.py, settings_prod.py。其中 common 是两种环境之间共享的通用配置,然后在 dev 和 prod 中分别引用 common:

from .settings_common import *

然后在 dev 和 prod 各自写一些配置(比如数据库等)。之后修改启动文件manage.py:

import os
import sys

if os.environ.get('DJANGO_ENV') == 'production':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings_prod')
else:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings_dev')

def main():
# 启动逻辑...

if __name__ == '__main__':
main()

如果要部署,记得把环境变量添加上,即设置 DJANGO_ENV 环境变量为 'production'. 而且如果是 uwsgi 作为 django 的服务器的话,还有修改wsgi.py

总结

前端 vue 的环境区别很简单,后端 django 的稍复杂些。主要分三步走:编写配置文件->修改启动文件->部署设置环境变量。

django+nginx+mysql 三容器部署

· 阅读需 4 分钟
Jason Lee
The owner of this blog site

前言

之前的部署直接采用默认的 sqlite3 作为数据库。因为 sqlite3 是文件式数据库,所以打造容器时,本地调试过程中的数据记录也被包含进去。另外,生产环境中,不可能使用 sqlite。因此,在之前的 django+nginx 的基础上,引入了 mysql 容器。

镜像设置

首先在根目录创建一个MySQL数据库,下面有 data.sql 和 Dockerfile。这里我们先单独启动一下 mydql 容器。这时候 mysql 容器里已经有 django 的数据库了(database)。这是因为 data.sql 已经提前创建好了数据库并设置为当前数据库:

CREATE DATABASE IF NOT EXISTS my_database;

USE my_database;

而 Dockerfile 的内容为:

FROM mysql:8

ENV MYSQL_ROOT_PASSWORD pass
COPY ./data.sql /docker-entrypoint-initdb.d/data.sql

最后一行使得 data.sql 在容器初始化时就被执行。这时候虽然有了数据库但是没有表格,需要去 django 项目下,执行python manage.py migrate来在库里面创建表格。运行docker exec -it <name> /bin/bash,进入容器 terminal,进入 mysql 命令行,show tables,可以得到:

16875289399621687528939092.png

说明,数据表格创建成功了。

django settings.py 变动

由于之前是默认的 sqlite3,配置非常简单,只需要指定数据库文件位置。在 django 的 settings.py 中,关于 mysql 数据库的设定为:

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'my_database',
'HOST': 'database', #compose中mysql容器的服务名称
'PORT': '3306',
'USER': 'root',
'PASSWORD': 'pass',
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"
},
}
}

docker-compose

由于我们是全 docker 运行,因此也要实验一下将 django 项目打包成 docker 容器后,能否也能与 mysql 容器通信且成功创建表格。这次用 compose 实现。

version: "3.9"
services:
nginx:
build: ./wey-frontend/
restart: always
ports:
- "8000:80"
volumes:
- web_static:/var/www/mysite/assets/static
- web_media:/var/www/mysite/assets/media
depends_on:
- django

django:
build: ./wey_backend/
restart: always
expose:
- 8000
command: >
sh -c "python manage.py collectstatic --noinput
&& python manage.py migrate
&& uwsgi --ini uwsgi.ini"
volumes:
- web_static:/var/www/mysite/assets/static
- web_media:/var/www/mysite/assets/media
depends_on:
- database

database:
build: ./MySQL/
ports:
- '3306:3306'
# environment:
# MYSQL_DATABASE: 'appdb'
# MYSQL_PASSWORD: 'root'
# MYSQL_ROOT_PASSWORD: 'root'
# volumes:
# - 'db:/var/lib/mysql'
# # command: ['mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci']

volumes:
web_static:
web_media:

总结

用 mysql 作为数据库,更符合实际生产场景。由于一开始 mysql 容器内没有数据库,因此需要在 Dockerfile 中创建自定义数据库并且使用。django 的 settings.py 中也要作相应调整。docker-compose 中,需要在创建 django 容器后,执行python manage.py migrate,来迁移数据库(即在自定义数据库中创建表来供上线后数据放置)。

django静态文件管理

· 阅读需 5 分钟
Jason Lee
The owner of this blog site

前言

可以分为开发和部署两个阶段进行介绍。

开发

STATIC_URL = "static/"(默认设置好的),这一项是告诉 django,静态文件存在每一个应用的哪一个文件夹下,方便后面收集静态文件到一处。还有一个用途是,给出资源的引用位置。例如,如果 STATIC_URL 设置为'/static/',则可以通过http://yourdomain.com/static/css/styles.css来访问名为styles.css的CSS文件。

另外,如果某些静态文件不属于任何一个应用,或者所在文件夹名字和 STATIC_URL 不同,可以将文件夹名放在 STATICFILES_DIRS:

STATICFILES_DIRSSTATICFILES_DIRSSTATICFILES_DIRSSTATICFILES_DIRSSTATICFILES_DIRS = [
BASE_DIR / "static",
"/var/www/static/",
]

这两项都和 STATIC_URL 不同。

当需要在开发阶段提供静态文件服务时,可以在根应用的 urls.py 里设置:

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
# ... the rest of your URLconf goes here ...
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

还有一种用户上传的静态文件,用 MEDIA_URL,类似于 STATIC_URL.

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
# ... the rest of your URLconf goes here ...
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

部署

首先要明确,部署阶段时的静态文件管理到底为什么要和开发阶段区分开来?

在开发阶段,开发人员通常会使用各种工具和技术来编写和测试代码。他们经常需要修改代码和调试,以确保功能的正确实现。在这个阶段,开发人员通常希望在每次修改代码后能够快速地查看结果,以便及时调试和验证功能。为了实现这个目标,他们通常会使用本地开发服务器,在其中运行代码并动态生成页面。在开发过程中,静态文件(如 HTML、CSS、JavaScript、图像等)可能会被直接从开发环境的文件系统加载,或者通过开发服务器进行实时编译和转换。

然而,当代码准备好部署到生产环境时,情况就会有所不同。在部署阶段,我们通常希望优化网站或应用程序的性能和加载速度。这就涉及到对静态文件的管理和优化。

静态文件管理在部署阶段的目标是将这些文件进行优化和准备,以便在生产环境中提供最佳的性能和用户体验。这可能包括压缩和合并 CSS 和 JavaScript 文件、优化图像大小和格式、设置缓存策略以减少加载时间等。

另外,静态文件管理还可以包括将文件上传到内容分发网络(Content Delivery Network,CDN)上,以确保文件在全球范围内的快速传输和分发。

通过将开发阶段和部署阶段的静态文件管理分离开来,可以使开发人员专注于开发和调试代码,而不必担心性能优化和部署的细节。这也有助于确保在生产环境中获得最佳的性能和用户体验。

STATIC_ROOT:collectstatic 将收集静态文件进行部署的目录的绝对路径。例如: "/var/www/example.com/static/",而搜集的源头就是上述的 STATIC_URL 和 STATICFILES_DIRS。

参考资料

https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-STATIC_URL

基于docker的django+vue网站部署

· 阅读需 5 分钟
Jason Lee
The owner of this blog site

前言

部署和开发是两个完全不同的工作。得益于 docker 等虚拟化技术,现在的部署已经简单许多了。尽管作者是用原生 linux 环境做示范,但是我选用 docker 作为部署工具。主要以查找博客和问 chatgpt 来学习,中间由于对 nginx 不了解,还看了相关的视频教程。大概花了三四天时间,从本地 win 环境,到最终的云主机。现在,我终于可以说,自己是一个全栈工程师了,真正从应用的开发到落地都有了粗浅的涉及。

总体流程图

这是两容器之间的配合示意图。外部的请求从宿主机的 8000 端口进来,然后被 docker 投射到容器 1,交给 nginx 处理。nginx 根据请求的 url,判断是动态还是静态请求。如果是静态,则去找 vue 项目打包来的文件夹 dist 内的资源并返回;如果是动态资源,通过 http 方式转发给容器 2 的端口 8000。uwsgi 处理完逻辑后,将可能有的响应转回给 nginx,再返回给用户。

项目结构

wey
├── docker-compose.yml
├── wey-frontend
│ ├── dist
│ ├── Dockerfile
│ ├── index.html
│ ├── nginx.conf
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── postcss.config.js
│ ├── public
│ ├── README.md
│ ├── src
│ ├── tailwind.config.js
│ └── vite.config.js
└── wey_backend
├── account
├── chat
├── db.sqlite3
├── Dockerfile
├── manage.py
├── media
├── notification
├── pip.conf ## 给容器2的pip换源
├── post
├── requirements.txt
├── scripts
├── search
├── uwsgi.ini
└── wey_backend

容器 1 配置

# nginx.conf

server {
listen 80;
server_name 127.0.0.1;

location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}

location /api/ {
proxy_pass http://django:8000;
}

location /static {
alias '/var/www/mysite/assets/static';
}

location /media {
alias '/var/www/mysite/assets/media';
}

location /admin/ {
proxy_pass http://django:8000;
}

}

对应的 dockerfile:

# 使用一个基础的 Node.js 镜像作为构建环境
FROM node:14 as builder

# 设置工作目录
WORKDIR /app

# 复制 package.json 和 package-lock.json 文件到容器中
COPY package*.json ./

# 安装依赖
RUN npm install

# 复制前端应用源代码到容器中
COPY . .

# 执行构建命令
RUN npm run build

# 创建一个新的容器镜像
FROM nginx:latest

# 复制构建产物到 Nginx 的默认静态文件目录
COPY --from=builder /app/dist /usr/share/nginx/html

# 将自定义的 nginx.conf 文件复制到容器中,覆盖默认配置
# remove the default conf
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 暴露容器的 80 端口
EXPOSE 80


容器 2 配置

[uwsgi]

module = wey_backend.wsgi
py-autoreload=1
http-timeout = 86400

http=0.0.0.0:8000

master=true
processes = 4
threads=2

chmod-socket=666

vaccum=true
die-on-term=true

对应的 dockerfile 配置:

FROM python:3.11
RUN mkdir /code
WORKDIR /code
COPY . /code

COPY pip.conf /root/.pip/pip.conf

# uwsgi setup
RUN pip install uwsgi
RUN pip install -r requirements.txt

EXPOSE 8000

CMD ["uwsgi", "--ini", "uwsgi.ini"]

docker-compose

docker compose 是在有多个容器且容器之间存在依赖关系时适用。它取代的是命令行构建镜像和创建容器的方式,使得部署更简洁。比如在 nginx 中,ports:"8000:80",是在建立端口映射。volumes:是在把特定的目录在整个 docker 应用进程内建立一个索引,实现文件共享。

version: "3.9"
services:
nginx:
build: ./wey-frontend/
restart: always
ports:
- "8000:80"
volumes:
- web_static:/var/www/mysite/assets/static
- web_media:/var/www/mysite/assets/media
depends_on:
- django


django:
build: ./wey_backend/
restart: always
expose:
- 8000
command: >
sh -c "python manage.py collectstatic --noinput
&& uwsgi --ini uwsgi.ini"
volumes:
- web_static:/var/www/mysite/assets/static
- web_media:/var/www/mysite/assets/media

volumes:
web_static:
web_media:

总结

这篇文章实现了用 docker 来部署 django+vue 的前后端分离网站,并用 docker-compose 来简化了部署。

为什么中间遭遇了较大的挫折并且一度想放弃呢?反思的结果是,自己一开始就把多个容器放在一起考虑,导致头绪纷乱无章。后面在尝试了测试两个容器是否能独立运行,然后联合运行,才成功。

django默认和自定义的用户身份验证

· 阅读需 3 分钟
Jason Lee
The owner of this blog site

前言

在做项目时,身份验证是目前疑虑最多的地方:为什么这里要新建一个 User 模型?为什么要重写管理器?这篇文章通过查阅文档,尝试从最佳实践的角度给出答案。

默认验证方式

模型 视图函数 表单

注册 登录 登出

默认 User 模型

默认 User 模型主要包含以下字段:username、password、email、first_name、last_name。也就是说,如果有别的字段的需求(比如用户头像),就要自定义用户模型。目前暂不涉及权限,等到后续项目中接触到了,再添加。

视图函数

注册

没有内置的注册视图,有内置的注册表单 UserCreationForm。UserCreationForm 的要求字段为用户名、邮箱和两次密码输入。

# views.py
class SignUpView(FormView):
template_name = 'core/signup.html'
form_class = forms.SignUpForm
success_url = '/login/'

def form_valid(self, form):
form.save()
return super().form_valid(form)

# forms.py
class SignUpForm(UserCreationForm):
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']

username = forms.CharField(widget=forms.TextInput(attrs={
'placeholder': 'Your username',
'class': 'w-full py-4 px-6 rounded-xl'
}))
email = forms.CharField(widget=forms.EmailInput(attrs={
'placeholder': 'Your email address',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password1 = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Your password',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password2 = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Repeat password',
'class': 'w-full py-4 px-6 rounded-xl'
}))

登录

有内置视图 LoginView,有内置的表单 AuthenticationForm

# forms.py
class LogInForm(AuthenticationForm):
username = forms.CharField(widget=forms.TextInput(attrs={
'placeholder': 'Your username',
'class': 'w-full py-4 px-6 rounded-xl'
}))
password = forms.CharField(widget=forms.PasswordInput(attrs={
'placeholder': 'Your password',
'class': 'w-full py-4 px-6 rounded-xl'
}))

# urls.py
path('login/', auth_views.LoginView.as_view(template_name='core/login.html', authentication_form=LogInForm), name='login'),

# settings.py
LOGIN_REDIRECT_URL = '/'

登出

有内置视图 LogoutView,无需视图

# urls.py
path('logout/', auth_views.LogoutView.as_view(), name='logout')

# settings.py
LOGOUT_REDIRECT_URL = '/'

自定义验证方式

有两个可以自定义的地方:认证后端和 User 模型

认证后端

前面提到的 authenticate 和 login 方法都是 django 默认认证后端提供的。

User 模型

一般有两个需要扩展:模型本身和管理器(manager)

# models.py

class CustomUserManager(UserManager):
def _create_user(self, name, email, password, **extra_fields):
if not email:
raise ValueError("You have not provided a valid e-mail address")

email = self.normalize_email(email)
user = self.model(email=email, name=name, **extra_fields)
user.set_password(password)
user.save(using=self._db)

return user

def create_user(self, name=None, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(name, email, password, **extra_fields)

def create_superuser(self, name=None, email=None, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
return self._create_user(name, email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True)
name = models.CharField(max_length=255, blank=True, default='')
avatar = models.ImageField(upload_to='avatars', blank=True, null=True)

is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)

date_joined = models.DateTimeField(default=timezone.now)
last_login = models.DateTimeField(blank=True, null=True)

objects = CustomUserManager()

USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = []

django-rest-framework使用入门

· 阅读需 2 分钟
Jason Lee
The owner of this blog site

浏览器行为

当我用 drf 提供的浏览器可视化界面,发现了一个奇怪的问题。同样是访问http://127.0.0.1:8000/snippets/,如果我之前填了一个表单并提交,那么按刷新按钮,会再提交一次,也就是会在数据库中添加一条和上一次一样的数据。只有在地址栏回车一下,才会真正回到列表中。这说明,如果页面中有表单,刷新按钮可能会执行 post 请求,而浏览器回车才能保证发出的 get 请求。

到底什么是序列化

python 的数据格式(字典、列表等),转化为可以传输的字符串(JSON、HTML 等),就是序列化。序列化类的实例并不是直接生成 json 数据,而是将模型实例转为一种 python 字典,然后由视图函数的JSONRenderer().render()函数转化为 json 字符串。

反过来,如果接受到了 json,按照下列方式:

import io

stream = io.BytesIO(content)
data = JSONParser().parse(stream)

serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

和表单的处理很相似。

类视图的使用

· 阅读需 4 分钟
Jason Lee
The owner of this blog site

前言

对于类视图和函数视图,在我初学 django 的时,有过这样的迷思:自己定义的类视图似乎和函数视图没有太大的区别,无非就是判断请求方式的时候,CBV 用函数,FBV 用if...else...。而如果上了框架提供的通用类视图,代码风格似乎过于抽象,且不灵活,无法完全替代函数视图。今天,就以刚刚用函数视图完成的项目为例,尝试将所有函数视图转为类视图,看看过程中有什么新的感悟。

通用类视图里的方法

添加额外的上下文:

def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books
context["book_list"] = Book.objects.all()
return context

根据 url 内传来的 query 参数来动态查询

这项工作的关键部分是当基于类的视图被调用的时候,各种常用的东西被存储在 self 上,而且请求 (self.request) 根据 URLconf 抓取位置(self.args) 和基于名称 (self.kwargs) 的参数。

def get_queryset(self):
self.publisher = get_object_or_404(Publisher, name=self.kwargs["publisher"])
return Book.objects.filter(publisher=self.publisher)

执行额外的任务

class AuthorDetailView(DetailView):
queryset = Author.objects.all()

def get_object(self):
obj = super().get_object()
# Record the last accessed date
obj.last_accessed = timezone.now()
obj.save()
return obj

通用类视图到底为开发者默认做了哪些事

django 框架内置的通用类视图之所以能少些很多代码,很明显是它有一些默认行为。那么这里就记录一下各种常见内置视图的默认行为。

FormView

class ContactFormView(FormView):
template_name = "contact.html"
form_class = ContactForm
success_url = "/thanks/"

def form_valid(self, form):
# This method is called when valid form data has been POSTed.
# It should return an HttpResponse.
form.send_email()
return super().form_valid(form)

表单视图的一般行为是判断请求方式,如果是 post,就检验字段有效性,且把错误返回给浏览器,成功则重定向;如果是 get 就返回待填写的表单。

所以,FormView 就是通过暴露一些简单的配置给开发者,比如 template_name,其他行为会自动完成。如果有特殊需求,重写 get 或 post 方法。form_valid 是执行表单验证之后的程序。

form_valid 函数是一定要重写的,这也说明 django 文档里一般都是最小实现形式,不能减少。

总结

  • 类视图完全可以替代函数视图。
  • 优先用内置的通用类视图,首先确认任务类型,如果是展示类,就考虑 ListView、DetailVIew;如果是修改类,就是 CreateView、UpdateView、DeleteView。
  • 修改类的通用视图,注意表单相关项。运用上面提到的三个函数,可以实现较复杂的需求。
  • 如果不方便,也可以不用内置视图,而采用根据请求方式划分方法,比如 get 请求。
  • 类视图在采用了内置类的写法后,代码会变少;但更重要的意义是,增加了代码重用的可能性。比如,另外一个 views.py 可以继承过来。

ModelForm详解

· 阅读需 3 分钟
Jason Lee
The owner of this blog site

前言

web 开发中,我目前遇到最复杂、又是最需要的需求,就是用户验证系统。除了一般的注册登录,还有权限、分组等。而从注册开始,需要理解 django 为什么设计了 modelForm 来处理用户的表单输入。

从 form 讲起

在注册的例子中,文档给出的方法是调用如下函数:

from django.contrib.auth.models import User
User.objects.create_user(username='', email='', password='')

但是正常的网站注册方式都是通过表单,怎么建立表单和 create_user 函数的联系呢?如果搜索文档里的表单,得到的答案是:

from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import NameForm


def get_name(request):
# if this is a POST request we need to process the form data
if request.method == "POST":
# create a form instance and populate it with data from the request:
form = NameForm(request.POST)
# check whether it's valid:
if form.is_valid():
# process the data in form.cleaned_data as required
# ...
# redirect to a new URL:
return HttpResponseRedirect("/thanks/")

# if a GET (or any other method) we'll create a blank form
else:
form = NameForm()

return render(request, "name.html", {"form": form})

我们知道了,对于提交上来的表单,先验证数据是否都有效,然后进一步的处理并重定向。可是什么是进一步的处理?

所以这大概就是文档有时候不能解决实际场景的问题,要结合别人写的代码,再回到文档中理解。

ModelForm

表单,一般都是和模型(也就是表中的字段)绑定的。因此,如果从头由forms.ModelForm继承来的表单类开始写,无疑是一种重复代码。因为与之绑定的模型字段在之前的 models.py 中定义过了。因此,django 创建了 ModelForm,直接在表单创建时和模型绑定,这样,提交上来的表单在验证通过后,保存表单就会同时保存提交上来的数据。

关于用户注册,需要知道的是,django 自带的 User 对象已经可以满足大部分场景的使用了,一般直接将其作为模型使用,而不用在 models.py 中创建。

通过这个例子,我的体会是:django 的写法太灵活了,往往一个问题有多种解决方案。可能最好的办法是摸索出一套自己习惯的写法。

django关系字段

· 阅读需 4 分钟
Jason Lee
The owner of this blog site

前言

mysql 断断续续地学,一直对诸如外键的概念不甚清晰,今天做一个备忘。在这篇博文的基础上,加上了一些自己的理解。

建表

#_*_coding:utf-8_*_
from django.db import models

# Create your models here.

class Colors(models.Model):
colors=models.CharField(max_length=10) #蓝色
def __str__(self):
return self.colors

class Ball(models.Model):
color=models.OneToOneField("Colors") #与颜色表为一对一,颜色表为母表
description=models.CharField(max_length=10) #描述
def __str__(self):
return self.description

class Clothes(models.Model):
color=models.ForeignKey("Colors") #与颜色表为外键,颜色表为母表
description=models.CharField(max_length=10) #描述
def __str__(self):
return self.description

class Child(models.Model):
name=models.CharField(max_length=10) #姓名
favor=models.ManyToManyField('Colors') #与颜色表为多对多

一对一(models.OneToOneField)

子表从母表中选出一条数据一一对应,母表中选出来一条就少一条,子表不可以再选择母表中已被选择的那条数据。一般用于某张表的补充,比如用户基本信息是一张表,但并非每一个用户都需要有登录的权限,不需要记录用户名和密码,此时,合理的做法就是新建一张记录登录信息的表,与用户信息进行一对一的关联,可以方便的从子表查询母表信息或反向查询。

## 增加

color_obj=models.Colors.objects.create(colors="黑") #先在母表中创建颜色,并实例化给颜色表对象
models.Ball.objects.create(color=color_obj,description="黑球") #更新Ball表,color字段为颜色表对象,添加description字段

## 删除
models.Ball.objects.get(description="灰球").delete()

## 修改
color_obj=models.Colors.objects.get(colors="黑") #.get()等同于.filter().first()
color_obj.colors="灰"
color_obj.save()
models.Ball.objects.filter(description="黑球").update(color=color_obj,description="灰球")

## 查询
### 子表查询母表
models.Ball.objects.get(description="红球").color.colors

### 母表查询子表
models.Colors.objects.get(colors="红").ball.description

一对多(models.ForeignKey)

子表从母表中选出一条数据一一对应,但母表的这条数据还可以被其他子表数据选择。这里的一和多指的是自身的表的数据在对方表里出现的次数,一次为一,多次为多。比如每个员工归属于一个部门,那么就可以让员工表的部门字段与部门表进行一对多关联,可以查询到一个员工归属于哪个部门,也可反向查出某一部门有哪些员工。

## 查

color_obj=models.Colors.objects.get(colors="红")
color_obj.clothes_set.all()

models.Clothes.objects.filter(color__colors="红")

多对多(models.ManyToManyField)

比如有多个孩子,和多种颜色。每个孩子可以喜欢多种颜色,一种颜色可以被多个孩子喜欢,对于双向均是可以有多个选择。

## 查

#写法1:
child_obj=models.Child.objects.get(name="小明") #写法:子表对象.子表多对多字段.过滤条件(all()/filter())
print(child_obj.favor.all())
#写法2,反向从母表入手:
print(models.Colors.objects.filter(child__name="小明")) #母表对象.filter(子表表名小写__子表字段名="过滤条件")

Django ORM记录

· 阅读需 3 分钟

记录曾经不懂的问题和踩过的坑

choices

Django 模型中的字段有个 choices 属性,这个属性可以提供被选数据。如果一个字段设置了这个属性,在模版中如果我要显示这个字段,那么 django 模版系统就会将它默认解析为一个下拉菜单。

# models.py
choices = (
(1, '男'), (2, '女'), (3, '其他')
)
gender = models.IntegerField(choices=choices)
# html {{ obj.get_gender_display }}
##在模板中,为了获取元组中id对应值,用左边的语法

外键

#model.py

## 外键要关联的表
class department(models.Model):
name = models.CharField(max_length=32)

## 在结合modelForm时,若for循环输出,下面语句可避免bug
def __str__(self):
return self.name


department = models.ForeignKey(to='department', on_delete=models.CASCADE)
## 外键关联上后,执行迁移,会在本表创建一个名为'department_id'字段。
# html {{ obj.department.name }} ##department字段会返回一个department表的对象

modelForm

添加数据

当想让用户向已有数据库中添加新的数据,应该用 modelForm 来创建一个对象。这个对象可以用 for 循环来在模板中显示字段,而且具有自动对数据进行校验和报错功能。

## views.py

class employModelForm(forms.ModelForm):
## 默认校验非空,可以对数据进行个性化校验,如:
## password = forms.CharField(min_length=3, label='密码')
class Meta:
model = employee
fields = "__all__"

def add(req):
if req.method == 'GET':
# form = employeeForm()
form = employModelForm()
return render(req, 'emp_add.html', {'form': form})

form = employModelForm(data=req.POST)
if form.is_valid():
form.save()
return redirect('/emp/list/')

## 数据校验失败,将已输入的信息和产生的错误提示返回给页面
return render(req, 'emp_add.html', {'form': form})
{{field.label}}: {{ field }} {{ field.errors.0 }} //错误信息输出

修改数据

修改数据时,要告诉 django 修改的对象是哪个。

## views.py
form = employModelForm(data=req.POST, instance=obj)