利用django开发自己的网站

1、pycharm创建项目,勾选继承全局环境

如果勾选 more setting 可以填写template 和新app的名字

或者用命令行 

python manage.py startapp bbs   来创建和上面app一样的效果
mysite
    - mysite    # 对整个程序进行配置
    - init      #一个空文件,它告诉Python这个目录应该被看做一个Python包
    - settings    # 项目配置文件
    - url      # URL对应关系(路由)
    - wsgi     # 遵循WSIG规范,uwsgi + nginx
- manage.py     # 一个命令行工具,可以使你用多种方式对Django项目进行交互
python manage.py startapp autonomy
blog                 #应用目录
│  admin.py        #对应应用后台管理配置文件。
│  apps.py         #对应应用的配置文件。
│  models.py       #数据模块,数据库设计就在此文件中设计。后面重点讲解
│  tests.py        #自动化测试模块,可在里面编写测试脚本自动化测试
│  views.py        #视图文件,用来执行响应代码的。你在浏览器所见所得都是它处理的。
│  __init__.py
│
├─migrations        #数据迁移、移植文目录,记录数据库操作记录,内容自动生成。
│  │  __init__.py

在setting.py里面添加autonomy 完成注册

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'autonomy',
]

常用命令

安装Django: pip install django  指定版本 pip3 install django==2.0

新建项目: django-admin.py startproject mysite

新建APP : python manage.py startapp blog

启动:python manage.py runserver 8080

同步或者更改生成 数据库:

python manage.py makemigrations

python manage.py migrate

清空数据库: python manage.py flush

创建管理员: python manage.py createsuperuser

修改用户密码: python manage.py changepassword username

Django项目环境终端: python manage.py shell

这个命令和 直接运行 python 进入 shell 的区别是:你可以在这个 shell 里面调用当前项目的 models.py 中的 API,对于操作数据的测试非常方便。

在setting.py的最后修改时区

# 把语言改为中文
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'
# 把国际时区改为中国时区
# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Shanghai'

在views.py里面输入

mysite/blog/views.py
from django.http import HttpResponse

def hello(request):
    return HttpResponse('Hello,world')

在urls.py里面输入

mysite/mysite/urls.py
from django.contrib import admin
from django.urls import path
from blog import views         #+ 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index', views.hello),   #+
]

我们可以点击Pytcharm右上角的三角符号,或者在Pycharm底部的Terminal里输入命令:

python manage.py runserver

path('index', views.hello),对应的就是URL就是http://127.0.0.1:8000/index

URL我们都集中写在一个文件中,每个URL函数都以path、re_path(2.0的写法)的形式写出来,放在urlpatterns列表里,每个URL函数包含三个参数:URL(正规表达式)、对应方法、名称。

这个URL对应的方法是views.hello,这个会给我们返回响应,也就是Hello,world,我们把这个方法方法写在了blog下的views.py文件里。这个方法其实是一个函数,这个函数通过HttpResponse,返回一个字符串'Hello,world'。

def hello(request):
    return HttpResponse('Hello,world')

每一个URL都会对应一个函数,函数必须存在一个参数,一般约定为request,每个响应(函数),都对应着一个URL。URL发出请求之后,这个函数就作出相对的响应,把'Hello,world'这个字符串渲染成HTML页面,在浏览器里呈现给用户。这个流程,就是请求与响应的流程,也是Django的处理流程。下面通过一张图片,更形象的的让你了解DJango的处理流程。

web1.jpg

blog/models.py添加数据库  对应表 标题(title)、内容(body)、发布时间(created_time)三个字段
from django.db import models

class Article(models.Model):
    title = models.CharField('标题',max_length=70)
    body = models.TextField('内容', max_length=200, blank=True)
    created_time = models.DateTimeField('发布时间')
    
    class Meta:
        verbose_name = '文章'
        verbose_name_plural = '文章'
        
    def __str__(self):
        return self.title

需要现在setting.py替换数据库,mysql数据库版本要大于5.5  django2.1之后不再支持5.5

https://blog.csdn.net/zb0567/article/details/104453864 此处要先替换,要不sqllite会写入,即使合并也不行。。。

敲黑板%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

替换database

########在setting原来默认的sqlite
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
############修改成mysql如下
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',    #你的数据库名称
        'USER': 'root',   #你的数据库用户名
        'PASSWORD': '19941028', #你的数据库密码
        'HOST': '', #你的数据库主机,留空默认为localhost
        'PORT': '3306', #你的数据库端口
    }}
#由于mysql默认引擎为MySQLdb,在__init__.py文件中添加下面代码
#在python3中须替换为pymysql,可在主配置文件(和项目同名的文件下,不是app配置文件)中增加如下代码
import pymysql
pymysql.install_as_MySQLdb()
#如果找不到pymysql板块,则通过pip install pymysql进行安装。

之后在Pycharm底部Terminal里输入下面的命令进行数据库迁移:   需要填写两条命令。。。。。。

(venv) D:\zz\PycharmProjects\zilv>python manage.py makemigrations
Migrations for 'autonomy':
  autonomy\migrations\0001_initial.py
    - Create model Event
(venv) D:\zz\PycharmProjects\zilv>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, autonomy, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying autonomy.0001_initial... OK
  Applying sessions.0001_initial... OK

blog/admin.py 添加对数据库的管理

from django.contrib import admin
from .models import Event

class EventAdmin(admin.ModelAdmin):
    list_display = ('id', 'title', 'created_time',)
    list_display_links = ('title',)

admin.site.register(Event,EventAdmin)

然后我们通过下面的命令创建一个管理员:

python manage.py createsuperuser

创建管理员的时候,会提示你输入管理员帐号、密码、邮箱。注意,密码不能输入太简单,不然没法通过校检。会提示重新来一次。

(venv) D:\zz\PycharmProjects\zilv>python manage.py createsuperuser
用户名 (leave blank to use 'zz'): zb0567
电子邮件地址: zb0567@sina.com
Password:
Password (again):
密码跟 电子邮件地址 太相似了。
密码长度太短。密码必须包含至少 8 个字符。
Bypass password validation and create user anyway? [y/N]:
当最后一个输入y的时候,一切都是浮云。。。

http://localhost:8000/admin 即可访问添加

往下面就是setting.py的调整

1、配置静态文件

#STATIC_URL = '/static/'为静态文件别名
STATIC_URL = '/static/'
#静态文件地址拼接,后面'static'文件为自己建立的存放静态文件(JS,IMG,CSS)的文件名
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'), #主文件下静态文件
    os.path.join(BASE_DIR, "autonomy", "statics"),#项目blog文件下静态文件
)
2、mysql数据库配置
########在setting原来默认的sqlite
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}
############修改成mysql如下
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'test',    #你的数据库名称
        'USER': 'root',   #你的数据库用户名
        'PASSWORD': '19941028', #你的数据库密码
        'HOST': '', #你的数据库主机,留空默认为localhost
        'PORT': '3306', #你的数据库端口
    }}
#由于mysql默认引擎为MySQLdb,在__init__.py文件中添加下面代码
#在python3中须替换为pymysql,可在主配置文件(和项目同名的文件下,不是app配置文件)中增加如下代码
import pymysql
pymysql.install_as_MySQLdb()
#如果找不到pymysql板块,则通过pip install pymysql进行安装。
3、语言
LANGUAGE_CODE = 'en-us'# 默认
LANGUAGE_CODE = 'zh-hans'# 改为中文,主要针对admin页面
4、配置模板路径

TEMPLATE_DIRS = (
        os.path.join(BASE_DIR,'templates'),
    )
#然后在项目根目录下添加templates文件夹
5、注册APP

INSTALLED_APPS = [
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'app1.apps.App1Config', 
 # 默认已有 如果没有只要添加app名称即可 例如: 'blog'
 # 新建的应用都要在这里添加
]
6、设置打印日志到屏幕 sql语句
当你的操作与数据库相关时 会将我们的写的语句翻译成sql语句在服务端打印。
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}
7、如果数据库中的UserInfo(用户表)继承django内置AbstractUser

1)model需导入

from django.contrib.auth.models import AbstractUser
2)Settings文件里添加

AUTH_USER_MODEL = "应用名.UserInfo"
8、中间件,自己写的中间件,例如在项目中的md文件夹下md.py文件中的M1与M2两个中间件

MIDDLEWARE = [
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'md.md.M1',
 'md.md.M2',
]
需要注意的是自己写的中间件,配置要写在系统中的后面

9、session存储的相关配置

1)数据库配置(默认)

Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
配置 settings.py
 SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
 SESSION_COOKIE_NAME = "sessionid"   # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
 SESSION_COOKIE_PATH = "/"    # Session的cookie保存的路径(默认)
 SESSION_COOKIE_DOMAIN = None    # Session的cookie保存的域名(默认)
 SESSION_COOKIE_SECURE = False    # 是否Https传输cookie(默认)
 SESSION_COOKIE_HTTPONLY = True    # 是否Session的cookie只支持http传输(默认)
 SESSION_COOKIE_AGE = 1209600    # Session的cookie失效日期(2周)(默认)
 SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # 是否关闭浏览器使得Session过期(默认)
 SESSION_SAVE_EVERY_REQUEST = False   # 是否每次请求都保存Session,默认修改之后才保存(默认)
2)缓存配置

配置 settings.py
 SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
 SESSION_CACHE_ALIAS = 'default'    # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
 SESSION_COOKIE_NAME = "sessionid"   # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
 SESSION_COOKIE_PATH = "/"    # Session的cookie保存的路径
 SESSION_COOKIE_DOMAIN = None    # Session的cookie保存的域名
 SESSION_COOKIE_SECURE = False    # 是否Https传输cookie
 SESSION_COOKIE_HTTPONLY = True    # 是否Session的cookie只支持http传输
 SESSION_COOKIE_AGE = 1209600    # Session的cookie失效日期(2周)
 SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # 是否关闭浏览器使得Session过期
 SESSION_SAVE_EVERY_REQUEST = False   # 是否每次请求都保存Session,默认修改之后才保存
3)默认配置

配置 settings.py
 SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
 SESSION_FILE_PATH = None     # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() 
 SESSION_COOKIE_NAME = "sessionid"    # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
 SESSION_COOKIE_PATH = "/"     # Session的cookie保存的路径
 SESSION_COOKIE_DOMAIN = None    # Session的cookie保存的域名
 SESSION_COOKIE_SECURE = False    # 是否Https传输cookie
 SESSION_COOKIE_HTTPONLY = True    # 是否Session的cookie只支持http传输
 SESSION_COOKIE_AGE = 1209600    # Session的cookie失效日期(2周)
 SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # 是否关闭浏览器使得Session过期
 SESSION_SAVE_EVERY_REQUEST = False    # 是否每次请求都保存Session,默认修改之后才保存
注意:
1)也可以自定义配置 但是自定义的配置都要写到配置文件最后 代码中使用时可以导入配置

from django.conf import settings
settings.配置名
2)上面所有配置都是针对特定问题需要修改的,系统默认配置不做说明
3)上面配置只是前面django入门教程所遇到的常用配置 后续所遇配置都会逐步在此教程中持续添加跟新

10、配置文件上传目录

#设置文件上传路径
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
如果想在浏览器里访问自己上传的文件则需要在urls.py做如下设置:

from django.views.static import serve
from django.conf import settings

urlpatterns = [
    ...
    re_path('^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
]

下面看段django settings最佳配置实例代码,具体代码如下所示:

import os
import socket
SITE_ID = 1
# 项目的根目录
# 简化后面的操作
PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
# 加载应用
# 把应用添加到INSTALLED_APPS中
from apps.kuser.mysetting import myapp as kuser_app
from apps.blog.mysetting import myapp as blog_app
MY_APPS = blog_app + kuser_app
# 加载静态文件
from apps.blog.mysetting import my_staticfiles as blog_staticfiles
from apps.kuser.mysetting import my_staticfiles as kuser_staticfiles
MY_STATIC_DIRS = blog_staticfiles + kuser_staticfiles
# 加载模板文件
from apps.blog.mysetting import my_templates as blog_templates
from apps.kuser.mysetting import my_templates as kuser_templates
MY_TEMPLATE_DIRS = blog_templates + kuser_templates
# 密钥配置
# 适用于开发环境和部署环境
# 可以从系统环境中,配置文件中,和硬编码的配置中得到密钥
try:
 SECRET_KEY = os.environ['SECRET_KEY']
except:
 try:
 with open(os.path.join(PROJECT_ROOT, 'db/secret_key').replace('\\', '/')) as f:
  SECRET_KEY = f.read().strip()
 except:
 SECRET_KEY = '*lk^6@0l0(iulgar$j)faff&^(^u+qk3j73d18@&+ur^xuTxY'
# 得到主机名
def hostname():
 sys = os.name
 if sys == 'nt':
 hostname = os.getenv('computername')
 return hostname
 elif sys == 'posix':
 host = os.popen('echo $HOSTNAME')
 try:
  hostname = host.read()
  return hostname
 finally:
  host.close()
 else:
 raise RuntimeError('Unkwon hostname')
#调试和模板调试配置
#主机名相同则为开发环境,不同则为部署环境
#ALLOWED_HOSTS只在调试环境中才能为空
if socket.gethostname().lower() == hostname().lower():
 DEBUG = TEMPLATE_DEBUG = True
 ALLOWED_HOSTS = []
else:
 ALLOWED_HOSTS = [
 'baidu.com',
 '0.0.0.0',
 ]
 DEBUG = TEMPLATE_DEBUG = False
#数据库配置
MYDB = {
 'mysql': {
 'ENGINE': 'django.db.backends.mysql',
 'NAME': 'books', #你的数据库名称
 'USER': 'root', #你的数据库用户名
 'PASSWORD': '', #你的数据库密码
 'HOST': '', #你的数据库主机,留空默认为localhost
 'PORT': '3306', #你的数据库端口
 },
 'sqlite': {
 'ENGINE': 'django.db.backends.sqlite3',
 'NAME': os.path.join(PROJECT_ROOT, 'db/db.sqlite3').replace('\\', '/'),
 }
}
# 给静态文件url一个后缀,在templates里用到的。
# 映射到静态文件的url
# STATIC_URL的含义与MEDIA_URL类似
STATIC_URL = '/static/'
# 总的static目录
# 可以使用命令 manage.py collectstatic 自动收集static文件
# STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static').replace('\\', '/')
#放各个app的static目录及公共的static目录
#STATICFILES_DIRS:和TEMPLATE_DIRS的含义差不多,就是除了各个app的static目录以外还需要管理的静态文件设置,
#比如项目的公共文件差不多。然后给静态文件变量赋值,告诉Django,静态文件在哪里
#另外,Django提供了一个findstatic命令来查找指定的静态文件所在的目录,例如:D:\TestDjango>python manage.py findstatic Chrome.jpg
# 默认情况下(如果没有修改STATICFILES_FINDERS的话),Django首先会在STATICFILES_DIRS配置的文件夹中寻找静态文件,然后再从每个app的static子目录下查找,
# 并且返回找到的第一个文件。所以我们可以将全局的静态文件放在STATICFILES_DIRS配置的目录中,将app独有的静态文件放在app的static子目录中。
# 存放的时候按类别存放在static目录的子目录下,如图片都放在images文件夹中,所有的CSS都放在css文件夹中,所有的js文件都放在js文件夹中。
STATICFILES_DIRS = (
 ("downloads", os.path.join(PROJECT_ROOT, 'static/downloads').replace('\\', '/')),
 ("uploads", os.path.join(PROJECT_ROOT, 'static/uploads').replace('\\', '/')),
)
# 将app中的静态文件添加到静态文件配置列表中
STATICFILES_DIRS += MY_STATIC_DIRS
# 最后关键的部分是STATICFILES_DIRS以下配置
# 简要说一下,static文件夹在项目里,有css js images 三个文件夹(看项目结构),他们的路径分别是:
# os.path.join(STATIC_ROOT,'css'),os.path.join(STATIC_ROOT,'js'),os.path.join(STATIC_ROOT,'images');
# 我们分别给他们起三个别名css,js,images(你可以随意给,不过为了易记,我们原名称指定别名了)
TEMPLATE_DIRS = (
 os.path.join(PROJECT_ROOT, 'templates').replace('\\', '/'),
)
# 配置应用的模板文件路径
TEMPLATE_DIRS += MY_TEMPLATE_DIRS
# 配置缓存
 CACHES = {
 'default': {
  'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
  'LOCATION': 'unix:/tmp/memcached.sock',
  'KEY_PREFIX': 'lcfcn',
  'TIMEOUT': None
 }
 }
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/auth/login/'
LOGOUT_URL = '/auth/logout/'
# 指用户上传的文件,比如在Model里面的FileFIeld,ImageField上传的文件。如果你定义
# MEDIA_ROOT=c:\temp\media,那么File=models.FileField(upload_to="abc/"),上传的文件就会被保存到c:\temp\media\abc。MEDIA_ROOT必须是本地路径的绝对路径。
MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'static/uploads')
# MEDIA_URL是指从浏览器访问时的地址前缀。
MEDIA_URL = '/uploads/'
# 应用注册列表
INSTALLED_APPS = (
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'django.contrib.sites',
 'django.contrib.sitemaps',
)
#为了不和系统应用混合,自己开发的应用放在这里
# 将自己写的app添加到应用列表中去
INSTALLED_APPS += MY_APPS
# django 中间件
# django处理一个Request的过程是首先通过django 中间件,然后再通过默认的URL方式进行的。
# 所以说我们要做的就是在django 中间件这个地方把所有Request拦截住,
# 用我们自己的方式完成处理以后直接返回Response,那么我们可以简化原来的设计思路,
# 把中间件不能处理的 Request统统不管,丢给Django去处理。
MIDDLEWARE_CLASSES = (
 'django.middleware.cache.UpdateCacheMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django.middleware.cache.FetchFromCacheMiddleware',
)
ROOT_URLCONF = 'lcforum.urls'
WSGI_APPLICATION = 'lcforum.wsgi.application'
#数据库配置
DATABASES = {
 'default': MYDB.get('sqlite'),
}
# 语言
LANGUAGE_CODE = 'zh-cn'
# 时区
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
# 在template中使用静态文件
# 采用这种方式需要有一些额外配置,打开settings.py,确认TEMPLATE_CONTEXT_PROCESSORS中包含有'django.core.context_processors.static'
# TEMPLATE_CONTEXT_PROCESSORS = (
# 'django.core.context_processors.debug',
# 'django.core.context_processors.i18n',
# 'django.core.context_processors.media',
# 'django.core.context_processors.static',
# 'django.contrib.auth.context_processors.auth',
# 'django.contrib.messages.context_processors.messages',
#
# 'django.core.context_processors.tz',
# 'django.contrib.messages.context_processors.messages',
# # 'blog.context_processors.custom_proc',自定义函数
# )
#from django.conf import settings
#gettext = lambda s: s
#getattr()
# 假设有个工程mysite,有两个app为blog跟bbs
# django处理static的方法是把各个app各自的static合并到一处
# 比如:
# mysite/mysite/static 放置公共静态文件
# mysite/bbs/static 放置该app自己的静态文件
# mysite/blog/static 放置该app自己的静态文件
# 可以这么设置:
# STATIC_ROOT = '/www/mysite/mysite/static '
# STATIC_URL = '/static/'
# STATICFILES_DIRS = (
# 'mysite/static',
# 'bbs/static/',
# 'blog/static/',
# )
# 使用命令
# manage.py collectstatic
# 就会自动把所有静态文件全部复制到STATIC_ROOT中
# 如果开启了admin,这一步是很必要的,不然部署到生产环境的时候会找不到样式文件
# 不要把你项目的静态文件放到这个目录。这个目录只有在运行python manage.py collectstatic时才会用到

