Django自身默认使用sqlite3这个轻量级的数据库,但是当我们开发网站时,sqlite3就没有mysql好,sqlite3适合一些手机上开发使用的数据库。

准备的软件

1、mysql数据库,版本5.7及以上。
2、navicat,(可选的)。使用navicat主要是简化我们对数据库的操作。没有也一样。
3、安装mysql驱动程序。
常见的驱动程序:
==a. MySQL-python==:也就是MySQLdb,是对c语言操作数据库的一个 简单封装。遵循了Python DB API v2,但是只支持python2,目前还不支持python3.
==b. mysqlclient==:是MySQL-python的另外一个分支,支持python3并且修复了一些bug。(分支就是在原来的基础上做了一些修改,但本质上还是MySQL-python。最大的一个改动就是可以支持python3。)
==c. pymysql==:纯python实现的一个驱动,因为是纯python编写的,因此效率不如MySQL-python,并且也是因为是纯python编写的,因此可以和python无缝衔接。
==d. MySQL Connecter/Python==:MySQL官方推出的使用纯python连接MySQL的驱动。因为是纯python开发的,效率也不高,但能无缝衔接。
因为我使用的时python3,所以我使用的是mysqlclient。

安装驱动的方式:

      pip install mysqlclient

Django中连接MySQL数据库

在项目中的settings.py文件中,修改DATABASES这个字典中的数据:
原来的代码为

DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
     }
}

修改为

DATABASES = {
    'default': {
            #数据库引擎
            'ENGINE': 'django.db.backends.mysql',
            #数据库的名字
            'NAME': 'test',
            #连接数据库的用户名
            'USER': 'root',
            #连接数据库的密码(下载mysql时配置的密码)
            'PASSWORD': 'root',
            #数据库的主机地址
            'HOST':'127.0.0.1',
            #数据库的端口号,默认为3306,也可以不写这一行代码
            'PORT':'3306'
    }
}

这样就能连接到数据库了,就能对数据库进行操作了。

在Django中操作数据库

在Django中操作数据库的方式有两种,第一种就是使用原生sql语句操作,第二种就是使用ORM模型来操作。

原生sql语句操作数据库

首先使用navicat连接了mysql数据库,新建一个test的数据库。然后在test中新建一个book的表,有三个值id(主键,自动增长),nameauthor
如果没有navicat的就是用sql语句创建上述信息。

新建一个项目,配置好settings.py中的信息并且能连接上数据库,在settings.py的同级目录下新建一个views.py的文件,templates文件夹下新建一个index.html的文件。

views.py中写入:

from django.shortcuts import render
#导入`connection`
from django.db import connection

def index(request):
    cursor = connection.cursor()
    #要想使用sql原生语句,必须用到execute()函数
    #然后在里面写入sql原生语句
    cursor.execute("insert into book(id,name,author) values (null ,'三国演义','罗贯中')")
    return render(request,'index.html')

urls.py中写入

from . import views
from django.urls import path

urlpatterns = [
    path('', views.index),
]

输入网址,在网页中我们看不到任何信息,是因为我们没有编写html文件,但是已经执行了views.index中的代码。刷新数据库可以看到

在这里插入图片描述
信息已经插入进去了。
我们也可以查询数据库里面的数据,修改index函数:

def index(request):
    cursor = connection.cursor()
    #cursor.execute("insert into book(id,name,author) values (null ,'三国演义','罗贯中')")
    cursor.execute("select * from book")
    #使用一个变量来接收查询到的数据,
    #fetchall()返回查询到的所有数据
    rows = cursor.fetchall()

    for row in rows:
        print(row)
    return render(request,'index.html')

然后我们再刷新网页,在控制台就能看到从数据库中查询到的信息了。
当然还可以执行删除,修改等操作,只需要修改execute()中sql语句就行了。

MySQL语句大全2018

Python DB API 下规范下cursor对象常用接口

1、description:如果cursor执行了查询的sql代码。那么读取cursor.description属性的时候,将返回一个列表,这个列表中装的是元组,元组中装的分别是(name,type_code,display_size,internal_size,precision,scale,null_ok),其中name代表的是查找出来的数据的字段名称,其他参数暂时用处不大。

2、rowcount:代表的是在执行了sql语句后受影响的行数。

3、close:关闭游标。关闭游标以后就再也不能使用了,否则会抛出异
常。

4、execute(sql[,parameters]):执行某个sql语句。如果在执行sql语句的时候还需要传递参数,那么可以传给parameters参数。示例代码如下:

   cursor.execute("select * from article where id=%s",(1,))

5、fetchone:在执行了查询操作以后,获取第一条数据。

6、fetchmany(size):在执行查询操作以后,获取多条数据。具体是多少条要看传的size参数。如果不传size参数,那么默认是获取第一条数据。

7、fetchall:获取所有满足sql语句的数据。

关于python DB API 的官方文档

ORM模型:

创建ORM模型:

ORM模型一般都是放在appmodels.py文件中。每个app都可以拥有自己的模型。并且如果这个模型想要映射到数据库中,那么这个app必须要放在settings.pyINSTALLED_APP中进行安装。以下是写一个简单的书籍ORM模型。示例代码如下:
models.py

from django.db import models

