阅读本文大概需要 3.6 分钟。
先问你个问题,框架和库有什么区别?
简单的说,框架控制你,库则由你控制,框架让你做填空题,库让你做问答题。
初学 Django,你觉得它是框架,用的久了,你也可以像三方库一样使用。
Django 之于 Python,犹如 Spring 之于 Java。Django 是 Python 的 web 开发框架,既然是框架,就是一套完整的解决方案,使用框架的时候,需要把你的代码放到框架合适的地方,框架会在合适的时机调用你的代码,框架控制一切,我们只需要按照规则写代码。
如果只是用 Django 进行 Web 开发,直接填空就好了。但是如果只想使用 Django 的部分功能,比如 Django 的 ORM、发送邮件、模版渲染,就像使用三方库那样,直接导入相关的包来自由使用 Django 呢?
为什么我会提出这个问题?
一是因为 Django 的 ORM 足够简单和好用,二是我懒得学习其他 ORM 框架,原理大同小异,我先入手的 Django,就想一直用 Django。说多点,我倾向通用的技术,也就是一招武功走天下。Django 的 ORM 有多好用,这里举个例子,User 对象对应数据库的一张表,操作 User,就是操作数据库,完全不用写 sql:
代码语言:javascript复制# 获取数据
from .models import User
User.objects.all()
User.objects.count()
User.objects.filter(name='somenzz').count()
# 匹配,对应SQL:select * from User where name = 'somenzz'
User.objects.filter(name='somenzz')
# 不匹配,对应SQL:select * from User where name != 'somenzz'
User.objects.exclude(name='somenzz')
# 获取单条数据
user_123 = User.objects.get(id=123)
# 修改数据
user_123.age = 1
user_123.save()
以上这些代码通常会在 Django 给你生成好的视图文件,比如 views.py 里出现,如果单独写一个文件,如 orm_demo.py,把上述代码贴过来,然后执行 python orm_demo.py 就会报错,下面带你顺藤摸瓜来解决这个问题。
Django 开发环境的启动往往是 python manage.py 命令
来实现,比如常见的 python manage.py runserver
, python manage.py shell
。关键就在于 manage.py 文件,让我们来看一下 manage.py 的内容:
版本 Django==3.1
代码语言:javascript复制#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
可以看到,使用 Django 的第一步需要指定 Django 的配置文件,这是必须的,不然 Django 怎么知道如何连接数据库呢,因此需要在我们的代码中加入
代码语言:javascript复制os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_project.settings')
如果你配置文件不在 django_project 同级的目录,请使用 sys.path.append 来添加,确保 Django 的配置文件 setting.py 可以被导入。
Django 官网也提到,不使用配置文件也是可以的,可以在代码中使用 settings.configure 来使用配置 Django,比如:
代码语言:javascript复制from django.conf import settings
settings.configure(DEBUG=True)
接下来我们跳转至函数 execute_from_command_line(sys.argv)
看看 Django 做了什么:
def execute_from_command_line(argv=None):
"""Run a ManagementUtility."""
utility = ManagementUtility(argv)
utility.execute()
继续看 execute 函数的主要代码:
代码语言:javascript复制if settings.configured:
# Start the auto-reloading dev server even if the code is broken.
# The hardcoded condition is a code smell but we can't rely on a
# flag on the command class because we haven't located it yet.
if subcommand == 'runserver' and '--noreload' not in self.argv:
try:
autoreload.check_errors(django.setup)()
except Exception:
# The exception will be raised later in the child process
# started by the autoreloader. Pretend it didn't happen by
# loading an empty list of applications.
apps.all_models = defaultdict(dict)
apps.app_configs = {}
apps.apps_ready = apps.models_ready = apps.ready = True
# Remove options not compatible with the built-in runserver
# (e.g. options for the contrib.staticfiles' runserver).
# Changes here require manually testing as described in
# #27522.
_parser = self.fetch_command('runserver').create_parser('django', 'runserver')
_options, _args = _parser.parse_known_args(self.argv[2:])
for _arg in _args:
self.argv.remove(_arg)
# In all other cases, django.setup() is required to succeed.
else:
django.setup()
self.autocomplete()
会发现有个django.setup()
会在 settings.configured 为真的情况下调用,至此已经真相大白。如果要想独立使用 Django,有两点是需要做的,一是配置 Django,二是调用执行 django.setup()
。setup 的作用就是加载设置并填充 Django 的应用程序注册表。setup 函数的代码如下:
def setup(set_prefix=True):
"""
Configure the settings (this happens as a side effect of accessing the
first setting), configure logging and populate the app registry.
Set the thread-local urlresolvers script prefix if `set_prefix` is True.
"""
from django.apps import apps
from django.conf import settings
from django.urls import set_script_prefix
from django.utils.log import configure_logging
configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
if set_prefix:
set_script_prefix(
'/' if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME
)
apps.populate(settings.INSTALLED_APPS)
因此,即使 Django 不作为 Web 开发的框架,也可以作为实用工具库来使用,例如,编写一个 Python 脚本来加载一些 Django 模板并进行渲染,或者使用 ORM 来获取某些数据。一种优雅的独立使用 Django 的方式如下:
代码语言:javascript复制import django
from django.conf import settings
settings.configure(
INSTALLED_APPS=["django_app.apps.DjangoAppConfig"],
DATABASES={
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": "db.sqlite3",}
},
)
if __name__ == "__main__":
django.setup()
from django_app.models import CrawlerMonitor, DownloadedDocs
crawler_monitor = CrawlerMonitor.objects.get(id=1)
print(crawler_monitor.web_site)
请注意 django.setup()
仅当您的代码真正独立时才需要调用,因此,避免将可重用的应用程序逻辑放在独立的脚本中,如果实在无法避免的话,你可以这样做:
if __name__ == '__main__':
import django
django.setup()
此外还有非常简洁实用的发邮件功能,比原始的 smtplib 好用太多:
代码语言:javascript复制def fun1():
from django.core.mail import send_mail
# 一次发送一封邮件
send_mail(subject='爬虫id=xx发生异常', message='异常信息如下:xxx', from_email= 'somezz@***.com', recipient_list= ['somenzz@***.com'], fail_silently=False)
# 一次发送多封邮件
message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)
# send_mail 每次发邮件都会建立一个连接,发多封邮件时建立多个连接。而 send_mass_mail 是建立单个连接发送多封邮件,所以一次性发送多封邮件时 send_mass_mail 要优于 send_mail。
# 发送附件
def fun2():
from django.core.mail import EmailMessage
email = EmailMessage(
subject='Hello',
body= 'Body goes here',
from_email= 'somezz@***.com',
to= ['somezz@***.com', 'somenzz@***.com'],
)
email.attach_file("./demo_sendmail.py")
email.send()
使用邮件功能需要配置邮件服务器的登录信息,如下:
代码语言:javascript复制EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 465
EMAIL_HOST_USER = '***@163.com'
EMAIL_HOST_PASSWORD = '***'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
完成可以独立运行的 django orm 操作 demo 我已经为你制作好,长按尾部二维码,关注本公众号后回复关键字「orm」获取。
这里附上其他 Django ORM 操作,感受一下 Django 的良苦用心:
代码语言:javascript复制# 小于等于,<=,对应SQL:select * from User where id <= 724
User.objects.filter(id__lte=724)
# 同时大于和小于, 1 < id < 10,对应SQL:select * from User where id > 1 and id < 10
User.objects.filter(id__gt=1, id__lt=10)
# 包含,in,对应SQL:select * from User where id in (11,22,33)
User.objects.filter(id__in=[11, 22, 33])
# 为空:isnull=True,对应SQL:select * from User where pub_date is null
User.objects.filter(pub_date__isnull=True)
# 不匹配,大小写敏感,对应SQL:select * from User where name not like '%sre%',SQL中大小写不敏感
User.objects.exclude(name__contains="sre")
# 不匹配,大小写不敏感,对应SQL:select * from User where name not like '%sre%',SQL中大小写不敏感
User.objects.exclude(name__icontains="sre")
# 范围,between and,对应SQL:select * from User where id between 3 and 8
User.objects.filter(id__range=[3, 8])
# 以什么开头,大小写敏感,对应SQL:select * from User where name like 'sh%'
User.objects.filter(name__startswith='sre')
# 以什么开头,大小写不敏感,对应SQL:select * from User where name like 'sh%'
User.objects.filter(name__istartswith='sre')
# 排序,order by,正序,对应SQL:select * from User where name = 'somenzz' order by id
User.objects.filter(name='somenzz').order_by('id')
# 多级排序,order by,先按name进行正序排列,如果name一致则再按照id倒叙排列
User.objects.filter(name='somenzz').order_by('name','-id')
# 排序,order by,倒序,对应SQL:select * from User where name = 'somenzz' order by id desc
User.objects.filter(name='somenzz').order_by('-id')
# limit,对应SQL:select * from User limit 3;
User.objects.all()[:3]
# offset,取出结果的第10-20条数据(不包含10,包含20),也没有对应SQL,参考上边的SQL写法
User.objects.all()[10:20]
# 分组,group by,对应SQL:select username,count(1) from User group by username;
from django.db.models import Count
User.objects.values_list('username').annotate(Count('id'))
# 去重distinct,对应SQL:select distinct(username) from User
User.objects.values('username').distinct().count()
# filter多列、查询多列,对应SQL:select username,fullname from accounts_user
User.objects.values_list('username', 'fullname')
# filter单列、查询单列,正常values_list给出的结果是个列表,里边里边的每条数据对应一个元组,当只查询一列时,可以使用flat标签去掉元组,将每条数据的结果以字符串的形式存储在列表中,从而避免解析元组的麻烦
User.objects.values_list('username', flat=True)
# int字段取最大值、最小值、综合、平均数
from django.db.models import Sum,Count,Max,Min,Avg
User.objects.aggregate(Count('id'))
User.objects.aggregate(Sum('age'))
如果觉得不错,请点个赞吧,感谢老铁的三连支持。