下面是url

每一个URL都会对应一个视图函数,当一个用户请求访问Django站点的一个页面时,然后就由Django路由系统(URL配置文件)去决定要执行哪个视图函数使用的算法。这个路由系统我们也称之为url控制器,一般是项目目录和应用目录里的urls.py文件。

URLconf是所有整个Django的入口,我们想要访问什么,想要去什么地方,都取决于URLconf,所以我们需要充分理解URLconf的用法。

一般情况下,一个URL,我们是这样写的:

urlpatterns = [
    path(正则表达式, views视图函数,参数,别名),
]
参数说明:
1、一个正则表达式字符串
2、一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串
3、可选的要传递给视图函数的默认参数(字典形式)
4、一个可选的name参数(别名)

下面是一个简单的URLconf例子:

from django.urls import path
from . import views
urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<int:year>/', views.year_archive),
    path('articles/<int:year>/<int:month>/', views.month_archive),
    path('articles/<int:year>/<int:month>/<slug:slug>/', views.article_detail),
    ]

注意:

  1. 要捕获一段url中的值,需要使用尖括号,而不是之前的圆括号;

  2. 可以转换捕获到的值为指定类型,比如例子中的<int:name>。默认情况下,捕获到的结果保存为字符串类型,不包含/这个特殊字符;

  3. 规则的前面不需要添加/,因为默认情况下,每个url都带一个最前面的/。比如:articles, 不能写成 /articles。

匹配例子:

1、/articles/2005/03/ 将匹配第三条,并调用views.month_archive(request, year=2005, month=3);

2、/articles/2003/匹配第一条,并调用views.special_case_2003(request);

3、/articles/2003将一条都匹配不上,因为它最后少了一个斜杠,而列表中的所有模式中都以斜杠结尾;

4、/articles/2003/03/building-a-django-site/ 将匹配最后一个,并调用views.article_detail(request, year=2003, month=3, slug="building-a-django-site"

一、path转换器

Django默认情况下内置下面的路径转换器:

1、str:匹配任何非空字符串,但不含斜杠/,如果你没有专门指定转换器,那么这个是默认使用的;
2、int:匹配0和正整数,返回一个int类型
3、slug:可理解为注释、后缀、附属等概念,是url拖在最后的一部分解释性字符。该转换器匹配任何ASCII字符以及连接符和下划线,比如’ building-your-1st-django-site‘;
4、uuid:匹配一个uuid格式的对象。为了防止冲突,规定必须使用破折号,所有字母必须小写,例如’075194d3-6885-417e-a8a8-6c931e272f00‘ 。返回一个UUID对象;
5、path:匹配任何非空字符串,重点是可以包含路径分隔符’/‘。这个转换器可以帮助你匹配整个url而不是一段一段的url字符串。

二、注册自定义路径转换器

对于更复杂的匹配需求,您可以定义自己的路径转换器。自定义,就是单独写一个类,它包含下面的内容:
1、类属性regex:一个字符串形式的正则表达式属性;
2、to_python(self, value) 方法:一个用来将匹配到的字符串转换为你想要的那个数据类型,并传递给视图函数。如果不能转换给定的值,则会引发ValueError。

3、to_url(self, value)方法:将Python数据类型转换为一段url的方法,上面方法的反向操作。

例如:

class FourDigitYearConverter:
    regex = '[0-9]{4}'
    def to_python(self, value):
        return int(value)
    def to_url(self, value):
        return '%04d' % value

在URLconf中注册自定义转换器类,并使用它:

from django.urls import path, register_converter
from . import converters, 
viewsregister_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    path('articles/<yyyy:year>/', views.year_archive),
    ...
    ]

 

三、使用正则表达式

如果路径和转换器语法不足以定义URL模式,也可以使用正则表达式。这时我们就需要使用re_path()而不是path()。

在Python正则表达式中,命名正则表达式组的语法是 (?P<name>pattern),其中name是组的名称,pattern是需要匹配的规则。

前面的URLconf示例,如果使用正则表达式重写,是这样子的:

from django.urls import path, re_path
from . import views
urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    #表示articles/2003/这个路径映射views模块的special_case_2003函数
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
    #表示匹配4个0-9的任意数字
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
    re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<slug>[\w-]+)/$', views.article_detail),
    ]
#注意:上面匹配都加了小括号,这些括号里面的值会当作参数传递到后面的视图函数中

re_path与path()不同的主要在于两点:
1、year中匹配不到10000等非四位数字,这是正则表达式决定的
2、传递给视图的所有参数都是字符串类型。而不像path()方法中可以指定转换成某种类型。

四、指定视图参数的默认值

有一个方便的小技巧是指定视图参数的默认值。 下面是一个URLconf 和视图的示例:

# URLconf
from django.urls import path
from . import views
urlpatterns = [
    path('blog/', views.page),
    path('blog/page<int:num>/', views.page),
    ]

# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
...

在上面的例子中,两个URL模式指向同一个视图views.page —— 但是第一个模式不会从URL 中捕获任何值。如果第一个模式匹配,page() 函数将使用num参数的默认值"1"。如果第二个模式匹配,page() 将使用正则表达式捕获的num 值。

五、URLconf匹配请求URL中的哪些部分

请求的URL被看做是一个普通的Python字符串,URLconf在其上查找并匹配。进行匹配时将不包括GET或POST请求方式的参数以及域名。
例如,在https://www.example.com/myapp/的请求中,URLconf将查找myapp/。
在https://www.example.com/myapp/?page=3的请求中,URLconf也将查找myapp/。
URLconf不检查使用何种HTTP请求方法,所有请求方法POST、GET、HEAD等都将路由到同一个URL的同一个视图。在视图中,才根据具体请求方法的不同,进行不同的处理。

六、错误页面处理

当Django找不到与请求匹配的URL时,或者当抛出一个异常时,将调用一个错误处理视图。错误视图包括400、403、404和500,分别表示请求错误、拒绝服务、页面不存在和服务器错误。它们分别位于:

  • handler400 —— django.conf.urls.handler400。

  • handler403 —— django.conf.urls.handler403。

  • handler404 —— django.conf.urls.handler404。

  • handler500 —— django.conf.urls.handler500。

这些值可以在根URLconf中设置。在其它app中的二级URLconf中设置这些变量无效。

Django有内置的HTML模版,用于返回错误页面给用户,但是这些403,404页面实在丑陋,通常我们都自定义错误页面。

首先,在根URLconf中额外增加下面的条目:

# urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^blog/$', views.page),
    url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# 增加的条目
handler400 = views.bad_request
handler403 = views.permission_denied
handler404 = views.page_not_found
handler500 = views.page_error

然后在,views.py文件中增加四个处理视图:

def page_not_found(request):
    return render(request, '404.html')
    
def page_error(request):
    return render(request, '500.html')
    
def permission_denied(request):
    return render(request, '403.html')
    
def bad_request(request):
    return render(request, '400.html')

再根据自己的需求,创建404.html、400.html等四个页面文件,就可以了。

七、urls分层模块化(路由分发)

通常,我们会在每个app里,各自创建一个urls.py路由模块,然后从根路由出发,将app所属的url请求,全部转发到相应的urls.py模块中。

例如,下面是Django网站本身的URLconf节选。 它包含许多其它URLconf:

from django.urls import include, path
urlpatterns = [
    # ... snip ...
    path('community/', include('aggregator.urls')),
    path('contact/', include('contact.urls')),
    # ... snip ...
]

路由转发使用的是include()方法,需要提前导入,它的参数是转发目的地路径的字符串,路径以圆点分割。

注意,这个例子中的正则表达式没有包含$(字符串结束匹配符),但是包含一个末尾的斜杠。 每当Django 遇到include()(来自django.conf.urls.include())时,它会去掉URL中匹配的部分并将剩下的字符串发送给include的URLconf做进一步处理,也就是转发到二级路由去。

另外一种转发其它URL模式的方式是使用一个url()实例的列表。 例如,下面的URLconf:

from django.urls import include, path
from apps.main import views as main_views
from credit import views as credit_views
extra_patterns = [
    path('reports/', credit_views.report),
    path('reports/<int:id>/', credit_views.report),
    path('charge/', credit_views.charge),
]
urlpatterns = [
    path('', main_views.homepage),
    path('help/', include('apps.help.urls')),
    path('credit/', include(extra_patterns)),
]

在这个例子中, /credit/reports/ URL将被 credit.views.report() 这个Django 视图处理。
上面这种方法可以用来去除URLconf 中的冗余,其中某个模式前缀被重复使用。例如,下面这个例子:

from django.urls import path
from . import views
urlpatterns = [
    path('<page_slug>-<page_id>/history/', views.history),
    path('<page_slug>-<page_id>/edit/', views.edit),
    path('<page_slug>-<page_id>/discuss/', views.discuss),
    path('<page_slug>-<page_id>/permissions/', views.permissions),
]

我们可以改进它,通过只声明共同的路径前缀一次并将后面的部分分组转发:

from django.urls import include, path
from . import views
urlpatterns = [
    path('<page_slug>-<page_id>/', include([
        path('history/', views.history),
        path('edit/', views.edit),
        path('discuss/', views.discuss),
        path('permissions/', views.permissions),
    ])),
]

八、捕获参数

被转发的URLconf会收到来自父URLconf捕获的所有参数,看下面的例子:

# In settings/urls/main.py
from django.urls import include, path
urlpatterns = [
    path('<username>/blog/', include('foo.urls.blog')),
]
# In foo/urls/blog.py
from django.urls import path
from . import views
urlpatterns = [
    path('', views.blog.index),
    path('archive/', views.blog.archive),
]

在上面的例子中,捕获的"username"变量将被传递给include()指向的URLconf,再进一步传递给对应的视图。

九、嵌套参数

正则表达式允许嵌套参数,Django将解析它们并传递给视图。当反查时,Django将尝试填满所有外围捕获的参数,并忽略嵌套捕获的参数。 考虑下面的URL模式,它带有一个可选的page参数:

from django.urls import re_path
urlpatterns = [
    re_path(r'^blog/(page-(\d+)/)?$', blog_articles),   # bad
    re_path(r'^comments/(?:page-(?P<page_number>\d+)/)?$', comments),  # good]

两个模式都使用嵌套的参数,其解析方式是:例如blog/page-2/将匹配page-2/并带有两个位置参数blog_articles和2。第二个comments的模式将匹配page_number并带有一个值为2的关键字参数comments/page-2/。这个例子中外围参数是一个不捕获的参数(?:...)。

blog_articles视图需要最外层捕获的参数来反查,在这个例子中是comments或者没有参数,而page-2/可以不带参数或者用一个page_number值来反查。

十、向视图传递额外的参数

URLconfs具有一个钩子(hook),允许你传递一个Python字典作为额外的关键字参数给视图函数。

像这样:

from django.urls import path
from . import views
urlpatterns = [
    path('blog/<int:year>/', views.year_archive, {'foo': 'bar'}),
]

在上面的例子中,对于/blog/2005/请求,Django将调用views.year_archive(request, year='2005', foo='bar')。理论上,你可以在这个字典里传递任何你想要的传递的东西。但是要注意,URL模式捕获的命名关键字参数和在字典中传递的额外参数有可能具有相同的名称,这会发生冲突,要避免。

十一、传递额外的参数给include()

类似上面,也可以传递额外的参数给include()。参数会传递给include指向的urlconf中的每一行。

例如,下面两种URLconf配置方式在功能上完全相同:

配置一:

# main.py
from django.urls import include, path
urlpatterns = [
    path('blog/', include('inner'), {'blog_id': 3}),
]
# inner.py
from django.urls import path
from mysite import views
urlpatterns = [
    path('archive/', views.archive),
    path('about/', views.about),
]

配置二:

# main.py
from django.urls import include, path
from mysite import views
urlpatterns = [
    path('blog/', include('inner')),
]
# inner.py
from django.urls import path
urlpatterns = [
    path('archive/', views.archive, {'blog_id': 3}),
    path('about/', views.about, {'blog_id': 3}),
]

注意,只有当你确定被include的URLconf中的每个视图都接收你传递给它们的额外的参数时才有意义,否则其中一个以上视图不接收该参数都将导致错误异常。

十二、url的反向解析

在实际的Django项目中,经常需要获取某条URL,为生成的内容配置URL链接。

比如,我要在页面上展示一列文章列表,每个条目都是个超级链接,点击就进入该文章的详细页面。

现在我们的urlconf是这么配置的:^post/(?P<id>\d+)

在前端中,这就需要为HTML的<a>标签的href属性提供一个诸如http://www.xxx.com/post/3的值。其中的域名部分,Django会帮你自动添加无须关心,我们关注的是post/3

此时,一定不能硬编码URL为post/3,那样费时、不可伸缩,而且容易出错。试想,如果哪天,因为某种原因,需要将urlconf中的正则改成^entry/(?P<id>\d+),为了让链接正常工作,必须修改对应的herf属性值,于是你去项目里将所有的post/3都改成entry/3吗?显然这是不行的!

我们需要一种安全、可靠、自适应的机制,当修改URLconf中的代码后,无需在项目源码中大范围搜索、替换失效的硬编码URL。

为了解决这个问题,Django提供了一种解决方案,只需在URL中提供一个name参数,并赋值一个你自定义的、好记的、直观的字符串。

通过这个name参数,可以反向解析URL、反向URL匹配、反向URL查询或者简单的URL反查。

在需要解析URL的地方,对于不同层级,Django提供了不同的工具用于URL反查:

  • 在模板语言中:使用url模板标签。(也就是写前端网页时)

  • 在Python代码中:使用reverse()函数。(也就是写视图函数等情况时)

  • 在更高层的与处理Django模型实例相关的代码中:使用get_absolute_url()方法。(也就是在模型model中)

示例:

from django.urls import path
from . import views
urlpatterns = [
    #...
    path('articles/<int:year>/', views.year_archive, name='news-year-archive'),
    #...
]

某一年nnnn对应的归档的URL是/articles/nnnn/

可以在模板的代码中使用下面的方法获得它们:

<a href="{% url 'news-year-archive' 2012 %}">2012 Archive</a>
{# Or with the year in a template context variable: #}
<ul>
{% for yearvar in year_list %}
<li><a href="{% url 'news-year-archive' yearvar %}">{{ yearvar }} Archive</a></li>
{% endfor %}
</ul>

在Python代码中,这样使用:

from django.http import HttpResponseRedirect
from django.urls import reverse
def redirect_to_year(request):
    # ...
    year = 2006
    # ...
    return HttpResponseRedirect(reverse('news-year-archive', args=(year,)))

其中,起到核心作用的是我们通过name='news-year-archive'为那条url起了一个可以被引用的名称。

URL名称name使用的字符串可以包含任何你喜欢的字符,但是过度的放纵有可能带来重名的冲突,比如两个不同的app,在各自的urlconf中为某一条url取了相同的name,这就会带来麻烦。为了解决这个问题,又引出了下面命名的URL模式。

 

十三、命名的URL模式(URL别名)

URL别名可以保证反查到唯一的URL,即使不同的app使用相同的URL名称。

第三方应用始终使用带命名空间的URL是一个很好的做法。

类似地,它还允许你在一个应用有多个实例部署的情况下反查URL。 换句话讲,因为一个应用的多个实例共享相同的命名URL,命名空间提供了一种区分这些命名URL 的方法。

实现命名空间的做法很简单,在urlconf文件中添加app_name = 'polls'namespace='author-polls'这种类似的定义。

范例

以两个实例为例子:'publisher-polls' 和'author-polls'。

假设我们已经在创建和显示投票时考虑了实例命名空间的问题,代码如下:

urls.py

from django.urls import include, path
urlpatterns = [
    path('author-polls/', include('polls.urls', namespace='author-polls')),
    path('publisher-polls/', include('polls.urls', namespace='publisher-polls')),
]

polls/urls.py

from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    ...
]

如果当前的app实例是其中的一个,例如我们正在渲染实例'author-polls'中的detail视图,'polls:index'将解析到'author-polls'实例的index视图。

根据以上设置,可以使用下面的查询:

在基于类的视图的方法中:

reverse('polls:index', current_app=self.request.resolver_match.namespace)

和在模板中:

{% url 'polls:index' %}

如果没有当前app实例,例如如果我们在站点的其它地方渲染一个页面,'polls:index'将解析到polls注册的最后一个app实例空间。 因为没有默认的实例(命名空间为'polls'的实例),将使用注册的polls 的最后一个实例。 这将是'publisher-polls',因为它是在urlpatterns中最后一个声明的。

十四、URL命名空间和include的URLconf

可以通过两种方式指定include的URLconf的应用名称空间。

第一种

在include的URLconf模块中设置与urlpatterns属性相同级别的app_name属性。必须将实际模块或模块的字符串引用传递到include(),而不是urlpatterns本身的列表。

polls/urls.py

from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    ...
]
urls.py
from django.urls import include, path
urlpatterns = [
    path('polls/', include('polls.urls')),
]

此时,polls.urls中定义的URL将具有应用名称空间polls。

第二种

include一个包含嵌套命名空间数据的对象。如果你include()一个url()实例的列表,那么该对象中包含的URL将添加到全局命名空间。 但是,你也可以include()一个2元组,其中包含:

(<list of path()/re_path() instances>, <application namespace>)

例如:

rom django.urls import include, path
from . import views
polls_patterns = ([
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
], 'polls')
urlpatterns = [
    path('polls/', include(polls_patterns)),
]

这将include指定的URL模式到给定的app命名空间。

可以使用include()的namespace参数指定app实例命名空间。如果未指定,则app实例命名空间默认为URLconf的app命名空间。

管理后台与model模型

from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class Category(models.Model):
    """
    Django 要求模型必须继承 models.Model 类。
    Category 只需要一个简单的分类名 name 就可以了。
    CharField 指定了分类名 name 的数据类型,CharField 是字符型,
    CharField 的 max_length 参数指定其最大长度,超过这个长度的分类名就不能被存入数据库。
    然后给name设置了一个'分类'的名称
    """
    name = models.CharField('分类', max_length=100)

class Tags(models.Model):
    """
    标签 Tag 也比较简单,和 Category 一样。
    再次强调一定要继承 models.Model 类!
    """
    name = models.CharField('标签', max_length=100)

