文章目录
- 一,模型名
- 二,关系字段命名
- 三,正确Related-Name
- 四,不要使用unique=True的外键
- 五,属性和方法在模型中排序
- 六,通过迁移添加模型
- 七,非正规化
- 八,BooleanField
- 九,模型中的业务逻辑
- 十,ModelForm中的字段重复
- 十一,不要使用ObjectDoesNotExist
- 十二,choices的使用
- 十三,你怎么需要一个额外的.all()?
- 十四,模型中有很多flag?
- 十五,字段名中的冗余模型名
- 十六,不应该在数据库中找到脏数据
- 十七,获取最早/最新的对象
- 十八,永远不要 len(queryset)
- 十九,if queryset是个坏主意
- 二十,使用help_text作为documentation
- 二十一,货币信息存储
- 二十二,如果不需要,不要使用null=true
- 二十三,删除_id
- 二十四,定义\_\_unicode\_\_或\_\_str\_\_
- 二十五,透明的字段列表
- 二十六,不要将用户加载的所有文件堆在同一个文件夹中
- 二十七,使用抽象模型
- 二十八,使用自定义管理器和QuerySet
一,模型名
一般建议模型命名时使用单数名词,例如:User, Post, Article。
也就是说,名称的最后一个组成部分应该是一个名词,例如:SomeNewShinyItem。
当模型的一个单元不包含关于几个对象的信息时,使用单数是正确的。
二,关系字段命名
对于像oreignKey, OneToOneKey,ManyToMany这样的关系,指定一个名称那是必须的。
假设有一个名为Article的模型,其中一个关系是model User的ForeignKey。如果这个字段包含关于文章作者的信息,那么author将是一个比user更合适的名称。
三,正确Related-Name
用复数表示Related-Name是合理的,因为相关名称寻址返回queryset。
请设置足够的相关名称。例如:
class Owner(models.Model):
pass
class Item(models.Model):
owner = models.ForeignKey(Owner, related_name='items')
四,不要使用unique=True的外键
将ForeignKey与unique = True一起使用是没有意义的,因为在这种情况下将导致ForeignKey转化为OneToOneField。
五,属性和方法在模型中排序
更好的属性和方法在模型中排序是:
- 用于choices和其他参数的常量
- 字段
- def __unicode__ (python 2) 或 def __str__ (python 3)
- def clean
- def save
- 其他方法
- meta
六,通过迁移添加模型
在创建了模型的类之后,就需要执行makemigrationations和migrate
七,非正规化
不应该轻率地在关系数据库中使用非规范化,除非你有意识地为了任何可能的原因(比如需快速实现)来非规范化数据。
如果在数据库设计阶段就能分析到需要对大部分数据进行非规范化,那么使用NoSQL是一个很好的选择。但是,如果大多数数据不需要去规范化(这是不可避免的),那么可以考虑使用带有JsonField的关系库来存储一些数据。
八,BooleanField
不要对布尔字段使用null=True或blank=True。还应该指出的是,最好为这些字段指定默认值。如果意识到该字段可以保持为空,那么就需要NullBooleanField。
九,模型中的业务逻辑
为项目分配业务逻辑的最佳位置是模型,即方法模型和模型管理器。
模型 方法可能仅会激发某些方法/功能。 如果不方便或无法在模型中分配逻辑,则需要在任务中替换其形式或序列化程序。
十,ModelForm中的字段重复
如果没有必要,不要在ModelForm或ModelSerializer中复制模型字段。如果要指定表单使用所有模型字段,请使用元字段。如果需要为一个字段重新定义一个小部件,而该字段中没有其他东西需要更改,请使用Meta widgets来指示小部件。
十一,不要使用ObjectDoesNotExist
使用ModelName.DoesNotExist而不是ObjectDoesNotExist可以使异常捕获更加专业,这是一种积极的做法。
十二,choices的使用
使用choices时,建议:
- 在数据库中保留字符串而不是数字(尽管从可选的数据库使用的角度来看,这不是最好的选择,但在实践中更方便,因为字符串更易于演示,这允许在REST框架中使用带有get选项的清晰过滤器)。
- 用于变量存储的变量是常量,这就是为什么它们必须用大写来表示。
- 在字段列表之前指示变量。
- 如果它是一个状态列表,则按时间顺序指示它(例如new, in_progress, completed)。
- 可以使用model_utils库中的选项。以一个范例文章为例:
from model_utils import Choices
class Article(models.Model):
STATUSES = Choices(
(0, 'draft', _('draft')),
(1, 'published', _('published')) )
status = models.IntegerField(choices=STATUSES, default=STATUSES.draft)
…
十三,你怎么需要一个额外的.all()?
使用ORM时,不要在filter()、count()等之前添加额外的all方法调用。
十四,模型中有很多flag?
如果它是合理的,将几个布尔字段替换为一个字段,类似于status,如:
class Article(models.Model):
is_published = models.BooleanField(default=False)
is_verified = models.BooleanField(default=False)
…
假设文章最初没有被发布和检查,然后对它进行检查并标记is_verified为True,然后发布它。可以确定,文章在没有经过检查的情况下不能发布,所以总共有3种情况,但是这里有2个布尔字段而我们却不需要有4情况。综上,为了确保没有文章有错误的布尔字段条件组合,使用一个状态字段而不是两个布尔字段是更好的选择:
class Article(models.Model):
STATUSES = Choices('new', 'verified', 'published')
status = models.IntegerField(choices=STATUSES, default=STATUSES.draft)
…
十五,字段名中的冗余模型名
如果没有必要,不要向字段添加模型名,例如,如果表User有一个字段user_status ,你就应该将字段重命名为status。
十六,不应该在数据库中找到脏数据
如果不是毫无意义的话,请始终使用PositiveIntegerField而不是IntegerField,因为“坏”数据一定不能进入数据库。
出于同样的原因,对于逻辑上唯一的数据,应该始终使用unique,unique_together,并且在每个字段中都不要使用required = False。
十七,获取最早/最新的对象
可以使用ModelName.objects.earliest(‘created’/‘earliest’)代替order_by(‘created’)[0],也可以将get_latest_by放在Meta中。应该记住,latest/earliest和get都可能导致DoesNotExist异常。因此,order_by(‘created’).first()是最有用的变体。
十八,永远不要 len(queryset)
不要使用len来获取queryset对象的数量——count方法可用于此目的。因为 len(ModelName.objects.all()),首先将从表中选择所有数据的查询,然后该数据将被转换为一个Python对象,最后len找到的是该对象的长度,强烈建议不要使用这个方法。而count会指向对应的SQL函数count(),有了count,在该数据库中执行查询会更容易,python代码性能所需的资源也会更少。
十九,if queryset是个坏主意
不要使用queryset作为布尔值,请使用 if queryset.exists(): Do something 代替 if queryset: Do something。
请记住,queryset是惰性的,如果使用queryset作为布尔值,将执行一个不合适的数据库查询。
二十,使用help_text作为documentation
在字段中使用 help_text 作为documentation的一部分肯定会促进你、你的同事和管理员用户对数据结构的理解。
二十一,货币信息存储
不要使用FloatField来存储有关金额的信息,使用专门的DecimalField可以将此信息保留更多小数细节。
二十二,如果不需要,不要使用null=true
在基于文本的字段中,最好保持默认值。
blank=True, default=''
这样对于没有数据的列,只能得到一个可能的值——空字符串。
二十三,删除_id
不要将_id后缀添加到ForeignKeyField和OneToOneField,这是为了避免与使用 id 进行字段查询时产生冲突。
二十四,定义__unicode__或__str__
在所有非抽象模型中,添加方法__unicode__或__str__,因为这些方法必须始终返回字符串,这在使用admin时相当有用。
二十五,透明的字段列表
不要在ModelForm中将Meta.exclude用于模型的字段列表描述,最好为此使用Meta.fields因为它会使字段列表透明。
出于相同的原因,请勿使用Meta.fields =” __ all__”。
二十六,不要将用户加载的所有文件堆在同一个文件夹中
有时,如果需要大量下载的文件,即使每个FileField都有一个单独的文件夹也不够用,因为在一个文件夹中存储许多文件意味着文件系统将更慢地搜索所需的文件。为避免此类问题,可以执行以下操作:
def get_upload_path(instance, filename):
return os.path.join('account/avatars/', now().date().strftime("%Y/%m/%d"), filename)
class User(AbstractUser):
avatar = models.ImageField(blank=True, upload_to=get_upload_path)
二十七,使用抽象模型
如果想在模型之间共享一些逻辑,可以使用抽象模型:
class CreatedatModel(models.Model):
created_at = models.DateTimeField(
verbose_name=u"Created at",
auto_now_add=True
)
class Meta:
abstract = True
class Post(CreatedatModel):
...
class Comment(CreatedatModel):
...
二十八,使用自定义管理器和QuerySet
项目越大,在不同地方重复相同代码的次数就越多,为了保持代码的精简度并在模型中分配业务逻辑,可以使用自定义管理器和Queryset。
比如如果需要获取帖子的评论数:
class CustomManager(models.Manager):
def with_comments_counter(self):
return self.get_queryset().annotate(comments_count=Count('comment_set'))
现在你可以使用:
posts = Post.objects.with_comments_counter()
posts[0].comments_count
如果想把这个方法和其他的queryset方法连接起来,就应该使用自定义QuerySet:
class CustomQuerySet(models.query.QuerySet):
"""Substitution the QuerySet, and adding additional methods to QuerySet
"""
def with_comments_counter(self):
"""
Adds comments counter to queryset
"""
return self.annotate(comments_count=Count('comment_set'))
现在你可以使用:
posts = Post.objects.filter(...).with_comments_counter()
posts[0].comments_count