Django学习20-数据库相关2

Django模型关系

  在模型中定义关系时(即,ForeignKey,OneToOneField或ManyToManyField),该模型的实例将具有访问相关对象的便捷API。
  例如,在使用Django官方的Blog-Entry模型,Entry对象e可以通过访问blog属性获取其关联的Blog对象:e.blog。Django还为关系的“其他”方创建API访问器 - 从相关模型到定义关系的模型的链接。 例如,Blog对象b可以通过entry_set属性访问所有相关Entry对象的列表:b.entry_set.all()。

一对多关系

  Topic和Post模型是One-to-many关系:

class Topic(models.Model):
    """主题"""
    id = models.AutoField(primary_key=True)
    text = models.CharField(max_length=20)
    date_added = models.DateTimeField(auto_now_add=True)
    owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='the owner of this topic')

    class Meta:
        app_label = 'learning_logs'

    def __str__(self):
        return self.text


class Post(models.Model):
    """同一Topic下的多篇文章"""
    # many to one relationship
    id = models.AutoField(primary_key=True)
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE, verbose_name='the related topic')
    owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='the owner of the post')
    # text = models.TextField('post content')
    text = MDTextField()
    html_content = models.TextField('html content', blank=True)
    date_added = models.DateTimeField(auto_now_add=True)
    date_edit = models.DateTimeField(auto_now=True)

    # Model metadata is “anything that’s not a field”, such as ordering options (ordering),
    #  database table name (db_table),
    # or human-readable singular and plural names (verbose_name and verbose_name_plural).
    # None are required, and adding class Meta to a model is completely optional.
    class Meta:
        verbose_name_plural = 'posts'
        app_label = 'learning_logs'

    def __str__(self):
        return self.text[:50] + '...'

  如果模型定义了ForeignKey,可以通过这个模型的实例来访问与之联系的模型对象,如:

>>> from learning_logs.models import Post
>>> p = Post.objects.get(id=3)
>>> p.topic
<Topic: Seem tonight begin rock. Daughter beyond better network magazine. Then knowledge wind alone.
Left themselves public impact possible figure animal. May professional tax religious. Product shake force.>

  可以将外键设为None来取消关联(外键要允许为空,null=True):

>>> p.topic=None
>>> p.save()

  在第一次访问相关对象时,将缓存对一对多关系:

>>> p = Post.objects.get(id=3)
>>> print(p.topic) # 会查询数据库
>>> print(p.topic)	# 使用缓存

  select_related()方法以递归的方式预先填充所有一对多关系的缓存:

>>> p = Post.objects.select_related().get(id=4)
>>> print(p.topic) # 会使用缓存
Suffer meeting customer anything. If run town ask win opportunity. Bit itself on throughout.
Administration security story wife actually. Take place arm attorney.
>>> 

  如果模型具有ForeignKey,则外键模型的实例将可以访问返回第一个模型的所有实例的Manager。 默认情况下,此Manager名为FOO_set,其中FOO是源模型名称,小写。 此Manager返回QuerySets,可以进行筛选、排序等操作。

>>> topic = Topic.objects.get(id =8)
>>> topic.post_set.all() # Returns all post objects related to Topic.
<QuerySet [<Post: Piece check system kind sister if listen. Among mo...>, <Post: Various sport director area. Up receive real stree...>, <Post: Rise star stay heavy say today rich born.
Board wh...>]>
>>> topic.post_set.count()
3

  处理关联对象的方法:

  • add(obj1, obj2,...): 将指定的模型对象添加到关联的对象集合;
  • create(**kwargs):创建新对象;
  • remove(obj1, obj2, ...):从集合中删除关联的对象;
  • clear():清除所有关联对象;
  • set(objs):用新对象替换旧的关联对象集合;
b = Blog.objects.get(id=1)
b.entry_set.set([e1, e2])

多对多关系

  如下,有2个模型:Topic和ExampleModel,

扫描二维码关注公众号,回复: 4356245 查看本文章
class Topic(models.Model):
    """主题"""
    id = models.AutoField(primary_key=True)
    text = models.CharField(max_length=20)
    date_added = models.DateTimeField(auto_now_add=True)
    owner = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='the owner of this topic')

    class Meta:
        app_label = 'learning_logs'

    def __str__(self):
        return self.text
  
class TestModel(models.Model):
    topic = models.ManyToManyField(Topic)
    headline = models.CharField(max_length=255)
    def __str__(seld):
        return self.headline

  要为加多对多关系添加记录,需使用add方法:

>>> from learning_logs.models import Topic, TestModel
>>> topic = Topic.objects.get(id =2)
>>> test = TestModel.objects.create(headline='ManyToMany')
>>> test.topic.add(topic)

  为test对象添加多个topic:

>>> from django.contrib.auth.models import User
>>> user = User.objects.get(id=3)
>>> topics = Topic.objects.filter(owner=user)
>>> test.objects.add(*topics)
>>> 

  反向添加,为topic添加test:

