普通视图

发现新文章,点击刷新页面。
昨天以前追梦人物的博客

[None] Django 老项目如何从 SQLite 迁到 PostgreSQL

2023年4月13日 19:47
因为 Django 项目配置 SQLite 非常简单,而且考虑到个人博客访问量不会很大,所以线上环境直接使用了 SQLite 数据库。 博客上线初期一切运行良好,直到最近频繁收到 `database is locked` 的异常告警。经过一番调研,大致确定了导致异常的原因:并发情况下,如果线程等待数据库锁的时间超过指定时间(默认为 5 秒)则会抛出 `database is locked` 异常。[Stackoverflow](https://stackoverflow.com/a/3172950/6022059) 上有人问了类似的问题,解决方案大致可总结为这几种: 1. 更换数据库引擎。 2. 优化应用程序,减少并发。 3. 增加锁超时时间。 方案 3 治标不治本,方案 2 比较麻烦,而且一直使用 SQLite 也不是长远之计,所以长痛不如短痛,决定线上环境彻底换掉 SQLite,投入 PostgreSQL 的怀抱。 ## 数据迁移方案 一开始想到的方案是将 SQLite 中的数据导出为 SQL 语句,再导入 PostgreSQL 数据库中,但毕竟分属 2 个不同的数据库引擎,很容易出现不兼容的 SQL 语句,所以这个方案被否掉了。 最终决定采用的方案是将 SQLite 中的数据导出为 JSON 格式的数据,再导入的 PostgreSQL 数据库中,Django 提供了导出和导入的命令,实施起来非常方便。 以下是整个迁移流程: 1. 导出 JSON 格式的数据:`python manage.py dumpdata > db.json`; 2. 修改 Django 项目的数据库引擎配置; 3. 生成新的数据库表:`python manage.py migrate`; 4. 删除新表中自动生成的 ContentType 有关的数据: ```bash $ python manage.py shell >>> from django.contrib.contenttypes.models import ContentType >>> ContentType.objects.all().delete() ``` 5. 导入 JSON 数据:`python manage.py loaddata db.json`。 第 4 步需要特别注意,新生成的数据库表,Django 会自动写入 ContentType 有关的数据,需要先删除掉,否则会和导入的数据的冲突。 ## 遇到的坑 尽管迁移步骤非常的简单,但是也存在不少的坑,以下就是我遇到的一些坑和填坑方法。 ### 坑1:不兼容的数据格式 SQLite 数据库字段的类型比较简单,而 PostgreSQL 字段类型更加丰富,有些能够存入 SQLite 的数据导入 PostgreSQL 时无法通过格式校验。 例如我的博客评论有一个 ip 字段,某些空记录的值为 `b''`。SQLite 中字段类型是 char,而 PostgreSQL 中是 inet,inet 施加的校验更强,值 `b''` 无法通过校验,因此导入时会报错。 解决方案就是导出数据前,先将 ip 字段的值修改为和 PostgreSQL inet 字段类型兼容的值。 ### 坑2:PostgreSQL 不允许 join 不同类型的字段 PostgreSQL 不同类型的字段不允许 join 操作。因此在 SQLite 和 MySQL 中执行没问题的 SQL 语句在 PostgreSQL 中就会报错。 没有太好的解决方案,只能把各个表中需要 join 的字段改为相同类型。如果无法修改,建议更换数据库为 MySQL。 ### 坑3:当心应用程序自动生成的数据 应用程序很可能会有当某条记录存入数据库时,自动生成某些关联数据的逻辑,导入数据时就会产生冲突。 例如我的博客应用中,当 Users 表中存入一条用户记录后,就会在 token 表中生成一条关联的记录,那么导入数据时,原数据中的 token 就会和新生成的 token 冲突。 解决方案是,找出自动生成的数据,将他们从导出的数据中排除。`dumpdata` 命令支持指定排除的数据,例如:`python manage.py dumpdata > db.json --exclude=authtoken` 将排除 authtoken 应用中的数据。 ## 总结 1. 更换数据库引擎是一件异常痛苦的事,所以在项目初期就要选好数据库引擎,减少数据迁移的麻烦。 2. 尽管 Django ORM 非常强大,在一定程度上提供了数据库引擎的抽象,但仍然无法避免在某个数据库工作良好的代码,换到另一个数据库就无法使用的情况,所以无论是开发、测试还是线上,尽量使用相同的数据库引擎。 3. 这种数据迁移方式效率非常低,我博客 200M 的数据导入就花了 30 多分钟,如果数据量比较大,最好还是换一种更加高效的方式。

[None] 如何在 Windows 下搭建高效的 django 开发环境

2023年4月13日 19:47
从初学 django 到现在(记得那时最新版本是 1.8,本文发布时已经发展到 3.1 了),开发环境一直都是使用从官方文档或者别的教程中学来的方式搭建的。但是在实际项目的开发中,越来越感觉之前的开发环境难以适应项目的发展。官方文档或一些教程中的环境搭建方式主要存在这些问题: 1. `python manage.py runserver` 启动的开发服务器热重载非常慢,尤其是当项目中导入了大量模块时,有时候改一次代码要等几秒钟才能完成重载。 2. 主力开发环境为 Windows + PyCharm,然而有时候依赖的一些服务只能在 Linux 下运行(例如 [Celery](https://github.com/celery/celery) 以及其他一些异步任务的库如 [django-q](https://github.com/Koed00/django-q))。 针对以上的一些痛点,我在实际开发当中逐步摸索出一套新的开发环境搭建方法,用来搭建一套舒适的 django 开发环境,总的来说,新的环境包括以下几个方面的改进: - 使用 [Uvicorn](https://github.com/encode/uvicorn) 代替 django 自带的开发服务器启动应用,极大提高代码热重载速度。 - 使用 [Pipenv](https://github.com/pypa/pipenv) 或者 [Poetry](https://github.com/python-poetry/poetry) 来管理虚拟环境和项目依赖。 - 使用 Docker 容器来运行需要在 Linux 平台下才能运行的服务。 - 使用 [AutoHotkey](https://www.autohotkey.com/) 为常用命令设置 alias。 ## PyCharm 创建 django 项目 Django 项目开发首选 PyCharm,当然你也可以使用 VS Code,不过极有可能随着一通折腾和配置之后,你会发现终于成功地把 VS Code 配置成了一个 PyCharm。所以为了节约有限的开发时间以及生命,推荐直接使用 PyCharm。 PyCharm 创建一个 django 项目非常简单,如果是第一次打开 PyCharm,点击 **+ Create New Project** 就会弹出创建新项目的对话框。如果已经打开过别的项目,则依次点击顶部导航条的 File > New Project 也会弹出创建新项目的对话框。 然后在对话框左侧点击 **django**,就会进入 django 项目的配置页面,完成一些初始化设置,PyCharm 就会自动为我们创建一个新的 django 项目。 Django 项目的配置页面如图: ![image-20200223142055938](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200228104548.png) **Location** 配置项会让你选择项目位置,例如我要在 `C:\Users\user\SpaceLocal\Workspace\G_Courses\` 目录下创建一个名为 `django_dev_env_demo` 的项目,就填写 `C:\Users\user\SpaceLocal\Workspace\G_Courses\django-dev-env-demo`。 然后 **Project Interpret** 选项的展开项中有 2 个选项 - **New environment using** 勾选这个选项将使用你选择的虚拟环境创建工具为新建的项目创建一个 Python 虚拟环境。PyCharm 支持多种虚拟环境创建工具,默认为自带的 Virtualenv。我之前习惯使用 Pipenv,只需要在系统全局使用 `pip install pipenv ` 后,PyCharm 就会自动发现 Pipenv 的存在。不过现在我更加倾向于使用 Poetry,PyCharm 目前还没有集成 Poetry,但是可以在创建完项目后手动设置 Poetry,配置也是超级简单,将在后面介绍。 创建新的虚拟环境需要指定一个 Base interpreter 基础 Python 环境,通常 PyCharm 会自动发现系统当前正在使用的 Python 解释器,如果 PyCharm 没有自动发现或者想要更换为系统中其它的 Python 解释器版本,也可以在 Base interpreter 选项里手动选择 Python 解释器。 使用 Pipenv 创建虚拟环境时,PyCharm 同样会自动发现系统中安装的 Pipenv 工具,如果没有发现,也可以在选项中手动选择。 - **Existing interpreter** 勾选这个选项将使用系统已有的 Python 环境。 **More Settings** 选项的展开项中包含更多的 django 初始化设置: - **Template language** 使用的模板引擎,如果没有特别理由,当然是选择 django 自带的模板引擎,当然不嫌麻烦也可以选择 jinja2。 - **Template folder** 存放模板的文件夹名称。默认即可,后续可以在项目中更改。 - **Application name** django app 名称,填不填都行,后续可以使用 `python manage.py startapp` 命令来创建 django app。 勾选 **Enable Django admin** 将启用 django 自带的 admin 管理后台。 配置完成后,点击 **Create** 就可以创建一个全新的 django 项目了。 ## Uvicorn 运行 django 之前说过,django 内置的开发服务器在修改代码后的热重载非常缓慢,我发现 uvicorn 的重载速度要快得多,所以新项目第一件事,就是使用在开发环境下使用 uvicorn 运行 django。 首先当然要安装 uvicorn。 安装 uvicorn 非常简单,首先打开 PyCharm 的 Terminal,PyCharm 会自动帮我们激活项目关联的虚拟环境,所有命令将在虚拟环境执行,非常方便。执行 `pipenv install uvicorn` 安装 uvicorn 就可以了。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200228105114.png) Uvicorn 安装好后,可以在命令行使用 uvicorn 命令来运行 django。但是,每次输命令启动 django 会非常麻烦!最好的做法是创建一个脚本,启动 django 只需要执行脚本就可以了。 在**项目根目录**创建一个 run_uvicorn.py 的脚本(脚本名可以自己随便取),代码如下: ```python filename="run_uvicorn.py" import uvicorn import os def main(): os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_dev_env_demo.settings") uvicorn.run( "django_dev_env_demo.asgi:application", host="0.0.0.0", port=8000, log_level="debug", reload=True, ) if __name__ == "__main__": main() ``` 脚本中可以随意定制代码的逻辑,非常灵活。比如这里我首先设置了一个环境变量 `DJANGO_SETTINGS_MODULE` 用来指定 django 启动时加载的配置文件。举一反三,可以在 `uvicorn.run` 之前进行更多的设置。例如对于更加复杂的项目,我通常会把一些重要的路径加入到 `sys.path` 中,以及设置项目所需的环境变量。 然后就是 `uvicorn.run` 启动 django。更确切点地说就是启动一个 ASGI 或者 WSGI 应用。django 3 以后逐渐支持 ASGI 应用,而在此之前则是 WSGI。第一个参数传入 ASGI 或者 WSGI 应用所在的模块。例如这里的 `django_dev_env_demo.asgi:application` 表示 django_dev_env_demo 下 asgi.py 模块里的 application(django 提供的 ASGI 应用的实例),在 django_dev_env_demo/asgi.py 下可以看到。如果是 django 3.0 以前版本,相应改为 WSGI application 所在模块即可。 其它启动参数顾名思义,最为重要的就是这个 `reload`,它启动热重载功能,比 django 自带的开发服务器重载快 很多。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200223145605785.png) PyCharm 执行一个 Python 脚本非常方便,点击 `if __name__=="__main__"` 旁边的绿色小三角就可以执行这个脚本,或者鼠标放到 run_uvicorn.py 上,然后点击鼠标右键,再点击 Run ‘run_uvicorn’。点击 Debug ‘run_uvicorn’,则以 debug 模式启动 django,开发过程中可以非常方便地打断点进行调试。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200223145859074.png) 脚本运行后的输出: ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200228105233.png) 说明应用已在 8000 端口启动,现在访问 localhost:8000,可以看到熟悉的 django 项目初始页面。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200223150406633.png) 对于运行过的脚本,PyCharm 都会记录在右上角,以后只要在右上角的下拉框中选择需要执行的脚本,然后点击绿色三角或者 debug 按钮执行就可以了,非常方便! ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200223150626322.png) ## Docker 运行外部服务 以前为了开发 django,我需要在系统中安装和配置各种服务,例如 MySQL、PostgreSQL、Redis、Elasticsearch,有些服务 Windows 还配不了,比如 Celery。 现在有了 Docker 容器,一切变得简单。 例如我需要一个 PostgreSQL,我只需要一条命令就可以启动一个 PostgreSQL 容器。当然更好的做法是编写一个 docker-compose 文件,并且设置该容器随 Docker 启动而启动,我系统中 PostgreSQL 服务的 Dockerfile 和docker-compose.yml 文件如下: ```dockerfile filename="Dockerfile" FROM postgres:11.3 ``` ```yaml filename="docker-compose.yml" version: '3' volumes: postgres_data: {} postgres_data_backups: {} services: postgres: restart: always build: context: . image: postgres container_name: postgres volumes: - postgres_data:/var/lib/postgresql/data - postgres_data_backups:/backups env_file: - postgres.env ports: - "14789:5432" ``` 各项配置的含义请参考 Docker 的官方文档,实现的效果就是系统开机自启动 Docker,Docker 启动后自动启动一个运行着 PostgreSQL 数据库服务的容器,服务映射到主机的 14789 端口,这样,我的 django 或者其他应用程序都可以通过 host.docker.internal:14789 访问到 PostgreSQL 数据库服务。 最棒的是,PyCharm 还集成了数据库管理工具,无论是 SQLite 还是 MySQL 或者 PostgreSQL,都可以轻松连接,这样开发过程中就不需要使用额外的数据库可视化工具来查看开发数据了。 以 SQLite 为例,示例项目中运行 python manage.py migrate 命令创建数据库。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200228105836.png) 对于 SQLite 数据库,只需要双击数据库文件就可以直接连接,然后就能查看数据库表结构以及表中的数据了: ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200228110016.png) 其它如 MySQL、PostgreSQL,只需要手动添加 Data Source 就可以连接。 类似的,Redis 现在几乎是开发必备,我会设置系统启动后也会自动启动一个 运行着 Redis 服务的容器。docker-compose.yml 文件内容如下: ```yaml version: '3' services: redis: restart: always image: 'bitnami/redis:5.0' container_name: redis ports: - '48988:6379' volumes: - 'redis_data:/bitnami/redis/data' env_file: - redis.env volumes: redis_data: driver: local ``` 服务映射到主机的 48988 端口,这样,我的 django 或者其他应用程序都可以通过 host.docker.internal:48988 访问到 redis 服务。 其它任何服务都可以类似地配置,例如 Elasticsearch、Celery 等等。 ## AutoHotkey 开发过程中难免需要输入大量命令,比如 django 中的这几条使用频率极高: ```shell python manage.py makemigrations python manage.py migrate python manage.py createsuperuser ``` 重复输入这些命令单调又枯燥,还浪费生命。 AutoHotkey 的其中一个功能是为指定的文本设置缩写词,并使用热键将缩写词展开为全文本。例如我会为以上命令设置缩写词: ``` python manage.py makemigrations 设置为 pmmm,按 tab 键展开 python manage.py migrate 设置为 pmm,按 tab 键展开 python manage.py createsuperuser 设置为 pmcs,按 tab 键展开 ``` 我只需要在命令行输入 pmmm 再按 tab,AutoHotkey 就会将其展开为 `python manage.py makemigrations` 的完整命令。 当然,AutoHotkey 的功能远不止如此,还可以用来设置快捷打开常用网站,常用文件夹等等,现在已是我在 Windows 下节约不少生命值的效率工具。 ## 使用 Poetry? ### Poetry 与 Pipenv 的争议 当年关于 Pipenv 还是 Poetry 曾引发过不少争议。《Flask Web 开发实战》的作者发文呼吁大家 [不要用 Pipenv](http://greyli.com/do-not-use-pipenv/),并列举了不少 Pipenv 存在的问题。 Pipenv 的确存在不少如文中所说的问题,很多问题 issue 维护者给出的建议是请安装 master 分支的代码。 在体验了 Poetry 以后,我发现迁移 Poetry 并非一件困难的事,而且对于日常使用来说,Poetry 一样简单易用,所以目前对于新的项目,我都会优先使用 Poetry。 当然话说回来,Pipenv 的一些问题都是一些特殊环境下的问题,这些问题 Poetry 同样也会存在。对于一般的使用场景,我并未发现 Pipenv 和 Poetry 的使用区别。 所以总结一句话,我的使用原则是: **优先用 Poetry,但对于一些小项目或者示例项目,为了更好地和 PyCharm 配合,我也会使用 Pipenv。** ### PyCharm 中设置 Poetry PyCharm 中设置 Poetry 非常简单。当然首先第一步是安装 Poetry,安装也是超级简单,各种操作系统平台都是一条命令搞定,安装命令可参考 Poetry 的 [官方文档](https://python-poetry.org/docs/#installation)。 Poetry 安装后,在项目根目录执行 `poetry init` 命令初始化项目,依据提示输入初始化设置后,poetry 会在项目根目录生成 pyproject.toml 项目描述文件。内容大概是这样: ```yaml [tool.poetry] name = "django_dev_env_demo" version = "0.1.0" description = "" authors = ["zmrenwu "] [tool.poetry.dependencies] python = "^3.7" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" ``` 安装依赖可以使用 add 命令,例如要添加 django 依赖: ```bash $ poetry add django ``` poetry 会自动将依赖写入 pyproject.toml 项目描述文件,`[tool.poetry.dependencies]` 的内容变成了这样: ```yaml [tool.poetry.dependencies] python = "^3.7" django = "^3.0.3" ``` 如果是开发环境下的依赖,和 Pipenv 类似,可以加 --dev 参数标记为开发环境依赖包: ```bash $ poetry add pytest pytest-django --dev ``` Poetry 会自动将依赖写入 pyproject.toml 项目描述文件,`[tool.poetry.dev-dependencies]` 的内容变成了这样: ```yaml [tool.poetry.dev-dependencies] pytest = "^5.3.5" pytest-django = "^3.8.0" ``` 为了 PyCharm 自动使用 poetry 创建的虚拟环境,需要在项目中配置 Python 解释器为 poetry 创建的虚拟环境中的解释器。 首先运行下面的命令输出创建的虚拟环境所在位置: ```bash $ poetry env info --path C:\Users\user\.virtualenvs\django_dev_env_demo-frkNfPtg ``` PyCharm 中:File -> Settings -> Porject: django_dev_env_demo -> Project Interpreter。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200303133829.png) 点击右上角的齿轮,再点击 Add 添加虚拟环境,选择 Existing environment,再点击 Interpreter 后面的省略号,选择 `poetry env info --path` 输出的虚拟环境路径下的 Scripts/python.exe 作为解释器。 ![](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/20200303134237.png) 然后点击 ok 确认就 ok 了。 这样,在使用 PyCharm 的 Terminal 时,就会自动激活 poetry 创建的虚拟环境,和用 Pipenv 创建的虚拟环境一样。 ## 总结 这里给大家展示了如何使用 PyCharm + Uvicorn + Docker + AutoHotkey + Pipenv or Poetry 搭建舒适的 django 开发环境,基本上能满足大部分不是很复杂的项目。 要记住,无论以什么样的方式搭建开发环境,核心目的只有一个:**让开发者只需关注核心业务逻辑的开发,而不是被各种环境问题分心。** 当然,对于一些更加复杂的项目,在此基础上我们还可以进一步优化环境的配置来满足各种复杂的环境要求,我会在以后的文章里进行进一步地介绍。

[None] 拓展Python Markdown

2023年4月13日 19:47
> 通过拓展 Python Markdown 来获得类似 django 官方文档的阅读体验。 最近阅读 django 的官方文档,发现一些很细节的文档内容展现形式,能够极大地提高文档的阅读体验。阅读其他技术文档时也会经常发现类似的内容展现形式。我的博客主要也是发布一些技术类文章,于是决定实现类似的功能以增强读者阅读博客文章的体验。 确定需求后,简单地研究了一下实现方式,然后花了一个晚上的时间把功能上线了,在这里分享记录一下整个功能的实现过程。 ## 确定需求 阅读技术类文档经常会看到这么几种内容:**Code block**、**Admonition**、**Command tab**。中文不太好翻译,来看一下实际的效果就知道了,下面是 django 中这几种内容的展现形式。 **Code block** ![django documentation code block](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/django-docs-code-block.png) 代码块的上方有一个 header,左边显示代码块所在文件路径,这样示例代码应该放在哪个文件就一目了然;右边是一个按钮,点击即可复制整个代码块中的内容。 **Admonition** ![django documentation admonition](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/django-docs-admonition.png) admonition 用来展现一些提示、警告等内容,文档中经常见到的有危险(danger)、警告(warning)、注意(attention)、重要(important)、提示(hint)等内容,不同类型的内容通常会以不同的背景和字体颜色区分。 **Command tab** ![django documentation command tab](https://blog-1253812787.cos.ap-chengdu.myqcloud.com/django-docs-command-tab.png) 技术类文档中少不了系统命令,很多相同效果的命令在不同操作系统中的字符内容是有一定差异的。写的不太好的文档通常只给出 Linux 下的执行命令;好点的文档则将执行命令分别列出;而 django 文档的处理就非常细节,以 tab 切换的形式给出不同系统下的命令执行方式,这样既能够列出不同系统下的执行命令,又不会重复占用文档的内容空间,提高了文档的紧凑感和阅读时的流畅性。 **我的需求就是要在自己博客文章中实现以上三种内容展现效果。** ## 方案研究 博客文章的标记语言采用的是 Markdown,具体的实现采用的是 [Python-Markdown/markdown](https://github.com/Python-Markdown/markdown/) 这个开源库。这个库不仅实现了 Markdown 标准语法的解析,还提供了很多丰富的拓展语法。 例如需求中提到的 **admonition** 功能,通过添加 `markdown.extensions.admonition` 拓展就可以直接实现(具体的实现原理和使用方式下面会介绍)。 **Code block** 的功能也有相应的拓展来实现的,但是调研发现官方自带拓展的功能弱了一点,无法通过拓展的语法在代码块的上方添加 header,只能部分满足需求。开源的第三方拓展中也没有找到可满足需求的实现,所以这里可能需要自己拓展实现。 **Command tab** 功能的实现在 markdown 的第三方拓展库 [facelessuser/pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) 中找到了一个 tabbed 拓展,提供的标记语法可被解析生成一个 tab 选项卡,完美满足需求。 至此,实现方案基本就可以确定了: 1. **admonition** 功能,直接使用 markdown 库的官方 admonition 拓展就可以; 2. **Code block** 在 [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) 中有一个更好的拓展实现,叫做 SuperFences,但是还是无法满足生成代码块 header 的需求,因此我们考虑对 SuperFences 再做进一步拓展; 3. **Command tab** 使用 [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) 的 tabbed 拓展可完美满足需求。 ## 具体实现 ### Admonition **admonition** 的实现最为简单,只需引入官方 `markdown.extensions.admonition` 拓展就可以了。它的实现原理是通过下面的语法标记 admonition 的内容: ```markdown !!! note "注意" 请注意这段内容! ``` markdown 会把标记内容解析为下面的 HTML 文本: ```html

注意

请注意这段内容!

``` 编写适当的 CSS 样式,就可以达到类似 django 文档中那样的展示效果了。 !!! hint "参考资料" `markdown.extensions.admonition` 拓展的使用可参考官方文档 [Admonition](https://python-markdown.github.io/extensions/admonition/#admonition)。 拓展的引入方式可参考博客项目的源码 [blogproject/core/utils.py#L57](https://github.com/zmrenwu/django-blog-project/blob/191e92da5e07a3be3e5461a6a95f12db9b4dfc40/blogproject/core/utils.py#L57)。 admonition 的 CSS 样式可参考博客中的源码 [frontend/src/style/_admonition.scss](https://github.com/zmrenwu/django-blog-project/blob/master/frontend/src/style/_admonition.scss)。 ### Code Block **code block** 的实现使用 [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) 中 SuperFences 拓展,不过遗憾的是,SuperFences 没有在代码块头部添加 header 内容的功能,这样就无法展示代码块所在的文件路径等信息了。花了不少时间读了一下 SuperFences 的源码,遗憾地发现 SuperFences 并没有暴露什么便捷的接口用于对已解析后的内容做进一步加工,如果通过继承等方式进行拓展的话可能需要覆盖重写大量方法,最后决定用一种 monkey patch 的方式进行拓展,以便使需要改动的代码量最小。 首先来看看 SuperFences 提供的代码块标记语法: ~~~markdown hl_lines="1" ```python linenums="1" def print_hello_world(): print("hello world") ``` ~~~ 注意到高亮的第一行代码,python 指定代码块中代码属于何种编程语言,其后紧跟的 key=value 形式的键值对是拓展选项(linenums 是代码行号拓展,指定后解析的代码块中的代码将包含代码行号)。 解析后的 HTML 文档大致如下: ~~~html
...
~~~ 可惜 SuperFences 原生只提供 linenums、hl_lines 两个拓展选项,我们希望能够添加一个拓展选项 filename,用于指定代码块所属文件路径,并将其值添加到解析后的代码块头部。标记语法如下: ~~~markdown hl_lines="1" ```python linenums="1" filename="pyproject/hello_world.py" def print_hello_world(): print("hello world") ``` ~~~ 预期的解析效果: ~~~html
pyproject/hello_world.py
...
~~~ 不过想基于 SuperFences 实现以上拓展并不容易,难点主要在以下两处: 1. SuperFences 在解析内容时会校验拓展选项,默认的校验器(validator)只接受 linenums、hl_lines 两个拓展选项,任何多余的选项都无法通过校验,所以我们添加的 filename 拓展选项就无法通过校验,而 SuperFences 并未暴露任何接口可以替换掉默认的校验器。 2. SuperFences 最终会调用 `SuperFencesBlockPreprocessor.highlight` 实例方法对代码块做代码高亮处理,然后返回 `
...
` 预排版内容,这是我们期望的。理想的拓展方法是对 `highlight` 方法返回的内容再进行包装,即在外层再包上 filename 选项的内容,但是 SuperFences 并未暴露任何接口可以替换 `SuperFencesBlockPreprocessor` 类,这样就无法通过继承覆盖重写 `highlight` 方法的方式增强 `SuperFencesBlockPreprocessor`。 好在 Python 语言足够灵活,我们可以通过 monkey patch 的方式以最小代码 kill 掉上述两个难点。 对于难点 1,SuperFences 使用的默认校验器 `highlight_validator` 是定义在 `pymdownx.superfences` 模块中的顶层函数,因此这里采用的方式就是在 SuperFences 调用这个函数之前,将 `highlight_validator` 替换为我们自定义的函数,这在 Python 中实现非常简单: ~~~python import pymdownx.superfences pymdownx.superfences.highlight_validator = _highlight_validator ~~~ `_highlight_validator` 是我们自定义的函数,放宽了原校验函数的校验逻辑,具体的实现代码可参考本博客的源码 [blogproject/core/utils.py#L18](https://github.com/zmrenwu/django-blog-project/blob/191e92da5e07a3be3e5461a6a95f12db9b4dfc40/blogproject/core/utils.py#L18)。 对于难点 2,想要对一个类方法返回的结果进一步包装,自然想到类方法装饰器。首先实现一个装饰器,对 `highlight` 方法返回的结果进行进一步的处理,然后再用 monkey patch 的方式将 `SuperFencesBlockPreprocessor.highlight` 方法替换为装饰后的方法。具体的实现代码请参考博客的源码 [blogproject/core/utils.py#L26](https://github.com/zmrenwu/django-blog-project/blob/191e92da5e07a3be3e5461a6a95f12db9b4dfc40/blogproject/core/utils.py#L26)。 最后编写适当的 CSS 样式,就可以达到类似 django 文档中代码块那样的展示效果了。相关的样式代码可参考博客的源码 [frontend/src/style/_literal.scss](https://github.com/zmrenwu/django-blog-project/blob/master/frontend/src/style/_literal.scss)。 !!! hint "参考资料" SuperFences 拓展还提供了很多丰富的功能,具体使用方式可参考其官方文档 [SuperFences](https://facelessuser.github.io/pymdown-extensions/extensions/superfences/#superfences)。 ### Command Tab **Command tab** 借助 [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) 的 tabbed 拓展实现,标记语法如下: ~~~markdown === "Linux/macOS" ```bash $ pipenv install django ``` === "Windows" ```shell ...\> pipenv install django ``` ~~~ 这段内容将被解析为一段具有 tab 选项卡结构的 HTML 代码段,编写相应的 CSS 样式就可以实现类似 django 文档中那样的命令切换选项卡效果,相关的样式代码可参考博客的源码 [frontend/src/style/_tabbed.scss](https://github.com/zmrenwu/django-blog-project/blob/master/frontend/src/style/_tabbed.scss)。 ## 效果演示 来看看最终的实现效果。 ### Admonition !!! danger "危险" 千万不要进行这样的操作:sudo rm -rf /*。 !!! error "错误" 如果这样做,你将造成不可修复的错误。 !!! warning "警告" 如果执行了 sudo rm -rf /* 导致系统无法恢复,后果自负。 !!! caution "当心" 千万当心在搜索历史命令时不经意间导致 sudo rm -rf /* 命令的执行。 !!! attention "注意" 千万注意你的猫在键盘上乱踩时敲出 sudo rm -rf /* 命令。 !!! important "重要" 最好不要在系统中留下 sudo rm -rf /* 的历史记录。 !!! note "备注" 以上内容请切记。 !!! hint "提示" 注意 sudo rm -rf /* 后也是可能被恢复的,所以如果你是删库跑路,一定要采取其他措施掩盖你的行径。 !!! tip "小贴士" 物理删除不如心理删除。 ### Code Block ```python filename="core/utils.py" linenums="1" def caption_fence_code_format(source, language, css_class, options, md): code = fence_code_format(source, language, css_class, options, md) caption = options.get("filename", "") if caption == "": return code return '
{}
{}
'.format(caption, code) ``` ### Command Tab === "Linux/macOS" ```bash $ export ENV_VAR=test ``` === "Windows" ```shell ...\> set ENV_VAR=test ``` ## 致谢 感谢 [Python-Markdown/markdown](https://github.com/Python-Markdown/markdown/)、[pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) 开发者们的辛勤付出。 感谢 [ djangoproject.com](https://github.com/django/djangoproject.com) 提供的参考实现。 感谢老婆大人在前端方面给予的指点。

[Django ORM 高级操作] Django 使用 union 合并不同模型(Model) 的查询集(QuerySet)

2023年4月13日 19:47
Django 开发中有时候会遇到这样的需求:查询到不同模型(Model) 的查询集(QuerySet),需要将其合并成一个查询集,甚至还希望能够对合并后的查询集排序,以便在模板中循环展示。 一个直观的想法就是将多个查询集合并为一个列表,然后使用 Python 的 `sorted` 方法排序,类似于: ```python >>> import itertools >>> qs1 = Post.objects.all() >>> qs2 = Material.objects.all() >>> qs = itertools.chain(qs1, sq2) >>> sorted(qs, key=lambda o: o.created_time, reverse=True) ``` 但是这种方法需要额外遍历两个 QuerySet,而且排序在 Python 层面进行,会损失一些性能。在对查询到的数据进行操作时的一个重要原则是**尽可能在最底层**完成操作。例如尽量在数据库层面进行数值计算或者排序等操作,数据库无法完成操作时再上升到 Python 层面。那么使用 Django 的 ORM 有没有办法同时查询出多个模型的数据并对其进行计算或者排序呢?答案是使用查询集的 `union` 方法。 ## QuerySet 的 union 方法 `union` 方法其实对应数据库的 `UNION` 操作。以我的博客为例(源代码位于 [django-blog-project](https://github.com/zmrenwu/django-blog-project)),博客有 2 个 app,其中一个 app 中有一个 `Post` 模型,用于记录**普通**类型的博客文章,另一个 app 中有一个 `Material` 模型,用于记录**教程**类文章。现在有一个需求,需要查询出全部的 `Post` 和 `Material`,并以文章发表时间 `pub_date` 逆序排序(但置顶的普通类型文章必须排在最前面)用于博客首页文章列表展示。2 个模型定义分别定义如下(适当简化,完整定义请参考源码): ```python frpm django.db import models class Post(modes.Model): title = models.CharField(max_length=255) body = models.TextField() pub_date = models.DateTimeField() pinned = models.BooleanField(default=False) class Meta: ordering = ['-pinned', '-pub_date'] class Material(models.Model): title = models.CharField(max_length=255) body = models.TextField() pub_date = models.DateTimeField() class Meta: ordering = ['-pub_date'] ``` 可以看到 `Material` 比 `Post` 少了一个 `pinned` 字段,`pinned` 字段用于标识文章是否置顶。首页文章展示需要查出除了 body 外的全部字段。ORM 的查询代码如下: ```python def get_index_entry_queryset(): post_qs = Post.objects.all().order_by().annotate( type=Value('p', output_field=CharField(max_length=1)), entry_pinned=F('pinned')) post_qs = post_qs.values_list( 'title','pub_date','entry_pinned','type' ) material_qs = Material.objects.all().order_by().annotate( type=Value('m', output_field=CharField(max_length=1)), entry_pinned=Value(False, BooleanField())) material_qs = material_qs.values_list( 'title','pub_date','entry_pinned','type' ) entry_qs = post_qs.union(material_qs) entry_qs = entry_qs.order_by('-entry_pinned', '-pub_date') return entry_qs ``` 除了查询出模型已有字段,还使用 `annotate` 设置了额外的查询字段,`type` 用于标识博客文章类型,`Material` 模型没有 `pinned` 字段,因此使用 `annotate` 设置了一个 `entry_pinned` 字段,其值恒定为 `False`,同时还对 `Post` 模型的 `pinned` 字段名使用 `annotate` 进行了别名设置,`pinned` 字段设置别名的具体原因会在后面说。 查看 `entry_qs` 的 `query` 属性,这个 ORM 查询实际执行的 SQL 语句如下: ```sql SELECT "blog_post"."title", "blog_post"."pub_date", 'p' AS "type", "blog_post"."pinned" AS "entry_pinned" FROM "blog_post" UNION SELECT "courses_material"."title", "courses_material"."pub_date", 'm' AS "type", False AS "entry_pinned" FROM "courses_material" ORDER BY (4) DESC, (2) DESC ``` 数据库查询结果如下: | title | pub\_date | type | entry\_pinned | | :----------------------------- | :------------------------- | ---- | :------------ | | Markdown 测试 | 2019-09-23 15:35:47.898271 | p | 1 | | test | 2019-09-15 13:13:00 | p | 0 | | 分类、归档和标签页 | 2019-09-07 01:41:00 | m | 0 | | 页面侧边栏:使用自定义模板标签 | 2019-08-29 23:49:00 | m | 0 | > Django 默认使用 `UNION` 操作,这会去除重复记录,保留重复记录可以给 `union` 方法传入 `all=True`,这将使用 `UNION ALL` 操作。 ## 注意事项 显然,要将两个不同模型的查询集合并为一个查询集,会有一些限制条件,因为涉及数据库的 `UNION` 操作,至少要保证两个模型查询出来的字段和类型都匹配。下面是 Django 的官方文档给出的 `union` 方法使用限制。 >- select 的字段类型必须匹配(字段名可以不同,但排列顺序要一致)。例如 field1 和 field 2 都是整数类型,select field1 和 select field 可以进行 union 操作,当引用时,以第一个 QuerySet 中的字段名进行引用。 >- 组合后的查询集,很多方法将不可用。 不过在实际使用过程中,发现还用很多的未提及的限制需要小心翼翼地处理。 例如看到示例中的这两句代码: ```python post_qs = Post.objects.all().order_by().annotate( type=Value('p', output_field=CharField(max_length=1)), entry_pinned=F('pinned')) material_qs = Material.objects.all().order_by().annotate( type=Value('m', output_field=CharField(max_length=1)), entry_pinned=Value(False, BooleanField())) ``` 代码中调用了 `order_by()` 取消模型的默认排序(模型在 `Meta` 通过 `ordering` 选项指定了默认排序),如果不这样做,将得到一个异常: > django.db.utils.DatabaseError: ORDER BY not allowed in subqueries of compound statements. 另外还要注意 `annotate` 的使用,尽管 `Post` 模型定义了 `pinned` 字段,可以直接进行查询,但是这种情况下必须要使用 annotate(对应数据库中的 as 别名)对 `pinned` 字段取一个别名,因为 `Material` 模型没有这个字段,但在查询时设置了一个固定值的别名(为了保证查询字段的个数、顺序和类型三者一致)。 有的同学可能想这样做: ```python post_qs = Post.objects.all().annotate( type=Value('p', output_field=CharField(max_length=1))) post_qs = post_qs.values_list( 'title','pub_date','pinned','type' ) material_qs = Material.objects.all().annotate( type=Value('m', output_field=CharField(max_length=1)), pinned=Value(False, BooleanField())) material_qs = material_qs.values_list( 'title','pub_date','pinned','type' ) ``` 即 `Post` 模型直接通过 `values_list` 选择需要的字段。看上去两个模型通过 `values_list` 方法 select 的字段数量、顺序、类型都相同,但实际上 Django 执行的 SQL 却是: ```sql SELECT "blog_post"."title", "blog_post"."pub_date", "blog_post"."pinned", 'p' AS "type" FROM "blog_post" UNION SELECT "courses_material"."title", "courses_material"."pub_date", 'm' AS "type", False AS "pinned" FROM "courses_material" ORDER BY (4) DESC, (2) DESC ``` 注意这里 `pinned` 的顺序不匹配了,这会导致字段顺序错乱,字段值错位,得不到想要的结果。其原因就是 `annotate` 的字段顺序不匹配。`annotate` 方法传入的关键字参数会被收集为字典,而字典是无序的,所以看到这段代码: ```python material_qs = Material.objects.all().annotate( type=Value('m', output_field=CharField(max_length=1)), pinned=Value(False, BooleanField())) ``` `type` 和 `pinned` 顺序无法确定,导致 SQL 查询中,`UNION` 后的那条查询教程类文章的 select 语句中 `type` 和 `pinned` 字段顺序无法确定,导致字段不匹配。 解决办法也很简单,按顺序使用 `annotate` 即可: ```python post_qs = Post.objects.all().annotate( type=Value('p', output_field=CharField(max_length=1))) post_qs = post_qs.values_list( 'title','pub_date','pinned','type' ) material_qs = Material.objects.all().annotate( pinned=Value(False, BooleanField())).annotate( type=Value('m', output_field=CharField(max_length=1))) material_qs = material_qs.values_list( 'title','pub_date','pinned','type' ) ``` 注意这里 `material_qs` 查询中先 `annotate` 了 `pinned` 字段,然后再是 `type` 字段,这样在省掉对 `pinned` 字段设置别名的同时又保持了字段顺序的一致。 还有看到生成的 SQL 语句中的 `ORDER BY` 子句,Django 使用了查询字段的位置而不是字段名引用待排序的字段。ORM 中无法使用 F 表达式对 **annotate 设置的字段进行排序**,以及对模型字段,F 表达式排序方法不能设置 `null_first` 或者 `nulls_last` 参数,以下用法都会报错: ```python entry_qs = entry_qs.order_by(F('type').desc(), '-pub_date') entry_qs = entry_qs.order_by('-pinned', F('pub_date').desc(nulls_last=True)) ``` 将得到如下错误: > django.db.utils.DatabaseError: ORDER BY term does not match any column in the result set. 然而对于实际的 SQL 语句,使用 as 设置的别名字段是可以进行排序的: ```mysql SELECT "blog_post"."title", "blog_post"."pub_date", "blog_post"."pinned", 'p' AS "type" FROM "blog_post" UNION SELECT "courses_material"."title", "courses_material"."pub_date", False AS "pinned", 'm' AS "type" FROM "courses_material" ORDER BY type DESC, pub_date DESC ``` 直接执行这条查询语句可以得到正确的查询结果,不知道为何 django 会报错。 ## 总结 查询集的 `union` 方法可以将不同模型查询结果合并为一个查询集(使用数据库的 `UNION` 操作),这样可以将两条查询语句合并为一条,减少数据库的查询次数,同时还能在数据库层面对组合的数据进行排序等操作。但使用时要注意: 1. select 的字段类型必须匹配(字段名可以不同,但排列顺序要一致) 2. 确保 `annotate` 方法设置的查询字段顺序一致 3. 合并后的查询集,很多方法将不可用 4. 待合并的查询集不能有排序操作 5. 合并后的查询集不能对 `annotate` 设置的字段使用 F 表达式 6. 合并后的查询集排序时不能指定 null 的顺序

[django-blog-project] 博客代码开源啦

2023年4月13日 19:47
此前很多同学和我要过博客的源码,但由于当时代码实在写的太烂,没好意思给,也没好意思把代码放到 GitHub 上,怕人见笑。 最近一段时间终于下定决心来把博客的代码好好重构了一遍,由于前期代码实在写的太烂,重构过程也是异常艰辛,几乎等同于重写,当然也从中学习了很多新的知识,稍微列举一下重构的内容吧。 - 重构全部模板。之前为了省事,各种同内容的模板都是复制粘贴,导致大量重复的模板内容,模板也没有拆分,一个模板包含大量内容,难以进行模块化维护。重构后,灵活使用自定义模板标签和 include 标签,模板内容更加模块化,复用性大大增强。 - 重构模型。此前由于模型多次改动,以及重构过程中模型的改动,导致产生了大量 migrations 记录。这里我学会了在不破坏数据库数据的情况下,如何清理掉历史 migrations 记录,重新生成全新的 migrations 文件。 - 重构全文搜索。搜索用的 django-haystack + whoosh + jieba分词的方案,关键是要解决中文搜索的问题。之前从网上参考他人的博客文章,但实现其实很不优雅。这次直接参考 django-haystack 的源码,优雅地实现了中文全文搜索。 - 重写整个前端。重写了整个 UI,非常注重响应式设计,移动端访问也有非常好的效果。此前静态资源是一股脑地扔到 app 里,没有打包,导致博客访问时需要加载大量静态资源。重构后引入 Webpack 作为打包工具,前端开发变得异常轻松。 - 重构了整个评论系统,采用 Vue + django-restframework 前后端分离开发方案,评论体验前所未有地提升。 - 引入 Docker 部署方式,几条命令就可无痛上线运行。 - 其他各种细节上的优化... 倾注了大量精力重构后,自我感觉博客代码勉强可以达到与君一阅的水平,所以将代码放在了 GitHub 上,请君指点。 当然,现在博客仍然有很多不足的地方,所以暂且 release 为 0.1.0 版本吧。 不过,v0.1.0 只是开始,后续将持续加入各种新的特性,具体可以看 [django-blog-project 版本规划与新功能开发路线图](https://www.zmrenwu.com/post/89/)。 说了这么多,代码在哪呢? 在这里:[django-blog-project](https://github.com/zmrenwu/django-blog-project) 走过路过还请不吝给个 star(o(* ̄3 ̄)o

[django-blog-project] django-blog-project 版本规划与新功能开发路线图

2023年4月13日 19:47
## v0.1.0 于 GitHub 正式开源,发布时间 2019-09-18 - 简约优雅的 UI,移动端优先的响应式设计。 - Webpack 前端资源打包。 - 基于 Vue 的多级评论系统。 - 文章、评论内容支持 Markdown 与代码高亮。 - 支持 GitHub、新浪微博社交账户登录。 - 中文全文搜索,关键词高亮。 - 完善的通知系统,评论、回复博客内通知,同时邮件提醒。 - 独有的教程系统,方便地管理系列文章。 - Docker 部署,无痛上线。 ## v0.2.0 此版本专注开发与使用体验的提升,预计发布时间为 2019年11月份 - 更多个性化配置选项 - 代码风格检查、单元测试、持续集成 - 开发者文档 - 用户使用说明手册 ## v0.3.0 此版本专注于性能提升,预计发布时间为 2019年12月份 - 数据库优化 - 前端打包优化 - 开启缓存优化

[None] (赠书)推荐一本django书籍:Django企业开发实战

2023年4月13日 19:47
今天向所有 django 学习者推荐一本值得一读的书:《Django 企业开发实战》。 说来很惭愧,作者胡阳在新书上市时的第一时间就给我快递了一本。我还清楚记得当时是情人节前一天,收到快递后的我迫不及待地撕开了包装读起来,花了近一周的时间将书中的内容完整地学了一遍,酣畅淋漓之感让我欲罢不能。 其实当时就想写这篇推荐博文的,好的东西应该让更多的人知道,无论它是收费的还是免费的。然而那时一是忙于工作和自己的一些 side project,二是有点沉迷于 xx 游戏无法自拔,所以始终没有拿起笔的动力,结果一搁浅就搁了快半年。猛然想起还欠胡阳大大一篇读后感,赶快趁着书中内容还新鲜,给大家推荐一波。 首先介绍一下这本书的作者胡阳。我想对于国内的 django 开发的初学者,或多或少地应该都有一点了解。他是 [the5fire的技术博客](https://www.the5fire.com/) 的博主,博客里分享了很多关于 Python、django 开发的技术类文章,也正是因为这样,我在当年初学 django,满世界找 django 有关的教程和参考资料时了解到了他。通过他的过往博文了解到了他的一些经历:一开始在一家小公司,后台通过不断地自我学习和提升,进入搜狐从事 Python,特别是 django 和 tornado 方面的开发工作,近两年又转战到了知乎。他的博客会经常发布这方面的优秀文章,于是我就一直默默地关注着。 初学 django 的开发者应该都有这样一个感受,比起满天飞的 PHP 和 Java Web 开发书籍、入门教程和文档,django 这方面的内容实在是少的可怜,为数不多的流传下来的经典,譬如[《The Django Book》](http://djangobook.py3k.cn/2.0/)等免费的开源书籍,要么内容偏于理论,要么就是内容严重过时,完全跟不上 django 的更新步伐,而且大部分书籍语言都是英语,中文版翻译又质量堪忧,大大增加了国内初学者的学习难度。 胡阳大大在博客中虽然会经常分享 django 开发类的文章,但一般主题都比较独立,不具备系统性,虽然对熟悉 django 的人能带来很大帮助和收获,但对于初学者却意义不大。针对 django 在国内学习资源匮乏的现象,我当时在对 django 有一定程度的了解后,自己还写过一套入门级的 [Django博客教程](https://www.zmrenwu.com/courses/django-blog-tutorial/)。 但那个时候其实自己也是个新手,虽然对 django 框架的特性和开发流程比较熟悉了,但因为源码深入的不够,所以写出来的东西,很多知识点讲的总是没有那么透。而且自己是半路出家,Web 开发纯粹是业余兴趣,没有进入像搜狐、知乎这样的互联网企业锻炼过,写出来的应用很少考虑性能,更别说从系统设计、编码开发、功能测试、性能调优、上线发布、运维监控、运行维护这一整套流程,写出来的东西自然也只能当个玩具。 所以当我看到 《Django 企业开发实战》时我就兴奋了,胡阳大大终于出了一套成体系的 django 开发书籍,书中的内容,既包含了 django 开发的基础,又包含了上面我提到的 django 应用从设计到发布整个生命周期里的一些东西,可以说胡阳大大将他这几年的工作经验,尽可能地沉淀在了这本书中。即使是对于我这样一个对 django 开发已经有了一定经验的人,从书中还是能学到很多的东西。尤其是*上线前的准备和线上问题排查*这几个部分,更是让我获益匪浅,让我一个从未在互联网公司锻炼过的人,通过书本习得了很多互联网公司应用开发、发布和维护的经验。比如说如何使用工具对应用的性能进行调优、配置缓存以优化应用性能、压力测试、自动化部署等等。 这几年的业余软件开发经历让我有一个感悟:人们在反反复复的解决前人的前人已经解决过了无数遍的工程问题,但如果他们没有把这个经验和解决方案告诉你,你可能永远也不知道如何解决这个问题。所以,一定要站在巨人的肩膀上,如果你不是在研究宇宙起源这类问题,工程上尽可能地学习他人的经验,肯定是事半功倍的。 学习 django,自然也是要站在他人的肩膀上,把他人的经验学过来,到你自己的项目上实践,这些他人的经验就变成了你的经验。让你能够更加高效地工作,更加游刃有余地工作,更加优雅地解决问题。所以,再次向大家推荐这本《Django 企业开发实战》。 当然,由于书中的内容比较丰富,因此对于过于基础的东西不会讲的太细,所以如果你是一个完全的新手的话,看起来还是有点费劲的,不过相信我,随着你对 django 不断深入的了解,你会不断从这本书中学到新的东西,发现新的价值。 最后再对[追梦人物的博客](https://www.zmrenwu.com/)读者说一句,[Django博客教程](https://www.zmrenwu.com/courses/django-blog-tutorial/)写了也快2年了,初版教程完成时还是基于 django 那时候最新发布的版本 1.10。根据不完全统计,至少有上千人通过我的这套教程入门了 django 开发。《Django 企业开发实战》中的内容对我启发很大,近期我已经在更新我的《Django博客教程》,随着对 django 更加深入的了解,教程的内容将更加充实,在仍然注重基础,讲解细致的同时,还会加入很多新的东西,比如: - 升级到最新发布的 django 2.2 - 基于 Pipenv 的工作流 - 全新的部署上线方案,基于 Nginx、Gunicorn、Supervisor/Docker - 完善的单元测试 更新进度已过一半,直接在我的个人博客教程专栏的 [HelloDjango - Django博客教程(第二版)](https://www.zmrenwu.com/courses/hellodjango-blog-tutorial/)就可以阅读,代码提交在 GitHub 仓库 [HelloDjango-blog-tutorial](https://github.com/HelloGitHub-Team/HelloDjango-blog-tutorial),敬请关注! 感谢大家胡阳和对我的支持,为了表示谢意,我将通过博客留言抽奖的形式送出 5 本胡阳大大的《Django 企业开发实战》。在本篇博客下留言谈谈你是如何接触到 django 的即可参与抽奖(最多 5 个,只针对有效楼层)送出胡阳大大的新书,抽奖的 Python 代码如下,抽奖结果也会在公众号和我的个人博客公布,届时请联系我领取奖励(包邮(✧◡✧))。 有效期截止至 2019年9月31日。 ```python import random # 留言会做去重处理,重复留言不会增加中奖概率,恶意刷屏将取消抽奖资格。 # 楼层数定义为去掉单人发布的多条留言(除第一条)后形成的评论列表的顺位数字(从 1 算起)。 # 首先去重,得到有效楼层数 n for i in range(5): x = random.randint(1, n) print("恭喜第 {} 楼层的留言者获得《Django 企业开发实战》一书!".format(x) ```

[None] 一种自顶而下的Python装饰器设计方法

2023年4月13日 19:47
装饰器是 Python 的一种重要编程实践,然而如果没有掌握其原理和适当的方法,写 Python 装饰器时就可能遇到各种困难。犹记得当年校招时应聘今日头条 Python 开发岗位,因一道 Python 装饰器的设计问题而止于终面,非常遗憾。随着编程技术的提升以及对 Python 装饰器更加深入的理解,我逐渐总结出一套自顶而下的装饰器设计方法,这个方法能够指导我们轻松写出各种类型的装饰器,再也不用像以前那样死记硬背装饰器的模板代码。 ## Python装饰器原理 下面是 Python 装饰器的常规写法: ```python @decorator def func(*args, **kwargs): do_something() ``` 这种写法只是一种语法糖,使得代码看起来更加简洁而已,在 Python 解释器内部,函数 `func` 的调用被转换为下面的方式: ```python >>> func(a, b, c='value') # 等价于 >>> decorated_func = decorator(func) >>> decorated_func(a, b, c='value') ``` 可见,装饰器 `decorator` 是一个函数(当然也可以是一个类),它接收被装饰的函数 `func` 作为唯一的参数,然后返回一个 callable(可调用对象),对被装饰函数 `func` 的调用实际上是对返回的 callable 对象的调用。 ## 自顶而下设计装饰器 从原理分析可见,如果我们要设计一个装饰器,将原始的函数(或类)装饰成一个功能更加强大的函数(或类),那么我们要做的就是要**写一个函数(或类),其被调用后返回我们需要的那个功能更加强大的函数(或类)**。 ### 简单装饰器 简单的装饰器函数就像上面介绍的那样,不带任何参数。假设我们要设计一个装饰器函数,其功能是能使得被装饰的函数调用结束后,打印出函数运行时间,我们来看看使用自顶而下的方法来设计这个装饰器该怎么做。 所谓“顶”,就是先不关注实现细节,而是做好整体设计和分解函数调用过程。我们把装饰器命名为 `timethis`,其使用方法像下面这样: ```python @timethis def fun(*args, **kwargs): pass ``` 分解对被装饰函数 `fun` 的调用过程: ```python >>> func(*args, **kwargs) # 等价于 >>> decorated_func = timethis(func) >>> decorated_func(a, b, c='value') ``` 由此可见,我们的装饰器 `timethis` 应该接收被装饰的函数作为唯一参数,返回一个函数对象,根据惯例,返回的函数命名为 `wrapper`,因此可以写出 `timethis` 装饰器的模板代码: ```python def timethis(func): def wrapper(*args, **kwargs): pass return wrapper ``` 装饰器的框架搭好了,接下来就是“下”,丰富函数逻辑。 对被装饰的函数调用等价于对 `wrapper` 函数的调用,为了使 `wrapper` 调用返回和被装饰函数调用一样的结果,我们可以在 `wrapper` 中调用原函数并返回其调用结果: ```python def timethis(func): def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result return wrapper ``` 可以随意丰富 `wrapper` 函数的逻辑,我们的需求是打印 `func` 的调用时间,只需在 `func` 调用前后计时即可: ```python import time def timethis(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end-start) return result return wrapper ``` 由此,一个可以打印函数调用时间的装饰器就完成了,来看看使用效果: ```python @timethis def fibonacci(n): """ 求斐波拉契数列第 n 项的值 """ a = b = 1 while n > 2: a, b = b, a + b n -= 1 return b >>> fibonacci(10000) fibonacci 0.004000663757324219 ...结果太大省略 ``` 基本上看上去没有问题了,不过由于函数被装饰了,因此被装饰函数的基本信息变成了装饰器返回的 `wrapper` 函数的信息: ```python >>> fibonacci.__name__ wrapper >>> fibonacci.__doc__ None ``` > 注意这里 `fibonacci.__name__` 等价于 `timethis(fibonacci).__name__`,所以返回值为 wrapper。 修正方法也很简单,需要使用标准库中提供的一个 `wraps` 装饰器,将被装饰函数的信息复制给 `wrapper` 函数: ```python from functools import wraps import time def timethis(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(func.__name__, end - start) return result return wrapper ``` 至此,一个完整的,不带参数的装饰器便写好了。 ### 带参数的装饰器 上面设计的装饰器比较简单,不带任何参数。我们也会经常看到带参数的装饰器,其使用方法大概如下: ```python @logged('debug', name='example', message='message') def fun(*args, **kwargs): pass ``` 分解对被装饰函数 `fun` 的调用过程: ```python >>> func(a, b, c='value') # 等价于 >>> decorator = logged('debug', name='example', message='message') >>> decorated_func = decorator(func) >>> decorated_func(a, b, c='value') ``` 由此可见,`logged` 是一个函数,它返回一个装饰器,这个返回的装饰器再去装饰 `func` 函数,因此 `logged` 的模板代码应该像这样: ```python def logged(level, name=None, message=None): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): pass return wrapper return decorator ``` `wrapper` 是最终被调用的函数,我们可以随意丰富完善 `decorator` 和 `wrapper` 的逻辑。假设我们的需求是被装饰函数 `func` 被调用前打印一行 log 日志,代码如下: ```python from functools import wraps def logged(level, name=None, message=None): def decorator(func): logname = name if name else func.__module__ logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args, **kwargs): print(level, logname, logmsg, sep=' - ') return func(*args, **kwargs) return wrapper return decorator ``` ### 多功能装饰器 有时候,我们也会看到同一个装饰器有两种使用方法,可以像简单装饰器一样使用,也可以传递参数。例如: ```python @logged def func(*args, **kwargs): pass @logged(level='debug', name='example', message='message') def fun(*args, **kwargs): pass ``` 根据前面的分析,不带参数的装饰器和带参数的装饰器定义是不同的。不带参数的装饰器返回的是被装饰后的函数,带参数的装饰器返回的是一个不带参数的装饰器,然后这个返回的不带参数的装饰器再返回被装饰后的函数。那么怎么统一呢?先来分析一下两种装饰器用法的调用过程。 ```python # 使用 @logged 直接装饰 >>> func(a, b, c='value') # 等价于 >>> decorated_func = logged(func) >>> decorated_func(a, b, c='value') # 使用 @logged(level='debug', name='example', message='message') 装饰 >>> func(a, b, c='value') # 等价于 >>> decorator = logged(level='debug', name='example', message='message') >>> decorated_func = decorator(func) >>> decorated_func(a, b, c='value') ``` 可以看到,第二种装饰器比第一种装饰器多了一步,就是调用装饰器函数再返回一个装饰器,这个返回的装饰器和不带参数的装饰器是一样的:接收被装饰的函数作为唯一参数。唯一的区别是返回的装饰器携带固定参数,固定函数参数正是 `partial` 函数的使用场景,因此我们可以定义如下的装饰器: ```python from functools import wraps, partial def logged(func=None, *, level='debug', name=None, message=None): if func is None: return partial(logged, level=level, name=name, message=message) logname = name if name else func.__module__ logmsg = message if message else func.__name__ @wraps(func) def wrapper(*args, **kwargs): print(level, logname, logmsg, sep=' - ') return func(*args, **kwargs) return wrapper ``` 实现的关键在于,若这个装饰器以带参数的形式使用,这第一个参数 `func` 的值为 `None`,此时我们使用 `partial` 返回了一个其它参数固定的装饰器,这个装饰器与不带参数的简装饰器一样,接收被装饰的函数对象作为唯一参数,然后返回被装饰后的函数对象。 ### 装饰类 由于类的实例化和函数调用非常类似,因此装饰器函数也可以用于装饰类,只是此时装饰器函数的第一个参数不再是函数,而是类。基于自顶而下的设计方法,设计一个用于装饰类的装饰器函数就是轻而易举的事情,这里不再给出示例。 ## 练习 最后,以当时今日头条的面试题作为一个练习。现在看来这道题只是一个简单的装饰器设计需求,只怪自己学艺不精,后悔没有早点掌握装饰器的设计方法。 > 题目: > > 设计一个装饰器函数 `retry`,当被装饰的函数调用抛出指定的异常时,函数会被重新调用,直到达到指定的最大调用次数才重新抛出指定的异常。装饰器的使用示例如下: > > ```python > @retry(times=10, traced_exceptions=ValueError, reraised_exception=CustomException) > def str2int(s): > pass > ``` > > `times` 为函数被重新调用的最大尝试次数。 > > `traced_exceptions` 为监控的异常,可以为 None(默认)、异常类、或者一个异常类的列表。如果为 None,则监控所有的异常;如果指定了异常类,则若函数调用抛出指定的异常时,重新调用函数,直至成功返回结果或者达到最大尝试次数,此时重新抛出原异常(`reraised_exception` 的值为 `None`),或者抛出由 `reraised_exception` 指定的异常。 **参考代码** 要注意实现方式不止一种,以下是我的实现版本: ```python from functools import wraps def retry(times, traced_exceptions=None, reraise_exception=None): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): n = times trace_all = traced_exceptions is None trace_specified = traced_exceptions is not None while True: try: return func(*args, **kwargs) except Exception as e: traced = trace_specified and isinstance(e, traced_exceptions) reach_limit = n == 0 if not (trace_all or traced) or reach_limit: if reraise_exception is not None: raise reraise_exception raise n -= 1 return wrapper return decorator ``` ## 总结 总结一下,自定而下设计装饰器分以下几个步骤 1. 确定你的装饰器该如何使用,带参数或者不带参数,还是都可以。 2. 将 @ 语法糖分解为装饰器的实际调用过程。 3. 根据装饰的调用过程,写出对应的模板代码。 4. 根据需求编写装饰器函数和装饰后函数的逻辑。 5. 完工! > 我分享编程感悟与学习资料的公众号,敬请关注:**程序员甜甜圈**

[None] Python提取支付宝和微信支付二维码

2023年4月13日 19:47
支付宝或者微信支付导出的收款二维码,除了二维码部分,还有很大一块背景图案,例如下面就是微信支付的收款二维码: ![](https://ws3.sinaimg.cn/large/005BYqpgly1g051yenjerj305k07bmxa.jpg) 有时候我们仅仅只想要图片中间的方形二维码部分,为了提取出中间部分,我们可以使用图片处理软件,但图片处理软件不利于批处理,且学习也需要一定成本。本文将教你使用 Python 的图像处理库 pillow,轻松批量提取图片中间的方形二维码部分。 ## 提取思路 以微信支付收款码图片为例: 分析图片我们可以看到,二维码位于白色背景中,而白色背景又位于绿色背景上。我们以图片左上角为坐标原点,横向为 x 轴(向右为正方向),纵向为 y 轴(向下为正方向)。我们的目标是需要确定白色背景部分 4 个角的坐标。 从图片左边正中向右横向穿过,当背景色从绿色变为白色时,该点所在位置的横坐标即为左上角和左下角的横坐标,记为 x_left。 同理从图片右边正中向左横向穿过,当背景色从绿色变为白色时,该点所在位置的横坐标即为右上角和右下角的横坐标,记为 x_right。 则白色背景宽度和高度为 h = x_right - x_left。 再从绿色背景转为白色背景时的点向上(或者向下,此处以向上为例)出发,当背景色从白色又变为绿色时,该点所在位置的纵坐标即为左上角和右上角的纵坐标,记为 y_top。 则可以计算出左下角和右下角的纵坐标为 (y_top + h)。 由此,白色背景部分 4 个角的坐标均确定,分别为(从左上角开始顺时针):(x_left, y_top)、(x_right, y_top)、(x_right, y_top+h)、(x_left, y_top+h)。 ## 代码实现 有了上述思路,我们就可以轻松写出 Python 脚本了。代码中给出了详细注释,其基本思路就是导入图片,将其转为一个二维矩阵,矩阵的元素为图片对应像素点的 RGBA 值,然后根据 RGBA 值的变化(即颜色的变化)确定待裁剪边界即可。 ```python import glob from PIL import Image if __name__ == '__main__': filenames = glob.glob('*.png') # 微信支付收款码导出为 png 格式 filenames.extend(glob.glob('*.jpg')) # 支付宝收款码导出为 jpg 格式 for filename in filenames: with Image.open(filename) as img: img.convert('RGBA') pix_data = img.load() # 图片左上角为原点,横向为 x 轴(向右为正方向),纵向为 y 轴(向下为正方向) width, height = img.size # 图片宽和高 mid_height = height // 2 # 图片正中纵坐标 # 确定左边界横坐标: x_left = 0 for x in range(width): rgba = pix_data[x, mid_height] if rgba[:3] == (255, 255, 255): x_left = x break # 确定右边界横坐标: x_right = width - 1 # 右边界 for x in range(width - 1, 0, -1): rgba = pix_data[x, mid_height] if rgba[:3] == (255, 255, 255): x_right = x break h = x_right - x_left # 白色背景高度(正方形) mid_height_rgba = pix_data[x_left, mid_height] if filename.endswith('png'): # 微信支付往下确定下边界纵坐标,因为当设置了收款金额时,金额显示在上方 y_bottom = mid_height for y in range(mid_height, height): rgba = pix_data[x_left, y] if rgba != mid_height_rgba: y_bottom = y break box = (x_left, y_bottom - h, x_right, y_bottom) else: # 支付宝往上确定上边界纵坐标,因为当设置了收款金额时,金额显示在下方 y_top = mid_height for y in range(mid_height, 0, -1): rgba = pix_data[x_left, y] if rgba != mid_height_rgba: y_top = y break box = (x_left, y_top, x_right, y_top + h) crop = img.crop(box) # box 参数为四元组,分别为左上角和右下角的横纵坐标 crop.save('./result/{}'.format(filename)) ``` 脚本代码同时上传在 GitHub,使用方法请看 README 文档即可。脚本源码仓库:[clip-pay-pic](https://github.com/zmrenwu/clip-pay-pic) > 我分享编程感悟与学习资料的公众号,敬请关注:**程序员甜甜圈**

[None] 2018 - 我的学生生涯最后一年回顾

2023年4月13日 19:47
2018,对大部分人而言是普普通通的一年,但对我却是意义非凡的一年,因为我的学生生涯在这一年结束了,以为这我要从学校踏入社会了。抓住 2018 年的尾巴做个回顾,算是在人生的某个阶段留下些许痕迹吧! ## 关键词一:毕业与秋招 7 年电子科技大学土著终于毕业,从本科到研究生,意味着我的学生生涯彻底结束了。秉着对计算机科学与技术的热爱,毅然从本科的电子工程跨专业到计算机,很庆幸遇到了一位好导师。家人很希望我读博,然而在我的摇摆不定下错过了博士申请,研究生后半段过得也有点浑浑噩噩,匆忙找工作下 BAT 面试都没进去,秋招只拿到下面几个还算不错的 offer: - 搜狐 - 网易游戏 - 携程 - 浙商银行 最终选择了稳定的银行,福利待遇都挺不错,2018 后半年发生了互联网大裁员,还挺庆幸选择了稳定的银行工作。 ## 关键词二: 技术 研究生期间主要用 Python 进行数据分析,在此期间又对 Python 爬虫和 Web 开发产生了兴趣,深入学习了 django、flask 等 Python Web 开发框架,同时还学习了一些前端开发知识,总结起来,2018 年在技术方面做了以下事情: - 写了一套 [Django博客教程](https://www.zmrenwu.com/category/django-blog-tutorial/),一开始只是为了自己更好地巩固 django 开发技术,后来这个教程获得了大家的广泛关注,目前该教程项目在 GitHub 已有近 1700 个 star。2019 计划升级教程,将 django 版本升级到最新的 2.x 版,并引入关于单元测试技术的教程。 - 写了一个 django 开源评论项目 [django-mptt-comments](https://github.com/zmrenwu/django-mptt-comments) - 创建了 [Django学习小组知乎专栏]() 用于分享平时所学的 django 知识,专栏目前关注人数已经快达到 7000 人。不过后期因为找工作的原因疏于打理,2019 一定会好好打理。 - 写了学习 Vue,写了一套 Vue 入门教程:[Vue 2.x Todo 教程](https://www.zmrenwu.com/category/vue2x-todo-tutorial/)。 - 为了追女生,用 Python 写了一个爬虫,爬取女神最新微博动态以及动态下的最新评论并推送到个人邮箱或者微信。后来对爬虫做了拓展,并用 flask 写了一个服务网站,访问 http://www.JustCareForYou.com 就可以订阅服务。如果你也有微博上想关注的人,欢迎订阅。(By the way,这个服务成功帮我追到了女神,目前女生接管了网站的前端开发工作,2019 会有更多更棒的功能加入,敬请期待(づ ̄3 ̄)づ╭❤~)。 ## 关键词三:读书 2018 年读过的书不算多,上半年集中在专业类书籍,下半年则主要集中在技术类的书籍了。 - 网络科学引论 - 科学计量学 - 古巴比伦以来的科学 - 学术研究,你的成功之道 - 人类简史 - 把时间当做朋友 - 精益创业 - 人人都是产品经理 - 结网 - 自控力 - 刻意练习 - 清单革命 - 吉田医生哈佛求学记 - 零秒工作 - 红楼梦 - 万万没想到 - 高效能人士的七个习惯 - 高效能人士的执行四原则 - 把时间当做朋友 - 未来简史 - 必然 - 区块链:技术驱动金融 - 东周列国志 - 如何阅读一本书 - 芳华 - Python Cookbook(在读一半) - Flask Web 开发实战 - Python 编程之美 - Two Scoops of Django 1.11 - Spring in Action(4th edition) - The Linux Command Line - MySQL 必知必会 对于技术类书籍,一定要一遍读,一边动手实践书中的代码或者示例,这样才能加深理解和印象。另外,关于某类具体的技术,官方文档永远是最好的学习材料,书籍仅仅只是辅助参考。 ## 关键词四:情感 2018 年最幸运的事应该就是遇到了她,常年单身狗终于脱单了。脱单后的生活切切实实发生了变化,尽管我自己有时候也未察觉。最大的变化,应该就是未来里不全部再是学习与工作,还有个她。 ## 结语 2019 从心再出发,和大家一样,贴一下 2019 的 flag,到 2019 年底再来啪啪打脸吧,哈哈! - 升级django博客教程 - 出一套 django-rest-framework 开发教程 - 稳定运营知乎专栏、微信公众号 - 出 3 套 Vue 教程(2018 已出一套) - 出 2 套 django 开发视频教程 - 出一本关于 django 开发的书籍(开源或与出版社合作) - 出一套 leetcode 解题视频 - 读书读书!

[None] 帮小姐姐发一条杭州有赞的内推消息 (校招、实习都可)

2023年4月13日 19:47
有赞大家都知道,是一个总部位于杭州专注于在线商城开发的互联网公司。小姐姐目前在共享技术团队实习,组内大都是来自阿里、网易的牛人,技术氛围很好,组里十分缺人,leader求贤若渴。欢迎各位同学投递简历,实习、社招、校招都可以。简历接收邮箱: zmrenwu@163.com ### 福利待遇: - 全额缴纳五险一金,每月980元有赞E卡补贴 - 工程师标配MAC Pro,加班打车企业支付无需报销 - 每年享有免费体检和团队outing,带薪年假7天起 - 每月每人180元团建费,鼓励团队一起吃喝玩乐 - 还有丰富多彩的兴趣俱乐部等你一起浪 ### 共享技术团队介绍: 大部分前端共用的工具都是由这个团队负责的,按照业务范围一共分了 6 个业务小组,每个人都归属于一个业务小组,同时会参与若干技术项目组。目前团队主要负责的业务有: - 微信小程序:一套完善的电商系统,买家可浏览商品、下单购买,并含多种营销工具,满足多种营销场景,助力商家营收。 - H5/小程序交易:支持购买,查看订单,维权,查询等交易线功能。 - 支付:提供跨平台统一的支付流程,保证稳定流畅的支付体验。 - 微页面:有赞微商城的核心能力,通过拖拽搭建动态的店铺页面,一处编辑,支持同时在H5和小程序展示。 - 商品:从商品的管理及发布到C端详情展示。 - UMP营销:这里有各促销活动、交易玩法和互动游戏来支撑商家运营,市面上找得到得我们基本都有了。 - 数据:多维度的图表给商家呈现不一样的经营数据。 - 订单:帮助商家管理订单、发货、评价。 - 微信微博经营渠道:统一管理商家在微信、微博内的营销方案。 - 消息推送:查看、管理、设置涉及到交易物流、会员等级、余额等变跟的信息推送及商家主动发起的消息群发。 - 多客服:有赞官方的IM,有赞商家与客户的沟通桥梁。 - 广告需求⽅平台(DSP):为广告主提供跨竞价市场、跨平台、跨终端的程序化广告投放平台,通过数据整合、分析受众、实现精准投放。 - 社会化客户关系管理(SCRM):帮助商家打造客户成长体系,管理生命周期,提升效率的利器。 - 账号/认证:有赞系统的守门人。 - App Engine: 提供商家部署应用接入有赞流程的能力。 - 电商云:开发者快速接入有赞底层服务能力,基于有赞微商城做二次开发。 - 有赞资产: 帮助有赞商家管理资产。 - 有赞担保: 提供有赞商家消费保障服务。 ### 前端开发工程师岗位需求: **岗位职责:** 1. 技术栈:React(PC) + Vue(Mobile) + Node(展示层+SSR) 2. 我们的前端无需兼容IE,几乎不做活动页; 3. 每年有公费参加行业会议的机会; 4. 标配 Macbook Retina + 大显 + 机械键盘; 5. 参与H5端、微信小程序、PC WebApp 等前端开发; 6. 参与公司内部系统建设(包括Node全栈开发); 7. 参与改进开发、构建、发布、监控等前端工程化体系; 8. 参与Web性能优化、体验优化; 9. 参与新技术探索、推进系统架构的演化; **任职条件:** 1. 本科及以上学历; 2. 扎实的计算机以及网络基础; 3. 阅读英文技术文档和书籍无障碍; 4. HTML、CSS、JavaScript 基础扎实,了解 HTTP 协议以及浏览器原理; 5. 有扎实的数据结构及算法基础的 加分; 6. 参与开源项目并贡献过代码 加分; 7. 有前端工程化工具、大型 Node 项目实施经验 加分; 8. 请在简历里写明 Github 账号或博客地址; 更加具体的信息可以点击下面链接了解: [https://tech.youzan.com/fe-open-day-2018/](https://tech.youzan.com/fe-open-day-2018/) [https://tech.youzan.com/tag/front-end/](https://tech.youzan.com/tag/front-end/) [https://tech.youzan.com/youzan-fe-2018/](https://tech.youzan.com/youzan-fe-2018/)

[None] 在学习django-rest-framework时收集的学习资料推荐

2023年4月13日 19:47
由于我平时开发的 django 项目都比较小,所以一直以来都是使用 django 模板引擎渲染 html 页面这种比较原始的方式在开发。最近发起了一个 [Django中文社区](https://github.com/DjangoChinaOrg/Django-China-API/tree/dev) 的项目,因为是团队形式的开发,所以决定采用更加现代化的前后端分离的开发方式。技术方面前端选择了 Vue,后端基于 django 的话毫无疑问肯定是使用 django-rest-framework。 Django中文社区的项目已经进入正式的开发阶段了,django-rest-framework 框架之前从未接触过,相当于是从零开始学。从网上搜集资料的过程中发现了一些很不错的中文学习资料,在此推荐给想要或者需要学习 django-rest-framework 框架的朋友,同时也分享一下我的个人学习经验,希望对大家快速学习这个框架有帮助。 学习任何一个框架第一步自然是看入门教程,一般优秀的框架都会为初学者准备一个 tutorial,通过一个小小的例子来介绍框架的核心特性。django-rest-framework 自然也遵循这个惯例,我学习的第一步便是通读这个教程,并且根据教程的指导一步步在本机上完成了教程中的项目,还附带做了一些笔记。下面就是教程的地址: [django-rest-framework 官方入门教程](http://www.django-rest-framework.org/tutorial/1-serialization/) 当然也有好心人翻译成了中文版放到 GitBook 上,喜欢阅读中文版的可以参考这个:[django-rest-framework 官方入门教程中文翻译版](https://www.gitbook.com/book/whatwewant/django-rest-framework-tutorial-cn/details) 学习了入门教程,理解了框架中涉及的核心概念,明白了哪些模块提供哪些功能,能做哪些事情后就可以开始着手自己的项目开发了。我就是在读完了入门教程文档后开始着手 [Django中文社区](https://github.com/DjangoChinaOrg/Django-China-API/tree/dev) API 的开发。当然面对更多复杂的项目,肯定会遇到入门教程中没有遇到过的需求,这个时候一方面是看其他人使用 django-rest-framework 开发的项目,学习他们的实现方式。比如我要实现社区的回复 API,就在 youtube 上找到了 一个很棒的使用 django-rest-framework 开发博客的教程,学习了它评论的实现方式,自己项目中的问题也就对照着解决了。下面是教程的 GitHub 源码地址,顺着项目的 readme 就能找到视频教程地址,注意 youtube 需要使用特殊方式才能访问:) [Blog-API-with-Django-Rest-Framework](https://github.com/codingforentrepreneurs/Blog-API-with-Django-Rest-Framework) 另一方面则是根据需求找到 django-rest-framework 官方文档相应的内容,参考其详细的 API 文档,从中寻求问题的解决方案:[django-rest-framework 官方文档](http://www.django-rest-framework.org/) 当然已经有国内的好心人将框架的 API 参考文档全部翻译成中文版了:[Django REST framework API 指南](https://github.com/jianshijiuyou/django-rest-framework-api-guide/) 好了,有了这些资料,一边开发项目一边学习,由需求驱动着学习,慢慢地就会对 django-rest-framework 的使用有一个更加熟练的掌握。而随着使用越来越深入,对 django-rest-framework 理解也会越来越透彻,慢慢地就会不自觉开始探索它的源码实现,那时候相信你已经成为 django-rest-framework 框架的专家了,甚至可以在 GitHub 上为 django-rest-framework 项目贡献你的代码。 > **PS**:[Django中文社区](https://github.com/DjangoChinaOrg/Django-China-API/tree/dev)项目在Django学习小组开发团队的协作下正紧锣密鼓地开发中,如果你对这个项目感兴趣并且熟悉 django-rest-framework 框架的话,欢迎加入我们,review 我们的代码,或者贡献你的代码。

[None] 区块链理论与应用研究小组成员招募书

2023年4月13日 19:47
## 概述 以区块链为底层技术的比特币近年来被炒得沸沸扬扬,区块链技术也逐渐为人熟知,不少人甚至认为这是改变现代社会人类信任体系的革命性技术,其应用场景将十分广泛。作为区块链技术的准从业人员,现决定发起成立**“区块链理论与应用研究小组”**,招募全国各地区块链技术爱好者、学习者、开发者,以组织的形式开展区块链技术的学习与研究工作。学习与研究的内容将包含两个方面: **学术性研究** 学习与研究区块链技术相关的理论知识,并为在实际开发应用中遇到的问题提供解决方案,对区块链技术的底层提供理论上的鲁棒性。**研究内容包括但不限于分布式、隐私保护、智能合约、用户行为规范等可能涉及的问题。**,用到的理论包括分布式系统设计、密码学、算法博弈论等多智能体系统。 **应用性开发** 结合理论知识,研究并开发可以应用于各行业的区块链应用,包括但不限于政务、医疗、交通、供应链、代币等行业领域。 ## 组织愿景 我们希望将组织打造为一个**极具影响力的区块链技术研究组织**。 ## 加入条件 不要求您有任何区块链技术的基础,但您必须是区块链技术爱好者或准从业人员,高校大三及以上本科、硕士研究生、博士研究生或同等学力及以上人员;区块链相关技术从业人员。且至少具备以下条件: - 扎实的高等数学基础(微积分、线性代数、概率论)。修习过近世代数、密码学、信息论等更佳。 - 良好的英文技术书籍与文档阅读能力。 - 具有一定的科研能力和学术素养(学术性研究必备)。 - 具有较好的编程能力(应用性开发必备)。 ## 能得到的福利 本组织为志愿者类型组织,运营经费来自于组织成员和社会各界人士及企业的捐赠,组织不为非全职成员提供薪酬待遇,但作为组织成员将有以下显而易见的好处: - 和来自全国各地的优秀区块链技术学习者更加高效地学习和研究区块链技术。 - 乘着区块链技术的浪潮,依托组织越来越大的影响力,获得猎头的青睐,找到区块链技术相关的高薪工作岗位。 - 未来与政府、企业、社会组织等合作而获得相应的报酬和社会声望。 - 基于对区块链技术的了解而投资获益。 - ... ## 组织前期任务 - 撰写区块链技术国内外理论研究和应用研究进展综述 - 编写一本区块链技术理论教材 - 开发一款基于区块链技术的示范性应用 目前计划招募组织成员5人,有意者请发送个人简历到邮箱:zmrenwu_blog@163.com 若简历通过筛选,我们将和你进行一个简单的在线交流,最终决定成员资格的认定。 我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan

[None] Python界网红,豆瓣工程师董伟明加了我的QQ后

2023年4月13日 19:47
提到董伟明,初学 Python 且经常逛知乎的朋友应该都不会陌生。我关注董伟明这个人也是始于知乎,当时正逢他新书《Python Web 开发实战》预售,董伟明几乎在每个知乎回答上大力推广他的著作。当然成绩是卓著的,截止目前,该书已经销售 15000 册。对一本技术书籍来说这是一件非常值得庆贺的事了。 作为一名业余的 Python 爱好者和 Django 开发者,我一直都很关注一些在 Python 和 Web 开发界活跃的牛人。我也是从董伟明新书出版时开始关注他,并了解到他的一些技术生涯。据说他当时专科毕业,在不服输精神和强大上进心的驱使下自学编程,一路披荆斩棘,在 2014 年终于成功入职豆瓣,成为一名 Web 开发工程师。之后他又坚持终生学习的理念,在工作中不断磨砺自己的技术,在工作外不断接触和学习新技能,因此在公司里迅速脱颖而出,很快成为豆瓣产品高级开发工程师。他的多年耕耘也终于迎来了丰收季,新书《Python Web 开发实战》从出版到现在累积销量 15000 册,知乎关注人数超过 20000 人,知乎 3 场 Live 参与人数超过 12000 人,这是豆瓣公司、市场以及广大 Python 初学者对他的肯定吧! 前些天一个陌生人加了我 QQ 好友,我看头像觉得非常眼熟,交流后才发现原来他就是我一直关注的董伟明。说实话,被自己一直关注的大佬主动联系还是让我有一点受宠若惊的。之前我的朋友圈一直在盛传一个 [爱湃森2017年度Pytohn榜单](https://annual2017.pycourses.com/),我惊奇地发现我的个人博客 [追梦人物的博客](https://www.zmrenwu.com) 竟然出现在 Python 界最受欢迎的 10 大中文博客中。 交流之下才知道原来这个榜单就是董伟明牵头做的,他开发了爱湃森学院,准备要进军知识付费领域了。 经过之前他的内容输出和市场对他的检验,我对董伟明的个人实力是非常认可的。在中国,Python 的发展不温不火,社区更是一盘散沙,缺乏一个领军式的人物。Python 培训界更是乱象丛生,培训机构抓住用户想速成的心态,输出大堆 Python 课程,但大部分都可以说毫无质量可言,沦为垃圾,而真正想学习 Python 的用户只能是被宰了一波又一波。 董伟明说他“希望这个市场未来会被我「搅」的趋于良性:真的用心做事的会被我督促更快的进步、降到合理的价格、产出更好的课程”。这正与我的想法不谋而合。此前我也是较为活跃在 Django 开发学习领域,深刻体会的一套好的课程能给初学者带来的巨大帮助。董伟明凭借自身多年的技术积累来实践这个想法,是值得肯定的。如果他真能不忘初心,始终如一的把他的理想贯彻下去,这绝对是中国 Python 学习者的一大福音。 目前董伟明已经在计划开发一套课程,会员价 1099 元。也许你觉得太贵了,但别急,看看这套教程包含的内容你就会觉得多么物超所值: - Python入门 - Python进阶 - Python Web开发 - Python项目实战 - Linux基础 - Python爬虫从入门到进阶 - Vue.js入门到进阶 - 微信小程序从入门到进阶 从 Python 入门,到前端热门技术框架,还有时下最热门的微信小程序开发,半全栈式的内容,通过这一套课程学习到大公司技术开发人员近十年的技术经验积累和最佳实践,究竟值不值,可以自己掂量掂量。 是的,这就是一篇帮董伟明的爱湃森学院推广课程的软文,但我更希望的是好的东西应该让更多的人知道,从而让更多的人获益。而且好的东西一定是收费的!免费的东西没人能保证质量,而且免费得到的东西,人们也更加不懂得珍惜。 如果你是一名 Python 初学者,强烈推荐你购买该课程进行学习;如果你熟悉了 Python 开发,想了解更多 Web 开发、前端开发和微信小程序开发的知识,也强烈推荐你购买学习这套课程。你可以使用**微信**扫描下方二维码购买全套课程。 ![爱湃森年度会员](https://bkt.zmrenwu.com/mmexport1517901867385.png)

[None] 招募Django学习小组项目组核心成员

2023年4月13日 19:47
Django学习小组成立1年多,目前QQ群内人数达上千人,知乎专栏关注人数超过4000人。为了进一步促进小组发展,为 django 学习者和开发者提供更好学习交流平台,现小组计划招募若干名项目组核心开发人员。 **微信公众号开发(1人)** 负责Django学习小组微信平台的开发与探索。要求: - 熟悉微信公众号开发流程及相关API - 了解Django web开发框架 - 熟悉Git与GitHub的使用 - 积极的学习热情与良好的团队合作精神 **Django后台开发(2人)** 负责django学习小组官方网站的后台开发。要求: - 熟悉Django web开发框架 - 良好的英语文档阅读能力 - 熟悉Git与GitHub的使用 - 积极的学习热情与良好的团队合作精神 成为项目组核心开发成员的福利: - 良好的学习氛围,不断激励成员学习与进步 - 提交的代码将由经验丰富的开发者review,在交流中不断提升代码编写水平 - 遇到问题小组成员积极深入探讨,并有经验丰富的开发者指导解决,迅速提高自身解决问题的能力 - 实战项目开发,为自己的简历增添精彩的一笔,对未来个人发展大有好处 - 享受因小组的发展而带来的一切福利(例如内推面试机会、流量增长带来的广告收入、由我本人为小组核心成员提供的小福利等等。) 招募说明: Django学习小组目前为开源性质的组织,所有运营费用来源于个人捐赠。目前小组没有能力提供相关的薪酬待遇。但所有小组核心成员将享受因未来小组的发展而带来的一切福利,付出终会有回报。 如有意向,欢迎投递个人简历至 djangostudyteam@163.com。若简历通过筛选,我们将和你进行一个简单的在线交流,最终决定成员资格的认定。邮件或者简历中请注明开发方向,邮件标题统一为【Django学习小组核心成员申请】【开发方向】

[None] django开发时遇到问题的正确求助姿势

2023年4月13日 19:47
自 [django博客教程](http://zmrenwu.com/post/2/)发布以来,已有超过上万名读者学习了该教程。一些学习者跟随教程顺利地完成了个人博客的搭建,但一直以来也不断地收到读者的评论留言、QQ 留言、邮件等求助信息,他们被开发中的一些问题卡主了,并且不知道该如何解决。随着教程阅读者越来越多,我收到的求助信息也越来越多。一个人的力量始终是有限的,我个人也难以回答所有求助者的问题。为此,我想向大家介绍一些当初我学习 django 时遇到问题如何有效求助的一些经验,一些更容易得到解决方案的求助渠道,以及一些可供查阅的 django 资料等。 ## 求助首选项:django 官方文档 我所遇到过的,以及我收到的很多新人的问题 70% 都能够通过 django 的官方文档找到答案。但是为什么还是有很多人会问这些在官方文档中可以找到答案的问题呢?原因他们对官方文档不熟悉。学习 django 开发,官方文档是最为全面、权威的学习资料。我的建议是在简单地入门了 django 之后,一定要花费一定量的时间开始通读官方文档的内容。也许你害怕内容太多,但我们要做的是通读文档,知道文档的哪一部分讲了一个什么问题,对 django 相关组件的文档说明有一个鸟瞰式的掌握,这样当遇到某个问题时你就能想起这个问题曾在文档的某个部分有过讨论,因此可以快速定位到文档的相关部分,找到问题的解决方案。同理,对于你正在使用的第三方库,文档依然是首选求助对象。 当然,我了解绝大部分人不想阅读官方文档的原因不是被庞大的内容量吓退的,而是被英语吓退的。但是,**在目前这样的技术环境下,熟练阅读英文技术文档和书籍是一个合格的开发者必备的技能之一**。如果英语对你来说是一个大的挑战,你应该为此制定一个长期的英语学习计划,而不该急功近利地想快速掌握一门开发的技术。如果项目紧急,你可以尝试先求助一些中文翻译文档,例如 django 有 1.8 的中文文档(我不贴地址,希望你阅读本文后已经学到如何寻找资料的技巧)。但是注意,大部分英文文档翻译都是热心的网友贡献的,一是文档更新缓慢,翻译不全,二是翻译人员众多,错误在所难免,因此一定不能长期依赖,提高自身英文水平才是硬道理。 ## 求助搜索引擎 开发过程中不可避免的会遇到很多问题,这时候要善于利用社区和搜索引擎来帮助自己解决问题。千万不要一个人关起门来和问题死磕,有时候卡了你几天不得解的问题,可能经他人一句话提醒就会是使你茅塞顿开。在这里分享一下我遇到问题通常是如何求助的。 首先最重要的一点就是要**抛弃百度**。从我个人经验来看,django 开发的大部分问题很难在百度搜到答案。与之相比的是 Google,我通常遇到问题会使用 Google 搜索,使用关键字 django + 问题简短的英文描述,90% 以上的问题都可以在 Google 的搜索结果里找到解决方案,几乎不用求助于他人。如果你没有适当的科学上网的方法,也可以使用**雅虎搜索**或者**必应搜索**代替。 当然,我知道很多人不是不想使用 Google,而是不知道问题对应的关键字该如何用英语表达。还是那句话,**在目前这样的技术环境下,熟练阅读英文技术文档和书籍是一个合格的开发者必备的技能之一。如果英语对你来说是一个大的挑战,你应该为此制定一个长期的英语学习计划。**如果情况紧急,你也可以尝试使用一些翻译软件,Google 的搜索一大好处是能够帮你自动纠正语法错误。例如曾今一个朋友问我 django 该如何显示图片,显然这种问题问 Google 比问我更容易得到更快更好的答案,只需要在搜索框输入:how to display pictures in django,你会看到排名靠前的基本都是你所需要的答案。 ## 求助开发者问答社区 不知道 stackoverflow 的开发者不是一个好的开发者,有一句话叫面向 stackoverflow 编程,甚至还有人出版从如何 stackoverflow copy 代码到项目上线的过程,stackoverflow 对开发者的重要性由此可见一般。通常,我们使用第二步提及的搜索引擎搜索到的答案基本来自 stackoverflow。但如果遇到搜索引擎都无法解决的问题,你就可以尝试在 stackoverflow 上提问,只要问题描述的很好(见下文关于如何正确提问),基本上很快就能得到热心的来自世界各地的开发者的解答。 我任然知道很多人不想使用 stackoverflow 的原因,但还是那句话,重要的事情说三遍:**在目前这样的技术环境下,熟练阅读英文技术文档和书籍是一个合格的开发者必备的技能之一。如果英语对你来说是一个大的挑战,你应该为此制定一个长期的英语学习计划。** 短期来说,可以寻找一些国内的问答社区。例如可以把 [segmentfault](https://segmentfault.com/) 看作是国内版的 stackoverflow,尽管其获取解答的时效性可能不及 stackoverflow。还有国内的技术社区 [v2ex](https://www.v2ex.com/) 的 Python 板块,也是国内较为活跃,大牛云集的地方。但在这些地方,请讨论一些有价值的问题,而不是 stupid question。还有为了解决 django 开发者的问题,我搭建一个论坛 [pythonzhcn](http://pythonzh.cn/),我推荐在以上一些地方发表了问题后,可以转载到论坛上来,一些朋友(包括我)看到了的话有时间就可以为你提供解答。 ## 求助有经验的开发者 如果你使用了以上方法任然无法获得问题的解决方案,那说明你这个问题有一定挑战性了。你可以请教一些有经验的人,或者也欢迎把问题发给我,如果我有能力和时间的话,会和你一起探讨这个问题。但请确保正确的提问方式,只有问题越清晰明确,信息越完整,回答者才能尽快地使用他们的经验为你寻找解决方案,否则在来回的沟通过程中会浪费大量的时间(如何正确提问请看下方)。 ## 如何正确提问 正确的提问就是要保证问题的目的性、完整性、清晰性、明确性、和信息量。当然如何区分一个问题是否是好问题难以找到一个合适的标准,我们不妨从反面来定义这个问题,以下一些问题我相信你一看就知道是有问题的,但我任然经常收到此类让我不知所措的问题: > 我运行开发服务器,但总是报错,运行不起来,我该怎么办? > > 我在运行程序后提示 XX 异常,这是怎么回事呀? > > 我写好了代码,但是看不到你所说的效果是怎么回事? > > 我该怎么在服务器上创建一个文件并写入内容? 诸如此类,总之以上问题的通病就是信息不完整,或者只有一个问题的笼统描述,或者就只有一个程序的异常报错信息,我相信这种问题即使是相关技术的创始人恐怕也很难回答,更别说我这种只比你多学了一点点东西的老菜鸟了。 推荐阅读这一篇文章:[能有效解决问题的提问方法](http://mp.weixin.qq.com/s?__biz=MzI3NDI5ODQ2Ng==&mid=2247483666&idx=1&sn=201c119360515cfd7765a6fe6fb5d855) ------ 最后,如果有 Python 和 Django 相关的问题,欢迎和我讨论交流,当然前提是你已经按照这篇文章的指导对问题进行了正确的处理。 我的个人博客:[追梦人物的博客](http://zmrenwu.com/)

[None] Django开发社交类网站必备的10个第三方应用

2023年4月13日 19:47
Django 的好处就是大而全,不仅内置了 ORM、表单、模板引擎、用户系统等,而且第三方应用的生态也是十分完善,开发中大部分常见的功能都能找到对应的第三方实现。在这里给大家推荐 10 个十分优秀的 Django 第三方库(GitHub 星星数基本都在 1000 以上,而且都在持续维护与更新中)。虽然这些库很适合用于社交网站的开发,但也有很大一部分是通用的,可以用于任何用 Django 开发的项目。使用这些库将大大提高开发效率和生产力。 ## django-model-utils 简介:Django model mixins and utilities. GitHub 地址:https://github.com/jazzband/django-model-utils 文档地址:http://django-model-utils.readthedocs.io/en/latest/ 点评:增强 Django 的 model 模块。内置了一些通用的 model Mixin,例如 `TimeStampedModel` 为模型提供一个创建时间和修改时间的字段,还有一些有用的 Field,几乎每个 Django 项目都能用得上。 ## django-allauth 简介:Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication. GitHub 地址:https://github.com/pennersr/django-allauth 文档地址:https://django-allauth.readthedocs.io/en/latest/ 点评:增强 Django 内置的 django.contrib.auth 模块,提供登录、注册、邮件验证、找回密码等一切用户验证相关的功能。另外还提供 OAuth 第三方登录功能,例如国内的微博、微信登录,国外的 GitHub、Google、facebook 登录等,几乎囊括了大部分热门的第三方账户登录。配置简单,开箱即用。 ## django-crispy-forms 简介:The best way to have DRY Django forms. The app provides a tag and filter that lets you quickly render forms in a div format while providing an enormous amount of capability to configure and control the rendered HTML. GitHub 地址:https://github.com/django-crispy-forms/django-crispy-forms 文档地址:http://django-crispy-forms.rtfd.org/ 点评:大大增强 Django 内置的表单功能,Django 内置的表单生成原生的 HTML 表单代码还可以,但为其设置样式是一个麻烦的事情。django-crispy-forms 帮助你使用一行代码渲染一个 Bootstrap 样式的表单,当然它还支持其它一些热门的 CSS 框架样式的渲染。 ## django-mptt 简介:Utilities for implementing a modified pre-order traversal tree in django. GitHub 地址:https://github.com/django-mptt/django-mptt 文档地址:https://django-mptt.readthedocs.io/ 点评:配合 Django 的 ORM 系统,为数据库的记录生成树形结构,并提供便捷的操作树型记录的 API。例如可以使用它实现一个多级的评论系统。总之,只要你的数据结构可能需要使用树来表示,django-mptt 将大大提高你的开发效率。 ## django-contrib-comments 简介:Django used to include a comments framework; since Django 1.6 it's been separated to a separate project. This is that project. This framework can be used to attach comments to any model, so you can use it for comments on blog entries, photos, book chapters, or anything else. GitHub 地址:https://github.com/django/django-contrib-comments 文档地址:https://django-contrib-comments.readthedocs.io/ 点评:用于提供评论功能,最先集成在 django 的 contrib 内置库里,后来被移出来单独维护(可能觉得评论并非是一个通用的库吧)。这个评论库提供了基本的评论功能,但是只支持单级评论。好在这个库具有很好的拓展性,基于上边提到的 django-mptt,就可以构建一个支持层级评论的评论库,就像 [我的博客评论区](http://zmrenwu.com/post/20/#comment-area) 中展示的这样(个人博客的评论模块就是基于 django-contrib-comments 和 django-mptt 写的)。 ## django-imagekit 简介:Automated image processing for Django. GitHub 地址:https://github.com/matthewwithanm/django-imagekit 文档地址:http://django-imagekit.rtfd.org/ 点评:社交类网站免不了处理一些图片,例如头像、用户上传的图片等内容。django-imagekit 帮你配合 django 的 model 模块自动完成图片的裁剪、压缩、生成缩略图等一系列图片相关的操作。 ## django-brace 简介:Reusable, generic mixins for Django GitHub 地址:https://github.com/brack3t/django-braces 文档地址:http://django-braces.readthedocs.io/en/latest/index.html 点评:django 内置的 class based view 很 awesome,但还有一些通用的类视图没有包含在 django 源码中,这个库补充了更多常用的类视图。类视图是 django 的一个很重要也很优雅的特性,使用类视图可以减少视图函数的代码编写量、提高视图函数的代码复用性等。深入学习类视图可以看[Django类视图源码分析](http://zmrenwu.com/post/51/)。 ## django-notifications-hq 简介:GitHub notifications alike app for Django GitHub 地址:https://github.com/django-notifications/django-notifications 文档地址:https://pypi.python.org/pypi/django-notifications-hq/ 点评:没什么好说的,为你的网站提供类似于 GitHub 这样的通知功能。未读通知数、通知列表、标为已读等等。 ## django-simple-captcha 简介:Django Simple Captcha is an extremely simple, yet highly customizable Django application to add captcha images to any Django form. GitHub 地址:https://github.com/mbi/django-simple-captcha 文档地址:http://django-simple-captcha.readthedocs.io/en/latest/ 点评:配合 django 的表单模块,方便地为表单添加一个验证码字段。对验证性要求不高的需求,例如注册表单防止机器人自动注册等使用起来非常方便。 ## django-anymail 简介:Django email backends and webhooks for Mailgun, Mailjet, Postmark, SendGrid, SparkPost and more GitHub 地址:https://github.com/anymail/django-anymail 文档地址:https://anymail.readthedocs.io/ 点评:配合 django 的 email 模块,只需简单配置,就可以使用 Mailgun、SendGrid 等发送邮件。 ## django-activity-stream 简介:Generate generic activity streams from the actions on your site. Users can follow any actors' activities for personalized streams. GitHub 地址:https://github.com/justquick/django-activity-stream 文档地址:http://django-activity-stream.rtfd.io/en/latest/ 点评:社交类网站免不了关注、收藏、点赞、用户动态等功能,这一个 app 全搞定。甚至用它实现一个朋友圈也不是不可能。

[None] 终于找完工作(内附秋招经历)开始恢复博客更新

2023年4月13日 19:47
历经近两个月的秋招,工作的事终于尘埃落定。现在对秋招的经历做一个总结,分享一下各大公司的面试经历,同时以自己对各公司的所见所闻,分析当下市场对计算机专业类各个岗位的需求情况和待遇情况,供后来者参考。同时个人博客也开始恢复更新,近期还会继续分享一些和 Django 开发相关的文章。坚持写博客是一个好习惯,个人博客在面试中也为我加分不少。 先报一下自家背景:硕士末流985计算机专业。秋招总共投的公司不多,简历投递大概有20+家,收到笔试的10+家吧,收到面试邀约的就只剩几家了,以下是面试的各个公司和面试经历的简短介绍(面试官考的具体题目就不透漏了,如有需要请参考互联网上的公开资料吧,或者与我联系): ## 搜狐(北京,后台开发,拿到 offer) 面的第一家公司,参加完宣讲会就直接线下笔试,第二天就收到面试邀请,一天下来三轮面试。由于第一次面试有点紧张,一面试官的第一道题就卡住了答不出来,面试官也可能看我太紧张,换了个单链表反转的题让我缓一缓,好在这个题不难,磕磕绊绊的用 Python 把代码写了交给面试官检查。之后面试官要我介绍自己的优势,我说我高等数学功底很好,于是面试官开始考概率论的题。可能面试官见我确实概率论的题都答得不错,就顺利进入了第二面。 第二面还是考数学知识相关的算法,现场写代码。虽然写出来了,但是复杂度比最优解高了一个量级,最后在面试官的提示下才写出正确答案。但面试官还是让过来。之后沟通了一下意向部门。 第三面应该是部门 leader 面,还是各种数学题,算概率,不过 leader 面轻松了很多,题目也容易很多。面完第二天就收到 offer 电话了,不得不说搜狐是我面过的公司里效率最高的。 ## 美团(上海,Java 后台开发,一面挂) 我是搞 Python 开发的,但美团后台只有 Java 岗,一个女面试官面的我。面试一开始准备问 Java 相关问题,然后一看我不会 Java 就傻眼了。愣了一会开始聊一些数据结构和算法、计算机网络、Web 开发相关的基础知识,虽然感觉答得都挺不错,但最终没有再收到后续通知,很久以后才收到“已被加入人才库”的邮件。感觉美团只招和岗位对口的人,如果你不会他所要求的编程语言,那能过的概率就很小了。 ## 中国银联(上海,电子支付研究,等消息) 银联笔面试效率奇高,做完笔试当晚就收到面试消息,要求第二天带着各种材料去面试,好在平时有备份,否则可能简历份数都凑不够了(所以建议找工作的小伙伴平时要多备几份材料,以备不时之需)。先是群面,好在群面我还是比较有经验,只要既不抢风头、又不太沉默,把群面当成一个省委常委会议开就可以了,另外记得计时和群面结束时小组要得出统一结论。 群面结束后是多对一的面试,配置应该是一个部门 leader,一个人力资源 leader,和一个技术人员。半结构化面试,主要是针对个人简历一条条地问,但并不涉及深入的技术问题。 面完第二天就收到了面试通过的短信,让等体检(但至今没有等到,估计已经加入备胎池了)。总体来说,国企不像互联网一样过于关注你的个人技术,他们比较看重你个人的综合能力、表达能力、学历、在校期间的表现等。 ## DJI 大疆创新(深圳, 后端开发工程师, 一面挂) 大疆校招比较早,结果错过了笔试,但正好后来大疆又来学校做宣讲,就又投了一次简历。面试官看我简历项目经验还不错就叫我过去面试看看。但 DJI 就是 DJI,问的题目难度都要高其它公司一个档次(印象最深刻的是数据结构问我红黑树,后悔没把算法导论刷一遍,还有要写一个自动扫雷算法),结果即使在面试官的提示下一道题都答不上来,然后面试官可能觉得我太水了,就没有然后了。 ## 4399 游戏(广州,游戏研发工程师, 三面挂) 4399 是互联网公司招聘届的一股清流,全程面试不要求写代码,做算法,主要考察一些计算机本科学过的一些基础知识,然后针对你投递的岗位和简历上的项目问一些问题,有点偏国企面试的感觉。但最终可能他们觉得我投递的岗位和我的经历不太对口吧,而我又不想去他们的后台开发部门,很久很久以后给我发来了“已被加入人才库”的邮件。 ## 浙商银行总行信息科技部(杭州,开发,拿到 offer) 浙商银行提前批,提前几天投了简历,可能看我简历比较优秀就直接给了绿色通道免笔试。面试是多对多的面试,挨个就面试者各自的简历问问题。银行也是比较看重你个人的综合能力、表达能力、学历、在校期间的表现等,对技术要求不是特别高。个人感觉表现还不错,第二天领导叫去二面,然后是体检,一个多月后收到录用函。 ## 网易游戏雷火/盘古事业群(杭州,平台开发&前端开发工程师,拿到 offer) 网易是我面过笔试最正规,面试比较土豪的公司。首先网易游戏会组织一场全国线下笔试,监考严格,有点考研的感觉,之后他们会通知笔试通过的同学去杭州公司面试,并报销来回车费。面试氛围也很不错,主要考察了一些 Web 开发相关的知识,偏向于高并发、大流量和网络异常等相关的处理。面完后感觉很开心,趁着机会去西湖玩了一圈,大概半个月后收到了网易的录用函。在此要感谢在网易工作的朋友对我在杭州面试期间的收留和招待。另外网易杭州的食堂非常赞,员工一日三餐在里面吃是免费的,我在里面蹭了两顿。 ## 携程(上海,云平台,拿到 offer) 携程严格来说不算校招,是在 v2ex 上云平台的一个 leader 看了我的简历后喊我去上海面试的。携程是我面过的公司中面试轮数最多的。首先是两轮电话面试,考察一些 Python 相关的基础知识,之后就叫去上海携程总部面试,一共三轮,花费了一整个下午的时间,也是针对我的简历项目问一些 Python 和 Web 开发相关的问题,以及拿一些工作中碰到的问题作为考察,要求当场给出解决方案和实现。总体来说携程的面试非常专业,hr 也非常专业,办公场地也是高大上,团队氛围感觉非常的不错。整个面试表现个人感觉也还行,想着应该问题不大,当时就想干脆在携程工作得了。几天后在我回学校的途中收到了 hr 的录用电话。 ## 今日头条(深圳,效率工程团队后台开发,三面挂) 师兄内推的,面完携程后去武汉开一个学术会议,期间头条就打电话来安排面试,本来要求去北京总部,但沟通后改为视频面试。当时身上没带电脑,只能去网吧面试。总体来说头条面试要求比较高,面试考察的题目主要偏向于高流量、高并发的相关处理,以及数据结构算法、编程语言(我的主要是 Python)相关的基础知识。由于在网吧面试,环境比较差,很多题目在面试时怎么想都答不上来,之后一出网吧答案就想起来,有种期末考试刚交完卷发现不会做的题有了解法的感觉,最后一面也是一样的感觉(要求写一个 Python 装饰器竟然没有写出来,后来真想给自己一个嘴巴了),没能把握住机会。听说头条薪资非常诱人,感觉还是蛮可惜的。 总结:个人平时做一些 Web 开发相关的项目,只会 Python 一门编程语言,所以投递的岗位大都是后台开发相关的岗位。对后台开发来说,大公司普遍要求掌握 C++ 或者 Java, 会 Python、js 等脚本语言会成为你的加分项,但只会脚本语言的话会比较吃亏。后台开发岗公司会比较看重你平时个项目经历,如果面试时什么项目经历都拿不出来的那过的概率就很低了。另外名企实习经历也会为你的简历加分不少。 另外想对 Python 开发者说,目前大企业校招 Python 相关的岗位并不多,会 Python 会成为你的加分项,但只会 Python 则会成为你的软肋。所以最好还是能掌握一门编译型的语言吧,例如 C++ 或者 Java。特别是一些偏离主流的技术开发,例如 Django 开发,当个人爱好就好了,真的没几个公司对这感兴趣。 还有从我个人感受来看,算法类岗位需求旺盛,而且普遍薪资远超其它岗的同学。例如今日头条白菜价 30 几万起,我等只能徒有羡鱼情。所以如果你是搞机器学习、深度学习、AI 相关的,好好准备吧,竞赛拿点奖,基础知识搞透,高薪 offer 不是梦。 至于我最后到底去了哪里?就请猜猜看吧,哈哈。
❌
❌