03-django-typeidea-model

参考博客

Django 文档 | Django 文档 | Django
the5fire
表关系中的常用字段:on_delete=models.CASCADE,ForeignKey,db_constraint,related_name
执行python manage.py migrate 没有创建表的解决方案
QuerySet API 参考 | Django 文档 | Django
翻译了Django1.4数据库访问优化部分 | the5fire

创建项目及配置

创建项目结构

cd D:\github\python-django\2.typeidea
django-admin startproject typeidea
python manage.py runserver

拆分settings以适应不同运行环境

mkdir settings && cd settings && copy nul __init__.py
cd ..
move settings.py settings/base.py                      #将base.py抽象成settings的基类
cd settings && copy nul develop.py
  • 修改时区语言
# base.py文件中需要修改的部分,其他部分省略,
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
  • 开发环境使用指定数据库
#将base中的数据库配置粘贴到develop.py,并引入base.py的其他配置
# coding:utf-8
from .base import *   # NOQA
DEBUG = True    #从base.py剪贴过来
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR,'db.sqlite3'),
}
}
  • 修改manage.py和typeidea/wsgi.py。指定settings路径
替换:manage.py和typeidea/wsgi.py中
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'typeidea.settings')

为:
    profile = os.environ.get('TYPEIDEA_PROFILE', 'develop')   #如果环境变量有值就用环境的,没有就用develop
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "typeidea.settings.%s" % profile)

git仓库

配置.gitignore

  • 将不需要上传的git文件配置到.gitignore
*.pyc
*.swp
*.sqlite3

提交命令

git add .
git commit -m "初始提交"
git remote add origin <仓库url>
git push -u origin master

# 安全提交方式
git add -p       交互提交方式
git commit       进入编辑模式

编写Model层代码

  • model分为

创建app/blog

## 创建一个新分支
git checkout -b add-blog-app-model
cd typeidea/typeidea
python manage.py startapp blog

编写blog/model.py

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

# Create your models here.