class Event(models.Model):
    # id = models.IntegerField('id', primary_key=True)
    # 文章正文,我们使用了 TextField,并且指定了标题的长度
    title = models.CharField('标题', max_length=70)
    # 使用 TextField 来存储大段文本,文章摘要,我们指定了最大长度和允许可以为空。
    intro = models.TextField('摘要', max_length=200, blank=True)
    # 这是分类与标签,分类与标签的模型我们已经定义在上面。
    # 我们在这里把文章对应的数据库表和分类、标签对应的数据库表关联了起来,但是关联形式稍微有点不同。
    # 我们规定一篇文章只能对应一个分类,但是一个分类下可以有多篇文章,所以我们使用的是 ForeignKey,即一对多的关联关系。
    # 而对于标签来说,一篇文章可以有多个标签,同一个标签下也可能有多篇文章,所以我们使用 ManyToManyField,表明这是多对多的关联关系。
    # 同时我们规定文章可以没有标签,因此为标签 category 指定了 blank=True。
    # 文章分类,我们还使用了on_delete参数,这个是Django2.0强制ForeignKey必须使用的。
    # 具体更多的资料可以查看官网,也可以查看Django2.0外键参数on_delete的使用方法:https://www.django.cn/article/show-6.html
    # 后面我们也会有专门的文章对一对多、多对多进行详细介绍
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类', default='1')
    tags = models.ManyToManyField(Tags, blank=True)
    # 存储比较短的字符串可以使用 CharField,但对于文章的正文来说可能会是一大段文本,因此使用 TextField 来存储大段文本。
    body = models.TextField('内容', max_length=200, blank=True)
    # 文章作者,这里 User 是从 django.contrib.auth.models 导入的。
    # django.contrib.auth 是 Django 内置的应用,专门用于处理网站用户的注册、登录等流程,User 是 Django 为我们已经写好的用户模型。
    # 这里我们通过 ForeignKey 把文章和 User 关联了起来。
    # 因为我们规定一篇文章只能有一个作者,而一个作者可能会写多篇文章,因此这是一对多的关联关系,和 Category 类似。
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
    # created_time,我们使用了DateTimeField字段,添加了一个auto_now_add参数,自动获取添加时间!
    created_time = models.DateTimeField('发布时间', auto_now_add=True)
    modified_time = models.DateTimeField('修改时间', auto_now=True)

    class Meta:
        verbose_name = '必做三件事'
        verbose_name_plural = '必做三件事'

    def __str__(self):
        return self.title

 model里面添加的东西我们可以不动数据库,直接动用命令可以自动生成。。。。。。。。。。。。@@@@@@@@

数据模型设计好之后,我们就需要迁移数据到数据库。我们运行如下命令:

python manage.py makemigrations

通过运行makemigrations命令,相当于告诉Django你对模型有改动,并且你想把这些改动保存为一个“迁移(migration)”。
migrations是Django保存模型修改记录的文件,这些文件保存在对应的migration目录里。在例子中,它就是blog/migrations/0001_initial.py,你可以打开它看看,里面保存的都是人类可读并且可编辑的内容,方便你随时手动修改。

Migrations for 'blog':
  blog\migrations\0001_initial.py
    - Create model Article
    - Create model Category
    - Create model Tags
    - Add field category to article
    - Add field tags to article
    - Add field user to article

接下来有一个叫做migrate的命令将对数据库执行真正的迁移动作。

python manage.py migrate

migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表上面。Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些migrations尚未提交。
migrations的功能非常强大,允许你随时修改你的模型,而不需要删除或者新建你的数据库或数据表,在不丢失数据的同时,实时动态更新数据库。如果你想修改模型时的请记住,需要分如下三步操作:
1、在models.py中修改模型;
2、运行python manage.py makemigrations为改动创建迁移记录;
3、运行python manage.py migrate,将操作同步到数据库。

├─migrations         __init__.py  这两个文件不能删除

注册admin注册blog应用

现在后台,只有默认的Django认证和授权应用,我们前面设计的那个应用模型我们需要在管理后台注册之后才能对其进行管理。

方法如下:

打开blog/admin.py文件,加入如下内容:

from django.contrib import admin
from .models import Article,Tags,Category
admin.site.register(Tags)
admin.site.register(Article)
admin.site.register(Category)

刷新页面,我们就能看到blog下面所有的应用了~~~~

最好不要再数据库表上加s,django最后会自动添加s

模型元数据Meta选项详解

tag、category、article三个类、分类与标签里的内容对象、文章里的内容对象,显示的是一些复数的类名,或者是一些类的对象。这样的的体验很不好,不是我们想要的。这时我们可以通过models继承的一些方法和Meta选项来提升用户体验。

 #下面为新增代码
    class Meta:
        verbose_name = '分类'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.name

    #下面为新增代码
    class Meta:
        verbose_name = '标签'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.name

    #下面为新增代码
    class Meta:
        verbose_name = '文章'
        verbose_name_plural = verbose_name
        
    def __str__(self):
        return self.title

我们给每个类都增加了一个内部类Meta和__str__方法属性。

由于我们的model实例继承自models.Model,所以它自动继承了models.Model的大量方法。

__str__()是Python3的写法,它相当于python2的unicode,这是一个Python的"魔术方法",它以unicode方式返回任何对象的陈述。Python和Django需要输出字符串陈述时使用。例如在交互式控制台或管理后台显示的输出陈述。有时候默认的实现并不能很好的满足需要,所以最好自定义这个方法。而Meta是一个内部类,它用于定义一些Django模型类的行为特性。

注意:每个模型都可以有自己的元数据类,每个元数据类也只对自己所在模型起作用。

红框标记的地方,与原来对比,让人更加容易理解。内部类Meta对于models来说,不是必须的,但对于用户来说在实际使用中具有重要的作用,有些元数据选项能给予我们极大的帮助。Meta选项大致包含以下几类:

1.abstract
这个属性是定义当前的模型是不是一个抽象类。所谓抽象类是不会对应数据库表的。一般我们用它来归纳一些公共属性字段,然后继承它的子类可以继承这些字段。
Options.abstract
如果abstract=True这个model就是一个抽象类
2.app_label
这个选型只在一种情况下使用,就是你的模型不在默认的应用程序包下的models.py文件中,这时候需要指定你这个模型是哪个应用程序的。
Options.app_label
如果一个model定义在默认的models.py,例如如果你的app的models在myapp.models子模块下,你必须定义app_label让Django知道它属于哪一个app

app_label='myapp'

3.db_table
db_table是指定自定义数据库表名的。Django有一套默认的按照一定规则生成数据模型对应的数据库表明。
Options.db_table
定义该model在数据库中的表名称

db_table='Students'

如果你想使用自定义的表名,可以通过以下该属性

table_name='my_owner_table'

4.db_teblespace
Options.db_teblespace
定义这个model所使用的数据库表空间。如果在项目的settin中定义那么它会使用这个值
5.get_latest_by
Options.get_latest_by
在model中指定一个DateField或者DateTimeField。这个设置让你在使用model的Manager上的lastest方法时,默认使用指定字段来排序
6.managed
Options.managed
默认值为True,这意味着Django可以使用syncdb和reset命令来创建或移除对应的数据库。默认值为True,如果你不希望这么做,可以把manage的值设置为False
7.order_with_respect_to
这个选项一般用于多对多的关系中,它指向一个关联对象,就是说关联对象找到这个对象后它是经过排序的。指定这个属性后你会得到一个get_xxx_order()和set_xxx_order()的方法,通过它们你可以设置或者回去排序的对象
8.ordering
这个字段是告诉Django模型对象返回的记录结果集是按照哪个字段排序的。这是一个字符串的元组或列表,没有一个字符串都是一个字段和用一个可选的表明降序的'-'构成。当字段名前面没有'-'时,将默认使用升序排列。使用'?'将会随机排列

ordering=['order_date']#按订单升序排列
ordering=['-order_date']#按订单降序排列,-表示降序
ordering=['?order_date']#随机排序,?表示随机
ordering=['-pub_date','author']#以pub_date为降序,在以author升序排列

9.permissions
permissions主要是为了在DjangoAdmin管理模块下使用的,如果你设置了这个属性可以让指定的方法权限描述更清晰可读。Django自动为每个设置了admin的对象创建添加,删除和修改的权限。

permissions=(('can_deliver_pizzas','Candeliverpizzas'))

10.proxy
这是为了实现代理模型使用的,如果proxy=True,表示model是其父的代理model
11.unique_together
unique_together这个选项用于:当你需要通过两个字段保持唯一性时使用。比如假设你希望,一个Person的FirstName和LastName两者的组合必须是唯一的,那么需要这样设置:

unique_together=(("first_name","last_name"),)

一个ManyToManyField不能包含在unique_together中。如果你需要验证关联到ManyToManyField字段的唯一验证,尝试使用signal(信号)或者明确指定through属性。
12.verbose_name
verbose_name的意思很简单,就是给你的模型类起一个更可读的名字一般定义为中文,我们:
verbose_name="学校"
13.verbose_name_plural
这个选项是指定,模型的复数形式是什么,比如:

verbose_name_plural="学校"

如果不指定Django会自动在模型名称后加一个’s’

一些常见的元信息的例子:

class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)
        
        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"
            
            # 联合索引
            index_together = [
                ("pub_date", "deadline"),
            ]

            # 联合唯一索引
            unique_together = (("driver", "restaurant"),)
            
            # admin中显示的表名称
            verbose_name
            
            # verbose_name加s
            verbose_name_plural
多表关系和参数的例子:

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
        to,                         # 要进行关联的表名
        to_field=None,              # 要关联的表中的字段名称
        on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
                                        - models.CASCADE,删除关联数据,与之关联也删除
                                        - models.DO_NOTHING,删除关联数据,引发错误IntegrityError
                                        - models.PROTECT,删除关联数据,引发错误ProtectedError
                                        - models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
                                        - models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
                                        - models.SET,删除关联数据,
                                                      a. 与之关联的值设置为指定值,设置:models.SET(值)
                                                      b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
                                                        def func():
                                                            return 10
                                                        class MyModel(models.Model):
                                                            user = models.ForeignKey(
                                                                to="User",
                                                                to_field="id"
                                                                on_delete=models.SET(func),)
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}
                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        db_constraint=True          # 是否在数据库中创建外键约束
        parent_link=False           # 在Admin中是否显示关联数据
    OneToOneField(ForeignKey)
        to,                         # 要进行关联的表名
        to_field=None               # 要关联的表中的字段名称
        on_delete=None,             # 当删除关联表中的数据时,当前表与其关联的行的行为
                                    ###### 对于一对一 ######
                                    # 1. 一对一其实就是 一对多 + 唯一索引
                                    # 2.当两个类之间有继承关系时,默认会创建一个一对一字段
                                    # 如下会在A表中额外增加一个c_ptr_id列且唯一:
                                            class C(models.Model):
                                                nid = models.AutoField(primary_key=True)
                                                part = models.CharField(max_length=12)
                                            class A(C):
                                                id = models.AutoField(primary_key=True)
                                                code = models.CharField(max_length=1)
    ManyToManyField(RelatedField)
        to,                         # 要进行关联的表名
        related_name=None,          # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作时,使用的连接前缀,用于替换【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中显示关联数据时,提供的条件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}
                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        symmetrical=None,           # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
                                    # 做如下操作时,不同的symmetrical会有不同的可选字段
                                        models.BB.objects.filter(...)
                                        # 可选字段有:code, id, m1
                                            class BB(models.Model):
                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)
                                        # 可选字段有: bb, code, id, m1
                                            class BB(models.Model):
                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)
        through=None,               # 自定义第三张表时,使用字段用于指定关系表
        through_fields=None,        # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
                                        from django.db import models
                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)
                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )
                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在数据库中创建外键约束
        db_table=None,              # 默认创建第三张表时,数据库中表的名称

定制Admin管理后台

就是在admin.py里注册blog应用,这样blog应用才会在后台显示出来,我们才能在后台对这个应用进行管理。这就是Django自带的后台管理的特色之一,它可以让我们快速便捷管理数据,后台管理可以在各个app的admin.py文件中进行控制。

想要对APP应用进行管理,最基本的前提是要先在settings里对其进行注册,就是在INSTALLED_APPS里把APP名添加进去,

下面我们以一个blog应用来举例,向大家介绍一些常用的自定制admin的方法。

一、管理后台注册需要管理的应用

只有注册了,我们才能在管理后台看到这个APP应用,才能对其进行管理,这个注册有两种方式:

1、装饰器注册

from django.contrib import admin
from .models import Article
  
#Blog模型的管理器
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display=('id', 'category', 'title', 'user','views','created_time')

2、注册参数

from django.contrib import admin
from .models import Article
  
#Blog模型的管理器
class ArticleAdmin(admin.ModelAdmin):
    list_display=('id', 'category', 'title', 'user','views','created_time')
     
#在admin中注册绑定
admin.site.register(Article,ArticleAdmin)

注册参数将管理器和注册语句分开,有时容易忘记写注册语句,或者模型很多,不容易对应。所以一般推荐第一种方法。

注意:我们注册的时候,先需要在头部把需要注册的类导入。如:from .models import Article

注册好之后,我们就能在后台看到我们的注册的应用:

二、应用类的列表管理界面设置

什么是应用类的列表?我们都知道每个应用里有很多个类,每个类都对应着数据库里的一个表,每个表里都有着多个字段。点击刚才注册的blog应用,点击文章,进入文章列表管理界面:

1、类列表的基本设置

这里面我们用得比较多的设置是:显示字段、每页记录数和排序等。点击列头可以进行升序或降序排列。

2、进入编辑界面

默认的情况下,我们点击第一个字段,就能进入编辑界面。

3、"操作选项"的设置

操作选项,如上图标记所示。

#列表顶部,设置为False不在顶部显示,默认为True。
actions_on_top=True

#列表底部,设置为False不在底部显示,默认为False。
actions_on_bottom=False

数据较多的话,建议顶部和底部都加上,操作的时候能节省点时间。

如果我们想定制自己的行为的话,可以用下面的方法:

# 定制Action行为具体方法
def func(self, request, queryset):
    queryset.update(created_time='2018-09-28')
    #批量更新我们的created_time字段的值为2018-09-28
        
func.short_description = "中文显示自定义Actions"
actions = [func, ]

4、将方法作为列

列可以是模型字段,还可以是模型方法,要求方法有返回值。
通过设置short_description属性,可以设置在admin站点中显示的列名。

blog/models.py

class Article(models.Model):
...
...
#定义一个方法
def riqi(self):
    return self.created_time.strftime("%b %d %Y %H:%M:%S")
# 设置方法字段在admin中显示的标题
riqi.short_description = '发布日期'

然后在admin.py里把我们定义的方法加在list_display里:

blog/admin.py
class ArticleAdmin(admin.ModelAdmin):
    list_display = ('id', 'category','title',  'user','views','created_time','riqi')

注意:方法列是不能排序的,如果需要排序需要为方法指定排序依据。

admin_order_field='模型类字段'
blog/models.py

class Article(models.Model):
...
...
#定义一个方法
def riqi(self):
    return self.created_time.strftime("%b %d %Y %H:%M:%S")
# 设置方法字段在admin中显示的标题
riqi.short_description = '发布日期'
#指定排序依据
riqi.admin_order_field='created_time'

5 关联对象

无法直接访问关联对象的属性或方法,可以在模型类中封装方法,访问关联对象的成员。例如,blog模型里有一个文章分类,分类里有一个分类排序字段'index',我们需要把这个关联进来。

blog/models.py

class Article(models.Model):
....
....
  #定义关联对象字段
    def paixu(self):
        return self.category.index
    paixu.short_description = '分类排序'

然后在admin.py里把我们定义的方法加在list_display里:

blog/admin.py

class ArticleAdmin(admin.ModelAdmin):
    list_display = ('id', 'category','title',  'user','views','created_time','paixu')

6、搜索框

属性如下,用于对指定字段的值进行搜索,支持模糊查询。列表类型,表示在这些字段上进行搜索。

7、右侧栏过滤器和日期筛选

右侧栏过滤器属性如下,只能接收字段,会将对应字段的值列出来,用于快速过滤。一般用于有重复值的字段。

list_filter=[]

日期筛选属性:

date_hierarchy = ''

然在admin.py里添加list_filter,指定作者user做为搜索字段::

blog/admin.py
class ArticleAdmin(admin.ModelAdmin):
...
    list_filter=['user']  #右侧栏过滤器,按作者进行筛选
    date_hierarchy = 'created_time'    # 详细时间分层筛选 

注意:

一般ManyToManyField多对多字段用过滤器;标题等文本字段用搜索框;日期时间用分层筛选。过滤器如果是外键需要遵循这样的语法:本表字段__外键表要显示的字段。如:“user__user_name”。

二、修改后台管理页面头部显示内容和页面标题

blog/admin.py

admin.site.site_header = 'Django中文网管理后台'
admin.site.site_title = 'Django中文网'

三、重写模板

这个可治任何不服。想要什么效果,就直接自己写。

1)在templates/目录下创建admin目录

2)打开当前虚拟环境中Django包的目录,再向下找到admin的模板。不知道目录在哪,可以使用下面的方法:

进入python终端

root@server-zc:/home/x/mysite# python
>>> import django
>>> django.__file__
'/usr/local/lib/python3/dist-packages/django/__init__.pyc'

C:\Users\zz>python
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.__file__
'C:\\Python\\Python37\\lib\\site-packages\\django\\__init__.py'
>>>

找到包的目录之后,复制它,我们需要的模板目录为

/usr/local/lib/python3/dist-packages/django/contrib/admin/

C:\Python\Python37\lib\site-packages\django\contrib\admin

3)将需要更改文件拷贝到(在项目的templates文件夹里面创建admin文件夹)里,此处以base_site.html为例。

{% extends "admin/base.html" %}
{% block title %}
{{ title }} | {{ site_title|default:_('Django site admin') }}
{% endblock %}
{% block branding %}
<h1 id="site-name">
<a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a>
</h1>
<hr>
<h1>自定义的管理页模板</h1>
<hr>
{% endblock %}
{% block nav-global %}{% endblock %}

4)刷新即可看到效果
其它后台的模板可以按照相同的方式进行修改。

数据模型字段及属性详解

在设计数据模型的时候,我们需要根据不同的需求,设计不同的表和不同的字段。不同的字段也可以设置不同的参数。

一个模型(model)就是一个单独的、确定的数据的信息源,包含了数据的字段和操作方法。Django是通过Model操作数据库,不管你数据库的类型是MySql或者Sqlite,Django它自动帮你生成相应数据库类型的SQL语句,所以不需要关注SQL语句和类型,对数据的操作Django帮我们自动完成。只要回写Model就可以了!

django根据代码中定义的类来自动生成数据库表。我们写的类表示数据库的表,如果根据这个类创建的对象是数据库表里的一行数据,对象.id 对象.value是每一行里的数据。

基本的原则如下:
每个模型在Django中的存在形式为一个Python类
每个模型都是django.db.models.Model的子类
模型里的每个类代表数据库中的一个表
模型的每个字段(属性)代表数据表的某一列
Django将自动为你生成数据库访问API

Django这种操作数据库的方式,我们把它叫做:关系对象映射(Object Relational Mapping,简称ORM)。

字段是模型中最重要的内容之一,也是唯一必须的部分。字段在Python中表现为一个类属性,体现了数据表中的一个列。Django不允许下面两种字段名:1、与Python关键字冲突。2、字段名中不能有两个以上下划线在一起,因为两个下划线是Django的查询语法。也不要使用clean、save、delete等Django内置的模型API名字,防止命名冲突。

每一个字段都是一个类属性,每个类属性表示数据表中的一个列。

