Django之Models操作

时间:Oct. 9, 2017 分类:

目录:

module是django提供的ORM模型,其他的python的web框架就只能使用像SQLAlchemy来实现,django的提供的module比SQLAlchemy强大,并且django框架的其他方法也可以获取module或根据module来实现一些功能。

别人问我,django是什么,我都告诉他django是python最屌的框架,没有之一。

module连接数据库

对于module,操作ORM首先要有数据库,默认使用SQLite3,还支持MySQL,Oracle和PostgreSQL

sqlite的引擎为django.db.backends.sqlite3,mysql的引擎为django.db.backends.mysql

配置在setting中

DATABASES = {
    'default': {
    'ENGINE': 'django.db.backends.mysql',
    'NAME':'dbname',
    'USER': 'root',
    'PASSWORD': 'password',
    'HOST': 'hostname',
    'PORT': '3306',
    }
}

需要安装MySQLdb模块,如果想使用例如pymysql也是可以的,需要在__init__.py文件中配置一下,可以参考

创建表

表结构

创建表的时候需要继承models.Model

from django.db import models

class userinfo(models.Model):
    name = models.CharField(max_length=30)
    password = models.CharField(max_length=30)
    introduce = models.TextField()

表字段

  • AutoField(Field) int类型,如果要设置为自增列需要添加参数primary_key=True
  • BigAutoField(AutoField) bigint类型,如果要设置为自增列需要添加参数primary_key=True,对于django,如果表没有自增列,会进行自动创建名为id的自增整数列
  • SmallIntegerField(IntegerField) 小整数,范围为 -32768~32767
  • PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)正小整数,范围为0~32767
  • IntegerField(Field) 整数列,有符号,范围为-2147483648~2147483647
  • PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) 正整数,范围为0~2147483647
  • BigIntegerField(IntegerField) 长整型,有符号,范围为-9223372036854775808~9223372036854775807
  • BooleanField(Field) 布尔值类型
  • NullBooleanField(Field) 可以为空的布尔值类型
  • CharField(Field)字符串类型,必须提供表示字符长度的max_length参数
  • TextField(Field)文本类型
  • EmailField(CharField)字符串类型,django admin和module form中提供验证机制
  • IPAddressField(Field)字符串类型,django admin和module form中提供验证IPV4的机制
  • GenericIPAddressField(Field)字符串类型,django admin和module form中提供验证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和module form中提供验证url的机制
  • SlugField(CharField)字符串类型,django admin和module form中提供验证字母、数字、下划线、连接符(减号)的机制
  • CommaSeparatedIntegerField(CharField)字符串类型,django admin和module form中提供验证格式为逗号分隔的数字
  • FilePathField(Field)字符串类型,django admin和module form中提供读取文件夹下文件的功能,参数有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上传图片的宽度保存的数据库字段名(字符串)
  • UUIDField(Field)字符串类型,django admin和module form中提供验证UUID格式的机制
  • 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)二进制类型

字段参数

数据库字段参数

  • null 数据库中字段是否可以为空
  • db_column 数据库中字段的列名重新命名name = models.CharField(max_length=30),在数据库中列名为name,如果想使用别的名称就用这个参数
  • db_tablespace
  • default 数据库中字段的默认值
  • primary_key 数据库中字段是否为主键
  • db_index 数据库中字段是否可以建立索引
  • unique 数据库中字段是否可以建立唯一索引
  • unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
  • unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
  • unique_for_year 数据库中字段【年】部分是否可以建立唯一索引
  • auto_now 数据库中该字段的操作时间,创建和修改都算操作
  • auto_now_add 数据库中该字段的创建时间

admin参数

  • verbose_name Admin中显示的字段名称
  • blank Admin中是否允许用户输入为空,用于admin的表单验证功能
  • editable Admin中是否可以编辑
  • help_text Admin中该字段的提示信息
  • choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作,如:favor= models.IntegerField(choices=[(0, 'python'),(1, 'linux'),],default=1),由django在内存中创建,与数据库无关

form表单参数

  • error_messages 自定义错误信息
  • validators 自定义错误验证

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