# 如果要将一个普通的类变成一个可以映射到数据库中的ORM模型
# 那么必须要将父类设置为models.Model或者他的子类
class Book(models.Model):
    # 1. id:int类型,是自增长的。
    id = models.AutoField(primary_key=True)  #必须要指定primary=True
    # 2. name:varchar(100),图书的名字
    name = models.CharField(max_length=100,null=False)
    # 3. author:varchar(100),图书的作者
    author = models.CharField(max_length=100,null=False)
    # 4. price:float,图书的价格
    price = models.FloatField(null=False,default=0)

class Publisher(models.Model):
    name = models.CharField(max_length=100,null=False)
    address = models.CharField(max_length=100,null=False)
    def __str__(self):
        # 定义输出的样式<Book:(name,author,price)>
        return "<Book:({name},{author},{price})>".format(name=self.name,author=self.author,price=self.price)

# 1. 使用makemigrations生成迁移脚本文件
# python manage.py makemigrations

# 2. 使用migrate将新生成的迁移脚本文件映射到数据库中
# python manage.py migrate

以上便定义了一个模型。这个模型继承自django.db.models.Model,如果这个模型想要映射到数据库中,就必须继承自这个类。这个模型以后映射到数据库中,表名是模型名称的小写形式,为book。在这个表中,有四个字段,一个为name,这个字段是保存的是书的名称,是varchar类型,最长不能超过20个字符,并且不能为空。第二个字段是作者名字类型,同样也是varchar类型,长度不能超过20个。第三个是出版时间,数据类型是datetime类型,默认是保存这本书籍的时间。第五个是这本书的价格,是浮点类型。

还有一个字段我们没有写,就是主键id,在django中,如果一个模型没有定义主键,那么将会自动生成一个自动增长的int类型的主键,并且这个主键的名字就叫做id

映射模型到数据库中:

ORM模型映射到数据库中,总结起来就是以下几步:

  1. settings.py中,配置好DATABASES,做好数据库相关的配置。
  2. app中的models.py中定义好模型,这个模型必须继承自django.db.models
  3. 将这个app添加到settings.pyINSTALLED_APP中。
  4. 在命令行终端,进入到项目所在的路径,然后执行命令python manage.py makemigrations来生成迁移脚本文件。
  5. 同样在命令行中,执行命令python manage.py migrate来将迁移脚本文件映射到数据库中。(创建app,切记要注册,否则报错No changes detected)

ORM对数据库的基本操作:

添加数据:

只要使用ORM模型创建一个对象。然后再调用这个ORM模型的save方法就可以保存了。
示例代码如下:
views.py

from django.shortcuts import render
from . import models
from django.http import HttpResponse

# Create your views here.
def index(request):
    #1. 使用ORM添加一条数据到数据库中
    book = models.Book(name='三国演义',author='罗贯中',price=200)
    book.save()

    return HttpResponse('图书插入成功')

查找数据:

所有的查找工作都是使用模型上的objects属性来完成的。当然也可以自定义查询对象。这部分功能会在后面讲到。

  1. 根据主键进行查找:使用主键进行查找。可以使用objects.get方法。然后传递pk=xx的方式进行查找。示例代码如下:

views.py

 from django.shortcuts import render
from . import models
from django.http import HttpResponse

def index(request):
    # 1. 使用ORM添加一条数据到数据库中
    #book = models.Book(name='三国演义',author='罗贯中',price=200)
    #book.save()

    # 2. 查询
    # 2.1 使用主键进行查询
    book = models.Book.objects.get(pk=1) #若知道表头序号,也可以用表头序号来代替pk值,如 id=1
    print(book.name,book.author,book.price)
    return HttpResponse('图书插入成功')
  1. 根据其他字段进行查找:可以使用objects.filter方法进行查找。示例代码如下:

    views.py

     from django.shortcuts import render
     from . import models
     from django.http import HttpResponse
    
     # Create your views here.
     def index(request):
         # 1. 使用ORM添加一条数据到数据库中
         #book = models.Book(name='三国演义',author='罗贯中',price=200)
         #book.save()
    
         # 2. 查询
         # 2.1 使用主键进行查询
         # book = models.Book.objects.get(pk=1)
         # print(book.name,book.author,book.price)
    
         # 2.2 根据其他条件进行查找
         #       这个方法返回的是一个列表,将所有满足条件的信息都放在列表里面
         #       即使只有一条满足信息,也是返回一个列表
         books = models.Book.objects.filter(name='三国演义')
         print(books[0].name,books[0].author,books[0].price)
         return HttpResponse('图书插入成功')
    

    使用filter方法返回来的是一个QuerySet对象。这个对象类似于列表。我们可以使用这个对象的.first()[0]方法来获取第一个值。

删除数据:

首先查找到对应的数据模型。然后再执行这个模型的delete方法即可删除。示例代码如下:

views.py

from django.shortcuts import render
from . import models
from django.http import HttpResponse