表名由Django自动生成,例如我们的Article类会自动生成为blog_Article,默认格式为“应用名称+下划线+小写类名”。如果你不指定主键,Django默认自动创建自增主键id。每个APP应用都有独立属于自己的模型,创建了APP之后,在使用它之前,你需要先在settings文件中的INSTALLED_APPS 处,注册models.py文件所在的app名称。看清楚了,是注册app,不是模型,也不是models.py。

当你每次对模型进行增、删、修改时,我们都需要执行python manage.py makemigrations请务必执命令,然后再执行:python manage.py migrate,让操作实际应用到数据库上。

下面我把常用的字段和常用的参数列出来,供大家参考。

一、常用字段:

1、AutoField   ---自增列 = int(11)    如果没有的话,默认会生成一个名称为 id 的列,如果要显示的自定义一个自增列,必须将给列设置为主键 primary_key=True。
2、CharField   ---字符串字段  单行输入,用于较短的字符串,如要保存大量文本, 使用 TextField。必须 max_length 参数,django会根据这个参数在数据库层和校验层限制该字段所允许的最大字符数。
3、BooleanField   ---布尔类型=tinyint(1)   不能为空,Blank=True
4、ComaSeparatedIntegerField   ---用逗号分割的数字=varchar   继承CharField,所以必须 max_lenght 参数,
5、DateField   ---日期类型 date   对于参数,auto_now = True 则每次更新都会更新这个时间;auto_now_add 则只是第一次创建添加,之后的更新不再改变。
6、DateTimeField   ---日期类型 datetime   同DateField的参数
7、Decimal   ---十进制小数类型 = decimal   必须指定整数位max_digits和小数位decimal_places
8、EmailField   ---字符串类型(正则表达式邮箱) =varchar   对字符串进行正则表达式   一个带有检查 Email 合法性的 CharField,不接受 maxlength 参数。
9、FloatField   ---浮点类型 = double   浮点型字段。 必须提供两个 参数, 参数描述:
max_digits:总位数(不包括小数点和符号)
decimal_places:小数位数。如:要保存最大值为 999 (小数点后保存2位),你要这样定义字段:FloatField(…,max_digits=5, decimal_places=2),要保存最大值一百万(小数点后保存10位)的话,你要这样定义:FloatField(…,max_digits=19, decimal_places=10)
10、IntegerField   ---整形   用于保存一个整数
11、BigIntegerField   ---长整形

integer_field_ranges = {
    'SmallIntegerField': (-32768, 32767),
    'IntegerField': (-2147483648, 2147483647),
    'BigIntegerField': (-9223372036854775808, 9223372036854775807),
    'PositiveSmallIntegerField': (0, 32767),
    'PositiveIntegerField': (0, 2147483647),
}

12、IPAddressField   ---字符串类型(ip4正则表达式)   一个字符串形式的 IP 地址, (如 “202.1241.30″)。
13、GenericIPAddressField   ---字符串类型(ip4和ip6是可选的)   参数protocol可以是:both、ipv4、ipv6   验证时,会根据设置报错
14、NullBooleanField   ---允许为空的布尔类型   类似 BooleanField, 不过允许 NULL 作为其中一个选项。 推荐使用这个字段而不要用 BooleanField 加 null=True 选项。 admin 用一个选择框     <select> (三个可选择的值: “Unknown”, “Yes” 和 “No” ) 来表示这种字段数据。
15、PositiveIntegerField   ---正Integer   类似 IntegerField, 但取值范围为非负整数(这个字段应该是允许0值的…可以理解为无符号整数)
16、PositiveSmallIntegerField   ---正smallInteger  正小整型字段,类似 PositiveIntegerField, 取值范围较小(数据库相关)SlugField“Slug” 是一个报纸术语。 slug 是某个东西的小小标记(短签), 只包  含字母,数字,下划线和连字符。它们通常用于URLs。 若你使用 Django 开发版本,你可以指定 maxlength。 若 maxlength 未指定, Django 会使用默认长度: 50,它接受一个额外的参数:
prepopulate_from: 来源于slug的自动预置列表
17、SlugField   ---减号、下划线、字母、数字   它们通常用于URLs。
18、SmallIntegerField   ---数字   数据库中的字段有:tinyint、smallint、int、bigint.   类似 IntegerField, 不过只允许某个取值范围内的整数。(依赖数据库)
19、TextField   ---字符串=longtext ,一个容量很大的文本字段, admin 管理界面用 <textarea>多行编辑框表示该字段数据。
20、TimeField   ---时间 HH:MM[:ss[.uuuuuu]]   时间字段,类似于 DateField 和 DateTimeField。
21、URLField   ---字符串,地址正则表达式   用于保存URL。若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在(即URL是否被有效装入且没有返回404响应).
22、BinaryField   ---二进制
23、ImageField   ---图片   类似 FileField, 不过要校验上传对象是否是一个合法图片。用于保存图像文件的字段。其基本用法和特性与FileField一样,只不过多了两个属性height和width。默认情况下,该字段在HTML中表现为一个ClearableFileInput标签。在数据库内,我们实际保存的是一个字符串类型,默认最大长度100,可以通过max_length参数自定义。真实的图片是保存在服务器的文件系统内的。
height_field参数:保存有图片高度信息的模型字段名。width_field参数:保存有图片宽度信息的模型字段名。
使用Django的ImageField需要提前安装pillow模块,pip install pillow即可。
使用FileField或者ImageField字段的步骤:
在settings文件中,配置MEDIA_ROOT,作为你上传文件在服务器中的基本路径(为了性能考虑,这些文件不会被储存在数据库中)。再配置个MEDIA_URL,作为公用URL,指向上传文件的基本路径。请确保Web服务器的用户账号对该目录具有写的权限。
添加FileField或者ImageField字段到你的模型中,定义好upload_to参数,文件最终会放在MEDIA_ROOT目录的“upload_to”子目录中。
所有真正被保存在数据库中的,只是指向你上传文件路径的字符串而已。可以通过url属性,在Django的模板中方便的访问这些文件。例如,假设你有一个ImageField字段,名叫mug_shot,那么在Django模板的HTML文件中,可以使用{{object.mug_shot.url}}来获取该文件。其中的object用你具体的对象名称代替。
可以通过name和size属性,获取文件的名称和大小信息。

24、FilePathField   ---选择指定目录按限制规则选择文件,有三个参数可选, 其中”path”必需的,这三个参数可以同时使用, 参数描述:
path:必需参数,一个目录的绝对文件系统路径。 FilePathField 据此得到可选项目。 Example: “/home/images”;
match:可选参数, 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名。 注意这个正则表达式只会应用到 base filename 而不是路径全名。 Example: “foo。*\。txt^”, 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif;
recursive:可选参数, 是否包括 path 下全部子目录,True 或 False,默认值为 False。
match 仅应用于 base filename, 而不是路径全名。 如:FilePathField(path=”/home/images”, match=”foo.*”, recursive=True)…会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif
25、FileField   ---文件上传字段。 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径。 这个路径必须包含 strftime formatting, 该格式将被上载文件的 date/time 替换(so that uploaded files don’t fill up the given directory)。在一个 model 中使用 FileField 或 ImageField 需要以下步骤:在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件。 (出于性能考虑,这些文件并不保存到数据库。) 定义 MEDIA_URL 作为该目录的公共 URL。 要确保该目录对 WEB 服务器用户帐号是可写的。在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 使用 MEDIA_ROOT 的哪个子目录保存上传文件。你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT)。 出于习惯你一定很想使用 Django 提供的 get_<fieldname>_url 函数。举例来说,如果你的 ImageField 叫作 mug_shot, 你就可以在模板中以 {{ object。get_mug_shot_url }} 这样的方式得到图像的绝对路径。
26、PhoneNumberField   ---一个带有合法美国风格电话号码校验的 CharField(格式:XXX-XXX-XXXX)
27、USStateField   ---美国州名缩写,由两个字母组成(天朝人民无视)。
28、XMLField   ---XML字符字段,校验值是否为合法XML的 TextField,必须提供参数:
schema_path:校验文本的 RelaxNG schema 的文件系统路径。

二、常用选项参数意义

1、null   数据库中字段是否可以为空(null=True)
2、db_column  数据库中字段的列名(db_column="test")
3、db_tablespace
4、default  数据库中字段的默认值
5、primary_key  数据库中字段是否为主键(primary_key=True)
6、db_index  数据库中字段是否可以建立索引(db_index=True)
7、unique  数据库中字段是否可以建立唯一索引(unique=True)
8、unique_for_date  数据库中字段【日期】部分是否可以建立唯一索引
9、unique_for_month  数据库中字段【月】部分是否可以建立唯一索引
10、unique_for_year  数据库中字段【年】部分是否可以建立唯一索引
11、auto_now  更新时自动更新当前时间
12、auto_now_add  创建时自动更新当前时间
13、verbose_name  Admin中显示的字段名称
14、blankAdmin  中是否允许用户输入为空表单提交时可以为空
15、editableAdmin  中是否可以编辑
16、help_textAdmin  中该字段的提示信息
17choicesAdmin  中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:

sex=models.IntegerField(choices=[(0,'男'),(1,'女'),],default=1)

error_messages自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null,blank,invalid,invalid_choice,unique,andunique_for_date
如:{'null':"不能为空.",'invalid':'格式错误'}
18、validators 自定义错误验证(列表类型),从而定制想要的验证规则

from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
    max_length=32,
    error_messages={
    'c1': '优先错信息1',
    'c2': '优先错信息2',
    'c3': '优先错信息3',
},
validators=[
    RegexValidator(regex='root_\d+', message='错误了', code='c1'),
    RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
    EmailValidator(message='又错误了', code='c3'), ]
)

多个数据模型间的关系

多表关联是模型层的重要功能之一, Django提供了一套基于关联字段独特的解决方案.那就是用:OneToOneField,ForeignKey,ManyToMany

先来区分一下什么是一对一、一对多、多对多
一对一:子表从母表中选出一条数据一一对应,母表中选出来一条就少一条,子表不可以再选择母表中已被选择的那条数据
一对多:子表从母表中选出一条数据一一对应,但母表的这条数据还可以被其他子表数据选择
共同点是在admin中添加数据的话,都会出现一个select选框,但只能单选,因为不论一对一还是一对多,自己都是“一”
多对多:
比如有多个孩子,和多种颜色、
每个孩子可以喜欢多种颜色,一种颜色可以被多个孩子喜欢,对于双向均是可以有多个选择
应用场景:
一对一(OneToOneField):一般用于某张表的补充,比如用户基本信息是一张表,但并非每一个用户都需要有登录的权限,不需要记录用户名和密码,此时,合理的做法就是新建一张记录登录信息的表,与用户信息进行一对一的关联,可以方便的从子表查询母表信息或反向查询
外键(ForeignKey):有很多的应用场景,比如每个员工归属于一个部门,那么就可以让员工表的部门字段与部门表进行一对多关联,可以查询到一个员工归属于哪个部门,也可反向查出某一部门有哪些员工
多对多(ManyToMany):如很多公司,一台服务器可能会有多种用途,归属于多个产品线当中,那么服务器与产品线之间就可以做成对多对,多对多在A表添加manytomany字段或者从B表添加,效果一致.

1、ForeignKey

模型示例

from django.db import models
classBlog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()
classAuthor(models.Model):
    name = models.CharField(max_length=50)
    email = models.EmailField()
classEntry(models.Model):
    blog = models.ForeignKey(Blog)
    authors = models.ManyToManyField(Author)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

classForeignKey
ForeignKey字段接受一个Model类作为参数,类型与被参照的字段完全相同:

 blog = models.ForeignKey(Blog)

ForeignKey.to_field
关联到的关联对象的字段名称。默认地,Django使用关联对象的主键。

blog = models.ForeignKey(Blog, to_field=Blog.name)

ForeignKey.db_constraint
Django Model的ForeignKey字段的主要功能是维护一个一对多的关系, 以进行关联查询.
只有在db_constraint=True时Django model才会在数据库上建立外键约束, 在该值为False时不建立约束.
默认db_constraint=True.
ForeignKey.related_name
这个名称用于让关联的对象反查到源对象.
如果你不想让Django 创建一个反向关联,请设置related_name 为 '+' 或者以'+' 结尾.
ForeignKey.related_query_name以ForeignKey.related_name作为默认值。

2、ManyToManyField

来自官网的例子

from django.db import models
 
class Person(models.Model):
    name = models.CharField(max_length=50)
 
class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership', through_fields=('group', 'person'))
 
class Membership(models.Model):
    group = models.ForeignKey(Group)
    person = models.ForeignKey(Person)
    inviter = models.ForeignKey(Person, related_name="membership_invites")
    invite_reason = models.CharField(max_length=64)

ManyToManyField.through
Django会自动创建一个表来管理多对多关系,若要手动指定关联表则需要使用through关键字参数.
ManyToManyField.through_fields
上文示例中Membership有两个外键指向Person(person和inviter),这使得关联关系含混不清并让Django不知道使用哪一个。在这种情况下,必须使用through_fields明确指定Django应该使用哪些外键
through_fields接收一个二元组('field1','field2'),其中field1为指向定义ManyToManyField字段的模型的外键名称(本例中为group),field2为指向目标模型的外键的名称(本例中为person).
ManyToManyField.db_table
默认情况下,关联表的名称使用多对多字段的名称和包含这张表的模型的名称以及Hash值生成,如:memberShip_person_3c1f5
若要想要手动指定表的名称,可以使用db_table关键字参数指定.
others
下列API和ForeignKey中的同名API相同.
ManyToManyField.db_constraint
ManyToManyField.related_name
ManyToManyField.related_query_name

注意:

对于最新版本的django2.0在使用OneToOneField和ForeignKey时,需要加上on_delete参数。具体on_delete参数的详细用法请阅读下面的文章:Django2.0外键参数on_delete的使用方法

视图函数

Django请求的生命周期:通过URL对应关系匹配 ->找到对应的函数(或者类)->返回字符串(或者读取Html之后返回渲染的字符串)。这里RUL对应的函数,我们就叫视图函数

视图函数是一个简单的Python 函数,它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. . . 是任何东西都可以。无论视图本身包含什么逻辑,都要返回响应。这个视图函数代码一般约定是放置在项目或应用程序目录中的名为views.py的文件中。

http请求中产生两个核心对象:

1、http请求---->HttpRequest对象,用户请求相关的所有信息(对象)

2、http响应---->HttpResponse对象,响应字符串

例子:

新建views1.py
from django.http import HttpResponse
def index(request):
    return HttpResponse("你好")
 
在urls.py中修改配置
from . import views1
path('', views1.index, name='index'),

例子里,我们用到的request,就是HttpRequest对象。HttpResponse("你好"),就是HttpResponse对象,它向http请求响应了一段字符串。

视图函数,就是围绕着HttpRequest和HttpResponse这两个对象进行的。

一、HttpRequest对象,request请求信息和属性和方法。

属性和方法包含下面几个:

1、request.path:这个主要是用来获取访问文件路径

from django.shortcuts import render,HttpResponse
def index(request):
    print(request.path)
    print(request.get_full_path())
    return render(request,'index.html')

如果我们是通过http://127.0.0.1:8000/post/23?page=1请求的话。

request.path的结果为:/post/23

request.get_full_path()的结果为:/post/23?page=1

2、request.method属性:获取请求中使用的HTTP方式(POST/GET)

from django.shortcuts import render,HttpResponse
def index(request):
    print(request.method)
    return render(request,'index.html')

3、request.body属性:含所有请求体信息 是bytes类型

4、request.GET,获取HTTP GET方式请求传参,的参数(字典类型)

from django.shortcuts import render,HttpResponse
def index(request):
    print(request.GET)
    return render(request,'index.html')

如果我们通过http://127.0.0.1:8000/post/?fenlei=123 & page=3 请求。

获取到:<QueryDict: {' page': ['456'], 'fenlei': ['123 ']}>

5、request.POST,获取POST请求的数据(类字典对象) 请求体里拿值。服务器收到空的POST请求的情况也是可能发生的,也就是说,表单form通过服务器收到空的POST请求的情况也是可能发生的,也就是说,表单form通过if request.POST来判断是否使用了HTTP POST 方法;应该使用  if request.method=="POST"。

6、request.COOKIES,包含所有cookies的标准Python字典对象;keys和values都是字符串。

7、request.FILES,包含所有上传文件的类字典对象;FILES中的每一个Key都是<input type="file" name="" />标签中name属性的值,FILES中的每一个value同时也是一个标准的python字典对象,包含下面三个Keys:filename:上传文件名,用字符串表示、content_type:上传文件的Content Type、content:上传文件的原始内容。

8、request.user,是一个django.contrib.auth.models.User对象,代表当前登陆的用户。如果访问用户当前没有登陆,user将被初始化为django.contrib.auth.models.AnonymousUser的实例。你可以通过user的is_authenticated()方法来辨别用户是否登陆:if request.user.is_authenticated();只有激活Django中的AuthenticationMiddleware时该属性才可用。

9、request.session,唯一可读写的属性,代表当前会话的字典对象;自己有激活Django中的session支持时该属性才可用

10、request.GET.get('name'),拿到GET请求里name的值,如果某个键对应有多个值,则不能直接用get取值,需要用getlist,如:request.POST.getlist("hobby")。

二、HttpResponse响应对象方法和属性。

对于HttpRequest请求对象来说,是由django自动创建的,但是,HttpResponse响应对象就必须我们自己创建。每个view请求处理方法必须返回一个HttpResponse响应对象。HttpResponse类在django.http.HttpResponse。

HttpResponse对象的常用方法:

1、render函数。将指定页面渲染后返回给浏览器。

render(request, template_name[, context])
结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的HttpResponse对象。

def index(request):
    blog_index = models.Article.objects.all().order_by('-id')
    print(request.body)
    context = {
        'blog_index':blog_index,
    }
    return render(request, 'index.html',context)

参数:
request: 用于生成响应的请求对象。
template_name:要使用的模板的完整名称,可选的参数
context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。
content_type:生成的文档要使用的MIME类型。默认为DEFAULT_CONTENT_TYPE 设置的值。
status:响应的状态码。默认为200。

render方法主要是将从服务器提取的数据,填充到模板中,然后将渲染后的html静态文件返回给浏览器。这里一定要注意:render渲染的是模板。

 

<head>
</head>
<body>
<div style="margin: 0 auto">
    <ul>
    <h3>最新文章</h3>
        {% for x in blog_index %}
        <li>{{ x.title }}</li>
        {% endfor %}
    </ul>
</div>
</body>
</html>

上面 {% for x in blog_index %}到{% endfor %}之间包括的就是我们要从数据库取出的数据,进行填充。对于这样一个没有填充数据的html文件,浏览器是不能进行渲染的,所以,对于上述{% for x in blog_index %}到{% endfor %}之间的内容先要被render进行渲染之后,才能发送给浏览器。

Views里是这样写的:

def index(request):
    blog_index = models.Article.objects.all().order_by('-id')#从数据库中取出文章数据
    print(request.body)
    context = {
        'blog_index':blog_index,#将数据保存在blog_index
    }
    return render(request, 'index.html',context)#通过render进行模板渲染

2、redirect函数,多用于页面跳转。

redirect的参数可以是:
一个模型:将调用模型的get_absolute_url() 函数
一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
一个绝对的或相对的URL,将原封不动的作为重定向的位置。
默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