validators为(列表类型),从而定制想要的验证规则

示例代码,会在后边form表单验证的时候进行具体实现方式

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'), ]
    )

表元数据信息

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

更多的可以参考官方文档

创建单表

modules.py代码

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models

# Create your models here.

class userinfo(models.Model):
    name = models.CharField(max_length=30)
    password = models.CharField(max_length=30)
    introduce = models.TextField()

setting.py代码中的INSTALLED_APPS列表中添加对应的app

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

同步表结构到数据库

C:\pycode\mysite>python manage.py makemigrations
Migrations for 'app':
  app\migrations\0001_initial.py
    - Create model userinfo

C:\pycode\mysite>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, app, auth, 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 app.0001_initial... 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 sessions.0001_initial... OK

在数据库中查询一下创建的表

mysql> use django_test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+----------------------------+
| Tables_in_django_test      |
+----------------------------+
| app_userinfo               |
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
11 rows in set (0.00 sec)

mysql> show create table app_userinfo \G;
*************************** 1. row ***************************
       Table: app_userinfo
Create Table: CREATE TABLE `app_userinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `password` varchar(30) NOT NULL,
  `introduce` longtext NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR: 
No query specified

可以看到会为userinfo表创建一个id的非空自增字段

表操作

基本操作

增删改查

用于展示的代码

原理是通过请求来触发增删改查操作

url配置

from django.conf.urls import url, include
from . import views

urlpatterns = [
    url(r'articles/(?P<year>[0-9]{4})/$', views.year_archive,{ 'mouth': '09' }),
    url(r'add/$', views.add),
]

views逻辑

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.shortcuts import render, HttpResponse
from app.models import userinfo

def add(request):
    if request.method == 'GET':
        return render(request, "index.html")
    if request.method == 'POST':
        name = request.POST.get('name')
        password = request.POST.get('password')
        obj = userinfo.objects.create(name=name,password=password)
        obj.save()
        return HttpResponse('yes')
    else:
        return HttpResponse('no')

models表结构,参考创建单表

index的html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/why.css">
</head>
<body>
<form action="/app/add/" method="POST" >
    <input type="text" name="name">
    <input type="text" name="password">
    <input type="submit" value="提交">
</form>
</body>
</html>

然后重启后访问

提交结果

数据库中也可以查询到结果

mysql> select * from app_userinfo;
+----+------+----------+-----------+
| id | name | password | introduce |
+----+------+----------+-----------+
|  1 | why  | 123456   |           |
+----+------+----------+-----------+
1 row in set (0.00 sec)

需要注意的是,这里<form action="/app/add/" method="POST" >后边一定要有/,如果没有加/会报错误

主要的原因是django默认情况get请求会给url加上末尾的/,但是post的请求到没有/的url会报错的。

默认情况,APPEND_SLASH=True,设置项为是否开启URL访问地址后面不为/跳转至带有/的路径

刚才我们是from app.models import userinfo的,就可以直接使用userinfo。

models.Tb1.objects.create(c1='xx', c2='oo')  增加一条数据,可以接受字典类型数据 **kwargs
obj = models.Tb1(c1='xx', c2='oo')
obj.save()

models.Tb1.objects.get(id=123)         # 获取单条数据,不存在则报错(不建议)
models.Tb1.objects.all()               # 获取全部
models.Tb1.objects.filter(name='seven') # 获取指定条件的数据

因为查询到的是一个对象,支持分片,例如

models.Tb1.objects.all()[10:20]

参考上一个例子,获取到的结果

obj = userinfo.objects.all()
print obj
print type(obj)
print obj[0]
print type(obj[0])
print obj[0].name
print type(obj[0].name)

获取结果

<QuerySet [<userinfo: userinfo object>]>
<class 'django.db.models.query.QuerySet'>
userinfo object
<class 'app.models.userinfo'>
why
<type 'unicode'>

即all获取的是对象的列表。

上大学的时候代码虽然写的不咋地,增删改查这个顺序倒是记得很熟,因为删和改都是要在查的基础上完成的,就把查提前了

models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据

models.Tb1.objects.filter(name='seven').update(gender='0')  # 将指定条件的数据更新,均支持 **kwargs

或者

obj = models.Tb1.objects.get(id=1)
obj.c1 = '111'
obj.save()                                                 # 修改单条数据

获取查询的SQL

obj = models.Tb1.objects.all()
obj.query

查的进阶操作

获取个数

models.Tb1.objects.filter(name='seven').count()

大于,小于

models.Tb1.objects.filter(id__gt=1)              # 获取id大于1的值
models.Tb1.objects.filter(id__gte=1)              # 获取id大于等于1的值
models.Tb1.objects.filter(id__lt=10)             # 获取id小于10的值
models.Tb1.objects.filter(id__lte=10)             # 获取id小于10的值
models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值

in

models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in

isnull

Entry.objects.filter(pub_date__isnull=True)

contains

models.Tb1.objects.filter(name__contains="ven")
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
models.Tb1.objects.exclude(name__icontains="ven")

其他类似的有startswith,istartswith, endswith, iendswith

一些方法和SQL语句对应关系也可以参考

operators = {
        'exact': '= %s',
        'iexact': 'LIKE %s',
        'contains': 'LIKE BINARY %s',
        'icontains': 'LIKE %s',
        'regex': 'REGEXP BINARY %s',
        'iregex': 'REGEXP %s',
        'gt': '> %s',
        'gte': '>= %s',
        'lt': '< %s',
        'lte': '<= %s',
        'startswith': 'LIKE BINARY %s',
        'endswith': 'LIKE BINARY %s',
        'istartswith': 'LIKE %s',
        'iendswith': 'LIKE %s',
    }

范围(range)

models.Tb1.objects.filter(id__range=[1, 2])   # 范围betwwen and

排序(order by)

models.Tb1.objects.filter(name='seven').order_by('id')    # 正序(asc)
models.Tb1.objects.filter(name='seven').order_by('-id')   # 倒叙(desc)

分组(group by)

from django.db.models import Count, Min, Max, Sum
models.Tb1.objects.filter(c1=1).values('id').annotate(c=Count('num'))

对应的SQL语句为

SELECT "app01_tb1"."id", COUNT("app01_tb1"."num") AS "c" FROM "app01_tb1" WHERE "app01_tb1"."c1" = 1 GROUP BY "app01_tb1"."id"

这里values('id').annotate(c=Count('num'))是连在一起的,意思是根据id进行分组,拿到num列的个数

limit 、offset

regex正则匹配,iregex 不区分大小写

Entry.objects.get(title__regex=r'^(An?|The)+')
Entry.objects.get(title__iregex=r'^(an?|the)+')

date

Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

year

Entry.objects.filter(pub_date__year=2005)
Entry.
objects.filter(pub_date__year__gte=2005)

month

Entry.objects.filter(pub_date__month=12)
Entry.objects.filter(pub_date__month__gte=6)

day

Entry.objects.filter(pub_date__day=3)
Entry.objects.filter(pub_date__day__gte=3)

week_day

Entry.objects.filter(pub_date__week_day=2)
Entry.objects.filter(pub_date__week_day__gte=2)

hour

Event.objects.filter(timestamp__hour=23)
Event.objects.filter(time__hour=5)
Event.objects.filter(timestamp__hour__gte=12)

minute

Event.objects.filter(timestamp__minute=29)
Event.objects.filter(time__minute=46)
Event.objects.filter(timestamp__minute__gte=29)

second

Event.objects.filter(timestamp__second=31)
Event.objects.filter(time__second=2)
Event.objects.filter(timestamp__second__gte=31)

其他操作

extra

extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
   Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
   Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
   Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
   Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

F

F操作更多的是使在update的时候在字段数据的基础上进行操作,例如涨价,打折等

from django.db.models import F
models.Tb1.objects.update(num=F('num')+1)

Q

Q操作是为了实现和与或关系的问题

方式一:

Q(nid__gt=10)
Q(nid=8) | Q(nid__gt=10)
Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')

方式二:

con = Q()
q1 = Q()
q1.connector = 'OR'
q1.children.append(('id', 1))
q1.children.append(('id', 10))
q1.children.append(('id', 9))
q2 = Q()
q2.connector = 'OR'
q2.children.append(('c1', 1))
q2.children.append(('c1', 10))
q2.children.append(('c1', 9))
con.add(q1, 'AND')
con.add(q2, 'AND')

models.Tb1.objects.filter(con)

执行原生SQL

from django.db import connection, connections
cursor = connection.cursor()  # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone()

连表结构

连表结构一般有三种

  • 一对多:models.ForeignKey(其他表)
  • 多对多:models.ManyToManyField(其他表)
  • 一对一:models.OneToOneField(其他表)

这三种连表结构的应用场景

  • 一对多:当一张表中创建一行数据时,有一个单选的下拉框(可以被重复选择) 例如:创建用户信息时候,需要选择一个用户类型【普通用户】【金牌用户】【铂金用户】等。
  • 多对多:在某表中创建一行数据是,有一个可以多选的下拉框 例如:创建用户信息,需要为用户指定多个爱好
  • 一对一:在某表中创建一行数据时,有一个单选的下拉框(下拉框中的内容被用过一次就消失了) 例如:原有含10列数据的一张表保存相关信息,经过一段时间之后,10列无法满足需求,需要为原来的表再添加5列数据

django建立联表

model代码

class UserProfile(models.Model):
    user_info = models.OneToOneField('UserInfo')
    username = models.CharField(max_length=64)
    password = models.CharField(max_length=64)

    def __unicode__(self):
        return self.username


class UserInfo(models.Model):
    user_type_choice = (
        (0, u'普通用户'),
        (1, u'高级用户'),
    )
    user_type = models.IntegerField(choices=user_type_choice)
    name = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
    address = models.CharField(max_length=128)

    def __unicode__(self):
        return self.name


class UserGroup(models.Model):

    caption = models.CharField(max_length=64)
    user_info = models.ManyToManyField('UserInfo')
    def __unicode__(self):
        return self.caption


class Host(models.Model):
    hostname = models.CharField(max_length=64)
    ip = models.GenericIPAddressField()
    user_group = models.ForeignKey('UserGroup')

    def __unicode__(self):
        return self.hostname

上述表结构用ER图来表示为

更多用于外键参数

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,              # 默认创建第三张表时,数据库中表的名称

数据库建表情况

数据库中表

mysql> show tables;
+----------------------------+
| Tables_in_django_test      |
+----------------------------+
| app_host                   |
| app_usergroup              |
| app_usergroup_user_info    |
| app_userinfo               |
| app_userprofile            |
| auth_group                 |
| auth_group_permissions     |
| auth_permission            |
| auth_user                  |
| auth_user_groups           |
| auth_user_user_permissions |
| django_admin_log           |
| django_content_type        |
| django_migrations          |
| django_session             |
+----------------------------+
15 rows in set (0.00 sec)

详细各表创建语句

app_userprofile

mysql> show create table app_userprofile \G;
*************************** 1. row ***************************
       Table: app_userprofile
Create Table: CREATE TABLE `app_userprofile` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `password` varchar(64) NOT NULL,
  `user_info_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_info_id` (`user_info_id`),
  CONSTRAINT `app_userprofile_user_info_id_18dfdc0a_fk_app_userinfo_id` FOREIGN KEY (`user_info_id`) REFERENCES `app_userinfo` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR: 
No query specified

可以看到app_userprofile除了类中的两个字段,django自动添加了主键索引的id字段和用于一对一的user_info_id字段,UNIQUE KEY user_info_id (user_info_id)创建唯一索引,外键字段为app_userinfoid

app_userinfo

mysql> show create table app_userinfo \G;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    84
Current database: django_test

*************************** 1. row ***************************
       Table: app_userinfo
Create Table: CREATE TABLE `app_userinfo` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `address` varchar(128) NOT NULL,
  `email` varchar(32) NOT NULL,
  `user_type` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR: 
No query specified

可以看到对于app_userinfo,django仅仅添加了主键索引的id字段

app_usergroup_user_info

mysql> show create table app_usergroup_user_info \G;
*************************** 1. row ***************************
       Table: app_usergroup_user_info
Create Table: CREATE TABLE `app_usergroup_user_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `usergroup_id` int(11) NOT NULL,
  `userinfo_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `app_usergroup_user_info_usergroup_id_userinfo_id_d8c7ee6e_uniq` (`usergroup_id`,`userinfo_id`),
  KEY `app_usergroup_user_info_userinfo_id_1cee34b5_fk_app_userinfo_id` (`userinfo_id`),
  CONSTRAINT `app_usergroup_user_i_usergroup_id_f54ab88a_fk_app_userg` FOREIGN KEY (`usergroup_id`) REFERENCES `app_usergroup` (`id`),
  CONSTRAINT `app_usergroup_user_info_userinfo_id_1cee34b5_fk_app_userinfo_id` FOREIGN KEY (`userinfo_id`) REFERENCES `app_userinfo` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR: 
No query specified

app_usergroup_user_info是django自动生成的用于映射多对多关系的表,有自增主键id,usergroup_idusergroup_id字段,分别为app_usergroupapp_usergroup字段的外键,创建了usergroup_iduserinfo_id联合唯一索引

app_usergroup

mysql> show create table app_usergroup \G;
*************************** 1. row ***************************
       Table: app_usergroup
Create Table: CREATE TABLE `app_usergroup` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `caption` varchar(64) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR: 
No query specified

可以看到对于app_usergroup,django仅仅添加了主键索引的id字段

app_host

mysql> show create table app_host \G;
*************************** 1. row ***************************
       Table: app_host
Create Table: CREATE TABLE `app_host` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `hostname` varchar(64) NOT NULL,
  `ip` char(39) NOT NULL,
  `user_group_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `app_host_user_group_id_9cc9827b_fk_app_usergroup_id` (`user_group_id`),
  CONSTRAINT `app_host_user_group_id_9cc9827b_fk_app_usergroup_id` FOREIGN KEY (`user_group_id`) REFERENCES `app_usergroup` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR: 