class Category(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = (
        (STATUS_NORMAL, '正常'),
        (STATUS_DELETE, '删除'),
    )

    name = models.CharField(max_length=50, verbose_name="名称")
    status = models.PositiveBigIntegerField(default=STATUS_ITEMS,
                                            choices=STATUS_ITEMS, verbose_name="状态")
    is_nav = models.BooleanField(default=False, verbose_name="是否为导航")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")

    class Meta:
        verbose_name = verbose_name_plural = '分类'

class Tag(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = (
        (STATUS_NORMAL, '正常'),
        (STATUS_DELETE, '删除'),
    )

    name = models.CharField(max_length=255,verbose_name="标题")
    status = models.CharField(max_length=1024,blank=True,verbose_name="摘要")
    owner = models.ForeignKey(User,verbose_name="作者", on_delete=models.CASCADE)
    create_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")

    class Meta:
        verbose_name = verbose_name_plural = "标签"

class Post(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_DRAFT = 2
    STATUS_ITEMS = (
        (STATUS_NORMAL,'正常'),
        (STATUS_DELETE,'删除'),
        (STATUS_DRAFT,'草稿'),
    )

    title = models.CharField(max_length=255,verbose_name="标题")
    desc = models.CharField(max_length=1024,blank=True,verbose_name="摘要")
    content = models.TextField(verbose_name="正文",help_text="正文必须为MarkDown格式")
    status = models.PositiveIntegerField(default=STATUS_NORMAL,
        choices=STATUS_ITEMS,verbose_name="状态")
    category = models.ForeignKey(Category,verbose_name="分类", on_delete=models.CASCADE)
    tag = models.ManyToManyField(Tag,verbose_name="标签")
    owner = models.ForeignKey(User,verbose_name="作者", on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")

    class Meta:
        verbose_name = verbose_name_plural = "文章"
        ordering = ['-id']  # 根据id进行降序排列

创建app/config,编写model.py

from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Link(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = (
        (STATUS_NORMAL,'正常'),
        (STATUS_DELETE,'删除'),
    )
    title = models.CharField(max_length=50,verbose_name="标题")
    href = models.URLField(verbose_name="链接")      #默认长度为200
    status = models.PositiveIntegerField(default=1,choices=STATUS_ITEMS,
                                         verbose_name="状态")
    weight = models.PositiveIntegerField(default=1,choices=zip(range(1,6),range(1,6)),
                                         verbose_name="权重",
                                         help_text="权重靠前展示顺序靠前")
    owner = models.ForeignKey(User,verbose_name="作者", on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")

    class Meta:
        verbose_name = verbose_name_plural = "友链"

# 侧边栏
class SideBar():
    STATUS_SHOW = 1
    STATUS_HIDE = 0
    STATUS_ITEMS = (
        (STATUS_SHOW,'展示'),
        (STATUS_HIDE,'隐藏'),
    )
    SIDE_TYPE =(
        (1,'HTML'),
        (2,'最新文章'),
        (3,'最热文章'),
        (4,'最近评论'),
    )
    title = models.CharField(max_length=50,verbose_name="标题")
    display_type = models.PositiveIntegerField(default=1,choices=SIDE_TYPE,
                                               verbose_name="展示类型")
    content = models.CharField(max_length=500,blank=True,verbose_name="内容",
                              help_text="如果设置的不是HTML类型,可为空")
    status = models.PositiveIntegerField(default=STATUS_SHOW,choices=STATUS_ITEMS,
                                         verbose_name="状态")
    created_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")
    owner = models.ForeignKey(User,verbose_name="作者", on_delete=models.CASCADE)
    created_time = models.DateTimeField(auto_now_add=True,verbose_name="创建时间")

    class Meta:
        verbose_name = verbose_name_plural = "侧边栏"

创建评论app/comment,编写model.py

from django.db import models
from blog.models import Post


# Create your models here.

class Comment(models.Model):
    STATUS_NORMAL = 1
    STATUS_DELETE = 0
    STATUS_ITEMS = (
        (STATUS_NORMAL, '正常'),
        (STATUS_DELETE, '删除'),
    )

    target = models.ForeignKey(Post, verbose_name="评论", on_delete=models.CASCADE)
    content = models.CharField(max_length=2000, verbose_name="内容")
    nickname = models.CharField(max_length=50, verbose_name="昵称")
    website = models.URLField(verbose_name="网站")
    email = models.EmailField(verbose_name="邮箱")
    status = models.PositiveIntegerField(default=STATUS_NORMAL, choices=STATUS_ITEMS, verbose_name="状态")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")

    class Meta:
        verbose_name = verbose_name_plural = "评论"

配置INSTALLED_APPS

  • 编写完model之后,将创建的app配置到settings,使django可以识别到
  • 注意INSTALLED_APPS列表的顺序,static和templates模块寻找app资源时,按从上到下的app顺序查找
  • 开发中可以写一份路径和名称与admin(app)一样的来覆盖自带的,django可以重载


创建数据库

  • 创建迁移文件,就是把model定义的数据库信息迁移到数据库中
python manage.py makemigrations
  • 执行迁移操作
  • 数据库的名称是在settings/develop.py中配置的DATABASES里的default中的name
  • 使用其他类型数据库如mysql需要先建好数据库,通过这2个命令来建表
python manage.py migrate
  • 查看数据库,进入交互模式
python manage.py dbshell
  • 注意数据库库生成的位置


ORM基本概念

常用字段类型

数值型

- AutoField int(11):自增主键,django model默认提供,可以被重写
id = models.AutoField(primary_key=True)

- BooleanField tinyint(1):布尔类型字段,一般用于记录状态

- DecimalField decimal :  对数据精度要求高,支付金融相关
cash = models.DecimalField(max_digits=8,decimal_places=2,default=0,verbose_name="消费金额")
定义长度为8位,精度为2的数字。比如保存666.66这样的数字,max_digits=5,decimal_places=2

- IntegerField int(11):同AutoField一样,就是不自增

- PostivieIntegerField:只包含正整数

- SmallIntegerField smallint:小整数用到

字符型

CharField varchar              基础的varchar类型
URLField                       继承自CharFiled,实现了对url的特殊处理
UUIDField char(22)           在postgreSQL中使用的是uuid类型,其他存放生成的唯一id用char(32)
EmailField                        继承CharFiled,多了对email的特殊处理
FileField                            继承自CharField,多了对文件的处理,admin展示部分会生成一个可上传文件的按钮
TextField longtext            存放大容量文本,博客正文
ImageField                      继承自FileField,处理图片相关数据

日期类型

ForeginKey
OneToOneField
MangToManyField
外键和一对一其实是一种,只是一对一在外键字段上加了unique,多对多会创建一个中间表

参数

null              null,在数据库层面是否允许为空
blank              针对业务层面,该值是否允许为空
choices            配置字段choices,在admin页面上就可以看到对应的可选项展示
db_column           指定Field对应数据库哪个字段,一般是name对应数据库字段
db_index            索引配置,业务上需要经常作为查询条件的字段
default             默认值配置
editable            是否可编辑,默认True,如果不想将字段展示到页面上,可以配置为False
error_messages         用来自定义字段值校验失败时的异常提示,他是字典格式,key的选项可以为null、blank、invalid、invalid_choice、unique、unique_for_date
unique               唯一约束
unique_for_date       针对date(日期)的联合约束,比如我们一天只能有一篇django文章配置字段时配置参数:unique_for_date="created_time"

querySet对象

  • 数据库查询与更新,使用queryset
  • 通过给Model增加一个objects属性来提供数据操作的接口
  • queryset是懒加载的,当用到时才会执行数据库查询语句
  • 支持链式查询

下面2句不会执行数据库查询,只是拿到了queryset对象

  • 当执行print(availabled_posts)时,才会执行数据库查询
posts = Post.objects.all()    #拿到quertset对象,这个对象中包含了我们需要的数据,用到他时会去db中获取数据
availabled_posts= posts.filter(status=1)

# 链式调用:每个函数或者方法的执行结果返回对象本身,这样还能继续调用它的方法

常用的queryset接口

支持链式调用的接口

all 接口:相当于select * from table_name 语句
filter 接口:根据条件过滤,常用等于,不等于,大于,小于。比如能改成like查询
                  Model.objects.filter(content_contains="条件")
exclude 接口:同filter,只是相反的逻辑
reverse 接口:把queryset中的结果颠倒
distinct 接口:用来进行去重查询,产生select distint这样的查询
none 接口:返回空的queryset

不支持链式调用的接口

get 接口:Post.objects.get(id=1)用于查询id为1的文章,如果存在则直接返回对应的Post实例;如果不存在,抛出DoesNotExit异常
  try:
  post =Post.objects.get(id=1)
  except Post.DoesNotExist:
  # 做异常处理

create 接口:用来创建一个Model对象,比如post = Post.objects.create(title="一起学习")

get_or_create接口:根据条件查找,如果没有,就调用create创建

update_or_create接口:同get_or_create,只是用来更新

count接口:用于返回queryset有多少条记录,相当于select count(*) from table_name

lastest 接口:返回最新的一条记录,但需要在Model的Meta中定义:get_latest_by = <用来排序的字段>

earliest接口:同上,返回最早的一条记录

first接口:从当前QuerySet记录中获取第一条

last接口:同上,获取最后一条

exists接口:返回True,或者False,在数据库层面执行select(1)As "a" FROM table_name LIMIT 1的查询,用来判断queryset是否有数据最合适。不要用count

bulk_create接口:同create,用来批量创建记录

in_bulk接口:批量查询,接受2个参数id_list和 filed_name,可以通过Post.objects.in_bulk([1,2,3])查询出id为1、2、3的数据。返回结果是字典类型,字典类型的key为查询条件。

update接口:根据条件批量更新记录。比如Post.objects.filter(owner__name='the5fire').update(title='测试更新')

delete接口:同update,这个接口是用来根据条件批量删除记录。update和delete会触发django的signal

values接口:只需要返回某个字段的值,不需要model实例,可以使用
title_list = Post.objects.filter(category_id=1).values('title')
返回的结果包含dict的queryset,<queryset[{'title':xxx},]>

values_list接口:同values,但是直接返回的是包含元组的queryset
titles_list = Post.objects.filter(category=1).values_list('title')
返回的结果类似<queryset[('标题'),]>
如果只有一个字段,可以通过增加flat=True参数,便于后续处理
title_list = Post.objects.filter(category=1).values_list('title',flat=True)
for title in title_list:
    print(title)

进阶接口

defer接口:把不需要的字段做延迟加载,比如说获取到文章中除正文之外的其他字段。通过posts = Post.object.all().defer('content'),这样拿到的记录就不包含content部分
posts = Post.object.all().defer('content')
for post in posts:
    print(post.content)   #此时会执行数据查询,获取到content
N+1 问题:一条查询请求返回n条数据,一般由外键查询产生的比较多

only接口:同defer相反,比如指向获取title记录,就可以使用only

select_related接口:用来解决外键产生的N+1问题的方案。(只能解决一对多的关联关系)
posts = Post.objects.all()
for post in posts:         #产生数据库查询
    print(post.owner)    #产生额外的数据库查询
代码同上面类似,只是用的是owner
他的解决办法
posts = Post.objects.all().select_related('category')
for post in posts:                  #产生数据库查询,category数据也会一次性查询出来
    print(post.category)
prefetch_related 接口:准备多对多关系的数据,可以通过这个接口来避免N+1查询
比如,post和tag的关系可以通过这种方式来避免:
posts = Posts.object.all().prefetch_related('tag')
for post in posts:    #产生两条查询语句,分别查询post和tag
     print(post)

常用的字段查询

Post.objects.filter(content__contains='查询条件')中的contains就属于字段查询。

  • contians:包含,用来进行like查询
  • icontains:同contains,只是忽略大小写
  • exact:精确匹配
  • iexact:同exact,忽略小写
  • in:指定某个集合,比如Post.objects.filter(id__in=[1,2,3])相当于select * from blog_post where in (1,2,3);
  • gt:大于某个值
  • gte:大于某个值
  • lt:小于某个值
  • lte:小于等于每个值
  • startswith:以某个字符串开头,与contains类似,只会产生LIKE'<关键字>%'这样的sql
  • istartwith:同startswith,忽略大小写
  • endswith:以某个字符串结尾
  • iendswith:忽略大小写
  • range:范围查询,多用于时间范围,如Post.objects.filter(created_time__range=('2018-05-01','2018-06-01'))会产生这样的查询:select 。。。where created_time BETWEEN '2018-05-01' AND '2018-06-01';

关于日期类的查询还有很多,比如date、year和month等,具体等需要时查文档即可。

进阶查询

满足复杂查询,比如select ... where id = 1 or id = 2 这样的查询,就需要进阶查询

  • F表达式
保证数据库的原子性
post = Post.objects.get(id=1)
post.pv = post.pv + 1
pos.save()

from django.db.models import F
post = Post.objects.get(id=1)
post.pv = F('pv') + 1          #保证是原子性操作
pos.save
  • Q表达式
解决or查询
from django.db.models import Q
Post.objects.filter(Q(id=1) | Q(id=2))

解决and查询
Post.objects.filter(Q(id=1) & Q(id=2))
  • Count聚合查询(annotate)
得到某个分类下有多少篇文章
category =  Category.objects.get(id=1)
posts_count = category.post_set.count()

增加属性:annotate
from django.db.models import Count
categories = Category.objects.annotate(posts_count=Count('post'))
print(categories[0].posts_count)
这相当category动态增加了属性posts_count,而这个属性的值来源于Count('post')
  • Sum聚合查询(aggregate)
    同count类似,只是他是用来做合计的
from django.db.models import Sum
Post.objects.aggregate(all_pv=Sum('pv'))
# 输出类似结果:{'all_pv'L487}
还有Avg、Mix、Max等表达式,军用来满足sql查询
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容