钉子の次元

Dimpurr – an artist, designer and developer from China.

Django 学习手记 三 数据操作的常用方法

上一篇中,我们已经建立了基本的 Model 数据模型,并且熟悉了关于数据结构的存取、更新操作。本篇中主要介绍了对 Model 数据进行操作的常用方法,包含有许多示例 参见 ,亲自操作一下就很容易明白。

为了方便,我们可以通过 Python 在终端提供的交互式 Shell 来尝试和熟悉各种数据接口类的 API 。

python manage.py shell

使用这个命令会启动 Python 的交互式 Shell ,并且预先加载好 Django 所需的环境和变量。

示例:创建和修改新对象

涉及到的新方法:

  • all() 列出所有对象
  • save() 将该对象存入数据库

from polls.models import Question, Choice # 导入要操作的 Model
Question.objects.all()

from django.utils import timezone
q = Question(question_text="What's new?", pub_date=timezone.now()) # 创建一个新的 Question 对象
q.save() # 保存后才会被写入数据库

q.id # 输出属性
q.question_text
q.pub_date
q.question_text = "What's up?" # 修改属性
q.save()
Question.objects.all()

示例:为 Model 编写自定义方法

因为 Question.objects.all() 的输出 [<Question: Question object>] 是一个没有任何意义的对象标识,我们可以为 Question 类型增一个 __str__ 方法。同时可以了解一下如何添加自定义方法。修改之前的 polls/models.py ,在里面加入:

import datetime
from django.utils import timezone
# …

class Author(models.Model):
# …
def __str__(self):
return self.name

class Question(models.Model):
# ...
def __str__(self):
return self.question_text
def was_published_recently(self): # 判断该 Question 是否为 1 天内添加的自定义方法
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text

现在再回到交互式 Shell ,测试刚才新增的方法的效果。在 models.py 中对方法的修改并不需要重新迁移生成,就可以马上生效:

from polls.models import Question, Choice
Question.objects.all() # 此时的输出已经带有描述文字

当然,如果改动了 models.py 中的数据结构,记得要重新 makemigrations 和 migrate 。

关联对象常用方法参考

这些方法常用在含有 ForeignKey 或 ManyToManyField 类的关联对象中。

  • add(obj1, obj2, ...) 添加新的关联
  • remove(obj1, obj2, ...) 删除关联
  • clear() 删除所有关联
  • set([obj1, obj2, …]) 直接用新的对象组覆盖已有的关联
  • count() 计数子对象数
  • delete() 删除对象
  • create() 新建关联对象并直接引用到数据库(不需要再手动保存)

示例:数据的添加