No query specified

可以看到对于app_host,和app_userprofile表类似,只不过user_group_id不是唯一的索引。

对于多对多的三种表创建方式

直接通过models的ManyToManyField方法

参考上述方式中的方法

通过SQLAlchemy的实现方式直接创建第三张表

参考SQLAlchemy博文中创建方式

通过models的ManyToManyField方法,然后定义through来实现对第三张表自定义字段

以上的方式ManyToManyField非常省事,而创建第三张表可以自行添加字段

这里主要是记录一下,如何通过django的ManyToManyField方法和through来创建第三张表的自定义字段。

through字段的值是第三张表的表名

class UserGroup(models.Model):

    caption = models.CharField(max_length=64)
    user_info = models.ManyToManyField('UserInfo', through='HostToUsergroup')
    def __unicode__(self):
        return self.caption

然后在HostToUsergroup定义多对多关系的字段和自定义字段,这样依然可以使用django的model的一些内置方法。

填充数据

普通填充方式

from app.models import *
省略方法
        userinfo1 = UserInfo.objects.create(name='why',email='why@whysdomain.com',address='xierqi',user_type=1)
        userinfo2 = UserInfo.objects.create(name='mb',email='mb@whysdomain.com',address='xierqi',user_type=0)
        userprofile1 = UserProfile.objects.create(user_info_id=1,username='wanghongyu',password='123456')
        userprofile2 = UserProfile.objects.create(user_info_id=2,username='mabiao',password='123456')
        usergroup1 = UserGroup.objects.create(caption='ops')
        usergroup2 = UserGroup.objects.create(caption='dev')
        host1 = Host.objects.create(user_group_id=1,hostname='ops1.whysdomain.com',ip='10.30.100.1')
        host2 = Host.objects.create(user_group_id=1,hostname='ops2.whysdomain.com',ip='10.30.100.2')
        host3 = Host.objects.create(user_group_id=1,hostname='ops3.whysdomain.com',ip='10.30.100.3')
        host4 = Host.objects.create(user_group_id=2,hostname='dev1.whysdomain.com',ip='10.30.101.1')
        host5 = Host.objects.create(user_group_id=2,hostname='dev2.whysdomain.com',ip='10.30.101.2')

