Django小技巧22: 设计一个好的模型

翻译整理自: simpleisbetterthancomplex.com

本篇将分享一些技巧,用户改进 Model 的设计。其中有很多与命名约定有关, 这可以大大的提高代码的可读性。

PEP8规范, 广泛用于 Python 领域, 因此我建议你在项目中使用它.

除了 PEP8 , 我更喜欢Django编程风格.

本篇目录:

命名 Model

模型定义使用CapWords约定(没有下划线). 例如: User, Permission, ContentType.

模型的属性使用 snake_case. 例如: first_name, last_name.

如:

Python

from django.db import models

class Company(models.Model):
    name = models.CharField(max_length=30)
    vat_identification_number = models.CharField(max_length=20)

始终使用单数明明你的模型, 用Company代替Companies. 模型的定义是对单个对象的表示, 而不是公司的集合.

这通常会导致混淆,因为我们倾向于通过数据库思考。模型最终被翻译成table.该表使用其复数形式命名的.

在 DJango 中,我们可以通过Company.objects来访问集合. 我可以通过定义models.Manager重命名objects属性.

Python

from django.db import models

class Company(models.Model):
    # ...
    companies = models.Manager()

而后, 可以通过下面语句来使用 Django ORM QuerySet 查询.

Python

Company.companies.filter(name='Google')

这样看起来代码就很有可读性了

Model 定义顺序

Django Coding Style 建议内部类,方法和属性的顺序为:

  • 如果字段有choices参数, 则每个选项定义为元祖中元祖.并使用全大写的名称作为值属性。
  • 所有数据库fields
  • Custom manager attributes
  • class Meta
  • def __str__()
  • def save()
  • def get_absolute_url()
  • 其他自定义方法

如:

Python

from django.db import models
from django.urls import reverse

class Company(models.Model):
    # CHOICES
    PUBLIC_LIMITED_COMPANY = 'PLC'
    PRIVATE_COMPANY_LIMITED = 'LTD'
    LIMITED_LIABILITY_PARTNERSHIP = 'LLP'
    COMPANY_TYPE_CHOICES = (
        (PUBLIC_LIMITED_COMPANY, 'Public limited company'),
        (PRIVATE_COMPANY_LIMITED, 'Private company limited by shares'),
        (LIMITED_LIABILITY_PARTNERSHIP, 'Limited liability partnership'),
    )

    # DATABASE FIELDS
    name = models.CharField('name', max_length=30)
    vat_identification_number = models.CharField('VAT', max_length=20)
    company_type = models.CharField('type', max_length=3, choices=COMPANY_TYPE_CHOICES)

    # MANAGERS
    objects = models.Manager()
    limited_companies = LimitedCompanyManager()

    # META CLASS
    class Meta:
        verbose_name = 'company'
        verbose_name_plural = 'companies'

    # TO STRING METHOD
    def __str__(self):
        return self.name

    # SAVE METHOD
    def save(self, *args, **kwargs):
        do_something()
        super().save(*args, **kwargs)  # Call the "real" save() method.
        do_something_else()

    # ABSOLUTE URL METHOD
    def get_absolute_url(self):
        return reverse('company_details', kwargs={'pk': self.id})

    # OTHER METHODS
    def process_invoices(self):
        do_something()

反向关系

related_name

ForeignKey 的 related_name 可以为反向关系定义一个有意义的名称

经验法则: 如果你不确定related_name是什么, 请使用包含所定义ForeignKey的模型的复数形式.

Python

class Company:
    name = models.CharField(max_length=30)

class Employee:
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name='employees')

上面代码意味着, Company 有一个employees特殊属性, 该属性将返回一个 QuerySet,其中包含与此公司相关的所有员工实例

Python

google = Company.objects.get(name='Google')
google.employees.all()

你也可以通过反向关系, 来更新Company的employees字段.

Python

vitor = Employee.objects.get(first_name='Vitor')
google = Company.objects.get(name='Google')
google.employees.add(vitor)

related_query_name

这种关系也是用于查询过滤器, 比如我们要查询雇佣名为「Vitor」的所有公司:

Python

