[Python自学] day-19 (2) (Django-ORM)

一、ORM的分类

ORM一般分为两类:

1.DB first:先在DB中创建数据库、表结构,然后自动生成代码中的类。在后续操作中直接在代码中操作相应的类即可。

2.Code first:直接在代码中实现各种类,然后执行,代码自动在DB中创建对应的数据库和表结构。

最常用的是后者,即Code First类型的ORM。例如 [Python自学] day-12 (Mysql、事务、索引、ORM) 中的SQLAlchemy,我们即将要了解的Django ORM也属于Code first。

二、利用ORM创建表

在我们不修改Django数据库配置的情况下,Django默认使用的数据库是sqlite3:

我们暂且使用该默认的数据库。

1.在cmdb APP中的models.py中创建一个类

from django.db import models


class UserInfo(models.Model):
    # ORM会自动帮我们生成一个id列,是自增的

    # 生成cmdb_userinfo表,里面含有username和password两列
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

2.使用命令行在数据库中生成表

python manage.py makemigrations

理论上,会在cmdb/migrations中生成一个临时文件。但是这里并未生成,这是因为cmdb这个APP的models.py并未装载到Django中,所以Django找不到该models.py。

3.配置settings.py(重要)

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

4.再次执行命令行

D:pycharm_workspacesecondsite>d:Dev_appsAnaconda5.3.0python.exe manage.py makemigrations
Migrations for 'cmdb':
  cmdbmigrations001_initial.py
    - Create model UserInfo

我们查看cmdb/migrations目录:

然后执行命令:

D:pycharm_workspacesecondsite>d:Dev_appsAnaconda5.3.0python.exe manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, cmdb, 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 auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying cmdb.0001_initial... OK
  Applying sessions.0001_initial... OK

我们可以看到,Django除了帮我们执行了cmdb.0001_initial,还执行了一大堆其他的东西,这些东西是Django默认帮我们生成的表,包括sessions等常用的东西。

三、配置使用Mysql数据库

查看settings.py:

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

可以看到,这里配置使用数据库后端为sqlite3。

我们将其修改为Mysql:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbname',  # 数据库名,这个django不能帮我们创建,需要我们手工创建
        'USER': 'root',  # 登录用户名
        'PASSWORD': 'xxx',  # 登录密码
        'HOST': '',  # IP
        'PORT': '',  # port
    }
}

修改完settings.py后,执行命令,即可在mysql中创建表:

python manage.py makemigrations
python manage.py migrate

特别注意:(非常重要)

Django连接mysql默认使用的事MySQLdb模块,但是在python3中还没有这个模块。

我们需要在工程目录的__init__.py:

写入以下代码:

import pymysql

pymysql.install_as_MySQLdb()

四、查看sqlite3数据库

使用Navicat软件连接sqlite3数据库,查看Django-ORM为我们创建的表。

1.运行Navicat软件:

2.连接sqlite3数据库:

3.查看数据库中的表

我们可以从中找到我们在cmdb/models.py中使用类创建的表,这里叫 cmdb_userinfo

4.查看cmdb_userinfo表

可以看到,表中有三个列,id列(django自动创建,自增的)、username列和password列。

 

五、ORM插入数据(基础)

我们写一个orm测试页面。

1.在cmdb/urls.py中添加一个映射关系:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('login', views.login),
    path('home', views.home),
    # path('mypage', views.MyPage.as_view()),
    path('mypage12376sjhdfjnwjer', views.MyPage.as_view(), name='mypage'),
    path('users', views.user_page),
    re_path('details-(d+).html', views.details),
    path('orm', views.orm_test),
]

2.在视图函数中使用ORM插入数据:

# 导入cmdb的models模块
from cmdb import models


def orm_test(request):
    # 在表中插入一条数据
    models.UserInfo.objects.create(
        username='root',
        password='123456'
    )
    return HttpResponse('ok')

另外一种方式:

def orm_test(request):
    obj = models.UserInfo(username='alex', password='123')
    obj.save()
    return HttpResponse('ok')