当然也可以批量添加

通过bulk_create批量添加数据

我们上述情况就可以认为是这个方式添加

for i in resultlist:
    p = Account(name=i) 
    p.save()

使用django.db.models.query.QuerySet.bulk_create()批量创建对象,减少SQL查询次数

querysetlist=[]
for i in resultlist:
    querysetlist.append(Account(name=i))        
Account.objects.bulk_create(querysetlist)

对于上述就可以换成

        host1 = Host(user_group_id=1,hostname='ops1.whysdomain.com',ip='10.30.100.1')
        host2 = Host(user_group_id=1,hostname='ops2.whysdomain.com',ip='10.30.100.2')
        host3 = Host(user_group_id=1,hostname='ops3.whysdomain.com',ip='10.30.100.3')
        host4 = Host(user_group_id=2,hostname='dev1.whysdomain.com',ip='10.30.101.1')
        host5 = Host(user_group_id=2,hostname='dev2.whysdomain.com',ip='10.30.101.2')
        Host.objects.bulk_create([host1,host2,host3,host4,host5])

填充完成的数据

mysql> select * from app_userinfo;
+----+------+---------+--------------------+-----------+
| id | name | address | email              | user_type |
+----+------+---------+--------------------+-----------+
|  1 | why  | xierqi  | why@whysdomain.com |         1 |
|  2 | mb   | xierqi  | mb@whysdomain.com  |         0 |
+----+------+---------+--------------------+-----------+
2 rows in set (0.00 sec)