示例:

传递一个对象,将调用get_absolute_url() 方法来获取重定向的URL:

from django.shortcuts import redirect
def my_view(request):
    ...
    object = MyModel.objects.get(...)
    return redirect(object)

传递一个视图的名称,可以带有位置参数和关键字参数;将使用reverse() 方法反向解析URL:

def my_view(request):
    ...
    return redirect('some-view-name', foo='bar')

传递要重定向的一个硬编码的URL:

def my_view(request):
    ...
    return redirect('/some/url/')

也可以是一个完整的URL:

def my_view(request):
    ...
    return redirect('https://www.django.cn/')

默认情况下,redirect() 返回一个临时重定向。以上所有的形式都接收一个permanent 参数;如果设置为True,将返回一个永久的重定向:

ef my_view(request):
    ...
    object = MyModel.objects.get(...)
    return redirect(object, permanent=True)  

render和redirect两者区别:    
第一,假如render返回一个登陆成功后的页面,刷新该页面将回复到跳转前页面。而redirect则不会
第二,如果页面需要模板语言渲染,需要的将数据库的数据加载到html,那么render方法则不会显示这一部分,render返回一个登陆成功页面,不会经过url路由分发系统,也就是说,不会执行跳转后url的视图函数。这样,返回的页面渲染不成功;而redirect是跳转到指定页面,当登陆成功后,会在url路由系统进行匹配,如果有存在的映射函数,就会执行对应的映射函数。

模型(Model)继承

之前我们说过models.Model里包含了大量的方法,Model的继承和普通的Python类继承几乎相同,但基类的继承必须是django.db.models.Model。

在Django中有三种继承风格:
1、如果你想用父类来保存每个子类共有的信息,并且这个类是不会被独立使用的,那么应该使用抽象基类。
2、如果你继承现有model(甚至可能这个类是在另一个应用程序中),并希望每个model都拥有自己对应的数据库,那就应该使用多表继承。
3、如果你只是想修改model的Python-level行为,而不改变modelsfields,则使用代理模式。

一、抽象基类-Abstract base classes

当您想要将一些公共信息放入许多其他模型时,抽象基类非常有用。编写基类并在Meta类中放置abstract=True。然后,此模型将不用于创建任何数据库表。相反,当它用作其他模型的基类时,其字段将添加到子类的字段中。
一个例子:

from django.db import models
class CommonInfo (models.Model):
    name = models.CharField (max_length = 100)
    age = models.PositiveIntegerField()
    class Meta :
        abstract = Trueclass Student (CommonInfo):
    home_group = models.CharField (max_length = 5)

Student模型将包含三个字段:name,age和home_group。CommonInfo模型不能用作普通的Django模型,因为它是一个抽象基类。它不生成数据库表或具有管理器,并且无法直接实例化或保存。从抽象基类继承的字段可以用另一个字段或值覆盖,或者使用None删除。对于许多用途,这种类型的模型继承将完全符合您的要求。它提供了一种在Python级别分解公共信息的方法,同时仍然只在数据库级别为每个子模型创建一个数据库表。

1、Meta继承-Meta inheritance

当创建抽象基类时,Django使您在基类中声明的任何Meta内部类可用作属性。如果子类没有声明自己的Meta类,它将继承父类的Meta。如果孩子想要扩展父类的Meta类,它可以将其子类化。例如:

from django.db import models
class CommonInfo(models.Model):
    # ...
    class Meta :
        abstract = True
        ordering = ['name']
        
class Student(CommonInfo):
    # ...
    class Meta (CommonInfo.Meta):
        db_table = 'student_info'

Django确实对抽象基类的Meta类进行了一次调整:在安装Meta属性之前,它设置abstract=False。这意味着抽象基类的子项本身不会自动成为抽象类。当然,您可以创建一个继承自另一个抽象基类的抽象基类。你只需要记住每次都明确设置abstract=True。
在抽象基类的Meta类中包含一些属性是没有意义的。例如,包括db_table意味着所有子类(未指定自己的Meta)将使用相同的数据库表,这几乎肯定不是您想要的。

2、related_name和related_query_name

如果在ForeignKey或ManyToManyField上使用ForeignKey或ManyToManyField,则必须始终为该字段指定唯一的反向名称和查询名称。这通常会导致抽象基类出现问题,因为此类中的字段包含在每个子类中,每次都具有完全相同的属性值(包括related_name和related_query_name)。
要解决此问题,当您在抽象基类(仅)中使用related_name或'%(app_label)s',该值的一部分应包含'%(app_label)s'和'%(class)s'。
'%(class)s'替换为使用该字段的子类的低级别名称。
'%(app_label)s'被包含在子类中的应用程序的低级名称替换。每个安装的应用程序名称必须是唯一的,并且每个应用程序中的模型类名称也必须是唯一的,因此生成的名称最终会有所不同。
例如,给定一个appcommon/models.py:

from django.db import models
class Base (models.Model):
    m2m = models.ManyToManyField (
        OtherModel ,
        related_name = " %(app_label)s _ %(class)s _related" ,
        related_query_name = " %(app_label)s _ %(class)s s" ,
    )
    class Meta :
        abstract = True
class ChildA (Base):
    pass
class ChildB (Base):
    pass

与另一个app rare/models.py :

from common.models import Base
class ChildB(Base):
    pass

示例中,common.ChildA.m2m字段的反向名称为common_childa_related,反向查询名称为common_childas。common.ChildB.m2m字段的反向名称为common_childb_related,反向查询名称为common_childbs。最后,rare.ChildB.m2m字段的反向名称将为rare_childb_related,反向查询名称将为rare_childbs。由你如何使用'%(class)s'和'%(app_label)s'部分来构造你的相关名称或相关的查询名称,但如果你忘了使用它,Django会在你执行系统检查时引发错误(或运行migrate)。
如果没有为抽象基类中的字段指定related_name属性,则默认反向名称将是子类的名称,后跟'_set',就像通常直接声明字段一样在孩子班上。例如,在上面的代码中,如果省略了childa_set属性,则m2m字段的反向名称将是ChildA案例中的ChildB和ChildA字段中的ChildB。

二、多表继承-Multi-table inheritance

Django支持的第二种模型继承是当层次结构中的每个模型都是模型本身时。每个模型对应于自己的数据库表,可以单独查询和创建。继承关系引入子模型与其每个父模型之间的链接(通过自动创建的OneToOneField)。例如:

from django.db import modelsclass Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)

class Restaurant(Place):
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

所有Place的字段都可以在Restaurant中使用,虽然数据存放在不同的数据表中。所以可以如下使用:

>>> Place.objects.filter(name="Bob'sCafe")
>>> Restaurant.objects.filter(name="Bob'sCafe")

如果一个Place对象存在相应的Restaurant对象,那么就可以使用Place对象通过关系获得Restaurant对象:

>>> p = Place.objects.get(id=12)
# If p is a Restaurant object, this will give the child class:
>>> p.restaurant
<Restaurant: ...>

但是,如果上面的示例中的p不是Restaurant(它已直接创建为Place对象或是其他类的父对象),则引用p.restaurant会引发一个Restaurant.DoesNotExist异常。
在Restaurant上自动创建的OneToOneField将其链接到Place如下所示:

place_ptr = models.OneToOneField(
    Place,on_delete=models.CASCADE,
    parent_link=True,
   )

1、Meta和多表继承-Meta and multi-table inheritance

在多表继承的情况下继承父类的Meta是没有意义的。所有的Meta都已经被应用到父类,再应用这些Meta只会导致矛盾。

所以子model不能访问到父model的Meta,然而也有少数的情况下,子model会从父model中继承一些行为,例如子model没有指定 ordering或 get_latest_by属性,那么就会从父model中继承。

如果父model中有一个排序,但你不希望子model有任何的排序规划,你可以明确的禁用:

class ChildModel(ParentModel):
    # ...
    class Meta:
        # Remove parent's ordering effect
        ordering = []

2、继承和反向关系-Inheritance and reverse relations

因为多表继承实际是隐式的使用OneToOneField来键接父Model和子model,在这种关系有可能会使用父model来调用子model,比如上面的例子。但是如果你把ForeignKey和ManyToManyField关系应用到这样一个继承关系中,Django会返回一个验证错误,必须要指定一个related_name字段属性。
例如上面的例子,我们再创建一个子类,其中包含一个到父model的ManyToManyField关系字段:

class Supplier(Place):
    customers = models.ManyToManyField(Place)

这时会产生一个错误:

Reverse query name for 'Supplier.customers' clashes with reverse query
name for 'Supplier.place_ptr'.
HINT: Add or change a related_name argument to the definition for'Supplier.customers' or 'Supplier.place_ptr'.

解决这个问题只需要在customers字段属性中增加related_name属性:models.ManyToManyField(Place, related_name='provider').

3、指定父链接字段-Specifying the parent link field
如上所述,Django会自动创建一个OneToOneField链接你的子model和任何非抽象父model。如果你想自定义子model键接回父model的属性名称,你可以创建自己的OneToOneField并设置parent_link=True,表示这个字段是对父model的回链。

三、代理模型-Proxy models

当使用多表继承时一个新的数据表model会在每一个子类中创建,这是因为子model需要存储父mdoel不存在的一些数据字段。但有时只需要改变model的操作行为,可能是为了改变默认的管理行为或添加新的方法。

这时就应该使用代理模式的继承:创建原始model的代理。你可以创建一个用于 create, delete 和 update的代理model,使用代理model的时候数据将会真实保存。这和使用原始model是一样的,所不同的是当你改变model操作时,不需要去更改原始的model。

代理模式的声明和正常的继承声明方式一样。你只需要在Meta class 中定义proxy为True就可以了。

例如,你想为Person model添加一个方法:

from django.db import modelsclass Person (models.Model ):
    first_name = models.CharField ( max_length = 30 )
    last_name = models.CharField ( max_length = 30 )
    
class MyPerson ( Person ):
    class Meta :
        proxy = True
    def do_something ( self ):
        # ...
        pass

MyPerson这个类将作用于父类Person所对应的真实数据表。可通过MyPerson进行所有相应的操作:

>>> p = Person.objects.create(first_name="foobar")
>>> MyPerson.objects.get(first_name="foobar")
<MyPerson: foobar>

你也可以使用代理模式来定义model的不同默认排序,例如:

class OrderedPerson(Person):
    class Meta:
        ordering = ["last_name"]
        proxy = True

这样当使用原始model查询时结果是无序的,而使用OrderedPerson进行查询时将按last_name进行排序。

1、QuerySets still return the model that was requested(QuerySets的类型依然会是原始model类型)

当你通过MyPerson来查询Person对象时,返回的QuerySet依然会是Person对象类型的集合。使用代理模式的model是依靠原始model的,是原始model的扩展。而不是用来替代父model。

2、Base class restrictions(基类限制)

代理model必须继承一个非抽像model。

不能从多个非抽像model继承,代理模式不能为不同model之间创建链接。

代理模式可以从任意没有定义字段的抽象model继承。

3、代理模型管理器-Proxy model managers

如果没有指定代理model的管理器(managers),它将继承父类的管理行为。如果你定义了代理model的管理器,它将会成为默认的,当然父类中定义的定义的任何管理器仍然是可以使用的。

继续上面的例了,增加一个默认的管理器:

from django.db import models
class NewManager(models.Manager):
    # ...
    passclass MyPerson(Person):
    objects = NewManager()    
    class Meta:
        proxy = True

可以通过创建一个含有新的管理器并进行继承,来增加一个新的管理器,而不需要去改变更有的默认管理器。

# Create an abstract class for the new manager.
class ExtraManagers(models.Model):
    secondary = NewManager()    
    class Meta:
        abstract = True
class MyPerson(Person, ExtraManagers):
    class Meta:
        proxy = True

4、Differences between proxy inheritance and unmanaged models

代理model看起来很像一个在Meta class中设置了manged的非托管模式model。但实际上这两种方案是不太一样,应该考虑在不同的情况下使用那一个:

两者区别在于:你可以设置model的Meta.managed=False以及通过Meta.db_table指定数据表有创建非托管模式model,并对其添加各种方法,但如果你可保持非托管模式和真实数据表之间的同步,做任何更改都将是很麻烦的事。

而代理model主要用于管理model的各种行为或方法,他们将继承父model的管理器等。

曾经尝试将两种模式合并,但由于API会变得非常复杂,并且难以理解,所以现在是分离成两种模式:

一般的使用规划是:

1、如果正使用现有的数据表,但不想在Django中镜像所有的列,所以应该使用Meta.managed=False,通过这个选项使不在django控制下的数据表或视图是可用的。

2、如果你想改变一个model的操作行为,但希望保持原始model不被改变,就应该使用Meta.proxy=True.

四、Multiple inheritance(多重继承)

和Python的继承方式一样,django中的model也可以从多个父model继承,当然也和Python的继承方式 一样,如果出现相同名字的时候只有第一个将被使用。例如:如果多个父model中都包含Meta类,将只有第一个将被使用,其他会被忽略。

通常情况下是不会用到多重继承的。主是用于“混合式”model:增加一个特殊的额外字段或方式是由多个父model组合而来。应该尽量保持继承层次的简单,不然会很难排查某个信息是从那里来的。

在django1.7以前,多个父model中有id主键字段时虽然不会引发错误,但有可能导致数据的丢失。例如像下面的model:

classArticle(models.Model):
    article_id=models.AutoField(primary_key=True)
    ...
classBook(models.Model):
    book_id=models.AutoField(primary_key=True)
    ...
classBookReview(Book,Article):
    pass

或者使用共同的祖先来保持AutoField。这需要使用OneToOneField从每个父模型到共同祖先的显式来避免子项自动生成和继承的字段之间的冲突:

classPiece(models.Model):
    pass
classArticle(Piece):
    article_piece=models.OneToOneField(Piece,on_delete=models.CASCADE,parent_link=True)
    ...
classBook(Piece):
    book_piece=models.OneToOneField(Piece,on_delete=models.CASCADE,parent_link=True)
    ...
classBookReview(Book,Article):
    pass

1、Fieldname“hiding”isnotpermitted
正常的Python类继承,允许一个子类覆盖父类的任何属性。在Django中是不允许覆盖父类的属性字段的。如果一个父类中定义了一个叫author的字段,你就不能在子model中创建别一个叫author的字段。
这种限制仅适用于字段(field),普通的python属性是可以的。也有一种情况是可以覆盖的:多表继承的时候进行手动指定数据库列名,可以出现子model和父model有同名的字段名称,因为他们属于不同的数据表。
如果你覆盖了父model的字段属性,django会抛出FieldError异常。

ORM增删改操作

我们建立模型、保存数据为的就是在需要的时候可以查询得到数据。Django自动为所有的模型提供了一套完善、方便、高效的API。

结合之前我们创建的Models,下面是我们的models.py代码:

from django.db import models
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField('分类',max_length=100)
    class Meta:
        verbose_name = '分类'verbose_name_plural = verbose_name
    def __str__(self):
        return self.name
        
class Tags(models.Model):
    name = models.CharField('标签',max_length=100)
    class Meta:
        verbose_name = '标签'verbose_name_plural = verbose_name
    def __str__(self):
        return self.name
        
class Article(models.Model):
    title = models.CharField('标题',max_length=70)
    intro = models.TextField('摘要', max_length=200, blank=True)
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类', default='1')
    tags = models.ManyToManyField(Tags, blank=True, verbose_name='标签')
    body = models.TextField()
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
    created_time = models.DateTimeField('发布时间',auto_now_add=True)
    class Meta:
        verbose_name = '文章'verbose_name_plural = verbose_name
    def __str__(self):
        return self.title

1、基础准备

配置URL

mysite/urls.py

from blog import views              #新增
urlpatterns = [
    path('admin/', admin.site.urls),
    path('orm/', views.orm),          #新增
]

添加一个与URL对应的视图函数

blog/views.py
from django.shortcuts import render, HttpResponse
from blog import models

def orm(requst):

    return HttpResponse('orm')

在浏览器里访问http://127.0.0.1:8000/orm/。我们这样做的目的是,我们一会把数据操作方法(查询语句)都写在 orm视图函数里,通过URL,触发视图函数,然后在Pycharm的Terminal里查看输出日志,验证我们查询的效果。

2.jpg

1.jpg

2、增加数据

blog/views.py

from django.shortcuts import render, HttpResponse
from blog import models

def orm(requst):
    #增加一篇文章
    models.Article.objects.create(title='增加标题一', category_id=3, body='增加内容一', user_id=1)
    
    return HttpResponse('orm')

 

操作之前,数据库里的文章内容如下:

1.jpg

在浏览器里访问http://127.0.0.1:8000/orm/之后,触发视图函数。然后刷新数据库看效果。

2.jpg

数据库里已经添加了一篇文章。说明我们之前的增加文章操作,已经成功。

如果不知道如何使用Database管理数据库,请查看文章:使用Pycharm里的Database对数据库进行可视化操作

数据增加的方法一共有三种:

from django.shortcuts import render, HttpResponse
from blog import models  # 导入数据库操作模块

def orm(requst):
    # 第一种方法:
    # models.Article.objects.create(title='增加标题一', category_id=3, body='增加内容一', user_id=1)
    # 第二种方法:添加数据,实例化表类,在实例化里传参为字段和值
    obj = models.Article(title='增加标题二', category_id=4, body='增加内容二', user_id=1)
    # 写入数据库
    obj.save()
    # 第三种方法:将要写入的数据组合成字典,键为字段值为数据
    dic = {'title': '增加标题三', 'category_id': '4', 'body': '增加内容三', 'user_id': '1'}
    # 添加到数据库,注意字典变量名称一定要加**
    models.Article.objects.create(**dic)
    
    return HttpResponse('orm')

使用第二和第三种方法增加数据,刷新页面之后,数据库效果如下:

3.jpg
我们看到,数据库里已经增加了两条数据。

严格来说,增加数据只有两种:create( )方法和save( )方法。一般我们推荐使用第三种方法。

例子:获取用户提交的的数据创建用户:

#hmtl
<form action="/useradd/" method="post">
    <p>用户名:{{ obj.username }}</p>
    <p>密码:{{ obj.password }}</p>
    <input type="submit" value="submit"/>
</form>
 
#views
def useradd(request):
    obj = AccountForm.UserAdd(request.POST)
    if request.method == 'POST':
        if obj.is_valid():
            user_input = obj.clean()
            print user_input
            #这里直接通过**加上用户的输入即可,因为用户的输入时字典类型的
            models.UserInfo.objects.create(**user_input)
            print models.UserInfo.objects.all()
            return render(request,'account/useradd.html',{'obj':obj})
    return render(request,'account/useradd.html',{'obj':obj})
 
#结果
    --用户输入
        {'username': 'dashuaige', 'password': '123123'}
    --print models.UserInfo.objects.all() 返回值
    #注这里我们是通过__unicode__方法进行输出了否则是对象!
        [<UserInfo: sunqihu>, <UserInfo: alex>, <UserInfo: wutenglan>, <UserInfo: dashuaige>]

三、删除数据delete()

from django.shortcuts import render, HttpResponse
from blog import models
def orm(requst):
    #删除id=6的文章(数据)
    title = models.Article.objects.filter(id=6).delete()
    return HttpResponse('orm')

删除之前:

3.jpg

删除之后:

1.jpg

ID为6的文章,已经被删除。

四、修改数据update(字段=值)

from django.shortcuts import render, HttpResponse
from blog import models

def orm(requst):
    #把标题'增加标题二',修改成'我被修改了'。将指定条件的数据更新,支持 **kwargs,支持字典。
    title = models.Article.objects.filter(title='增加标题二').update(title='我被修改了')
    return HttpResponse('orm')

修改前:

1.jpg

修改之后:

2.jpg

修改成功!

ORM之QuerySet API

ORM增删改操作文章里,主要讲了ORM的增删改查的基本操作,这节我们主要是讲ORM查询操作,查询操作是Django的ORM框架中最重要的内容之一,下面是我们常用到的与查询相关的API。

注意,本章节的例子都是在上节的modesl.py基础上做的。

<1>all():         查询所有结果
<2>filter(**kwargs)    它包含了与所给筛选条件相匹配的对象
<3>get(**kwargs):     返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。
<4>exclude(**kwargs)    它包含了与所给筛选条件不匹配的对象
<5>values(*field)     返回一个ValueQuerySet 一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列
<6>values_list(*field)   它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
<7>order_by(*field)    对查询结果排序
<8>reverse()        对查询结果反向排序
<9>distinct()       从返回结果中剔除重复纪录
<10>count()        返回数据库中匹配查询(QuerySet)的对象数量。
<11>first()        返回第一条记录
<12>last()         返回最后一条记录
<13>exists()        如果QuerySet包含数据,就返回True,否则返回False
<14>annotate()       使用聚合函数
<15>dates()        根据日期获取查询集
<16>datetimes()      根据时间获取查询集
<17>none()         创建空的查询集
<18>union()        并集
<19>intersection()     交集
<21>difference()      差集
<22>select_related()    附带查询关联对象
<23>prefetch_related()   预先查询
<24>extra()        附加SQL查询
<25>defer()        不加载指定字段
<26>only()         只加载指定的字段
<27>using()        选择数据库
<28>select_for_update()  锁住选择的对象,直到事务结束。
<29>raw()         接收一个原始的SQL查询

注意:一定要区分出object与querySet的区别 !!!

1、检索所有对象all()

使用all()方法,可以获取某张表的所有记录。返回当前QuerySet(或QuerySet子类)的副本。通常用于获取全部QuerySet对象。

def orm(requst):
    #获取所有文章,对应SQL:select * from Article
    all_article = models.Article.objects.all()
    print(all_article)
    return HttpResponse('orm')

保存之后,我们通过浏览器访问,然后查看 Terminal,看到我们的打印出来的查询结果,一共有四篇文章。

1.jpg

查询出来的是一个QuerySet的对象。

2、用filter过滤对象

filter(**kwargs)
返回满足查询参数的对象集合。
查找的参数(**kwargs)应该满足下文字段查找中的格式。多个参数之间是和AND的关系。

常用例子:

# 大于,>,对应SQL:select * from Article where id > 724
Article.objects.filter(id__gt=724)
# 大于等于,>=,对应SQL:select * from Article where id >= 724
Article.objects.filter(id__gte=724)
# 小于,<,对应SQL:select * from Article where id < 724
Article.objects.filter(id__lt=724)
# 小于等于,<=,对应SQL:select * from Article where id <= 724
Article.objects.filter(id__lte=724)
# 同时大于和小于, 1 < id < 10,对应SQL:select * from Article where id > 1 and id < 10
Article.objects.filter(id__gt=1, id__lt=10)
# 包含,in,对应SQL:select * from Article where id in (11,22,33)
Article.objects.filter(id__in=[11, 22, 33])
# 不包含,not in,对应SQL:select * from Article where id not in (11,22,33)
Article.objects.filter(pub_date__isnull=True)
# 不为空:isnull=False,对应SQL:select * from Article where pub_date is not null
Article.objects.filter(pub_date__isnull=True)
# 匹配,like,大小写敏感,对应SQL:select * from Article where name like '%sre%',SQL中大小写不敏感
Article.objects.filter(name__contains="sre")
# 匹配,like,大小写不敏感,对应SQL:select * from Article where name like '%sre%',SQL中大小写不敏感
Article.objects.filter(name__icontains="sre")
# 范围,between and,对应SQL:select * from Article where id between 3 and 8
Article.objects.filter(id__range=[3, 8])
# 以什么开头,大小写敏感,对应SQL:select * from Article where name like 'sh%',SQL中大小写不敏感
Article.objects.filter(name__startswith='sre')
# 以什么开头,大小写不敏感,对应SQL:select * from Article where name like 'sh%',SQL中大小写不敏感
Article.objects.filter(name__istartswith='sre')
# 以什么结尾,大小写敏感,对应SQL:select * from Article where name like '%sre',SQL中大小写不敏感
Article.objects.filter(name__endswith='sre')
# 以什么结尾,大小写不敏感,对应SQL:select * from Article where name like '%sre',SQL中大小写不敏感
Article.objects.filter(name__iendswith='sre')
# 排序,order by,正序,对应SQL:select * from Article where name = '关键词' order by id
Article.objects.filter(name='关键词').order_by('id')
# 多级排序,order by,先按name进行正序排列,如果name一致则再按照id倒叙排列
Article.objects.filter(name='关键词').order_by('name','-id')
# 排序,order by,倒序,对应SQL:select * from Article where name = '关键词' order by id desc
Article.objects.filter(name='关键词').order_by('-id')

3、查询单一对象 get

filter方法始终返回的是QuerySets,那怕只有一个对象符合过滤条件,返回的也是包含一个对象的QuerySets,这是一个集合类型对象,你可以简单的理解为Python列表,可迭代可循环可索引。
如果你确定你的检索只会获得一个对象,那么你可以使用get()方法来直接返回这个对象。get返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。

#查询ID=1的文章
Article.objects.get(id=1)
#等同
Article.objects.get(pk=1)

4、查询不匹配条件的对象 exclude

exclude(**kwargs)

返回一个新的QuerySet,它包含满足给定的查找参数的对象。

查找的参数(**kwargs)应该满足下文字段查找中的格式。多个参数通过AND连接,然后所有的内容放入NOT() 中。

下面的示例排除所有created_time晚于2018-7-15headline为“Hello” 的记录:

Article.objects.exclude(created_time__gt=datetime.date(2018,7,15), headline='Hello')

下面的示例排除所有pub_date晚于2005-1-3或者headline 为“Hello” 的记录:

Article.objects.exclude(created_time__gt=datetime.date(2018,7,15)).exclude(headline='Hello')

5、查询返回一个字典 values

返回一个ValueQuerySet,一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列。每个字典表示一个对象,键对应于模型对象的属性名称。

例:

#values()与普通的模型对象比较:
def orm(requst):
    article = models.Article.objects.filter(title__startswith='增加')
    article_values = models.Article.objects.filter(title__startswith='增加').values()
    print(article)
    print('----------------------------------------------')
    print(article_values)
    return HttpResponse('orm')
    
#打印结果  
<QuerySet [<Article: 增加标题一>, <Article: 增加标题二>]>
----------------------------------------------
<QuerySet [
{'id': 4, 'title': '增加标题一', 'intro': '', 'category_id': 3, 'body': '增加内容一', 'user_id': 1},
{'id': 6, 'title': '增加标题二', 'intro': '测试增加标题二', 'category_id': 4, 'body': '增加内容二', 'user_id': 2}
 ]>
 
#更多用法:
#不指定字段会获取所有字段的键和值
article_values_all = models.Article.objects.values()
#如果指定字段,每个字典将只包含指定的字段的键/值。
article_values_filter = models.Article.objects.values('id', 'title')

#values()方法还有关键字参数**expressions,这些参数将传递给annotate()注释
from django.db.models.functions import Lower
article_values=models.Article.objects.values(标题=Lower('title'))
<QuerySet [{'标题': '增加标题一'}, {'标题': '我被修改了'}, {'标题': '增加标题二'}]>

#聚合应用,统计作者下的文章数量
from django.db.models import Count
article_values_annotate = models.Article.objects.values('user').annotate(数量=Count('title'))
<QuerySet [{'user': 1, '数量': 3}, {'user': 2, '数量': 2}]>

#如果你有一个字段category是一个ForeignKey,默认的category_id参数返回的字典中将有一个叫做category的键,
#因为这是保存实际值的那个隐藏的模型属性的名称
#当调用category_id并传递字段的名称,传递category或values()都可以,得到的结果是相同的。
#像这样:
category=models.Article.objects.values('category')
category_id = models.Article.objects.values('category_id')
<QuerySet [{'category': 3}, {'category': 3}, {'category': 4}, {'category': 4}, {'category': 4}]>
<QuerySet [{'category_id': 3}, {'category_id': 3}, {'category_id': 4}, {'category_id': 4}, {'category_id': 4}]>

6、查询返回一个元组 values_list

values_list(*fields, flat=False)
与values()类似,只是在迭代时返回的是元组而不是字典。每个元组包含传递给values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。

看例子:

from django.db.models.functions import Lower
values_list=models.Article.objects.values_list('id','title')
values_list_lower = models.Article.objects.values_list('id', Lower('title'))
<QuerySet [(3, 'virtualenv使用技巧大全'), (4, '增加标题一'), (5, '我被修改了'), (6, '增加标题二')]>
<QuerySet [(3, 'virtualenv使用技巧大全'), (4, '增加标题一'), (5, '我被修改了'), (6, '增加标题二')]>

如果只传递一个字段,还可以传递flat参数。 如果为True,它表示返回的结果为单个值而不是元组。 如下所示:

values_list=models.Article.objects.values_list('id').order_by('id')
values_list_flat = models.Article.objects.values_list('id', flat=True).order_by('id')
<QuerySet [(2,), (3,), (4,), (5,), (6,)]>
<QuerySet [2, 3, 4, 5, 6]>

如果有多个字段,传递flat将发生错误。

7、查询返回一个元组 order_by

order_by(*fields)
默认情况下,根据模型的Meta类中的ordering属性对QuerySet中的对象进行排序

article = models.Article.objects.filter(created_time__year=2018).order_by('-created_time', 'title')

上面的结果将按照created_time降序排序,然后再按照title升序排序。"-created_time"前面的负号表示降序顺序。 升序是默认的。 要随机排序,使用"?",如下所示:

article = models.Article.objects.order_by('?')

注:order_by('?')可能耗费资源且很慢,这取决于使用的数据库。

若要按照另外一个模型中的字段排序,可以使用查询关联模型的语法。即通过字段的名称后面跟两个下划线(__),再加上新模型中的字段的名称,直到希望连接的模型。

article = models.Article.objects.order_by('category__name', 'title')

如果排序的字段与另外一个模型关联,Django将使用关联的模型的默认排序,或者如果没有指定Meta.ordering将通过关联的模型的主键排序。 例如,因为Blog模型没有指定默认的排序:

article = models.Article.objects.order_by('category')
#等于:
article = models.Article.objects.order_by('category__id')

如果Blog设置了ordering = ['name'],那么第一个QuerySet将等同于:

article = models.Article.objects.order_by('category__name')

还可以通过调用表达式的desc()或者asc()方法:

article = models.Article.objects.order_by(Coalesce('summary', 'title').desc())

8、对查询结果反向排序 reverse

反向排序QuerySet中返回的元素。 第二次调用reverse()将恢复到原有的排序。
如要获取QuerySet中最后三个元素,可以这样做:

my_queryset.reverse()[:3]

这与Python直接使用负索引有点不一样。 Django不支持负索引,只能曲线救国。

9、从返回结果中剔除重复纪录 distinct

distinct(*fields)
去除查询结果中重复的行。
默认情况下,QuerySet不会去除重复的行。当查询跨越多张表的数据时,QuerySet可能得到重复的结果,这时候可以使用distinct()进行去重。

10、返回数据库中匹配查询(QuerySet)的对象数量  count

返回一个整数,该整数表示数据库中与QuerySet匹配的对象的数量。

例子:

#返回数据库中的条目总数
article = models.Article.objects.count()
#返回标题中包含“增加”的条目数
article_filter = models.Article.objects.filter(title__contains='增加').count()

count()调用在幕后执行SELECT count(*),因此您应该始终使用count(),而不是将所有记录加载到Python对象中,然后对结果调用len()(除非无论如何都需要将对象加载到内存中,在这种情况下,len()会更快)。
注意,如果您想要查询一个QuerySet中的项目数量,并且正在从它检索模型实例(例如,通过遍历它),那么使用len(QuerySet)可能会更高效,因为它不会像count()那样导致额外的数据库查询。

11、返回由queryset匹配的第一个对象 first()

返回由queryset匹配的第一个对象,如果没有匹配的对象,则返回None。如果QuerySet没有定义任何排序,那么QuerySet将由主键自动排序。

 

例子:

p = models.Article.objects.order_by('title', 'created_time').first()
#文章的'上一页'就是通过这个实现的
previous_blog = Article.objects.filter(created_time__gt=article_obj.created_time).first()

注意,first()是一种简洁的写法,下面的代码示例与上面的示例等价:

try:
    p = models.Article.objects.order_by('title', 'created_time')[0]
except IndexError:
    p = None

12、返回最后一条记录last()

与first()类似,它是返回queryset中的最后一个对象。

#文章下一页
netx_blog = Article.objects.filter(created_time__lt=article_obj.created_time).last()

13、返回最后一条记录exists()

如果QuerySet包含数据,就返回True,否则返回False   ---只判断是否有记录

 

如果 QuerySet 包含任何结果,则返回 True,否则返回 False。这尽可能以最简单和最快的方式执行查询,但它确实执行与普通 QuerySet 查询几乎相同的查询。

exists() 对于与 QuerySet 中的对象成员资格以及 QuerySet 中的任何对象(特别是大型 QuerySet 的上下文)中存在的相关搜索都很有用。

查找具有唯一字段(例如 primary_key)的模型是否为 QuerySet 的成员的最有效方法是:

entry = Entry.objects.get(pk=123) if some_queryset.filter(pk=entry.pk).exists():     print("Entry contained in queryset")

这将比以下要求更快,需要对整个查询集进行评估和迭代:

if entry in some_queryset:    print("Entry contained in QuerySet")

查找查询集是否包含任何项目:

if some_queryset.exists():     print("There is at least one object in some_queryset")

这将比以下更快:

if some_queryset:     print("There is at least one object in some_queryset")

...但不是很大程度上(因此需要大量查询来提高效率)。

14、使用提供的聚合表达式查询对象annotate()

函数原型annotate(*args, **kwargs),返回QuerySet。

表达式可以是简单的值、对模型(或任何关联模型)上的字段的引用或者聚合表达式(平均值、总和等)。

annotate()的每个参数都是一个annotation,它将添加到返回的QuerySet每个对象中。

关键字参数指定的Annotation将使用关键字作为Annotation 的别名。 匿名参数的别名将基于聚合函数的名称和模型的字段生成。 只有引用单个字段的聚合表达式才可以使用匿名参数。 其它所有形式都必须用关键字参数。

例如,如果正在操作一个Blog列表,你可能想知道每个Blog有多少Entry:

>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))# The name of the first blog
>>> q[0].name'Blogasaurus'# The number of entries on the first blog
>>> q[0].entry__count
42

Blog模型本身没有定义entry__count属性,但是通过使用一个关键字参数来指定聚合函数,可以控制Annotation的名称:

>>> q = Blog.objects.annotate(number_of_entries=Count('entry'))
# The number of entries on the first blog, using the name provided
>>> q[0].number_of_entries
35

15、根据日期获取查询集dates()

dates(field, kind, order='ASC')

返回一个QuerySet,表示QuerySet内容中特定类型的所有可用日期的datetime.date对象列表。

field参数是模型的DateField的名称。 kind参数应为"year","month"或"day"。 结果列表中的每个datetime.date对象被截取为给定的类型。

"year" 返回对应该field的所有不同年份值的列表。

"month"返回字段的所有不同年/月值的列表。

"day"返回字段的所有不同年/月/日值的列表。

order参数默认为'ASC',或者'DESC'。 它指定如何排序结果。

例子:

>>> Entry.objects.dates('pub_date', 'year')
[datetime.date(2005, 1, 1)]
>>> Entry.objects.dates('pub_date', 'month')
[datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)]
>>> Entry.objects.dates('pub_date', 'day')
[datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)]
>>> Entry.objects.dates('pub_date', 'day', order='DESC')
[datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)]
>>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[datetime.date(2005, 3, 20)]

16、根据时间获取查询集datetimes()

datetimes(field_name, kind, order='ASC', tzinfo=None)

返回QuerySet,为datetime.datetime对象的列表,表示QuerySet内容中特定种类的所有可用日期。

field_name应为模型的DateTimeField的名称。

kind参数应为"hour","minute","month","year","second"或"day"。

结果列表中的每个datetime.datetime对象被截取到给定的类型。

order参数默认为'ASC',或者'DESC'。 它指定如何排序结果。

tzinfo参数定义在截取之前将数据时间转换到的时区。

17、 创建空的查询集none()

调用none()将创建一个不返回任何对象的查询集,并且在访问结果时不会执行任何查询。

例子:

>>> Entry.objects.none()<QuerySet []
>>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)True

18、并集union()

union(*other_qs, all=False)

Django中的新功能1.11。也就是集合中并集的概念!

使用SQL的UNION运算符组合两个或更多个QuerySet的结果。例如:

>>> qs1.union(qs2, qs3)

默认情况下,UNION操作符仅选择不同的值。 要允许重复值,请使用all=True参数。

19、交集intersection()

intersection(*other_qs)

Django中的新功能1.11。也就是集合中交集的概念!

使用SQL的INTERSECT运算符返回两个或更多个QuerySet的共有元素。例如:

>>> qs1.intersection(qs2, qs3)

21、差集difference()

difference(*other_qs)

Django中的新功能1.11。也就是集合中差集的概念!

使用SQL的EXCEPT运算符只保留QuerySet中的元素,但不保留其他QuerySet中的元素。例如:

>>> qs1.difference(qs2, qs3)

22、附带查询关联对象select_related()

select_related(*fields)

沿着外键关系查询关联的对象的数据。这会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要再次数据库查询。

下面的例子解释了普通查询和select_related()查询的区别。 下面是一个标准的查询:

# 访问数据库。
e = Entry.objects.get(id=5)
# 再次访问数据库以得到关联的Blog对象。
b = e.blog

下面是一个select_related查询:

# 访问数据库。
e = Entry.objects.select_related('blog').get(id=5)
# 不会访问数据库,因为e.blog已经在前面的查询中获得了。
b = e.blog

select_related()可用于objects任何的查询集:

from django.utils import timezone

# Find all the blogs with entries scheduled to be published in the future.
blogs = set()

for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
    # 没有select_related(),下面的语句将为每次循环迭代生成一个数据库查询,以获得每个entry关联的blog。
    blogs.add(e.blog)

filter()select_related()的顺序不重要。 下面的查询集是等同的:

Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())

可以沿着外键查询。 如果有以下模型:

from django.db import modelsclass City(models.Model):
    # ...
    passclass Person(models.Model):
    # ...
    hometown = models.ForeignKey(
        City,
        on_delete=models.SET_NULL,
        blank=True,
        null=True,
    )class Book(models.Model):
    # ...
    author = models.ForeignKey(Person, on_delete=models.CASCADE)