a1 = Author(name="dim”,email="dim@example.com")
a2 = Author(name="ikaros",email="ikaros@example.com")
a1.save()
a2.save()
q.authors.add(a1,a2) # 给 ManyToManyField 添加多个指向
q.authors.all()

q.authors.count()
q.authors.all()[1].delete()
q.authors.all()

q.choice_set.create(choice_text='Not much', votes=0) # 在 Question 下新建一个 Choice
q.choice_set.create(choice_text='The sky', votes=0)
q.choice_set.all()

c = q.choice_set.create(choice_text='Just hacking again', votes=0)
c.question # 从 q.choice_set 新创建的 Choice 已经自动指向了父级 q

其他 Model 类型方法参考

以下是前文未提到的一些对 Model 对象实例进行操作的方法。参见

  • refresh_from_db() 将缓存中该对象的值更新为与数据库相同
  • clean_fields(exclude=None) 验证 Field 有效性
  • clean() 应当由你自行定义,提供验证 Field 是否有效和自动修复(填入数据)的功能
  • full_clean() 调用 clean_fields() 和 clean() 方法(如果有)
  • validate_unique() 类似 clean_fields() ,用于检测重复
  • save(force_insert=True, force_update=True) 启用强制插入或更新

数据的取出、复制和筛选

针对每个 Models ,都提供了一个称为 objects 的对象管理器 (Manager)。利用 objects 的各种方法,可以方便的进行数据的管理工作。关于筛选对象的条件语句称为 QuerySet 。

涉及到的新方法:

  • objects.get() 获取单个对象
  • objects.filter() 获取多个对象
  • exclude() 排除符合条件的对象
  • F() 在条件语句中引用其他属性的值
  • Q() 合并复杂查询
  • update() 批量修改

为了方便,再增加一组测试数据,然后尝试示例:

q2 = Question(question_text=“Try another?”, pub_date=timezone.now())
q2.save()

Question.objects.get(id=1) # 根据 id 获取对象,也可以用其他 Field 的值搜索
q3 = Question.objects.get(pk=1) # 根据主键 (Primary Key) 获取对象,在这里就是 id

将一个对象副本的唯一标识 (pk, id 等) 删除,再重新存入,此时会自动分配 pk 或 id ,即实现了对象的复制。

q3.pk = None
q3.save()
q3.pk # 可以看出 id 已经更新

get() 只能取得一个对象,无匹配或者不止一个匹配时都会报错,而 filter() 可以取得多个匹配。

from django.utils import timezone
current_year = timezone.now().year
Question.objects.get(pub_date__year=current_year) Question.objects.filter(pub_date__year=current_year)

qlist = Question.objects.filter(question_text__startswith='What')
from datetime import timedelta
qlist2 = qlist.exclude(pub_date__gte=datetime.date.today() + timedelta(days=3))

对 ManyToManyField 类 Field 指向的对象集,同样可以应用 get() 和 filter() 。举例来说,要获取关联了该 Question 对象的全部 Choice 对象,只需要加上一个 _set 后缀:

q.choice_set.filter(choice_text__startswith='Just hacking')

至于 F() 和 Q() ,使用时需要额外引用其所在的模块。

from django.db.models import F
Author.objects.filter(email__startswith=F('name'))

from django.db.models import Q
Question.objects.filter(Q(question_text__startswith='What'),Q(pub_date__lt=date(2011, 11, 11)) | Q(pub_date__gte=date(2012, 3, 4)))

Author.objects.all().update(email="admin@example.com")
Author.objects.all().update(email=F('name'))

QuerySet 条件语句参考

这里是在括号中使用的一些常用条件语句后缀,在这些条件语句中,利用双下划线区分层级来描述最终的指向,比如连接 Model 和 Field 的名称,最后再加上双下划线以及条件后缀。具体可以参考上方的示例。

  • __excat 精确等于,如果是字符则大小写也要相同
  • __contains 内容包含
  • __startswith, __endswith 开始于,结尾于
  • __iexact, __istartswith, __endswith 不精确等于, 不开始于,不结尾于
  • __gt, __gte, __lt, __lte 大于,大于等于,小于,小于等于
  1. Mireas说道:

    失踪人口回归=。=

  2. Dimpurr说道:

    大过年的时候我居然就用卡的不行的 iBook 在老家坑出了这么一篇文章,个中辛苦 …… 反正说了也没人想听 (:зゝ∠)
    现在还没什么余力去对高中生活做什么解释。就这样吧。请不抱期待的等待咱的更新w

    1. Dimpurr说道:

      我发现我还漏了一坨 QuerySet 的 API ˊ_>ˋ 算了明天再说吧 ……

  3. Dring说道:

    路过点赞

  4. tcdw说道:

    钉子终于更文了

    1. Dimpurr说道:

      (:зゝ∠) 然而更的是这种没人看的文

  5. wallena3说道:

    真心佩服。我高中时还什么都不懂呢。。。

  6. 陶心昊说道:

    新年快乐……竟然正好看到更新,然而啥都不懂TAT

  7. CDog说道:

    钉子终于更了w
    最近在捣鼓Flask,相比Django感觉更自由的样子~
    也是不错的框架2333

  8. Sendya说道:

    春节有心写这写真心不错呢,吾辈春节感觉一点都没休息(撸自己的小项目),给家里当了5天专职司机,全家出去旅游. 于是乎由上班了

  9. Mireas说道:

    话说,跪求钉子的头像插件,博客里评论头像神low….用插件设置下结果评论者和我一个头像了…( •̀ ω •́ )

    1. Dimpurr说道:

      是说随机默认头像这个么?这个是 WordPress 的默认功能,试试看后台的「讨论」设置看看吧。

  10. Rakume Hayashi说道:

    钉子居然更新惹(

  11. zengda说道:

    不错,不错,看看了!

  12. 2414119386说道:

    看看!

  13. しろ说道:

    围观菊苣!
    话说菊苣的主题真的超赞!
    作为一个即将高考的高三党,给你鼓鼓气。另外,作为一只还算挺学霸的狗,我想说,你的生活是绝对充实的!加油!

  14. 龙龙说道:

    菊苣教一下我那个你那个关键词那个框框是怎么加上去的?

    1. Dimpurr说道:

      誒?是說行內的 Code 標籤麼?

      1. 龙龙说道:

        嗯嗯。那个怎么弄的?

        1. Dimpurr说道:

          就是 CSS 改了下样式外加 JS 加了个 coneditable 属性罢了 …… 你可以参考下我的 style.css 和 script.js 有关 code 和 pre 标签的部分

  15. Thiece说道:

    钉触!

  16. 圆环之理说道:

    感谢大触

  17. 屿喵说道:

    博客风格真的挺喜欢的

  18. mr.zha说道:

    从大学用到工作一年,nice