以下为极客时间 Django快速开发实战 课程中一些常见文件的问题解答,以及部分课程中 Bug 的勘误表。 由于这些问题被频繁遇到,而对于新接触 django初学者来说处理起来比较花时间,因此总结如下,方便您能快速解决这些问题。 课程链接

一、项目相关

1.创建一个 Django 项目, 启动项目的过程有哪些步骤?

可以通过3个步骤创建和启动一个 Django 项目:创建项目,启动应用, 初始化数据库3个步骤。 详细步骤的命令如下

1.创建Django项目

django-admin startproject Hello #Hello为项目名

2.切换到项目的根目录,启动项目(监听本地所有的IP地址)

cd Hello

python manage.py runserver 0.0.0.0:8000

或者

python manage.py runserver

浏览器本地访问:127.0.0.1:8000即可看到默认的首页。

Django数据库

默认项目根目录下自动创建“db.sqlite3”文件

可以在settings.py里面指定“db.sqlite3”文件的存放路径或者更改成其他的数据库引擎,如MySQL

3.初始化数据库,访问Django的admin管理后台

访问路径:127.0.0.1:8000/admin (开始无法访问,因为数据库还未初始化,提示没有这个表)

(1).数据库迁移

makemigrations 命令创建数据库迁移,产生SQL脚本,使用migrate命令把默认的model同步到数据库,Django会自动为model建立数据库表。

数据库迁移

python manage.py makemigrations

自动生成数据库表

python manage.py migrate

此时访问127.0.0.1:8000/admin即可看到后台登录页面。

(2).创建后台管理员账号

python manage.py createsuperuser

根据提示输入对应的用户名,邮箱和密码

(3).配置文件settings.py

2.创建一个 Django 创建项目,增加应用模块的过程有哪几个步骤?

  1. 运行startproject创建项目
  2. 在项目下面运行startapp创建应用模块
  3. 创建model
  4. 在应用和管理控制台配置中注册
  5. makemigrations生成数据库脚本
  6. migrate同步数据库表的变化
  7. runserver 启动应用

二、数据库与 Model

1.运行的时候遇到这个错误: Field specifies on_delete=SET_NULL, but cannot be null.

我的是 django 3.1.2运行报这个错误:

jobs.Job.creator: (fields.E320) Field specifies on_delete=SET_NULL, but cannot be null.

        HINT: Set null=True argument on the field, or change the on_delete rule.

解决方法, 字段定义的时候添加 null=True, 允许字段为空:

 creator = models.ForeignKey(User, verbose_name="创建人", null=True, on_delete=models.SET_NULL)

2.万一在原来model上要新增字段,修改字段类型等等表结构修改,migrations通不过怎么处理呢?

可以在数据库中手工加表子段,不做 migration也是可以的。也可以清理一下migration。原则上是要保证数据库的变化是兼容的,通常建议只添加字段,不修改字段。

3.如果我想在model里面定义一个只有年和月的Data字段该如何实现?

需要在页面上使用月份选择的话,可以自己定制widget。有一个 django-yearmonth-widget 有可以使用

4.如何自动设置数据的创建/更新日期?

数据的创建日期,在定义 model 时可以使用 auto_now_add = True 来定义,自动在创建数据时指定当前时间,并使用了默认时区

created_date = models.DateTimeField(verbose_name="创建日期", auto_now_add=True) 

修改日期的字段,使用 auto_now,可以自动在模型保存时,更新时间并使用了默认时区

modified_date = models.DateTimeField(verbose_name="修改日期", auto_now=True)

5.数据模型支持文件类型数据吗?如此处可以上传候选人简历文件或者自我介绍的短视频等等。 文件数据太多会影响数据库性能吗?

支持文件类型的,文件默认不存在数据库。可以存在服务器的目录上,或者存到云端文件服务器比如oss。 课程中候选人的简历,用到的是文件类型的字段,上传一个文件,存储下来之后是一个服务器上的路径,或者一个 URL 的路径。

6.听说数据库设计, 不建议使用任何外键

线上运行不建议使用外键。django 里面可以定义 Model 的外键关系,只是数据库里面不创建 Foreign key,避免外键带来的运行时性能损失,以及代码操作的复杂性。数据库里面不启用外键关系,但在 Model 中定义外键关系,这样运行性能好,同时能利用 django 的优点。

7.为什么数据库里面不能使用表 join,两张表的 join 也不可以吗?