调用Book.objects.select_related('author__hometown').get(id=4)将缓存相关的Person 和相关的City:

b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author         # Doesn't hit the database.
c = p.hometown       # Doesn't hit the database.
b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author         # Hits the database.
c = p.hometown       # Hits the database.

在传递给select_related()的字段中,可以使用任何ForeignKey和OneToOneField。

在传递给select_related的字段中,还可以反向引用OneToOneField。也就是说,可以回溯到定义OneToOneField 的字段。 此时,可以使用关联对象字段的related_name,而不要指定字段的名称。

23、预先查询prefetch_related()

prefetch_related(*lookups)

在单个批处理中自动检索每个指定查找的相关对象。

select_related类似,但是策略是完全不同的。

假设有这些模型:

from django.db import modelsclass Topping(models.Model):
    name = models.CharField(max_length=30)class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

    def __str__(self):              # __unicode__ on Python 2
        return "%s (%s)" % (
            self.name,
            ", ".join(topping.name for topping in self.toppings.all()),
        )

并运行:

>>> Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...

问题是每次QuerySet要求Pizza.objects.all()查询数据库,因此self.toppings.all()将在Pizza Pizza.__str__()中的每个项目的Toppings表上运行查询。

可以使用prefetch_related减少为只有两个查询:

>>> Pizza.objects.all().prefetch_related('toppings')

这意味着现在每次self.toppings.all()被调用,不会再去数据库查找,而是在一个预取的QuerySet缓存中查找。

还可以使用正常连接语法来执行相关字段的相关字段。 假设在上面的例子中增加一个额外的模型:

class Restaurant(models.Model):
    pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
    best_pizza = models.ForeignKey(Pizza, related_name='championed_by')

以下是合法的:

>>> Restaurant.objects.prefetch_related('pizzas__toppings')

这将预取所有属于餐厅的比萨饼,和所有属于那些比萨饼的配料。 这将导致总共3个查询 - 一个用于餐馆,一个用于比萨饼,一个用于配料。

>>> Restaurant.objects.prefetch_related('best_pizza__toppings')

这将获取最好的比萨饼和每个餐厅最好的披萨的所有配料。 这将在3个表中查询 - 一个为餐厅,一个为“最佳比萨饼”,一个为一个为配料。

当然,也可以使用best_pizza来获取select_related关系,以将查询数减少为2:

>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')

24、附加SQL查询extra()

extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)

有些情况下,Django的查询语法难以简单的表达复杂的WHERE子句,对于这种情况,可以在extra()生成的SQL从句中注入新子句。使用这种方法作为最后的手段,这是一个旧的API,在将来的某个时候可能被弃用。仅当无法使用其他查询方法表达查询时才使用它。

例如:

>>> qs.extra(
...     select={'val': "select col from sometable where othercol = %s"},
...     select_params=(someparam,),
... )

相当于:

>>> qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))

25、不加载指定字段defer()

defer(*fields)

在一些复杂的数据建模情况下,模型可能包含大量字段,其中一些可能包含大尺寸数据(例如文本字段),将它们转换为Python对象需要花费很大的代价。

当最初获取数据时不知道是否需要这些特定字段的情况下,如果正在使用查询集的结果,可以告诉Django不要从数据库中检索它们。

通过传递字段名称到defer()实现不加载:

Entry.objects.defer("headline", "body")

具有延迟加载字段的查询集仍将返回模型实例。

每个延迟字段将在你访问该字段时从数据库中检索(每次只检索一个,而不是一次检索所有的延迟字段)。

可以多次调用defer()。 每个调用都向延迟集添加新字段:

# 延迟body和headline两个字段。
Entry.objects.defer("body").filter(rating=5).defer("headline")

字段添加到延迟集的顺序无关紧要。对已经延迟的字段名称再次defer()没有问题(该字段仍将被延迟)。

可以使用标准的双下划线符号来分隔关联的字段,从而加载关联模型中的字段:

Blog.objects.select_related().defer("entry__headline", "entry__body")

如果要清除延迟字段集,将None作为参数传递到defer():

# 立即加载所有的字段。
my_queryset.defer(None)

defer()方法(及其兄弟,only())仅适用于高级用例,它们提供了数据加载的优化方法。

26、只加载指定的字段only()

only(*fields)

only()方法与defer()相反。

如果有一个模型几乎所有的字段需要延迟,使用only()指定补充的字段集可以使代码更简单。

假设有一个包含字段biography、age和name的模型。 以下两个查询集是相同的,就延迟字段而言:

Person.objects.defer("age", "biography")
Person.objects.only("name")

每当你调用only()时,它将替换立即加载的字段集。因此,对only()的连续调用的结果是只有最后一次调用的字段被考虑:

# This will defer all fields except the headline.
Entry.objects.only("body", "rating").only("headline")

由于defer()以递增方式动作(向延迟列表中添加字段),因此你可以结合only()和defer()调用:

# Final result is that everything except "headline" is deferred.
Entry.objects.only("headline", "body").defer("body")
# Final result loads headline and body immediately (only() replaces any
# existing set of fields).
Entry.objects.defer("body").only("headline", "body")

当对具有延迟字段的实例调用save()时,仅保存加载的字段。

27、选择数据库using()

using(alias)

如果正在使用多个数据库,这个方法用于指定在哪个数据库上查询QuerySet。方法的唯一参数是数据库的别名,定义在DATABASES。

例如:

# queries the database with the 'default' alias.
>>> Entry.objects.all()
# queries the database with the 'backup' alias
>>> Entry.objects.using('backup')

28、锁住选择的对象,直到事务结束。select_for_update() 
elect_for_update(nowait=False, skip_locked=False)

返回一个锁住行直到事务结束的查询集,如果数据库支持,它将生成一个SELECT ... FOR UPDATE语句。

例如:

entries = Entry.objects.select_for_update().filter(author=request.user)

所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。

一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。使用select_for_update(nowait=True)将使查询不阻塞。如果其它事务持有冲突的锁,那么查询将引发DatabaseError异常。也可以使用select_for_update(skip_locked=True)忽略锁定的行。nowait和skip_locked是互斥的。

目前,postgresql,oracle和mysql数据库后端支持select_for_update()。但是,MySQL不支持nowait和skip_locked参数。

29、接收一个原始的SQL查询raw()

raw(raw_query, params=None, translations=None)

接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。

这个RawQuerySet实例可以迭代,就像普通的QuerySet一样。

ORM QuerySet查询

一旦创建了数据模型,Django 就会自动为您提供一个数据库抽象 API,使您可以创建,检索,更新和删除对象。本文档介绍了如何使用此 API。

在本指南(和参考文献)中,我们将参考以下模型,它们构成了一个 Weblog 应用程序:

 from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    tagline = models.TextField()

    def __str__(self):
        return self.name

class Author(models.Model):
    name = models.CharField(max_length=200)
    email = models.EmailField()

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    body_text = models.TextField()
    pub_date = models.DateField()
    mod_date = models.DateField()
    authors = models.ManyToManyField(Author)
    n_comments = models.IntegerField()
    n_pingbacks = models.IntegerField()
    rating = models.IntegerField()

    def __str__(self):
        return self.headline

创建对象

为了在 Python 对象中表示数据库表数据,Django 使用直观的系统:模型类表示数据库表,该类的实例表示数据库表中的特定记录。

要创建一个对象,请使用模型类的关键字参数对其进行实例化,然后调用 save() 以将其保存到数据库。

假设模型存在于文件 mysite/blog/models.py 中,下面是一个例子:

>>> from blog.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.')
>>> b.save()

幕后执行 SQL 语句 INSERT 。 在你没有调用 save() 方法之前, Django 不会将数据保存到数据库

save() 方法没有返回值。

保存对对象的更改

要保存对已存在于数据库中的对象的更改,请使用 save()

给定一个已经保存到数据库的 Blog 实例 b5,这个例子改变它的名字并更新数据库中的记录:

>>> b5.name = 'New name'
>>> b5.save()

幕后执行 SQL 语句 UPDATE 。 在你没有调用 save() 方法之前, Django 不会将数据保存到数据库

保存 ForeignKey 和 ManyToManyField 字段

更新 ForeignKey 字段的方式与保存普通字段的方式完全相同 -- 只需将正确类型的对象分配给相关字段即可。

>>> from blog.models import Blog, Entry 
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk") 
>>> entry.blog = cheese_blog 
>>> entry.save()

还可以通过 add() 方法:

>>> from blog.models import Author 
>>> joe = Author.objects.create(name="Joe") 
>>> entry.authors.add(joe)

要一次添加多个记录:

>>> john = Author.objects.create(name="John") 
>>> paul = Author.objects.create(name="Paul") 
>>> george = Author.objects.create(name="George") 
>>> ringo = Author.objects.create(name="Ringo") 
>>> entry.authors.add(john, paul, george, ringo)

检索对象

要从数据库中检索对象,请在您的模型类上通过 Manager 构建一个 QuerySet

QuerySet 表示数据库中的对象集合。它可以有零个,一个或多个过滤器。过滤器根据给定的参数缩小查询结果的范围。在 SQL 术语中,QuerySet 等同于 SELECT 语句,过滤器是限制性子句,如 WHERE 或 LIMIT。

您可以使用模型的 Manager 获取 QuerySet。每个模型至少有一个 Manager,默认情况下是 objects。通过模型类直接访问它,如下所示:

>>> Blog.objects <django.db.models.manager.Manager object at ...> 
>>> b = Blog(name='Foo', tagline='Bar') 
>>> b.objects Traceback:     
... AttributeError: "Manager isn't accessible via Blog instances."

Managers 只能通过模型类访问,而不能从模型实例访问。

Manager 是模型的 QuerySet 的主要来源。例如,Blog.objects.all() 返回包含数据库中所有 Blog 对象的 QuerySet

检索所有对象

从表中检索对象的最简单方法是获取所有对象。为此,请使用 Manager 上的 all() 方法:

>>> all_entries = Entry.objects.all()

all() 方法返回数据库中所有对象的 QuerySet

使用过滤器检索特定的对象

filter(**kwargs)

返回包含匹配给定查找参数的对象的新 QuerySet

exclude(**kwargs)

返回包含与给定查找参数不匹配的对象的新 QuerySet

例如,要获取从 2006 年开始的博客条目的 QuerySet,请使用 filter(),如下所示:

Entry.objects.filter(pub_date__year=2006)

使用默认 manager 类,它与以下内容相同:

Entry.objects.all().filter(pub_date__year=2006)

链式过滤器

改进 QuerySet 的结果本身就是一个 QuerySet,因此可以将改进链接在一起。例如:

>>> Entry.objects.filter( ...     headline__startswith='What' ... ).exclude( ...     pub_date__gte=datetime.date.today() ... ).filter( ...     pub_date__gte=datetime.date(2005, 1, 30) ... )

过滤后的 QuerySet 是独一无二的

每次您完善 QuerySet 时,都会获得全新的 QuerySet,它绝不会绑定到以前的 QuerySet。每个优化都会创建一个独立且不同的 QuerySet ,可以存储,使用和重用。

举例:

>>> q1 = Entry.objects.filter(headline__startswith="What") 
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today()) 
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())

QuerySet 会延迟查询(lazy)

QuerySets 是懒惰的 -- 创建 QuerySet 的行为不涉及任何数据库活动。您可以将过滤器堆叠在一起,并且在使用 QuerySet 之前,Django 不会真正运行查询。看看这个例子:

>>> q = Entry.objects.filter(headline__startswith="What") 
>>> q = q.filter(pub_date__lte=datetime.date.today()) 
>>> q = q.exclude(body_text__icontains="food") 
>>> print(q)

看起来像是操作了三次数据库,实际上只有在执行 print(p) 时才会真正执行一次数据库查询操作。

用 get() 检索单个对象

filter() 总是返回一个 QuerySet,即使匹配的对象只有一个。

如果你知道只有一个对象和查询匹配,则可以直接使用 get() 方法返回单个对象:

>>> one_entry = Entry.objects.get(pk=1)

请注意,get() 方法如果没有查找到对象,将引发 DoesNotExist 异常。查询到多个对象则会引发 MultipleObjectReturned 异常,这两个异常都是模型类的一个属性。

截取 QuerySet

可以使用 Python 中的切片语法对 QuerySet 的数量进行限制。这相当于 SQL LIMIT 和 OFFSET 子句。

例如,返回前 5 个对象(LIMIT 5)。

>>> Entry.objects.all()[:5]

返回第六到第十个对象(OFFSET 5 LIMIT 5)。

>>> Entry.objects.all()[5:10]

不支持负数索引(例如 Entry.objects.all()[-1])。

通常切片一个 QuerySet 会返回一个新的 QuerySet,但这不会真的执行数据库操作。但是如果使用了 step 切片语法 例如下面这样,则是会执行数据库操作的:

>>> Entry.objects.all()[:10:2]

由于切片的工作原理是模糊的,所以禁止对切片后的 queryset 进行进一步的过滤或排序。

要检索单个对象而不是列表时,请使用下标索引而不是切片,例如:

>>> Entry.objects.order_by('headline')[0]

这大致相当于:

>>> Entry.objects.order_by('headline')[0:1].get()

请注意,如果没有检索到对应的对象,前者会引发 indexError,后者会引发 DoesNotExist

字段查找

字段查找是指你如何指定 SQL WHERE 子句。它们被指定为 QuerySet 方法 filter()exclude() 和 get() 的关键字参数。

基本查找关键字参数大概像 field__lookuptype=value(中间是一个双下划线)。例如:

>>> Entry.objects.filter(pub_date__lte='2006-01-01')

将(大致)转换为以下 SQL:

SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';

在查找中指定的字段必须是模型字段的名称。但是有一个例外,在使用 ForeignKey 的情况下,你可以指定带 _id 后缀的字段名称。在这种情况下, value 参数应该包含外部模型主键的原始值。例如:

>>> Entry.objects.filter(blog_id=4)

如果您传递了无效的关键字参数,则会引发 TypeError

一些常见的查询术语:

exact

精确匹配,例如:

>>> Entry.objects.get(headline__exact="Cat bites dog")

将(大致)转换为以下 SQL:

SELECT ... WHERE headline = 'Cat bites dog';

如果您不提供查找术语 -- 也就是说,如果您的关键字参数不包含双下划线 -- 则查找类型被假定为 exact

例如,以下两条语句是等价的:

>>> Blog.objects.get(id__exact=14)  # Explicit form >>> Blog.objects.get(id=14)         # __exact is implied

这是为了方便,因为 exact 查找是最常见的情况。

iexact

不区分大小写的匹配。所以,查询:

>>> Blog.objects.get(name__iexact="beatles blog")

将匹配 name 为 "Beatles Blog" ,"beatles blog" 甚至是 "BeAtlES blOG" 的 Blog 对象。

contains

Entry.objects.get(headline__contains='Lennon')

将(大致)转换为以下 SQL:

SELECT ... WHERE headline LIKE '%Lennon%';

请注意,这将匹配 'Today Lennon honored' 而不会匹配 'today lennon honored'

还有一个不区分大小写的版本 icontains

startswithendswith

分别匹配开始和结尾部分。也有不区分大小写的版本 istartswith 和 iendswith

跨越关系查找

Django 提供了一种强大且直观的方式来在查找中 “追踪” 关系,在幕后自动为您处理 SQL JOIN。要跨越关系,只需使用模型中相关字段的字段名称(用双下划线分隔),直到您到达所需的字段。

本示例使用 name 为 'Beatles Blog' 的 Blog 检索所有 Entry 对象:

>>> Entry.objects.filter(blog__name='Beatles Blog')

这种跨越可以尽可能的深入。

它也可以倒退。要引用“反向”关系,只需使用模型的小写名称即可。

此示例检索所有至少有一个 headline 包含 'Lennon' 的 Entry 的 Blog 对象:

>>> Blog.objects.filter(entry__headline__contains='Lennon')

如果您要跨多个关系进行筛选,并且其中一个中间模型的值不符合筛选条件,Django 会把它看作是空的(所有的值都是 NULL),但是它是有效的,在那里有对象。所有这一切意味着不会出现任何错误。例如,在这个过滤器中:

Blog.objects.filter(entry__authors__name='Lennon')

(如果有相关的 Author 模型),如果没有 Author 与某个条目相关联,则会将其视为没有附加 name,而不是由于缺少 Author 而引发错误。通常这正是你希望的结果。唯一可能引起混淆的情况是如果你使用 isnull。从而:

Blog.objects.filter(entry__authors__name__isnull=True)

将返回 Author 上具有空 name 的 Blog 对象以及 entry 上具有空 Author 的 Blog 对象。如果你不想要后者,你可以这样写:

Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)

跨越多值关系

当您基于 ManyToManyField 或反向 ForeignKey 过滤对象时,您可能会感兴趣的是两种不同类型的过滤器。考虑 Blog/Entry 关系(Blog to Entry 是一对多的关系)。我们可能会感兴趣的是找到一个有 entry 的 blogheadline 中有 “Lennon”,并于 2008 年发表。或者,我们可能想要找到在 headline 中有 “Lennon” entry 的 blog,以及 2008 年发表的一篇文章。由于有多个 entry 与单个 Blog 相关联,所以这些查询都是可能的,并且在某些情况下是有意义的。

同样的情况也出现在 ManyToManyField 上。例如,如果一个 Entry 有一个 ManyToManyField 的 tags,我们可能想要找到链接到名为 “music” 和 “bands” 的 tag 的 entry,或者我们可能需要包含 name 为 “music” 和 状态为 “public” 的 tag 的 entry

为了处理这两种情况,Django 拥有一致的处理 filter() 调用的方法。同时应用单个 filter() 调用中的所有内容以筛选出满足所有这些要求的项目。连续的 filter() 调用进一步限制了对象集合,但对于多值关系,它们适用于链接到主模型的任何对象,而不一定是由较早的 filter() 调用选择的那些对象。

这听起来有点令人困惑,所以希望有一个例子可以说明。要选择包含 headline 为 “Lennon” 并且在 2008 年发布的 entry(满足这两个条件的同一 entry)的所有 blog,我们会写:

Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008)

要选择 headline 中包含 “Lennon” entry 的所有 blog 以及 2008 年发表的 entry,我们将编写:

Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008)

假设只有一个 blog 有包含 “Lennon” 的 entry 和 2008 年的 entry ,但是 2008 年的 entry 中没有一个包含 “Lennon”。第一个查询不会返回任何 blog,但第二个查询将返回该 blog

在第二个示例中,第一个过滤器将查询集限制为链接到 headline 中带有 “Lennon”  entry 的所有 blog。第二个过滤器将这组 blog 进一步限制为那些也链接到 2008 年发布的 entry 的 blog。第二个过滤器选择的 entry 可能与第一个过滤器中的 entry 相同或不同。我们使用每个过滤器语句过滤 Blog item,而不是 Entry item。

如上所述,跨越多值关系的查询的 filter() 行为对于 exclude() 不等效。相反,单个 exclude() 调用中的条件不一定引用相同的 item。

例如,以下查询将排除同时包含 2008 年发布的 entry 和 headline 中包含 “Lennon” entry 的 blog

Blog.objects.exclude(
    entry__headline__contains='Lennon',
    entry__pub_date__year=2008,
)