def index(request):
    # 1. 使用ORM添加一条数据到数据库中
    #book = models.Book(name='三国演义',author='罗贯中',price=200)
    #book.save()

    # 2. 查询
    # 2.1 使用主键进行查询
    # book = models.Book.objects.get(pk=1)
    # print(book.name,book.author,book.price)
    # 2.2 根据其他条件进行查找
    #       这个方法返回的是一个列表,将所有满足条件的信息都放在列表里面
    #       即使只有一条满足信息,也是返回一个列表
    # books = models.Book.objects.filter(name='三国演义')
    # print(books[0].name,books[0].author,books[0].price)

    # 3. 删除数据
    book = models.Book.objects.get(pk = 1)
    book.delete()  #运行项目,就将pk=1这条数据删除了。
    return HttpResponse('图书插入成功')

修改数据:

首先查找到对应的数据模型。然后修改这个模型上的属性的值。再执行save方法即可修改完成。示例代码如下:

views.py

from django.shortcuts import render
from . import models
from django.http import HttpResponse

def index(request):
    # 1. 使用ORM添加一条数据到数据库中
    #book = models.Book(name='三国演义',author='罗贯中',price=200)
    #book.save()

    # 2. 查询
    # 2.1 使用主键进行查询
    # book = models.Book.objects.get(pk=1)
    # print(book.name,book.author,book.price)
    # 2.2 根据其他条件进行查找
    #       这个方法返回的是一个列表,将所有满足条件的信息都放在列表里面
    #       即使只有一条满足信息,也是返回一个列表
    # books = models.Book.objects.filter(name='三国演义')
    # print(books[0].name,books[0].author,books[0].price)

    # 3. 删除数据
    # book = models.Book.objects.get(pk = 1)
    # book.delete()

    # 4. 删除数据
    book = models.Book.objects.get(pk=2)
    book.price = 200
    book.save()
    return HttpResponse('图书插入成功')

模型常用字段

AutoField:

映射到数据库中是int类型,可以有自动增长的特性。一般不需要使用这个类型。只有在我们需要自己指定一个主键时才会使用这个字段。因为如果我们不指定主键,那么Django会自动的生成一个叫做id的自动增长的主键。所以一般不用我们自己创建主键。(想要自定义主键,必须设置primary_key=True中,否则的话就是一个普通的数据)

BigAutoField:

64位的整形,类似于AutoField,只不过是产生的数据的范围是从1-9223372036854775807。

BooleanField:

在模型层面接收的是True/False。在数据库层面是tinyint类型。如果没有指定默认值,默认值是None。
(一般情况下,在定义字段的时候,如果没有指定null=True,那么默认的情况下,null=False,就是不能为空)
而这个字段是不能为null的,如果想要使用可以为null的BooleanField字段,那么应该使用NullBooleanField来代替。

CharField:

在数据库层面是varchar类型。在Python层面就是普通的字符串。这个类型在使用的时候必须要指定最大的长度,也即必须要传递max_length这个关键字参数进去。
注意: 如果max_length超过255,就不建议使用这个字段了,而是使用TextField,这个字段在数据库层面就是longtext类型。

什么是navie时间?什么是aware时间?

  1. navie时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
  2. aware时间:知道自己的时间表示的是哪个时区的。也就是比较清醒。

pytz库:

专门用来处理时区的库。这个库会经常更新一些时区的数据,不需要我们担心。并且这个库在安装Django的时候会默认的安装。如果没有安装,那么可以通过pip install pytz的方式进行安装。

astimezone方法:

将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware类型的时间调用。不能被navie类型的时间调用。
示例代码如下:

import pytz
from datetime import datetime

now = datetime.now() # 这是一个navie类型的时间
utc_timezone = pytz.timezone("UTC") # 定义UTC的时区对象
utc_now = now.astimezone(utc_timezone) # 将当前的时间转换为UTC时区的时间
>> ValueError: astimezone() cannot be applied to a naive datetime # 会抛出一个异常,原因就是因为navie类型的时间不能调用astimezone方法


now = now.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
utc_now = now.astimezone(utc_timezone)
# 这时候就可以正确的转换。

replace方法:

可以将一个时间的某些属性进行更改。

django.utils.timezone.now方法:

会根据settings.py中是否设置了USE_TZ=True获取当前的时间。如果设置了,那么就获取一个aware类型的UTC时间。如果没有设置,那么就会获取一个navie类型的时间。

django.utils.timezone.localtime方法:

会根据setting.py中的TIME_ZONE来将一个aware类型的时间转换为TIME_ZONE指定时区的时间。

DateField:

日期类型。在Python中是datetime.date类型,可以记录年月日。在映射到数据库中也是date类型。使用这个Field可以传递以下几个参数:

  1. auto_now:在每次这个数据保存的时候,都使用当前的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
  2. auto_now_add:在每次数据第一次被添加进去的时候,都使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True

DateTimeField:

日期时间类型,类似于DateField。不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Field也可以使用auto_nowauto_now_add两个属性。示例代码如下:
views.py

rom django.shortcuts import render
from django.http import HttpResponse
from django.utils.timezone import now,localtime
from .models import Article

def index(request):
    # article = Article(removed=False)
    # article.save()
    # article = Article()
    # article.save()
    # import pytz
    # from datetime import datetime
    # now = datetime.now()  # 这是一个navie类型的时间
    # utc_timezone = pytz.timezone("UTC")  # 定义UTC的时区对象
    # utc_now = now.astimezone(utc_timezone)  # 将当前的时间转换为UTC时区的时间
    # article = Article(title='aaa')
    # article.save()
    article = Article.objects.get(pk=3)
    article.title = '111'
    article.save()
    # article = Article.objects.get(pk=1)
    # create_time = article.create_time
    # print('='*30)
    # print(create_time)
    # print(localtime(create_time))
    # print('='*30)
    return HttpResponse("success")
    # return render(request,'index.html',context={'create_time':create_time})