mysql> select * from app_userprofile;
+----+------------+----------+--------------+
| id | username   | password | user_info_id |
+----+------------+----------+--------------+
|  1 | wanghongyu | 123456   |            1 |
|  2 | mabiao     | 123456   |            2 |
+----+------------+----------+--------------+
2 rows in set (0.00 sec)

mysql> select * from app_usergroup;
+----+---------+
| id | caption |
+----+---------+
|  1 | ops     |
|  2 | dev     |
+----+---------+
2 rows in set (0.00 sec)

mysql> select * from app_host;
+----+---------------------+-------------+---------------+
| id | hostname            | ip          | user_group_id |
+----+---------------------+-------------+---------------+
|  1 | ops1.whysdomain.com | 10.30.100.1 |             1 |
|  2 | ops2.whysdomain.com | 10.30.100.2 |             1 |
|  3 | ops3.whysdomain.com | 10.30.100.3 |             1 |
|  4 | dev1.whysdomain.com | 10.30.101.1 |             2 |
|  5 | dev2.whysdomain.com | 10.30.101.2 |             2 |
+----+---------------------+-------------+---------------+
5 rows in set (0.00 sec)

一对一连表操作

first方法取值

        user_info_obj = UserInfo.objects.filter(id=1).first()
        print type(user_info_obj)
        print user_info_obj.user_type
        print user_info_obj.get_user_type_display()
        print user_info_obj.userprofile.password

