Django基础核心技术之Model模型的介绍与设计

发布于 2020-11-16  656 次阅读


Django网络应用开发的5项基础核心技术包括模型(Model)的设计,URL的配置,View(视图)的编写,Template(模板)的设计和Form(表单)的使用。今天用千字长文给你来介绍下第一项Django核心基础知识之Model的设计吧。

什么是Model模型?

Model (模型) 简而言之即数据模型。模型不是数据本身(比如数据库里的数据),而是抽象的描述数据的构成和逻辑关系。每个Django model实际上是个类,继承了models.Model。每个Model应该包括属性,关系(比如单对单,单对多和多对多)和方法。当你定义好Model模型后,Django的接口会自动帮你在数据库生成相应的数据表(table)。这样你就不用自己用SQL语言创建表格或在数据库里操作创建表格了,是不是很省心?

我们来看个问题与投票的实际案例。投票有问题、选项描述和当前得票数。问题有发布内容和发布时间。当然我们还要利用ForeignKey定义投票和问题之间的关系,因为一个投票应用可以出对很多问题进行投票。我们定义了如下模型,那你看看代码有问题吗?

class Question(models.Model):
    question_text = models.CharField(verbose_name="发布内容")
    desc = models.TextField(blank=True, null=True)
    pub_time = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text

class Choice(models.Model):
    question = models.ForeignKey(Question)
    choice_text = models.CharField(max_length=200, verbose_name='选项描述')
    votes = models.IntegerField(default=0, verbose_name='当前得票数')

    def __str__(self):
        return self.choice_text

当你运行python manage.py migrate创建表格的时候你会遇到错误,错误原因如下:

  • CharField里的max_length选项没有定义
  • ForeignKey(Question)里的on_delete选项有没有定义

所以当你定义Django模型Model的时候,你一定要十分清楚2件事:

  • 这个Field是否有必选项, 比如CharField的max_length和ForeignKey的on_delete选项是必须要设置的。
  • 这个Field是否必需(blank = True or False),是否可以为空 (null = True or False)。这关系到数据的完整性。

其实在上述案例中还有一个隐藏的错误,即TextField(blank = True, null = True)。blank = True 意味这个字段不是必需的,在客户端不是必填选项。null = True意味这个数据库里这个字段可以存储为null空值。但是Django对于空白的CharField和TextField永远不会存为null空值,而是存储空白字符串'',所以正确的做法是设置default=''。

下表才是一个比较正确的Django模型(Model)。

class Question(models.Model):
    question_text = models.CharField(max_length=200, verbose_name="发布内容")
    desc = models.TextField(blank=True, null=True, default='')
    pub_time = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200, verbose_name='选项描述')
    votes = models.IntegerField(default=0, verbose_name='当前得票数')

    def __str__(self):
        return self.choice_text

Django Model中字段(Field)的可选项和必选项

CharField() 字符字段

  • max_length = xxx or None(必选)
  • 如不是必填项,可设置blank = True和default = ''
  • 如果用于username, 想使其唯一,可以设置unique = True
  • 如果有choice选项,可以设置 choices = XXX_CHOICES

TextField() 文本字段

  • max_length = xxx
  • 如不是必填项,可设置blank = True和default = ''

DateField() and DateTimeField() 日期与时间字段

  • 一般建议设置默认日期default date.
  • For DateField: default=date.today - 先要from datetime import date
  • For DateTimeField: default=timezone.now - 先要from django.utils import timezone
  • 对于上一次修改日期(last_modified date),可以设置: auto_now=True

EmailField() 邮件字段

  • 如不是必填项,可设置blank = True和default = ''
  • 一般Email用于用户名应该是唯一的,建议设置unique = True

IntegerField(), SlugField(), URLField(),BooleanField()

  • 可以设置blank = True or null = True
  • 对于BooleanField一般建议设置defautl = True or False

FileField(upload_to=None, max_length=100) - 文件字段

  • upload_to = "/some folder/"(必选)
  • max_length = xxxx

ImageField(upload_to=None, height_field=None, width_field=None, max_length=100,)

  • upload_to = "/some folder/"(必选)
  • 其他选项是可选的.

ForeignKey(to, on_delete, **options) - 单对多关系

  • to必需指向其他模型,比如 Book or 'self' .(必选)
  • 必需指定on_delete options(删除选项): i.e, "on_delete = models.CASCADE" or "on_delete = models.SET_NULL" .(必选)
  • 可以设置"default = xxx" or "null = True" .
  • 如果有必要,可以设置 "limit_choices_to = ",如下面例子。
  • staff_member = models.ForeignKey( User, on_delete=models.CASCADE, limit_choices_to={'is_staff': True}, )
  • 可以设置 "related_name = xxx" 便于反向查询。

ManyToManyField(to, **options) - 多对多关系

  • to 必需指向其他模型,比如 User or 'self' .(必选)
  • 设置 "symmetrical = False " if 多对多关系不是对称的
  • 设置 "through = 'intermediary model' " 如果需要建立中间模型来搜集更多信息
  • 可以设置 "related_name = xxx" 便于反向查询。