但是,与使用 filter() 时的行为不同,这不会限制基于满足这两个条件的 entry 的 blog。为了做到这一点,例如,选择所有不包含 2008 年出版的 “Lennon” entry 的 blog,你需要做两个查询:

Blog.objects.exclude(
    entry__in=Entry.objects.filter(
        headline__contains='Lennon',
        pub_date__year=2008,
    ),
)

过滤器可以引用模型上的字段

在迄今为止给出的例子中,我们构建了一个过滤器,它将模型字段的值与常量进行比较。但是如果您想要将模型字段的值与同一模型中的另一个字段进行比较呢?

Django提供了 F 表达式 来允许这样的比较。F() 的实例充当对查询中的模型字段的引用。然后可以在查询过滤器中使用这些引用来比较同一模型实例上两个不同字段的值。

例如,要查找比 pingbacks 有更多注释的所有 blog entry 的列表,我们构造一个 F() 对象来引用 pingback 计数,并在查询中使用该 F() 对象:

>>> from django.db.models import F 
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))

Django 支持对 F() 对象使用常量和其他 F() 对象的加法,减法,乘法,除法,模和幂运算。为了找到所有比 pingbacks 多两倍的评论,我们修改查询:

>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)

要查找 entry 评级小于 pingback 计数和评论计数总和的所有条目,我们将发出查询:

>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

您还可以使用双下划线表示法来跨越 F() 对象中的关系。具有双下划线的 F() 对象将引入访问相关对象所需的任何连接。例如,要检索作者姓名与博客名称相同的所有条目,我们可以发出查询:

>>> Entry.objects.filter(authors__name=F('blog__name'))

对于 date 和 date/time 字段,可以添加或减去 timedelta 对象。以下内容将返回发布后超过 3 天修改的所有 entry

>>> from datetime import timedelta 
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))

F() 对象支持 .bitand().bitor().bitrightshift() 和 .bitleftshift() 的按位运算。例如:

>>> F('somefield').bitand(16)

通过 pk 查找

为了方便起见,Django 提供了一个 pk 查找快捷方式,代表 "primary key"。

在示例 Blog 模型中,primary key 是 id 字段,所以这三个语句是等同的:

>>> Blog.objects.get(id__exact=14) # Explicit form 
>>> Blog.objects.get(id=14) # __exact is implied 
>>> Blog.objects.get(pk=14) # pk implies id__exact

pk 的使用不限于 __exact 查询 -- 任何查询术语都可以与 pk 结合来对模型的主键执行查询:

# Get blogs entries with id 1, 4 and 7 
>>> Blog.objects.filter(pk__in=[1,4,7]) # Get all blog entries with id > 14 
>>> Blog.objects.filter(pk__gt=14)

pk 查找也可以在连接中使用。例如,这三个语句是等同的:

>>> Entry.objects.filter(blog__id__exact=3) # Explicit form 
>>> Entry.objects.filter(blog__id=3)        # __exact is implied 
>>> Entry.objects.filter(blog__pk=3)        # __pk implies __id__exact

在 LIKE 语句中转义百分号和下划线

等同于  LIKE SQL 语句(iexact,contains,icontains,startswith,istartswith,endswith 和  iendswith)的字段查找将自动转义为 LIKE 语句中使用的两个特殊字符 -- 百分号和下划线。(在 LIKE  语句中,百分号表示多字符通配符,下划线表示单字符通配符。)

这意味我们可以专注于业务逻辑,而不用考虑 SQL 语法。例如,要检索包含百分号的所有条目,只需要直接使用百分号:

>>> Entry.objects.filter(headline__contains='%')

Django 生成的 SQL 将如下所示:

SELECT ... WHERE headline LIKE '%\%%';

下划线也是如此。

缓存和 QuerySet

每个 QuerySet 都包含一个缓存以最大限度地减少数据库访问。了解它的工作原理将使您能够编写最高效的代码。

在新创建的 QuerySet 中,缓存为空。当 QuerySet 第一次被使用 -- 并因此发生数据库查询 -- Django 将查询结果保存在 QuerySet 的缓存中,并返回已明确请求的结果(例如,如果 QuerySet 正在迭代,则返回下一个元素)。随后对 QuerySet 的操作重新使用缓存的结果。

如果没有正确使用 QuerySet ,它可能会消耗里的资源。比如以下方式将创建两个 QuerySet ,对其进行操作,之后丢弃它们:

>>> print([e.headline for e in Entry.objects.all()]) 
>>> print([e.pub_date for e in Entry.objects.all()])

这意味着相同的数据库查询将执行两次,显著地加倍了数据库负载。另外,这两个列表可能不包含相同的数据库记录,因为在两个请求之间可能已经添加或删除了一个 Entry

为了避免这个问题,只需保存 QuerySet 并重用它:

>>> queryset = Entry.objects.all() 
>>> print([p.headline for p in queryset]) # Evaluate the query set. 
>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

当 QuerySet 没有被缓存时

查询集并不总是缓存它们的结果。在仅操作部分查询集时,会检查缓存,但如果未填充,则后续查询返回的项不会被缓存。具体而言,这意味着使用数组切片或索引截取查询集不会填充缓存。

例如,重复获取 queryset 对象中的某个索引将每次查询数据库:

>>> queryset = Entry.objects.all() 
>>> print(queryset[5]) # Queries the database 
>>> print(queryset[5]) # Queries the database again

但是,如果整个查询集已经被执行(读取使用过),记录将被缓存:

>>> queryset = Entry.objects.all() 
>>> [entry for entry in queryset] # Queries the database 
>>> print(queryset[5]) # Uses cache 
>>> print(queryset[5]) # Uses cache

以下是将导致整个查询集被使用并因此填充缓存的其他操作的一些示例:

>>> [entry for entry in queryset] 
>>> bool(queryset) 
>>> entry in queryset 
>>> list(queryset)

简单地打印查询集不会填充缓存。这是因为调用 __repr__() 仅返回整个查询集的一部分。

使用 Q 对象进行复杂查询

在 filter() 中使用多个关键字参数查询,对应到数据库中是使用 AND 连接起来的。如果你想使用更复杂的查询(例如,使用 OR 语句的查询),可以使用 Q 对象。

Q 对象(django.db.models.Q)是一个用于封装关键字参数集合的对象。这些关键字参数在上面的 “字段查找” 中指定。

例如,这个 Q 对象封装了一个 LIKE 查询:

from django.db.models import Q Q(question__startswith='What')

Q 对象可以使用  和 | 进行组合运算。当一个操作符用于两个 Q 对象时,它会生成一个新的 Q 对象。

例如,这个语句产生一个 Q 对象,它表示两个 "question__startswith" 查询的 "OR"

Q(question__startswith='Who') | Q(question__startswith='What')

这相当于以下 SQL WHERE 子句:

WHERE question LIKE 'Who%' OR question LIKE 'What%'

通过将 Q 对象与  和 | 相结合,您可以编写任意复杂的语句运算符并使用括号分组。此外,可以使用  运算符来否定 Q 对象,从而允许将普通查询和否定(NOT)查询组合在一起的组合查找:

Q(question__startswith='Who') | ~Q(pub_date__year=2005)

每个使用关键字参数的查找函数(例如 filter()exclude()get())也可以传递一个或多个 Q 对象作为位置(未命名)参数。如果您为查找函数提供多个 Q 对象参数,则参数将一起 “AND”。例如:

SELECT * from polls WHERE question LIKE 'Who%'
    AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')

底层的 SQL 大概是这样:

Poll.objects.get(
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
    question__startswith='Who',
)

查找函数可以混合使用 Q 对象和关键字参数。提供给查找函数的所有参数(不管它们是关键字参数还是 Q 对象)都是 “AND” 一起编辑的。如果提供了一个 Q 对象,它必须在任何关键字参数的定义之前。例如:

Poll.objects.get(  Q(pub_date=date(2005, 5, 2))|Q(pub_date=date(2005, 5, 6)), 
question__startswith='Who', )

...将是一个有效的查询,相当于前面的例子;但:

# INVALID QUERY
Poll.objects.get(
    question__startswith='Who',
    Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)

...将无效。

Django 单元测试中的 OR 查找示例显示了 Q 的一些可能的用法。

比较对象

要比较两个模型实例,只需使用标准的 Python 比较运算符双等号: ==。在底层,Django 将比较了两个模型的 primary key。

使用上面的 Entry 例子,以下两个语句是等价的:

>>> some_entry == other_entry 
>>> some_entry.id == other_entry.id

如果模型的 primary key 不是 id,也没有问题。不管它叫什么,比较总是使用 primary key。例如,如果模型的 primary key 字段被称为 name,则这两个语句是等同的:

>>> some_obj == other_obj 
>>> some_obj.name == other_obj.name

删除对象

方便的删除方法被命名为 delete()。此方法立即删除对象并返回删除的对象数量和每个对象类型具有删除次数的字典。例:

>>> e.delete()
(1, {'weblog.Entry': 1})

您也可以批量删除对象。每个 QuerySet 都有一个 delete() 方法,该方法删除该 QuerySet 的所有成员。

例如,这将删除 pub_date 年份为 2005 年的所有 Entry 对象:

>>> Entry.objects.filter(pub_date__year=2005).delete()
(5, {'webapp.Entry': 5})

当 Django 删除一个对象时,默认情况下它会模拟 SQL 约束 ON DELETE CASCADE 的行为 -- 换句话说,任何有外键指向要删除的对象的对象都将被删除。例如:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

此级联行为可通过 ForeignKey 的 on_delete 参数进行自定义。

请注意,delete() 是唯一不在 Manager 本身公开的 QuerySet 方法。这是一种安全机制,可防止您意外地请求 Entry.objects.delete() 并删除所有条目。如果您确实要删除所有对象,则必须明确请求一个完整的查询集:

Entry.objects.all().delete()

复制模型实例

虽然没有用于复制模型实例的内置方法,但可以轻松创建复制了所有字段值的新实例。在最简单的情况下,你可以将 pk设置为 None。使用我们的博客示例:

blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1

blog.pk = None
blog.save() # blog.pk == 2

如果你使用继承,事情变得更加复杂。考虑一下 Blog 的一个子类:

class ThemeBlog(Blog):
    theme = models.CharField(max_length=200)

django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
django_blog.save() # django_blog.pk == 3

由于继承的工作原理,你必须将 pk 和 id 都设置为 None

django_blog.pk = None
django_blog.id = None
django_blog.save() # django_blog.pk == 4

此过程不会复制不属于模型数据库表的关系。Entry 有一个 ManyToManyField to Author。复制 entry 后,您必须设置新 entry 的多对多关系:

entry = Entry.objects.all()[0] # some previous entry
old_authors = entry.authors.all()
entry.pk = None
entry.save()
entry.authors.set(old_authors)

对于 OneToOneField,您必须复制相关对象并将其分配给新对象的字段,以避免违反一对一唯一约束。例如,假设 entry 已经被重复如上:

detail = EntryDetail.objects.all()[0]
detail.pk = None
detail.entry = entry
detail.save()

一次更新多个对象

有时你想为 QuerySet 中的所有对象设置一个字段为特定的值。你可以用 update() 方法做到这一点。例如:

# Update all the headlines with pub_date in 2007.
Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')

您只能使用此方法设置非关系字段和 ForeignKey 字段。要更新非关系字段,请将新值作为常量提供。要更新ForeignKey字段,请将新值设置为要指向的新模型实例。例如:

>>> b = Blog.objects.get(pk=1)

# Change every Entry so that it belongs to this Blog.
>>> Entry.objects.all().update(blog=b)

该 update() 方法立即应用并返回查询匹配的行数(如果某些行已具有新值,则该行数不计算在更新的行数内)。QuerySet 被更新的唯一限制是它只能访问一个数据库表:模型的主表。您可以根据相关字段进行过滤,但只能更新模型主表中的列。例:

>>> b = Blog.objects.get(pk=1) 
# 更新属于这个博客的所有标题。 
>>> Entry.objects.select_related().filter(blog=b).update(headline='Everything is the same')

请注意 update() 方法直接转换为 SQL 语句。这是直接更新的批量操作。它不会在模型上运行任何 save() 方法,或者发出 pre_save 或 post_save 信号(这是调用 save() 的结果),或兑现 auto_now 字段选项。如果要将每个 item 保存在 QuerySet 中并确保在每个实例上调用 save() 方法,则不需要任何特殊函数来处理该 item。只需循环它们并调用 save()

for item in my_queryset:
     item.save()

调用更新也可以使用 F 表达式根据模型中另一个字段的值更新一个字段。这对于根据计数器的当前值递增计数器特别有用。例如,要增加博客中每个条目的 pingback 计数:

>>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)

但是,与 filter 和 exclude 子句中的 F() 对象不同,在 update 中使用 F() 对象时不能引入连接 -- 您只能引用正在更新的模型的本地字段。如果您尝试使用 F() 对象引入联接,则会引发 FieldError

# This will raise a FieldError 
>>> Entry.objects.update(headline=F('blog__name'))

关系对象

当您在模型中定义关系(即 ForeignKeyOneToOneField 或 ManyToManyField )时,该模型的实例将具有访问相关对象的方便的 API。

本节中的所有示例均使用本页顶部定义的示例 BlogAuthor 和 Entry 模型。

一对多的关系

正向访问

如果模型具有 ForeignKey ,则该模型的实例将通过模型的简单属性访问相关(外部)对象。

举例:

>>> e = Entry.objects.get(id=2) 
>>> e.blog # Returns the related Blog object.

您可以通过外键属性操作外键对象。在调用 save() 之前,对外键的更改不会保存到数据库。例:

>>> e = Entry.objects.get(id=2) 
>>> e.blog = some_blog 
>>> e.save()

如果 ForeignKey 字段具有 null=True(即,它允许 NULL 值),则可以分配 None 以移除关系。例:

>>> e = Entry.objects.get(id=2) 
>>> e.blog = None 
>>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"

第一次访问相关对象时,缓存对一对多关系的正向访问。随后访问同一对象实例上的外键将被缓存。例:

>>> e = Entry.objects.get(id=2) 
>>> print(e.blog)  # Hits the database to retrieve the associated Blog. 
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

请注意,select_related() QuerySet 方法会提前预先填充所有一对多关系的缓存。例:

>>> e = Entry.objects.select_related().get(id=2) 
>>> print(e.blog)  # Doesn't hit the database; uses cached version. 
>>> print(e.blog)  # Doesn't hit the database; uses cached version.

反向访问

如果模型具有 ForeignKey,则外键模型的实例将有权访问 Manager,该 Manager 返回第一个模型的所有实例。默认情况下,此 Manager 名为 FOO_set,其中 FOO 是源模型名称,小写。该 Manager 返回 QuerySet,可以按照上述 “检索对象” 一节中的描述进行过滤和操作。

举例:

>>> b = Blog.objects.get(id=1)
>>> b.entry_set.all() # Returns all Entry objects related to Blog.

# b.entry_set is a Manager that returns QuerySets.
>>> b.entry_set.filter(headline__contains='Lennon')
>>> b.entry_set.count()

您可以通过在 ForeignKey 定义中设置 related_name 参数来覆盖 FOO_set 名称。例如,如果 Entry 模型被更改为 blog=ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'),上面的示例代码将如下所示:

>>> b = Blog.objects.get(id=1)
>>> b.entries.all() # Returns all Entry objects related to Blog.

# b.entries is a Manager that returns QuerySets.
>>> b.entries.filter(headline__contains='Lennon')
>>> b.entries.count()

使用自定义反向 manager

默认情况下,用于反向关系的 RelatedManager 是该模型的默认 manager 的子类。如果您想为给定的查询指定不同的 manager,可以使用以下语法:

from django.db import models

class Entry(models.Model):
    #...
    objects = models.Manager()  # Default Manager
    entries = EntryManager()    # Custom Manager

b = Blog.objects.get(id=1)
b.entry_set(manager='entries').all()

如果 EntryManager 在其 get_queryset() 方法中执行了默认过滤,则该过滤将应用于 all() 调用。

当然,指定一个自定义反向 manager 也可以让你调用它的自定义方法:

b.entry_set(manager='entries').is_published()

处理关系对象的其他方法

除了上面 “检索对象” 中定义的 QuerySet 方法之外,ForeignKey Manager 还具有用于处理关系对象集的附加方法。

add(obj1, obj2, ...)

将特定的模型对象加入关联对象集合。

create(**kwargs)

创建一个新对象,将其保存并放入关系对象集中。返回新创建的对象。

remove(obj1, obj2, ...)

从关系对象集中删除指定的模型对象。

clear()

从关系对象集中删除所有对象。

set(objs)

替换一组相关的对象。

要分配相关集的成员,请将 set() 方法与可迭代的对象实例一起使用。例如,如果 e1 和 e2 是 Entry 实例:

b = Blog.objects.get(id=1) b.entry_set.set([e1, e2])

如果 clear() 方法可用,则在 iterable(本例中为列表)中的所有对象都添加到 set 之前,将从 entry_set 中删除任何预先存在的对象。如果 clear() 方法不可用,则会添加迭代中的所有对象而不删除任何现有元素。

本节中描述的每个 “反向” 操作对数据库都有直接影响。每一次添加,创建和删除都会立即自动保存到数据库中。

多对多的关系

多对多关系的两端都可以自动访问另一端的 API。API 的作用类似于上面的 “反向” 一对多关系。

一个区别在于属性命名:定义 ManyToManyField 的模型使用该字段本身的属性名称,而 “反向” 模型使用原始模型的小写模型名称和 “_set”(就像反向一对多关系)。

一个例子使得这更容易理解:

e = Entry.objects.get(id=3)
e.authors.all() # Returns all Author objects for this Entry.
e.authors.count()
e.authors.filter(name__contains='John')

a = Author.objects.get(id=5)
a.entry_set.all() # Returns all Entry objects for this Author.

像 ForeignKey 一样,ManyToManyField 可以指定 related_name。在上面的例子中,如果 Entry 中的 ManyToManyField 指定了 related_name='entries',那么每个 Author 实例将具有 entries 属性而不是 entry_set

与一对多关系的另一个区别是,除了模型实例之外,多对多关系中的 add()set() 和 remove() 方法接受主键值。例如,如果 e1 和 e2 是 Entry 实例,那么这些 set() 调用的工作原理是相同的:

a = Author.objects.get(id=5)
a.entry_set.set([e1, e2])
a.entry_set.set([e1.pk, e2.pk])

一对一的关系

一对一关系与多对一关系非常相似。如果您在模型上定义了 OneToOneField,则该模型的实例将通过模型的简单属性访问相关对象。

class EntryDetail(models.Model):
    entry = models.OneToOneField(Entry, on_delete=models.CASCADE)
    details = models.TextField()

ed = EntryDetail.objects.get(id=2)
ed.entry # Returns the related Entry object.

差异出现在 “反向” 查询中。一对一关系中的相关模型也可以访问 Manager 对象,但该 Manager 表示一个对象,而不是一组对象:

e = Entry.objects.get(id=2)
e.entrydetail # returns the related EntryDetail object

如果没有对象被分配给这个关系,Django 将引发一个 DoesNotExist 异常。

实例可以按照与分配转发关系相同的方式分配给反向关系:

e.entrydetail = ed

 

  • 4
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值