第三种方式(第一种的变种,其实就是拆包):

def orm_test(request):
    # 在表中插入一条数据
    dic = {'username': 'eric', 'password': '123'}
    models.UserInfo.objects.create(**dic)
    return HttpResponse('ok')

3.请求http://127.0.0.1/cmdb/orm

4.查看数据库中的cmdb_userinfo表

我们可以看到,数据已经成功插入表中。

六、ORM查询数据(基础)

1.查询表中所有数据(相当于select * from cmdb_userinfo;):

def orm_test(request):
    # 查询数据,返回的result是QuerySet类型,实际上相当于一个列表,每个元素是一个对象,相当于 select * from cmdb_userinfo;
    result = models.UserInfo.objects.all()
    # 便利result中的每个对象,其中每列对应一个成员属性
    for row in result:
        print(row.id, row.username, row.password)
    return HttpResponse('ok')

2.查询部分列(相当于 select username,password from cmdb_userinfo;):

def orm_test(request):
    # result同样是QuerySet,但每个元素变成了字典
    result = models.UserInfo.objects.all().values('username','password')
    # 遍历result中的每个对象
    for row in result:
        print(row['username'], row['password'])
    return HttpResponse('ok')

当然,我们如果将result通过render函数合并进模板,在模板语言中也要按result的结构来获取数据。

除了使用values()来获取部分列,还可以使用values_list('username','password'):

def orm_test(request):
    # result同样是QuerySet,但每个元素变成了元组
    result = models.UserInfo.objects.all().values_list('username','password')
    # 遍历result中的每个对象
    for row in result:
        print(row[0], row[1])
    return HttpResponse('ok')

3.过滤查询(相当于 select * from cmdb_userinfo where username='alex';):

def orm_test(request):
    # 过滤查询,过滤出username='alex'的记录
    result = models.UserInfo.objects.filter(username='alex')
    # 便利result中的每个对象,其中每列对应一个成员属性
    for row in result:
        print(row.id, row.username, row.password)
    return HttpResponse('ok')

如果是多个过滤条件组成交集(AND),则为:

models.UserInfo.objects.filter(username='alex', password='123')

七、ORM删除数据(基础)

def orm_test(request):
    # 删除所有数据
    models.UserInfo.objects.all().delete()
    # 删除过滤数据
    models.UserInfo.objects.filter(username='alex').delete()
    
    return HttpResponse('ok')

八、ORM更新数据(基础)

1.基本更新操作

def orm_test(request):
    # 修改所有条目的password
    models.UserInfo.objects.all().update(password='6666')
    # 修改部分条目的password
    models.UserInfo.objects.filter(username='alex').update(password='10000')

    return HttpResponse('ok')

2.同时更新多个数据

def orm_test(request):
    # 修改部分条目的password
    models.UserInfo.objects.filter(id=3).update(password='10000', username='Leo')

    return HttpResponse('ok')

九、实现基于数据库的用户登录

1.login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link rel="stylesheet" href="/static/commons.css"/>
    <style>
        label{
            width: 80px;
            text-align: right;
            display: inline-block;
        }
        .error_span{
            color: red;
        }
    </style>
</head>
<body class="common">
    <form action="/cmdb/login/" method="post">
        <p>
            <label for="username">用户名:</label>
            <input id="username" type="text" name="user"/>
            <span class="error_span">{{ user_error }}</span>
        </p>
        <p>
            <label for="password">密码:</label>
            <input id="password" type="password" name="pwd"/>
            <input type="submit" value="提交"/>
            <span class="error_span">{{ pwd_error }}</span>
        </p>
    </form>
    <script src="/static/jquery-1.12.4.js"></script>
</body>
</html>

2.cmdb/urls.py映射关系

urlpatterns = [
    path('login/', views.login),
]

3.cmdb/views.py视图函数