>>> test2 = TestModel(headline='many to many')
>>> test2.save()
>>> topic = Topic.objects.get(id = 22085)
>>> topic.testmodel_set.add(test, test2)

  观察数据库,生成了2张表:testmodel表和记录2个模型关系的表testmodel_topic.
在这里插入图片描述
在test模型表中只有headline字段。
在这里插入图片描述
在它们的关系表中,可以查询到testmodel模型对象和topic模型对象的关系:
在这里插入图片描述
在这里插入图片描述

一对一关系

  一对一关系和一对多关系很相似,在模型中使用OneToOneField后,这个模型的实例就能通过简单的方式访问关联的模型对象。

from django.contrib.auth.models import User, AbstractUser
class UserProfile(models.Model):
    """为已经使用了内置User模型的项目来拓展用户模型 """
    # user对象可使用 user.userprofile, 或 user.proflie 得到用户信息
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
    location = models.CharField('地址', max_length=64, blank=True)
    tel = models.CharField('联系方式', max_length=50, blank=True)
    about_me = models.TextField('关于我')
    mod_date = models.DateTimeField('上次修改日期', auto_now=True)
    avatar = models.ImageField('用户头像', upload_to='avatar/%Y/%m/%d', default='avatar/wenhuang.jpg',blank=True, null=True)

    class Meta:
        verbose_name = "用户信息"
        app_label = 'users'


    def __str__(self):
        return f"{self.user.__str__()} profile"

  因为是一对一关系,可以直接选择关联的模型对象:

>>> from users.models import User, UserProfile
>>> profile = UserProfile.objects.get(id=3)
>>> user = profile.user
>>> user.id
1

Manager

  Manager类是为Django模型提供数据库查询操作的接口。对于Django应用程序中的每个模型,至少存在一个Manager。Manager类的工作方式记录在Making queries
  默认情况下,Django为每个Django模型类添加一个名为objects的Manager。 但是,如果要将对象用作字段名称,或者如果要使用Manager的对象以外的名称,则可以基于每个模型对其进行重命名。 要为给定类重命名Manager,请在该模型上定义models.Manager()类型的类属性。 例如:

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

  通过扩展基本Manager类并在模型中实例化自定义Manager,可以在特定模型中使用自定义Manager。自定义Manager可能有两个原因:添加额外的Manager方法,或修改Manager返回的初始QuerySet。

添加额外的Manager方法

  添加额外的Manager方法是向模型添加“表级”功能的首选方法。自定义的Manager方法可以返回想要的任何内容,它不必返回QuerySet。

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        with connection.cursor() as cursor:
            cursor.execute("""
                SELECT p.id, p.question, p.poll_date, COUNT(*)
                FROM polls_opinionpoll p, polls_response r
                WHERE p.id = r.poll_id
                GROUP BY p.id, p.question, p.poll_date
                ORDER BY p.poll_date DESC""")
            result_list = []
            for row in cursor.fetchall():
                p = self.model(id=row[0], question=row[1], poll_date=row[2])
                p.num_responses = row[3]
                result_list.append(p)
        return result_list

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    poll_date = models.DateField()
    objects = PollManager()

class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

  PollManager提供了一个方法with_counts(),该方法返回一个包含所有OpinionPoll对象的列表,每个对象都有一个额外的num_responses属性,该属性是聚合查询count的结果。可以使用OpinionPoll.objects.with_counts()方法来返回一个包含num_responses属性的OpinionPoll对象列表。

修改manager返回的查询集

  默认情况下,Manger会返回所有查询到的对象。下面2个Managers,一个会返回所有的对象,一个会筛选出作者为Roald Dahl的对象。

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

  get_queryset()返回的是查询结果集合,可以使用filterexclude等方法。
  也可以在一个模型下定义多个Manager,变相的提供了筛选:

class AuthorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role='A')

class EditorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role='E')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
    people = models.Manager()
    authors = AuthorManager()
    editors = EditorManager()

使用自定义的QuerySet方法

  在自定义的Manger里可以使用自定义的查询方法:

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')

class PersonManager(models.Manager):
    def get_queryset(self):
        return PersonQuerySet(self.model, using=self._db)

    def authors(self):
        return self.get_queryset().authors()

    def editors(self):
        return self.get_queryset().editors()

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
    people = PersonManager()

  这样可以直接使用Person.people.author()或是Person.people.editors()方法了。

继承使用自定义的Manger的模型

  可以创建一个模型的抽象类,它使用自定义的Manger方法,通过继承这个抽象类,模型都能使用自定义的Manager方法。

class AbstractBase(models.Model):
    # ...
    objects = CustomManager()

    class Meta:
        abstract = True

        
class ChildA(AbstractBase):
    # ...
    # This class has CustomManager as the default manager.
    pass

class ChildB(AbstractBase):
    # ...
    # An explicit default manager.
    default_manager = OtherManager()

猜你喜欢

转载自blog.csdn.net/qq_19268039/article/details/84197886