models.py

from django.db import models
from django.utils.timezone import now


class Article(models.Model):
    # 如果想要使用自己定义的Field来作为主键,那么必须设置primary_key=True
    id = models.BigAutoField(primary_key=True)
    # 在定义字段的时候,如果没有指定null=True,那么默认情况下,null=False
    # 就是不能为空
    # 如果想要使用可以为null的BooleanField,那么应该使用NullBooleanField
    # 来代替
    removed = models.NullBooleanField()
    # CharField:如果是超过了254个字符,那么就不建议使用啦
    # 就推荐使用TextField:longtext
    title = models.CharField(max_length=200)
    # auto_now_add:是在第一次添加数据进去的时候会自动获取当前的时间
    # auto_now:每次这个对象调用save方法的时候都会将当前的时间更新
    create_time = models.DateTimeField(auto_now=True)
    # 创建时间
    # 更新时间

TimeField:

时间类型。在数据库中是time类型。在Python中是datetime.time类型。

https://docs.djangoproject.com/en/2.0/topics/i18n/timezones/

EmailField:

类似于CharField。在数据库底层也是一个varchar类型。最大长度是254个字符。示例代码如下:
views.py

from .models import Person

def email_view(request):
    p = Person(email='aaaa')
    p.save()
    return HttpResponse('success')

models.py

class Person(models.Model):
    # 需要配合ModelForm 验证表单模块才能验证是否是email格式
    # EmailField在数据库层面并不会限制字符串一定要满足邮箱格式
    # 只是以后在使用ModelForm等表单相关操作的时候会起作用
    email = models.EmailField()

ImageField 以及FileField的实现与用法:

ImageField 和FileField可以分别对图片和文件进行上传到指定的文件夹中。

效果:
在这里插入图片描述

1.在app下面的models.pyclass AssetModel(models.Model)[每个模型映射一个数据库表,其中的每个属性映射一个数据库表的字段] 定义出:

picture = models.ImageField(upload_to=’model/pic’, blank=True, null=True)

注:定义ImageField字段时必须制定参数upload_to。

这个参数会加在settings.py中的MEDIA_ROOT后面,形成一个路径,这个路径就是上传图片的存放位置。

所以要先设置好mysite/settings.py中的settings.py中的MEDIA_ROOT

2.在mysite/settings.py中:

MEDIA_ROOT=’/home/chuangyuan/project/uploadfile’

MEDIA_URL=’http://127.0.0.1:8000/’

MEDIA_URL:给这个属性设值之后,静态文件的链接前面会加上这个值,如果不设置它的话,点击这个连接,url会变成上个页面的url加上图片的路径。

MEDIA_ROOT:存放media的路径,这个值加上upload_to的值就是真实存放上传图片文件位置

3.FileField和ImageFiled相同。

FloatField:

浮点类型。映射到数据库中是float类型。

IntegerField:

整形。值的区间是-2147483648——2147483647

BigIntegerField:

大整形。值的区间是-9223372036854775808——9223372036854775807

PositiveIntegerField:

正整形。值的区间是0——2147483647

SmallIntegerField:

小整形。值的区间是-32768——32767

PositiveSmallIntegerField:

正小整形。值的区间是0——32767

TextField:

大量的文本类型。映射到数据库中是longtext类型。

signature = models.TextField()

UUIDField:

只能存储uuid格式的字符串。uuid是一个32位的全球唯一的字符串,一般用来作为主键。

URLField:

类似于CharField,只不过只能用来存储url格式的字符串。并且默认的max_length是200。

Field常用的参数

null:

如果设置为TrueDjango将会在映射表的时候指定是否为空。默认是为False。在使用字符串相关的Field(CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值False。因为Django在处理字符串相关的Field的时候,即使这个Fieldnull=False,如果你没有给这个Field传递任何值,那么Django也会使用一个空的字符串""来作为默认值存储进去。因此如果再使用null=TrueDjango会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。如果你的FieldBooleanField,那么对应的可空的字段则为NullBooleanField

blank:

标识这个字段在表单验证的时候是否可以为空。默认是False
这个和null是有区别的,null是一个纯数据库级别的。而blank是表单验证级别的。

choices

本身由恰好两个项目(例如)的可迭代项组成的可迭代项(例如列表或元组),用作该字段的选择。如果提供此选项,则默认表单窗口小部件将是具有这些选择的选择框,而不是标准文本字段。[(A, B), (A, B) …]

每个元组中的第一个元素是要在模型上设置的实际值,第二个元素是人类可读的名称。例如:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
)

通常,最好在模型类中定义选择,并为每个值定义一个适当命名的常量:

from django.db import models

class Student(models.Model):
    FRESHMAN = 'FR'
    SOPHOMORE = 'SO'
    JUNIOR = 'JR'
    SENIOR = 'SR'
    YEAR_IN_SCHOOL_CHOICES = (
        (FRESHMAN, 'Freshman'),
        (SOPHOMORE, 'Sophomore'),
        (JUNIOR, 'Junior'),
        (SENIOR, 'Senior'),
    )
    year_in_school = models.CharField(
        max_length=2,
        choices=YEAR_IN_SCHOOL_CHOICES,
        default=FRESHMAN,
    )

    def is_upperclass(self):
        return self.year_in_school in (self.JUNIOR, self.SENIOR)