user_info_obj获取的为UserInfo对象,可以通过该对象获取到对象的字段,例如user_info_obj.user_type,对于有描述的可以通过get_user_type_display()方法获取,而对于关联的userprofile表中的数据也可以进行查询,例如user_info_obj.userprofile.password

取值结果

<class 'app.models.UserInfo'>
1
高级用户                                         
123456

这是通过first获取的,如果通过all方法获取到的就是对象的列表

all方法

user_info_obj = UserInfo.objects.all()
        print type(user_info_obj)
        print user_info_obj
        for i in user_info_obj:
            print type(i)
            print i.user_type
            print i.get_user_type_display()
            print i.userprofile.password

获取结果

<class 'django.db.models.query.QuerySet'>
<QuerySet [<UserInfo: why>, <UserInfo: mb>]>
<class 'app.models.UserInfo'>
1
高级用户                                         
123456
<class 'app.models.UserInfo'>
0
普通用户                                         
123456

all获取的是一个可迭代的对象

连表查询

方式是通过values方法查询的时候通过"连表表明__字段"来取值

        user_info_obj = UserInfo.objects.values('email', 'userprofile__username').all()
        print type(user_info_obj)
        for i in user_info_obj:
            print type(i)
            print i.keys()
            print i.values()
        return HttpResponse('yes')