这个取决于系统的数据量和访问量。 通常而言数据量较少(几十万级别数据量以下,并发量不高)的情况,且没有需要大规模提升系统容量的需求时,数据库怎么用都没什么问题。 相反,如果你的系统是面向大量数据的, 特别是面向C端用户大数据量,高并发的互联网产品,如果淘宝,QQ,微信,或者美团点评,携程,知乎,豆瓣之类的产品, 抑或类似于企业微信,钉钉,飞书之类面向B端的PaaS/SaaS产品,使用数据库表的时候,大家会告诉你一定不能两张表 join, 一个方面随着数据量访问量的增长,join 的性能急剧下降,由于慢SQL导致数据库不可用,而数据库往往是这些系统的核心,系统随时都可能发生严重故障; 另一个方面,在数据量增长以后,两张表A, B 放到不同的数据库中, 这种情况下原先 join 的 SQL 在跨DB库的情况下无法运行。

三、页面与表单相关

1.遗留代码里面看到正则表达式的 URL 匹配, 能解释一下r'^job/(?P<job_id>\d+)/$‘吗?

比如如下的代码:

url(r'^job/(?P<job_id>\d+)/$', views.detail, name='detail'),

这个相当于 re_path(r’^job/(?P<job_id>\d+)/$', views.detail, name=‘detail’),

r'^job/(?P<job_id>\d+)/$'  这个 表达式匹配了  如 job/xxxx/ 的路径, xxxx是一个数字表示的 jobId, jobId 会作为变量被传递到视图层。

(?Ppattern) 是 python 的正则表达式的语法,表示一个带名字的正则表达式, name 是正则表达式的名称, pattern 是正则表达式的匹配规则。

前面路径的简写相当于是:

    path('job/<int:job_id>/$', views.detail, name='detail'),

2.Django 的表单页/详情页, 每一行只能显示两个属性,这个需要怎么改?

括号括起来的元组会在一行展示,括起来三个属性就是三个一行,两个就是两个一行。

3.Admin后台管理界面的样式表没有了,请问这是怎么回事,该如何解决?

生产环境资源文件要自己生成并且挂到web服务器或者放到cdn上。如果不用cdn直接把生成的静态文件目录挂到web服务器比如nginx下.

4.访问职位列表页, 提示找不到joblist.html

第7节课,视频的 03:44 加了 templates/joblist.html 的文件,确保这个加到代码库里了。 可以对比一下代码库。 https://gitee.com/geektime-geekbang/django

settings.py 里面, 课程是在 base.py 的配置文件中, TEMPLATES 里面配置的。 加了这个:

'DIRS': [os.path.join(BASE_DIR, 'recruitment/templates')],

完整的配置:

 TEMPLATES = [

     {

         'BACKEND': 'django.template.backends.django.DjangoTemplates',

         'DIRS': [os.path.join(BASE_DIR, 'recruitment/templates')],

         'APP_DIRS': True,

         'OPTIONS': {

             'context_processors': [

                 'django.template.context_processors.debug',

                 'django.template.context_processors.request',

                 'django.contrib.auth.context_processors.auth',

                 'django.contrib.messages.context_processors.messages',

             ],

         },

     },

 ]

四、域账号相关

1.Docker 下如何安装 OpenLDAP 和 phpldapadmin?

视频切到5:50秒,有讲解安装过程,docker安装比较方便,如下4个命令即可安装好:

docker pull osixia/openldap:1.4.0
docker pull osixia/phpldapadmin:0.9.0

创建容器,启动容器

docker run -p 389:389 -p 636:636 --name my-openldap-container --env LDAP_ORGANISATION="ihopeit" --env LDAP_DOMAIN="ihopeit.com" --env LDAP_ADMIN_PASSWORD="admin_passwd_4_ldap" --detach osixia/openldap:1.4.0

如上命令, 调试过程中 –detach 换成 -it,可以看到运行过程的输出

修改了命令,添加权限(禁用 https)

docker run -d --privileged -p 80:80 -p 443:443 --name phpldapadmin-service --hostname phpldapadmin-service --link my-openldap-container:ldap-host --env PHPLDAPADMIN_HTTPS=false --env PHPLDAPADMIN_LDAP_HOSTS=ldap-host --detach osixia/phpldapadmin:0.9.0

然后可以使用用户名、密码 登录管理控制台 http://127.0.0.1/

cn=admin,dc=ihopeit,dc=com

admin_passwd_4_ldap

注意前面使用的 openldap 镜像的版本是 1.4.0, phpldapadmin 的镜像版本是 0.9.0。其他版本我没有测试过,不同版本的用法可能会有不一样。

2.除了 docker 运行 openldap 和 myldapadmin 之外,还有其他什么方案?

一些同学 遇到关于 phpldapadmin 的问题,原因可能有很多,其中有可能是 docker 网络配置,导致两个 container 的地址无法互相解析。 如果不用 phpldapadmin container,了可以使用 ApacheDirectoryStudio,直接下载https://directory.apache.org/studio/downloads.html 本机装上,连接 LDAP container 就行。 这样你就只用 确保 LDAP container 运行正确就行,避免了可能去排查 docker 网络配置导致 LDAP container 和 phpldapadmin container 无法连通的问题。

3.域服务器 LDAP Server 的客户端,除了使用 phpmyadmin 还有哪些可以使用?

可以使用 jxplorer, ldapsearch 命令行, 也可以使用 Apache Directory Studio.

https://directory.apache.org/studio/screenshots.html

4.我自己的测试环境ldap账号都能同步到django里面,但ou=Shanghai下的账号都登录不成功,检查了用户名密码都没问题, ldapsearch 可以找到用户, 使用jxplorer gui也可以成功连接

使用如下命令也可以显示正常信息

ldapsearch -x -H ldap://192.168.3.13:389 -b dc=ihopeit,dc=com -D "cn=admin,dc=ihopeit,dc=com" -w 123456

如:

ldapsearch -x -H ldap://127.0.0.1:389 -b dc=ihopeit,dc=com -D "cn=admin,dc=ihopeit,dc=com" -w admin_passwd_4_ldap

域账号结构为 “cn=aaa,ou=ops,dc=ihopeit,dc=com”,带有 ou 信息的账号同步不成功。

域账号结构为“cn=aaa,dc=ihopeit,dc=com”,这个结构的账号可以同步,可以成功登录。

比如,在dc=ihopeit,dc=com的目录下新建了一个 cn=tom,ou=CQ,dc=ihopeit,dc=com 的用户,在django中无法用 tom登录,同步数据的时候只能同步 dc=ihopeit,dc=com 下的用户数据。

ldapsearch -x -H ldap://127.0.0.1:389 -b dc=ihopeit,dc=com -D "cn=kelly,dc=ihopeit,dc=com" -w kelly12345

5.使用 ldap_sync_users 命令同步域账号之后,为什么账号仍然不能登录?

ldap_sync_users 命令把账号同步到了 Django 数据库中,但账号默认是没有 管理员权限的, 权限并不是 staff status(管理员),登录不了管理后台。

django_python3_ldap 提供了 ldap_promote 的命令, 可以直接把用户升级为管理员,就可以登录了。参考:

https://github.com/etianen/django-python3-ldap

如 :

python3 manage.py ldap_sync_users
python3 manage.py ldap_promote david

另外也可以使用视频课程中演示的方法,以 admin 登录,把那个用户勾选为 工作人员即可。

6.LDAP的登录账号密码等敏感信息以明文写在 settings.py中,生产环境如何存放账号、密码信息?

生产环境的配置跟开发环境的配置隔离,生产环境的配置要做加密处理。可以放在 KMS 系统中。也可以放在 K8S Secrets 中管理。或者放在 Vault 中管理。

7.LDAP 集成时报错:LDAP bind failed: LDAPInvalidCredentialsResult - 49 - invalidCredentials  如何排查?

可以用 jxexplorer的客户端登录一下域服务器,验证一下看看能登陆吗,然后检查下 字段的配置跟域服务器是不是一致的。 每家公司的域服务器配置都不一样,这个地方容易踩坑。

检查 base.py, local.py 里面的配置是否正确,比如按照前面步骤启动的 ldap 容器,这样配置服务:

LDAP_AUTH_URL = "ldap://127.0.0.1:389"

LDAP_AUTH_CONNECTION_USERNAME = "admin"

LDAP_AUTH_CONNECTION_PASSWORD = "admin_passwd_4_ldap"

配置参考:

https://django-auth-ldap.readthedocs.io/en/latest/example.html

8.启动了 ldap 服务, 并且命令行能够正常连接, 登录的时候,还是报错: 我用aaa账号登录,报错信息:请输入一个正确的 用户名 和密码. 注意他们都是区分大小写的.

docker 安装好 ldap 服务, 准备好 ldap 账号, 使用如下命令也可以显示正常信息

ldapsearch -x -H ldap://192.168.3.13:389 -b dc=ihopeit,dc=com -D "cn=admin,dc=ihopeit,dc=com" -w 123456

使用jxplorer gui也可以成功连接

python manage.py ldap_sync_users

LDAP connect succeeded LDAP user lookup succeeded Synced aaa

LDAP user lookup succeeded

问题开始出现:我用aaa账号登录,报错信息:请输入一个正确的 用户名 和密码. 注意他们都是区分大小写的.

pycharm中的log信息:LDAP bind failed: LDAPInvalidCredentialsResult - 49 - invalidCredentials - None - None - bindResponse - None

问题排查:

1.django_python3_ldap 提供了两个 management commands:ldap_sync_users 和 ldap_promote

如果在 openldap 跑起来后并且创建了LDAP用户,然后执行了 :

ldap_sync_users,就可以在 用户表里面看到 所有的 LDAP users 了。

这个时候如果你尝试用 LDAP 用户登录自然会出错,原因是权限并不是 staff status(工作人员状态),但是登录界面的出错信息却是『请输入一个正确的 用户名 和密码. 注意他们都是区分大小写的。解决方法就是 如课程的介绍,以 admin 登录,把那个用户勾选为 工作人员即可。

2.还有一个技巧就是,启动 openldap 最好不要 用 –detach,而是用 -it。这样就可以把 ldap 放到前台执行,直接就可以看到输出的信息,这样 LDAP 用户登录失败,就可以一目了然到底是哪里出了问题。

9.容器启动了LDAP组件,怎么样在LDAP的管理页面上添加用户?

参考本节前面“容器化”里面的第1个问题,启动 LDAP 服务和 myldapadmin 后, 登录 http://127.0.0.1/ 打开管理页, 使用登录账号登录。 在页面上面点击 “dc=ihopeit,dc=com”, 再点击 “Create a child entry” (创建子条目),选择模板 “Default”,ObjectClasses 选择 “inetOrgPerson“来创建一个用户,在创建用户页输入用户名(不含空格)和密码,这样用户添加完成。

选择 rdn attributes 为 cn,页面上在 cn, sn 里面输入用户名(不含空格),密码,以及 User Name。

五、国际化与多语言

1.verbose_name=“创建日期”,和verbose_name=_(“创建日期”)这两种写法的区别?

第二种写法_,这个import 的 gettext_lazy 函数,这个函数用来根据 key 取到指定的语言对应的 文本内容,从而对不同语言展现不同内容。 用在多语言支持中。

2.如何读取有 BOM 头的 CSV 文件?

可以指定 utf-8-sg 编码来读取。

with open(path, 'rt', encoding='utf-8-sig') as f:

3.Windows下导出的csv用Excel打开是乱码,如何解决?

导出文件的时候,指定 BOM 头:

response = HttpResponse(content_type='text/csv', charset='utf-8-sig')

或者这样:

    response.write(codecs.BOM_UTF8) # 加上这句

    writer = csv.writer(response)

4.运行 makemessages 报错:CommandError: Can’t find msguniq. Make sure you have GNU gettext tools 0.15 or newer installed.

windows下执行:django-admin makemessages -l zh_HANS -l en

会报错如上。

解决方法:安装gettext

下载地址:http://gnuwin32.sourceforge.net/packages/gettext.htm

Ubuntu环境运行 django-admin makemessages -l zh_HANS -l en , 如果报错:

xgettext: error while loading shared libraries: libgettextsrc-0.21.so: cannot open shared object file: No such file or directory

也是同样的原因,安装 gettext 即可解决问题。

https://www.gnu.org/software/gettext/

六、CSV 处理

1.CSV文件保留第一行表头的字段定义,如何优雅地读取 CSV 文件?

比如 student.csv 文件:

Id,Name,Course,City,Session
21,Mark,Python,London,Morning
22,John,Python,Tokyo,Evening
23,Sam,Python,Paris,Morning
32,Shaun,Java,Tokyo,Morning

可以使用 csv 模块的 DictReader 来读取。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from csv import DictReader
# open file in read mode
with open('students.csv', 'r') as read_obj:
    # pass the file object to DictReader()
    csv_dict_reader = DictReader(read_obj)
    # get column names from a csv file
    column_names = csv_dict_reader.fieldnames
    print(column_names)

    # iterate over each line as a ordered dictionary
    for row in csv_dict_reader:
        # row variable is a dictionary that represents a row in csv
        print(row)
        print(row['Name'])

2.目前导入csv都是技术通过命令行实现的,这个做成产品功能让HR自己导入文件是不是更好呢

从体验的角度,做成产品功能让 HR 来用体验会更好。为了最快速的交付,用的命令行的方式实现了一个最简单的导入功能。

七、容器化

1.从 docker 官方仓库拉取镜像速度非常慢,如何加速?

可以使用阿里云的镜像仓库来加速。 登录阿里云的控制台,打开“容器镜像服务”,在“镜像加速器”里面找到镜像加速的 URL, 如果没有创建一个加速器。 然后添加到本机的配置中。Linux 服务器是把配置放到 /etc/docker/daemon.json 文件中。

https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

2.docker 容器运行的时候时间显示不正确,如何处理?

通常容器默认的时区是标准的UTC时区,不是北京时间。 北京时间比 UTC要快8小时。可以在 Dockerfile 或者 容器启动的时候加上参数设置时区。 以 Dockerfile 为例,加入这一行:

1
2
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone 

除了在 Dockerfile 中指定时区,也可以通过启动容器的命令行参数 -e TZ=“Asia/Shanghai” 设置时区:

docker run -e TZ="Asia/Shanghai" -d -p 80:80 --name nginx nginx

八、应用部署与监控告警

1.创建项目之后 recruitment文件夹内没有asgi.py

老版本的 Django 比如 django 2.2 创建项目,是没有 asgi.py 文件的。

检查一下 Django 的版本是否是 Django 3.x, Python 的版本是否是 3.x。

如果是从老的 django 项目升级到 django 新版本,可以手工创建 asgi.py 文件。文件里面的内容参考自动创建的文件内容即可。

2.不用CDN放置静态资源的话,课程中提到的另一个方法如何实现?

把静态资源的目录, 在 nginx 里面配置一个目录的映射,配置 /static 指向资源文件所在的目录就可以了。 比如 nginx 的conf 文件这样配置, 把 /static 指向应用的 static 目录:

1
2
3
      location /static {
          alias /data/workspace/recruitment/static;
      }

nginx 的 conf 文件配置参考 nginx 官网,或者 tengine的文档。

通过 nginx 来为静态资源提供服务的时候,同样也是可以在前端加 CDN 的, CDN 回源到 nginx 服务。 类似于 CDN 回源到 OSS 文件。 不同的是使用 nginx 就可以少一个上传文件到 OSS 的步骤。应用直接部署到服务器上就可以。

3.Sentry 解压后的目录中没有install.sh

要下载 onpremise 的版本:

https://github.com/getsentry/onpremise repository。

搜索引擎可能给出 https://github.com/getsentry/sentry 的链接在搜索结果第一位。注意要下载 onpremise 版本,这个版本是用来自己安装部署的。

九.Python 基础

1.为什么这样定义列表会报错: actions = (export_model_as_csv),写成这样会报错“‘method’ iobject is not iterable”

这个写法定义的 actions 是一个字符串, 只是字符串被括号括起来了。 后面要加一个逗号才是一个 tuple, tuple/list 都是可以遍历的。

actions = (export_model_as_csv,)

十.课程勘误 与 代码 Bug Fix

1).在save_model里面获取user给creator, 其user修改时会成为新的creator