尽管您可以在模型类外部定义一个选择列表,然后再引用它,但是在模型类内部定义每个选择的选择和名称,会将所有信息保留在使用它的类中,并使选择易于参考(例如,Student.SOPHOMORE 将Student在导入模型的任何地方工作)。

您还可以将可用的选择收集到命名组中,以用于组织目的:

MEDIA_CHOICES = (
    ('Audio', (
            ('vinyl', 'Vinyl'),
            ('cd', 'CD'),
        )
    ),
    ('Video', (
            ('vhs', 'VHS Tape'),
            ('dvd', 'DVD'),
        )
    ),
    ('unknown', 'Unknown'),
)

每个元组中的第一个元素是应用于组的名称。第二个元素是2元组的可迭代对象,每个2元组包含一个值和一个选项的人类可读名称。可以在单个列表中将分组的选项与未分组的选项组合在一起(例如本示例中的 unknown选项)。

对于已choices设置的每个模型字段,Django将添加一个方法来检索该字段的当前值的可读名称。请参阅 get_FOO_display()数据库API文档。

请注意,选择可以是任何可迭代的对象-不一定是列表或元组。这使您可以动态构造选择。但是,如果您发现黑客攻击 choices是动态的,那么最好使用带有的适当数据库表ForeignKeychoices表示静态数据,它们不会发生太大变化(如果有的话)。

除非blank=False在字段上与一起设置, 否则将使用选择框呈现default包含的标签”———“。要覆盖此行为,添加一个元组choices 包含None; 例如。或者,您可以使用空字符串代替有意义的位置-例如在上。(None, 'Your String For Display')NoneCharField

db_column:

这个字段在数据库中的名字。如果没有设置这个参数,那么将会使用模型中属性的名字。

default:

字段的默认值。这可以是值或可调用对象。如果可以调用,则每次创建新对象时都会调用它。

默认不能是可变对象(模型实例,list,set等),作为该对象的相同实例的引用将被用作在所有新的模型实例的默认值。而是将所需的默认值包装在可调用中。例如,如果要指定一个默认dictJSONField,使用函数:

def contact_default():
    return {"email": "to1@example.com"}

contact_info = JSONField("ContactInfo", default=contact_default)

lambda不能用于字段选项,例如,default因为它们不能通过迁移序列化。有关其他注意事项,请参阅该文档。

对于ForeignKey此类映射到模型实例的字段,默认值应为它们引用的字段的值(pk除非 to_field已设置),而不是模型实例。

创建新模型实例且未为该字段提供值时,将使用默认值。如果该字段是主键,则当该字段设置为时也使用默认值None

primary_key:

是否为主键。默认是False
如果为True,则此字段是模型的主键。

如果您未primary_key=True在模型中指定任何字段,则Django会自动添加一个AutoField来保留主键,因此primary_key=True除非您要覆盖默认的主键行为,否则无需在任何字段上进行设置。有关更多信息,请参见 自动主键字段。

primary_key=True暗示null=Falseunique=True。一个对象只允许使用一个主键。

主键字段是只读的。如果更改现有对象上的主键的值然后保存,则将在旧对象的旁边创建一个新对象。

unique:

在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。

更多Field参数请参考官方文档

模型中Meta配置:

对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta类中添加一个db_table的属性。示例代码如下:

class Book(models.Model):
    name = models.CharField(max_length=20,null=False)
    desc = models.CharField(max_length=100,name='description',db_column="description1")

class Meta:
    db_table = 'book_model'

以下将对Meta类中的一些常用配置进行解释。

db_table:

这个模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。

ordering:

设置在提取数据的排序方式。后面章节会讲到如何查找数据。比如我想在查找数据的时候根据添加的时间排序,那么示例代码如下:

class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
pub_date = models.DateTimeField(auto_now_add=True)

class Meta:
db_table = 'book_model'
ordering = ['pub_date']

更多的配置后面会慢慢介绍到。
官方文档

表关系笔记:

一对多:

  1. 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
  2. 实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解。
class User(models.Model):
    username = models.CharField(max_length=20)
    password = models.CharField(max_length=100)

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    author = models.ForeignKey("User",on_delete=models.CASCADE)

那么以后在给Article对象指定author,就可以使用以下代码来完成:

article = Article(title='abc',content='123')
author = User(username='zhiliao',password='111111')
# 要先保存到数据库中
author.save()
article.author = author
article.save()

并且以后如果想要获取某个用户下所有的文章,可以通过article_set来实现。示例代码如下:

user = User.objects.first()
# 获取第一个用户写的所有文章
articles = user.article_set.all()
for article in articles:
    print(article)

并且如果想要将文章添加到某个分类中。可以使用一下的方式:

category = Category.objects.first()

article = Article(title='bbb',content='vvv')
article.author = FrontUser.objects.first()