获取结果

<class 'django.db.models.query.QuerySet'>
<type 'dict'>
[u'email', u'userprofile__username']
[u'why@whysdomain.com', u'wanghongyu']
<type 'dict'>
[u'email', u'userprofile__username']
[u'mb@whysdomain.com', u'mabiao']

可以看到在使用value方法的时候,可以通过表“表明__字段的方式进行连表取值”

关于正向查找和反向查找

以上都是反向查找,就是去查有外键表的数据,而正向查找,就是有外键的表去查找外键关联的表的字段,对于一对一,正向和反向查找都统一使用这种方式来实现。

        user_profilter_obj = UserProfile.objects.filter(id=1).first()
        print type(user_profilter_obj)
        print user_profilter_obj.username
        print user_profilter_obj.user_info.email

获取结果

<class 'app.models.UserProfile'>
wanghongyu
why@whysdomain.com

不同的是没有外键的表进行反向查找的时候,使用"表对象.有外键表名.有外键表字段"来获取外键字段

而有外键的表进行正向查找的时候,使用"表对象.类中外键字段.关联表字段",即user_profilter_obj.user_info.email,这个user_info是UserProfile类中的user_info = models.OneToOneField('UserInfo')

一对多操作

一对多的关系主要在usergroup和host之间,一个usergroup可以对应多个host,外键在host表中

正向查找

正向查找就是在host表中查usergroup表中的数据

        host_obj = Host.objects.filter(id=1).first()
        print host_obj.user_group.caption

获取结果

ops

可以看到进行正向查找和一对一的时候是一样的。

!!!!!!!注意,以下使用的user_group是Host_info中定义的那个user_group

        host_obj = Host.objects.filter(user_group__caption='ops').all()
        print host_obj

获取结果

<QuerySet [<Host: ops1.whysdomain.com>, <Host: ops2.whysdomain.com>, <Host: ops3.whysdomain.com>]>

如果通过values方法也能直接过去到caption字段的值

        host_obj = Host.objects.filter(user_group__caption='ops').values('user_group__caption')
        print host_obj

获取结果

<QuerySet [{u'user_group__caption': u'ops'}, {u'user_group__caption': u'ops'}, {u'user_group__caption': u'ops'}]>

对于使用all()和不使用all()获取到的都是列表的形式,而如果用first()取到的是一个字典

        host_obj = Host.objects.filter(user_group__caption='ops').values('user_group__caption').first()
        print host_obj

获取结果

{u'user_group__caption': u'ops'}

反向查找

反向查找就是在usergroup表中查host表中的数据

        user_group_obj = UserGroup.objects.filter(id=1).first()
        print user_group_obj.host_set.all()
        for i in user_group_obj.host_set.all():
            print i.hostname

获取结果

<QuerySet [<Host: ops1.whysdomain.com>, <Host: ops2.whysdomain.com>, <Host: ops3.whysdomain.com>]>
ops1.whysdomain.com
ops2.whysdomain.com
ops3.whysdomain.com

对于使用all()和不使用all()获取到的都是列表的形式,而如果用first()取到的是一个字典

        user_group_obj = UserGroup.objects.filter(host__hostname='dev1.whysdomain.com').first()
        print user_group_obj.caption

获取结果

dev

通过values方法

        user_group_obj = UserGroup.objects.filter(host__hostname='dev1.whysdomain.com').values('host__hostname')
        print user_group_obj

获取结果

<QuerySet [{u'host__hostname': u'dev1.whysdomain.com'}]>

对于使用all()和不使用all()获取到的都是列表的形式,而如果用first()取到的是一个字典

        user_group_obj = UserGroup.objects.filter(host__hostname='dev1.whysdomain.com').values('host__hostname').first()
        print user_group_obj

获取结果

{u'host__hostname': u'dev1.whysdomain.com'}

对于反向查找,也就是多的一方会有以下操作