def login(request):
    if request.method == 'POST':
        username = request.POST.get('user', None)
        password = request.POST.get('pwd', None)

        pwd_error_msg = ''
        # 从数据库中验证用户名密码是否存在,filter获取的是一个列表,first是取列表的第一个元素,即一条记录或者None
        obj = models.UserInfo.objects.filter(username=username, password=password).first()
        if obj:
            return redirect('http://www.baidu.com')
        else:
            pwd_error_msg = "账号或密码不正确"
            return render(request, 'login.html', {"pwd_error": pwd_error_msg})

4.实现效果

输入账号密码提交给后台,然后从数据库验证,如果正确,则跳转到www.baidu.com,如果不正确则跳转回当前页面,重新输入。

十、ORM修改表结构

修改表结构,直接对app/models.py中的类进行修改就可以了。

修改完后都要执行以下命令:

python manage.py makemigrations
python manage.py migrate

1.修改数据类型(长度)

class UserInfo(models.Model):
    # ORM会自动帮我们生成一个id列,是自增的

    # 生成UserInfo表,里面含有username和password两列
    username = models.CharField(max_length=32)
    #password = models.CharField(max_length=64)
    password = models.CharField(max_length=80)   # 将密码的长度修改为80

如果将长度改短,则如果数据超出长度,会丢失。

2.增加列

class UserInfo(models.Model):
    # ORM会自动帮我们生成一个id列,是自增的

    username = models.CharField(max_length=32)
    #password = models.CharField(max_length=64)
    password = models.CharField(max_length=80)   # 将密码的长度修改为80
    email = models.CharField(max_length=100)  # 增加一列email

默认增加的列是不能为空的,所以在执行"python manage.py makemigrations"命令时会弹出提示:

(venv) D:pycharm_workspacesecondsite>d:Dev_appsAnaconda5.3.0python.exe manage.py makemigrations
You are trying to add a non-nullable field 'email' to userinfo without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>>  "xxx@email.com"
Migrations for 'cmdb':
  cmdbmigrations002_auto_20191218_1319.py
    - Add field email to userinfo
    - Alter field password on userinfo

选择1,然后输入默认值。接着再执行"python manage.py migrate"命令。

执行列可以为空:

class UserInfo(models.Model):
    # ORM会自动帮我们生成一个id列,是自增的

    username = models.CharField(max_length=32)
    password = models.CharField(max_length=80)
    email = models.CharField(max_length=100, null=True)  # 增加一列email,允许为空

使用"null=True"设置列允许为空。

3.删除列

class UserInfo(models.Model):
    # ORM会自动帮我们生成一个id列,是自增的

    username = models.CharField(max_length=32)
    password = models.CharField(max_length=80)
    #email = models.CharField(max_length=100, null=True)  # 删除email列

注释或删除需要删除的列对应属性。然后重新

十一、ORM字段类型

在mysql数据库中,有字符串、数字、二进制等类型,在Django的ORM中也有对应的字段类型。

前面我们使用了CharField类型:

username = models.CharField(max_length=32)

ORM提供的数据类型:

AutoField(Field)
    - int自增列,必须填入参数 primary_key=True

BigAutoField(AutoField)
    - bigint自增列,必须填入参数 primary_key=True

    注:当model中如果没有自增列,则自动会创建一个列名为id的列
    from django.db import models

    class UserInfo(models.Model):
        # 自动创建一个列名为id的且为自增的整数列
        username = models.CharField(max_length=32)

    class Group(models.Model):
        # 自定义自增列
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32)

SmallIntegerField(IntegerField):
    - 小整数 -3276832767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正小整数 032767
IntegerField(Field)
    - 整数列(有符号的) -21474836482147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正整数 02147483647

BigIntegerField(IntegerField):
    - 长整型(有符号的) -92233720368547758089223372036854775807