category.article_set.add(article,bulk=False)
  • 使用bulk=False,那么Django会自动的保存article,而不需要在添加到category之前先保存article。
  • 或者是另外一种解决方式是,在添加到category.article_set中之前,先将article保存到数据库中。但是如果article.category不能为空,那么就产生一种死循环了,article没有category不能保存,而将article添加到cateogry.artile_set中,又需要article之前是已经存储到数据库中的。
  • 如果是上面的那种需求,建议使用bulk=False的解决方案。

一对一:

  1. 在Django中一对一是通过models.OnetToOneField来实现的。这个OneToOneField其实本质上就是一个外键,只不过这个外键有一个唯一约束(unique key),来实现一对一。

  2. 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:

     class FrontUser(models.Model):
         username = models.CharField(max_length=200)
    
     class UserExtension(models.Model):
         school = models.CharField(max_length=100)
         user = models.OneToOneField("FrontUser",on_delete=models.CASCADE)
    
     # 通过userextension来访问UserExtension对象
     user = FrontUser.objects.first()
     print(user.userextension)

    UserExtension的对象,可以通过user来访问到对应的user对象。并且FrontUser对象可以使用userextension来访问对应的UserExtension对象。
    如果不想使用Django默认的引用属性名字。那么可以在OneToOneField中添加一个related_name参数。示例代码如下:

     class FrontUser(models.Model):
         username = models.CharField(max_length=200)
    
     class UserExtension(models.Model):
         school = models.CharField(max_length=100)
         user = models.OneToOneField("FrontUser",on_delete=models.CASCADE,related_name='extension')
    
     # 通过extension来访问到UserExtension对象
     user = FrontUser.objects.first()
     print(user.extension)

    那么以后就FrontUser的对象就可以通过extension属性来访问到对应的UserExtension对象。

多对多:

  1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。

  2. 实现方式:Django为这种多对多的实现提供了专门的Field。叫做ManyToManyField。还是拿文章和标签为例进行讲解。示例代码如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    tags = models.ManyToManyField("Tag",related_name="articles")

class Tag(models.Model):
    name = models.CharField(max_length=50)

在数据库层面,实际上Django是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到articletag两张表的主键。

查询条件笔记:

  1. exact:在底层会被翻译成=

  2. iexact:在底层会被翻译成LIKE

    • LIKE和=:大部分情况下都是等价的,只有少数情况下是不等价的。
    • exict和iexact:他们的区别其实就是LIKE和=的区别,因为exact会被翻译成=,而iexact会被翻译成LIKE。
    • 因为field__exact=xxx其实等价于filed=xxx,因此我们直接使用filed=xxx就可以了,并且因为大部分情况exactiexact又是等价的,因此我们以后直接使用field=xxx就可以了。
  3. QuerySet.query:query可以用来查看这个ORM查询语句最终被翻译成的SQL语句。但是query只能被用在QuerySet对象上,不能用在普通的ORM模型上。因此如果你的查询语句是通过get来获取数据的,那么就不能使用query,因为get返回的是满足条件的ORM模型,而不是QuerySet。如果你是通过filter等其他返回QuerySet的方法查询的,那么就可以使用query

  4. contains:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成SQL语句的时候,会使用like binary,而like binary就是使用大小写敏感的。

  5. icontains:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成SQL的时候,使用的是like,而likeMySQL层面就是不区分大小写的。

  6. contains和icontains:在被翻译成SQL的时候使用的是%hello%,就是只要整个字符串中出现了hello都能过够被找到,而iexact没有百分号,那么意味着只有完全相等的时候才会被匹配到。

  7. in:可以直接指定某个字段的是否在某个集合中。示例代码如下:

     articles = Article.objects.filter(id__in=[1,2,3])

    也可以通过其他的表的字段来判断是否在某个集合中。示例代码如下:

     categories = Category.objects.filter(article__id__in=[1,2,3])

    如果要判断相关联的表的字段,那么也是通过__来连接。并且在做关联查询的时候,不需要写models_set,直接使用模型的名字的小写化就可以了。比如通过分类去查找相应的文章,那么通过article__id__in就可以了,而不是写成article_set__id__in的形式。当然如果你不想使用默认的形式,可以在外键定义的时候传递related_query_name来指定反向查询的名字。示例代码如下:

     class Category(models.Model):
         name = models.CharField(max_length=100)
    
         class Meta:
             db_table = 'category'
    
    
class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    cateogry = models.ForeignKey("Category",on_delete=models.CASCADE,null=True,related_query_name='articles')

    class Meta:
        db_table = 'article'
```
因为在`cateogry`的`ForeignKey`中指定了`related_query_name`为`articles`,因此你不能再使用`article`来进行反向查询了。这时候就需要通过`articles__id__in`来进行反向查询。

反向查询是将模型名字小写化。比如`article__in`。可以通过`related_query_name`来指定自己的方式,而不使用默认的方式。
反向引用是将模型名字小写化,然后再加上`_set`,比如`article_set`,可以通过`related_name`来指定自己的方式,而不是用默认的方式。

并且,如果在做反向查询的时候,如果查询的字段就是模型的主键,那么可以省略掉这个字段,直接写成`article__in`就可以了,不需要这个`id`了。

`in`不仅仅可以指定列表/元组,还可以为`QuerySet`。比如要查询“文章标题中包含有hello的所有分类”,那么可以通过以下代码来实现:
```python
articles = Article.objects.filter(title__icontains='hello')
categories = Category.objects.filter(articles__in=articles)
for cateogry in categories:
    print(cateogry)