创建了一个新的usergroup

        UserGroup.objects.create(caption='test')
        user_group_obj = UserGroup.objects.all().values('caption','host__hostname')
        print user_group_obj

获取结果

<QuerySet [{u'caption': u'ops', u'host__hostname': u'ops1.whysdomain.com'}, {u'caption': u'ops', u'host__hostname': u'ops2.whysdomain.com'}, {u'caption': u'ops', u'host__hostname': u'ops3.whysdomain.com'}, {u'caption': u'dev', u'host__hostname': u'dev1.whysdomain.com'}, {u'caption': u'dev', u'host__hostname': u'dev2.whysdomain.com'}, {u'caption': u'test', u'host__hostname': None}]>

对于没有可以对应的test,对应查出来的hostname为None

多对多操作

添加多对多数据

如果只是简单的使用了ManyToMany的方法创建多对多,是没有第三张表这个类的,也就无法使用其create方法,当然如果是自行创建或者使用了thought字段关联了是可以使用create的。

也就是没有类对象可以使用

        user_info_obj = UserInfo.objects.get(name='why')
        print type(user_info_obj)
        user_info_objs = UserInfo.objects.all()
        print type(user_info_objs)
        group_obj = UserGroup.objects.get(caption='ops')
        print type(group_obj)
        group_objs = UserGroup.objects.all()
        print type(group_objs)
        return HttpResponse('yes')

获取到的结果为

<class 'app.models.UserInfo'>
<class 'django.db.models.query.QuerySet'>
<class 'app.models.UserGroup'>
<class 'django.db.models.query.QuerySet'>

可以看到user_info_objgroup_obj

添加数据

        user_info_obj = UserInfo.objects.get(name='why')
        user_info_objs = UserInfo.objects.all()
        group_obj = UserGroup.objects.get(caption='ops')
        group_objs = UserGroup.objects.all()
        group_obj.user_info.add(user_info_obj)

查询数据

mysql> select * from app_usergroup_user_info;
+----+--------------+-------------+
| id | usergroup_id | userinfo_id |
+----+--------------+-------------+
|  1 |            1 |           1 |
+----+--------------+-------------+
1 row in set (0.00 sec)

删除数据

group_obj.user_info.remove(user_info_obj)

查询数据

mysql> select * from app_usergroup_user_info;
Empty set (0.00 sec)

类中关联字段是在Group类中,可以直接使用user_info进行添加数据

group_obj.user_info.remove(*user_info_objs)

查询数据

mysql> select * from app_usergroup_user_info;
+----+--------------+-------------+
| id | usergroup_id | userinfo_id |
+----+--------------+-------------+
|  2 |            1 |           1 |
|  3 |            1 |           2 |
+----+--------------+-------------+
2 rows in set (0.00 sec)

同理删除数据

group_obj.user_info.remove(*user_info_objs)
mysql> select * from app_usergroup_user_info;
Empty set (0.00 sec)

对于userinfo就需要“表明_set”来进行添加数据

user_info_obj.usergroup_set.add(*group_objs)

查询数据

mysql> select * from app_usergroup_user_info;
+----+--------------+-------------+
| id | usergroup_id | userinfo_id |
+----+--------------+-------------+
|  4 |            1 |           1 |
|  5 |            2 |           1 |
|  6 |            3 |           1 |
+----+--------------+-------------+
3 rows in set (0.00 sec)

多对多的操作

添加数据

group_obj.user_info.add(user_info_obj)
group_obj.user_info.add(*user_info_objs)

删除数据

group_obj.user_info.remove(user_info_obj)
group_obj.user_info.remove(*user_info_objs)

添加数据

user_info_obj.usergroup_set.add(group_obj)
user_info_obj.usergroup_set.add(*group_objs)

删除数据

user_info_obj.usergroup_set.remove(group_obj)
user_info_obj.usergroup_set.remove(*group_objs)

获取数据

print group_obj.user_info.all()
print group_obj.user_info.all().filter(id=1)
print user_info_obj.usergroup_set.all()
print user_info_obj.usergroup_set.all().filter(caption='dev')
print user_info_obj.usergroup_set.all().filter(caption='ops')