自定义无符号整数字段

    class UnsignedIntegerField(models.IntegerField):
        def db_type(self, connection):
            return 'integer UNSIGNED'

    PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
        'AutoField': 'integer AUTO_INCREMENT',
        'BigAutoField': 'bigint AUTO_INCREMENT',
        'BinaryField': 'longblob',
        'BooleanField': 'bool',
        'CharField': 'varchar(%(max_length)s)',
        'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
        'DateField': 'date',
        'DateTimeField': 'datetime',
        'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
        'DurationField': 'bigint',
        'FileField': 'varchar(%(max_length)s)',
        'FilePathField': 'varchar(%(max_length)s)',
        'FloatField': 'double precision',
        'IntegerField': 'integer',
        'BigIntegerField': 'bigint',
        'IPAddressField': 'char(15)',
        'GenericIPAddressField': 'char(39)',
        'NullBooleanField': 'bool',
        'OneToOneField': 'integer',
        'PositiveIntegerField': 'integer UNSIGNED',
        'PositiveSmallIntegerField': 'smallint UNSIGNED',
        'SlugField': 'varchar(%(max_length)s)',
        'SmallIntegerField': 'smallint',
        'TextField': 'longtext',
        'TimeField': 'time',
        'UUIDField': 'char(32)',

BooleanField(Field)
    - 布尔值类型

NullBooleanField(Field):
    - 可以为空的布尔值

CharField(Field)
    - 字符类型
    - 必须提供max_length参数, max_length表示字符长度

TextField(Field)
    - 文本类型

EmailField(CharField):
    - 字符串类型,Django Admin以及ModelForm中提供验证机制

IPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制  (过期)

GenericIPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
    - 参数:
        protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
        unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"

URLField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
    - 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
    - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
    - 参数:
            path,                      文件夹路径
            match=None,                正则匹配
            recursive=False,           递归下面的文件夹
            allow_files=True,          允许文件
            allow_folders=False,       允许文件夹

FileField(Field)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
        width_field=None,   上传图片的高度保存的数据库字段名(字符串)
        height_field=None   上传图片的宽度保存的数据库字段名(字符串)

DateTimeField(DateField)
    - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

DateField(DateTimeCheckMixin, Field)
    - 日期格式      YYYY-MM-DD

TimeField(DateTimeCheckMixin, Field)
    - 时间格式      HH:MM[:ss[.uuuuuu]]

DurationField(Field)
    - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

FloatField(Field)
    - 浮点型

DecimalField(Field)
    - 10进制小数
    - 参数:
        max_digits,小数总长度
        decimal_places,小数位长度

BinaryField(Field)
    - 二进制类型

上述提供的字段类型主要分为四类:

  • 字符串
  • 数字
  • 时间
  • 二进制

注意:我们可以看到一些URLField、EmailField,看似有语法验证的功能,但实际上就是一个普通的CharField。

为什么要提供这种特殊的字段,是因为Django为我们提供的admin后台管理组件要使用他们,当我们创建这样的字段时,如果使用admin进行数据库后台管理,那么页面上就会进行语法验证(对于数据库还是varchar)。

AutoField:

from django.db import models


class UserInfo(models.Model):
    # 手工创建自增id列(手工创建了,Django就不会自动创建)
    uid = models.AutoField(primary_key=True)

    # 生成UserInfo表,里面含有username和password两列
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=80)

使用AutoField可以手工创建自增id列,必须指定为主键。

十二、ORM字段的参数

ORM提供的字段类型,可以使用以下参数:

null                数据库中字段是否可以为空  eg. null=True 允许为空
db_column           数据库中字段的列名  eg. db_column='c_name' 设置列名为c_name
db_tablespace
default             数据库中字段的默认值  eg. default='content' 默认值为content
primary_key         数据库中字段是否为主键    eg.  primary_key=True 设置为主键
db_index            数据库中字段是否可以建立索引  eg. db_index=True 建立索引
unique              数据库中字段是否可以建立唯一索引  eg. unique=True 唯一索引
unique_for_date     数据库中字段【日期】部分是否可以建立唯一索引  eg. 2019年1月1日11时11分11秒 只对其中的日期做索引
unique_for_month    数据库中字段【月】部分是否可以建立唯一索引   eg. 只对月份做索引
unique_for_year     数据库中字段【年】部分是否可以建立唯一索引   eg. 只对年份做索引