companies = Company.objects.filter(employee__first_name='Vitor')

如果你想自定义此关系的查询名称可以这样

Python

class Employee:
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    company = models.ForeignKey(
        Company,
        on_delete=models.CASCADE,
        related_name='employees',
        related_query_name='person'
    )

然后这样查询

Python

companies = Company.objects.filter(person__first_name='Vitor')

代码要保持一致, related_name是复数, related_query_name是单数.

Blank 和 Null

我在另一篇文章有讲过两者的区别 Blank or Null,在这里我会总结一下.

  • null: 数据库相关; 定义数据库字段的值是否接受空值。
  • blank: 验证相关, 当调用form.is_valid()时, 将会判断值是否为空.

虽然两者的是有区别的, 但一个拥有null=Trueblank=False的字段是完全没有问题的。 在数据库级别上, 该字段可以为 NULL, 但在应用程序级别上, 它是必填字段(前提你通过 Django 标准的 Form 进行判断)。

大多数开发人员都对基于字符串的字段(CharFieldTextField)定义null=True, 这其实是没有必要的, 应该避免这样做,因为 Django约定使用空字符串设置空值, 而非Null.

所以, 如果你想设置一个基于字符的字段可以为空,那么你应该这样做:

Python

class Person(models.Model):
    name = models.CharField(max_length=255)  # 强制填写
    bio = models.TextField(max_length=500, blank=True)  # 可选填写 (不要设置null=True)
    birth_date = models.DateField(null=True, blank=True) # 可选填写 (这里你应该设置null=True)

进阶

模型定义是应用程序重要的一部分, 请务必使用合适的字段类型. 这里是 Django 支持的所有字段类型.

如果你对代码风格规范感兴趣, 可以读一读Django Coding Style. 当然也可以看一看Flake8.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CDA数据分析师

工具 | 很全的 Python 面试题

Python语言特性 1 Python的函数参数传递 看两个例子: ? 所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。 ...

1.2K9
来自专栏机器学习从入门到成神

java.lang.StackOverflowError异常解决

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_35512245/articl...

1K2
来自专栏一个番茄说

让你在WebView中用JS调Native Object

之所做这个东西,源于之前项目中需要把一些页面用webView来呈现,但是web中需要调用native的方法,比如获取本地存的某些数据、调用摄像头等等,这里也就是...

1053
来自专栏比原链

剥开比原看代码13:比原是如何通过/list-balances显示帐户余额的?

Gitee地址:https://gitee.com/BytomBlockchain/bytom

781
来自专栏小灰灰

spring-boot & ffmpeg 搭建一个音频转码服务

利用FFMPEG实现一个音频转码服务 提供一个音频转码服务,主要是利用ffmpeg实现转码,利用java web对外提供http服务接口 背景 音频转码服务...

1.3K6
来自专栏数据结构与算法

家谱 要测试数据的在评论里发联系方式

家谱(gen) 时间限制  2S 【问题描述】     现代的人对于本家族血统越来越感兴趣,现在给出充足的父子关系,请你编写程序找到某个人的最早的祖先。 【输入...

3698
来自专栏Spark生态圈

[spark] 内存管理 MemoryManager 解析

spark的内存管理有两套方案,新旧方案分别对应的类是UnifiedMemoryManager和StaticMemoryManager。

2422
来自专栏郭霖

Android Volley完全解析(三),定制自己的Request

经过前面两篇文章的学习,我们已经掌握了Volley各种Request的使用方法,包括StringRequest、JsonRequest、ImageRequest...

2266
来自专栏MelonTeam专栏

What's New in LLVM 9

导语 :这绝不仅仅是一篇 WWDC 2017 Session 411 学习笔记。除了有关 LLVM 9.0 的新特性之外,还有关于静态分析器和 Clang 5 ...

31110
来自专栏Java呓语

单元测试以及JUnit框架解析

我们都有个习惯,常常不乐意去写个简单的单元测试程序来验证自己的代码。对自己的程序一直非常有自信,或存在侥幸心理每次运行通过后就直接扔给测试组测试了。然而每次测试...

1292

扫码关注云+社区

领取腾讯云代金券