一个复杂点的Django Model模型

我们现在来看一个更复杂点的Django模型。假设我们要开发一个餐厅(restaurant)的在线点评网站,允许用户(user)上传菜肴(dish)的图片并点评餐厅,我们就可以设计如下模型。用户与餐厅,餐厅与菜肴,及用户与菜肴都是单对多的关系。我们可以这样理解:一个用户可以访问点评多个餐厅,一个餐厅有多个菜肴,一个用户可以上传多个菜肴的图片。

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


class Restaurant(models.Model):
    name = models.TextField()
    address = models.TextField(blank=True, default='')
    telephone = models.TextField(blank=True, default='')
    url = models.URLField(blank=True, null=True)
    user = models.ForeignKey(User, default=1,on_delete=models.CASCADE)
    date = models.DateField(default=date.today)

    def __str__(self):
        return self.name

class Dish(models.Model):
    name = models.TextField()
    description = models.TextField(blank=True,  default='')
    price = models.DecimalField('USD amount', max_digits=8, decimal_places=2, blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)
    image = models.ImageField(upload_to="myrestaurants", blank=True, null=True)
# Related name "dishes" allows you to use restaurant.dishes.all to access all dishes objects
# instead of using restaurant.dish_set.all
    restaurant = models.ForeignKey(Restaurant, null=True, related_name='dishes', on_delete=models.CASCADE)

    def __str__(self):
        return self.name

class Review(models.Model):
    RATING_CHOICES = ((1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), (5, 'five'))
    rating = models.PositiveSmallIntegerField('Rating', blank=False, default=3, choices=RATING_CHOICES)
    comment = models.TextField(blank=True, null=True)
    user = models.ForeignKey(User, default=1, on_delete=models.CASCADE)
    date = models.DateField(default=date.today)

    class Meta:
        abstract = True


class RestaurantReview(Review):
    restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
    
    def __str__(self):
        return "{} review".format(self.restaurant.name)

你观察到Django模型设计里的细节了吗?

我们的Dish模型里有一个restaurant的字段,建立了一个单对多的关系。我们可以通过dish.restaurant.name直接查询到菜肴所属的餐厅的名字。然而我们Restaurant模型里并没有dish的字段,我们如何根据restaurant查询到某个餐厅的所有菜肴呢?Django非常聪明,可以通过在dish小写后面加上'_set'进行反向查询。我们本来可以直接通过restaurant.dish_set.all的方法来进行查找的,然而这个方法并不直观。为了解决这个问题,我们在dish模型里设置'related_name = dishes", 这样我们就可以直接通过restaurant.dishes.all来反向查询所有菜肴了。注意一但你设置了related name, 你将不能再通过_set方法来反向查询。

restaurant = models.ForeignKey(Restaurant, related_name='dishes', on_delete=models.CASCADE)

第2个细节你需要关注的是Review模型里,我们设置了META选项: Abstract = True。这样一来Django就会认为这个模型是抽象类,而不会在数据库里创建review的数据表。实际上Model自带的META选项还有很多,都非常有用。见下文。

常见的Django Model META类选项

from django.db import models

class Meta:
    # 按Priority降序, order_date升序排列,就是得到最近一行记录。如果你的数据模型中有 DateField 或 DateTimeField 类型的字段.
    get_latest_by = ['-priority', 'order_date']
    # 自定义数据库里表格的名字
    db_table = 'music_album'
    # 按什么排序
    ordering=['pub_date'] # 按发布时间升序排列
    ordering=['-pub_date'] # 按发布时间降序排列,-表示降序
    ordering=['?pub_date'] # 随机排序,?表示随机
    ordering=['-pub_date','author'] # 以pub_date为降序,在以author升序排列

    # 定义APP的标签,这个选项只在一种情况下使用,就是你的模型不在默认的应用程序包下的models.py文件中,这时候需要指定你这个模型是哪个应用程序的。
    app_label = 'myapp'
    # 声明此类是否为抽象
    abstract = True

    # 添加授权,permissions主要是为了在Django Admin管理模块下使用的,如果你设置了这个属性可以让指定的方法权限描述更清晰可读。Django自动为每个设置了admin的对象创建添加,删除和修改的权限
    permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)

    # 定义在管理后台显示的名称
    verbose_name = '话题'

    # 定义复数时的名称(去除复数的s)
    verbose_name_plural = verbose_name

    # unique_together这个选项用于:当你需要通过两个字段保持唯一性时使用。比如假设你希望,一个Person的FirstName和LastName两者的组合必须是唯一的,那么需要这样设置:
    unique_together = (("first_name", "last_name"),)

    # proxy
    # 这是为了实现代理模型使用的,如果proxy = True,表示model是其父的代理 model 


一名测试工作者,专注接口测试、自动化测试、性能测试、Python技术。