auto_now            用于时间字段,在更新数据时自动插入当前时间(也包括新插入数据时)
                    eg.
                        uptime = models.DateTimeTield(auto_now=True, null=True)
                    要使该时间更新,不能使用:
                        obj = models.UserInfo.objects.filter(id=1).update(password='123')
                    必须使用:
                        obj = models.UserInfo.objects.filter(id=1).first()
                        obj.password="123"
                        obj.save()  # 此时uptime才会更新

auto_now_add        用于时间字段,在插入数据时自动插入当前时间
                    eg.
                        ctime = models.DateTimeTield(auto_now_add=True, null=True)

verbose_name        Admin中显示的字段名称  eg. verbose_name="用户名" 页面上就不会显示"username",而是显示"用户名"
blank               Admin中是否允许用户输入为空  eg. blank=True 页面上不填内容提交时,不会进行内容为空的提示
editable            Admin中是否可以编辑  eg. editable=False 页面上相应输入框消失(不可编辑)
help_text           Admin中该字段的提示信息  eg.  help_text="输入密码" 页面上会在密码输入框下显示"输入密码"提示
choices             Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
                    如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)

error_messages      自定义错误信息(字典类型),从而定制想要显示的错误信息;
                    字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
                    如:{'null': "不能为空.", 'invalid': '格式错误'}

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_112233d+', message='又错误了', code='c2'),
                                EmailValidator(message='又错误了', code='c3'), ]
                        )

其中有很大一部分是专门用于操作Django admin后台管理页面的。

十三、ORM外键

例如有两个表:用户表和部门表

from django.db import models


class GroupInfo(models.Model):
    gid = models.AutoField(primary_key=True)
    groupname = models.CharField(max_length=32)


class UserInfo(models.Model):
    # 手工创建自增id列(手工创建了,Django就不会自动创建)
    uid = models.AutoField(primary_key=True)
    # 添加一个列,使用外键,外键为GroupInfo表中的gid列(该列的值必须是唯一的),如果不写to_field则默认使用主键,默认值为1
    group = models.ForeignKey('GroupInfo', to_field='gid', default=1)
    # 生成UserInfo表,里面含有username和password两列
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=80)

特别注意:虽然我们定义外键时使用的变量名叫"group",但Django帮我们在表里生成的实际的列名叫做"group_id"。如下图所示:

当我们查询某个用户信息时,联合UserGroup表一起查询该用户所在group:

def orm_test(request):
    # 向UserGroup表中插入一个group (gid=1,groupname='Dev')
    models.UserGroup.objects.create(groupname="Dev")
    # 向UserInfo表中插入一个user (uid=1,username='Leo',password='123',group_id=1),注意这里要使用真实列名group_id
    models.UserInfo.objects.create(username="Leo", password='123', group_id=1)

    # 联合查询UserInfo以及UserGroup
    # 获取第一个用户,这里只有一个用户Leo
    obj = models.UserInfo.objects.filter(uid=1).first()
    # 打印用户的用户名、密码(这些内容都在UserInfo表中)
    print(obj.username)
    print(obj.password)
    # 这里注意,group是外键,指向UserGroup表,所以这里的group属性是一个对象(UserGroup表的一条记录),我们通过该对象来获取groupname
    print(obj.group.groupname)

    return HttpResponse('ok')

当我们查询一个带有外键的表的记录时,Django会自动关联查询外键指向的表(UserGroup),并将对应的记录存放到一个对象中(UserGroup一条记录),我们通过该记录可以获取外键指向的表中的信息(groupname)。

def orm_test(request):
    # 查询数据,返回的result是QuerySet类型,实际上相当于一个列表,每个元素是一个对象,相当于 select * from cmdb_userinfo;
    result = models.UserInfo.objects.all()
    # 便利result中的每个对象,其中每列对应一个成员属性
    for row in result:
        print(row.id, row.username, row.password)
    return HttpResponse('ok')
原文地址:https://www.cnblogs.com/leokale-zz/p/12056917.html