这个地方的逻辑不够严谨,更严谨一点,是需要区分创建还是修改的。

2).第7节-添加自定义页面:让匿名用户可以浏览职位列表页, 视频中的两处赋值

在views.py文件中 :

for job in job_list:

     job.city_name = Cities[job.job_city]

     job.type_name = JobTypes[job.job_type]

视频的 8:10 这个地方的两行代码有误。

代码在 11:31 的页面做了修复, 正确的是这样的(参考代码仓库的代码):

    job.city_name = Cities[job.job_city][1]

    job.type_name = JobTypes[job.job_type][1]

3).容器中启动 Django 应用,日志里面记录的时间不是北京时间

通常容器默认的时区是标准的UTC时区,不是北京时间。 北京时间比 UTC要快8小时。可以在 Dockerfile 或者 容器启动的时候加上参数设置时区。

1
2
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone 

十一.相关插件与开源系统

Django实现的运维管理系统有哪些可以参考的?

这两个 cmdb 都是基于 django 开发的,可以参考, open-cmdb 是 前后端分离的架构,前端用的 vue

https://github.com/voilet/cmdb

https://github.com/open-cmdb/cmdb

Sentry 也是 基于 django 开发的监控告警系统。

有哪些参考文档推荐?

https://docs.djangoproject.com/en/3.2/

https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django