```
  1. gt、gte、lt、lte:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:

     articles = Article.objects.filter(id__lte=3)
  2. startswith、istartswith、endswith、iendswith:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:

     articles = Article.objects.filter(title__endswith="hello")
  3. 关于时间的查询条件:

    • range:可以指定一个时间段。并且时间应该标记为aware时间,不然django会报警告。示例代码如下:

        from django.utils.timezone    import make_aware
        from datetime    import datetime
        start_time = make_aware(datetime(year=2018,month=4,day=4,hour=17,minute=0,second=0))
        end_time = make_aware(datetime(year=2018,month=4,day=4,hour=18,minute=0,second=0))
        articles = Article.objects.filter(create_time__range=(start_time,end_time))
        print(articles.query)
        print(articles)
  • date:用年月日来进行过滤。如果想要使用这个过滤条件,那么前提必须要在MySQL中添加好那些时区文件。如何添加呢?点击这里下载时区包,然后导入到C:\ProgramData\MySQL\MySQL Server 8.0\Data中即可。注:ProgramData文件夹需要勾选显示隐藏的文件才能看到,示例代码如下:
       articles = Article.objects.filter(create_time__date=datetime(year=2018,month=4,day=4))
    • year/month/day:表示根据年/月/日进行查找。示例代码如下:
       articles = Article.objects.filter(create_time__year__gte=2018)
    • week_day:根据星期来进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查找星期三的所有文章,那么可以通过以下代码来实现:
       articles = Article.objects.filter(create_time__week_day=4)
    • time:根据分时秒来进行查找。如果要具体到秒,一般比较难匹配到,可以使用区间的方式来进行查找。区间使用range条件。比如想要获取17时/10分/27-28秒之间的文章,那么可以通过以下代码来实现:
       start_time = time(hour=17,minute=10,second=27)
       end_time = time(hour=17,minute=10,second=28)
       articles = Article.objects.filter(create_time__time__range=(start_time,end_time))

聚合函数笔记:

  1. 所有的聚合函数都是放在django.db.models下面。

  2. 聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如aggregate。示例代码如下:

     result = Book.objects.aggregate(Avg("price"))
  3. 聚合函数执行完成后,给这个聚合函数的值取个名字。取名字的规则,默认是filed+__+聚合函数名字形成的。比如以上代码形成的名字叫做price__avg。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:

     result = Book.objects.aggregate(avg=Avg("price"))

    以上传递了关键字参数avg=Avg("price"),那么以后Avg聚合函数执行完成的名字就叫做avg

  4. aggregate:这个方法不会返回一个QuerySet对象,而是返回一个字典。这个字典中的key就是聚合函数的名字,值就是聚合函数执行后的结果。

  5. aggregateannotate的相同和不同:

    • 相同:这两个方法都可以执行聚合函数。
    • 不同:
      • aggregate返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。而annotate返回的是一个QuerySet对象,并且会在查找的模型上添加一个聚合函数的属性。
      • aggregate不会做分组,而annotate会使用group by子句进行分组,只有调用了group by子句,才能对每一条数据求聚合函数的值。
  6. Count:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:

     result = Book.objects.aggregate(book_nums=Count("id"))

    并且Count可以传递distinct=True参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True。示例代码如下:

     result = Author.objects.aggregate(email_nums=Count('email',distinct=True))
  7. MaxMin:求指定字段的最大值和最小值。示例代码如下:

     result = Author.objects.aggregate(max=Max("age"),min=Min("age"))
  8. Sum:求某个字段值的总和。示例代码如下:

     result = BookOrder.objects.aggregate(total=Sum('price'))

    aggregateannotate方法可以在任何的QuerySet对象上调用。因此只要是返回了QuerySet对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:

     BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price'))
  9. F表达式: 动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:

     from django.db.models import F
     Book.objects.update(price=F("price")+10)
  10. Q表达式:使用Q表达式包裹查询条件,可以在条件之间进行多种操作。与/或非等,从而实现一些复杂的查询操作。例子如下:

    • 查找价格大于100,并且评分达到4.85以上的图书:
        # 不使用Q表达式的
        books = Book.objects.filter(price__gte=100,rating__gte=4.85)
        # 使用Q表达式的
        books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
    • 查找价格低于100元,或者评分低于4分的图书:
        books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
    • 获取价格大于100,并且图书名字中不包含”传“字的图书:
        books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='传'))

QuerySet API:

模型.objects:

这个对象是django.db.models.manager.Manager的对象,这个类是一个空壳类,他上面的所有方法都是从QuerySet这个类上面拷贝过来的。因此我们只要学会了QuerySet,这个objects也就知道该如何使用了。
Manager源码解析:

class_name = "BaseManagerFromQuerySet"

class_dict = {
    '_queryset_class': QuerySet
}

class_dict.update(cls._get_queryset_methods(QuerySet))

# type动态的时候创建类
# 第一个参数是用来指定创建的类的名字。创建的类名是:BaseManagerFromQuerySet
# 第二个参数是用来指定这个类的父类。
# 第三个参数是用来指定这个类的一些属性和方法
return type(class_name,(cls,),class_dict)

_get_queryset_methods:这个方法就是将QuerySet中的一些方法拷贝出来

filter/exclude/annotate:过滤/排除满足条件的/给模型添加新的字段。

order_by:

# 根据创建的时间正序排序
articles = Article.objects.order_by("create_time")
# 根据创建的时间倒序排序
articles = Article.objects.order_by("-create_time")
# 根据作者的名字进行排序
articles = Article.objects.order_by("author__name")
# 首先根据创建的时间进行排序,如果时间相同,则根据作者的名字进行排序
articles = Article.objects.order_by("create_time",'author__name')

一定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:

articles = Article.objects.order_by("create_time").order_by("author__name")

他会根据作者的名字进行排序,而不是使用文章的创建时间。
当然,也可以在模型定义的在Meta类中定义ordering来指定默认的排序方式。示例代码如下:

    class Meta:
        db_table = 'book_order'
        ordering = ['create_time','-price']

还可以根据annotate定义的字段进行排序。比如要实现图书的销量进行排序,那么示例代码如下:

books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
    for book in books:
        print('%s/%s'%(book.name,book.order_nums))

migrate怎么判断哪些迁移脚本需要执行:

他会将代码中的迁移脚本和数据库中django_migrations中的迁移脚本进行对比,如果发现数据库中,没有这个迁移脚本,那么就会执行这个迁移脚本。

migrate做了什么事情:

  1. 将相关的迁移脚本翻译成SQL语句,在数据库中执行这个SQL语句。
  2. 如果这个SQL语句执行没有问题,那么就会将这个迁移脚本的名字记录到django_migrations中。

执行migrate命令的时候报错的解决办法:

原因:

执行migrate命令会报错的原因是。数据库的django_migrations表中的迁移版本记录和代码中的迁移脚本不一致导致的。

解决办法:

使用–fake参数:

首先对比数据库中的迁移脚本和代码中的迁移脚本。然后找到哪个不同,之后再使用--fake,将代码中的迁移脚本添加到django_migrations中,但是并不会执行sql语句。这样就可以避免每次执行migrate的时候,都执行一些重复的迁移脚本。

终极解决方案:

如果代码中的迁移脚本和数据库中的迁移脚本实在太多,就是搞不清了。那么这时候就可以使用以下终极解决方案:

  1. 终极解决方案原理:就是将之前的那些迁移脚本都不用了。重新来过。要将出问题的app下的所有模型和数据库中表保持一致,重新映射。
  2. 将出问题的app下的所有模型,都和数据库中的表保持一致。
  3. 将出问题的app下的所有迁移脚本文件都删掉。再在django_migrations表中将出问题的app相关的迁移记录都删掉。
  4. 使用makemigrations,重新将模型生成一个迁移脚本。
  5. 使用migrate --fake-initial参数,将刚刚生成的迁移脚本,标记为已经完成(因为这些模型相对应的表,其实都已经在数据库中存在了,不需要重复执行了。)
  6. 可以做其他的映射了。

    根据已有的表生成orm模型

1、Django 给我们提供了一个inspectdb的命令,可以非常方便的将已经存在的表,自动的生成模型。想要使用inspectab自动将
表生成模型。首先需要在settings.py 中配置好数据库相关信息不然就找不到数据库。示例代码如下:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'database_name',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'your_database_name',
        'PASSWORD': 'your_database_password',
    }
}

再通过 python manage.py inspectdb 就会将表转换为模型后的代码显示在终端。

不过以上代码只是显示在终端,如果想要保存到文件,可以指定重定向文件,比如输出到 models.py 文件中,需要执行:

python manage.py inspectdb > models.py

以上命令只能在终端中执行,不能在pycharm->Tools->Run manage.py Task...中使用

如果只想要转换一个表生成模型,可以执行以下语句:python manage.py inspectdb table_name > models.py
2、修正模型:新生成的ORM模型有些地方可能不太适合使用。比如模型的名字,表之间的关系等等。那么以下选项还需要重新配置:
(1)、模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。
模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同个app中也是没有任何问题的。只是不方便管
理。
(2)、模型外键引用:将所有使用Foreignkey 的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用
的模型已经移动到其他的app中了,那么还要加上这个app的前缀。
(3)、让Django管理模型:将Meta下的managed=False 删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate都不会映射到数据库中。
(4)、表名:切记不要修改表的名字(这里的表名不是模型类名,而是class Meta: 中的db_table)。不然映射到数据库中,会发生找不到对应表的错误。

3、执行命令python manage.py makemigrations生成初始化的迁移脚本。方便后面通过ORM来管理表。在这时候还需要执行命令python manage.py migrate --fake-initial,因为如果不使用–fake-initial,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新到建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial 的状态修改为已经映射,而不真正执行映射,下次再migrate的时候,就会忽略他。

4、将Django的核心表映射到数据库中: Django中还有那些核心的表也是需要创建的。不然有些功能是用不了的。比如auth相关
表。如果过个数据库之前就是使用Django开发的,那么这些表就已经存在了,可以不用管了。如果之前这个数据库不是使用Django开发的,那么应该使用python manage.py migrate命令将Django中的核心模型映射到数据库中。

Django使用mysql视图
参考文章 : 猛击这里

未完待续。。。。。。


等待也是约会的一部分嘛