【问题标题】:Django newbie, struggling to understand how to implement a custom querysetDjango新手,努力理解如何实现自定义查询集
【发布时间】:2017-12-25 12:24:06
【问题描述】:

所以我对 Django 很陌生,我昨天开始玩,一直在玩标准投票教程。

上下文

我希望能够根据自定义方法(在本例中为 Question.is_open() 方法(下图 1)的结果过滤活动问题。

我理解的问题

当我尝试使用过滤器仅访问活动问题时 questions.objects.filter(is_open=true) 它失败了。如果我理解正确,这依赖于通过模型管理器公开的查询集,该模型管理器只能根据 sql 数据库中的记录进行过滤。

我的问题

1) 我是否以大多数 pythonic/django/dry 方式来解决这个问题?我应该通过继承 models.Manager 并生成自定义查询集来公开这些方法吗? (这似乎是网上的共识)。

2) 如果我应该使用带有自定义查询集的管理器子类,我不确定代码会是什么样子。例如,我是否应该通过 cursor.execute 使用 sql(根据文档here,这似乎非常低级)?还是在 django 本身中有更好、更高级别的方法来实现这一点?

我会很感激任何关于如何解决这个问题的见解。

谢谢

马特

我的models.py

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published',default=timezone.now())
    start_date = models.DateTimeField('poll start date',default=timezone.now())
    closed_date = models.DateTimeField('poll close date', default=timezone.now() + datetime.timedelta(days=1))


    def time_now(self):
        return timezone.now()

    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

    def is_open(self):
        return ((timezone.now() > self.start_date) and (timezone.now() < self.closed_date))

    def was_opened_recently(self):
        return self.start_date >= timezone.now() - datetime.timedelta(days=1) and self.is_open()

    def was_closed_recently(self):
        return self.closed_date >= timezone.now() - datetime.timedelta(days=1) and not self.is_open()

    def is_opening_soon(self):
        return self.start_date <= timezone.now() - datetime.timedelta(days=1)

    def closing_soon(self):
        return self.closed_date <= timezone.now() - datetime.timedelta(days=1)

[更新]

只是作为后续。我用硬编码的 SQL 字符串对默认管理器进行了子类化(仅用于测试),但是,它失败了,因为它不是属性

class QuestionManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset()

    def get_expired(self):
        from django.db import connection
        with connection.cursor() as cursor:
            cursor.execute("""
                      select id, question_text, closed_date, start_date, pub_date from polls_question
                      where ( polls_question.start_date < '2017-12-24 00:08') and (polls_question.closed_date > '2017-12-25 00:01') 
                      order by pub_date;""")
            result_list = []
            for row in cursor.fetchall():
                p = self.model(id=row[0], question=row[1], closed_date=row[2], start_date=row[3], pub_date=row[4])
                result_list.append(p)
        return result_list

我用 active_poll_list = Question.objects.get_expired()调用方法

但我得到了例外

Exception Value:    
'Manager' object has no attribute 'get_expired'

我真的不确定我是否理解为什么这不起作用。这一定是我对如何调用从管理器返回查询集的方法的误解。

任何建议将不胜感激。

谢谢

【问题讨论】:

    标签: django django-models django-queryset


    【解决方案1】:

    你的问题有很多东西,我会尽可能多地涵盖。

    当您尝试获取模型的查询集时,您只能使用字段属性作为查找。这意味着在您的示例中您可以这样做:

    Question.objects.filter(question_text='What's the question?')
    

    或:

    Question.objects.filter(question_text__icontains='what')
    

    但是你不能查询方法:

    Question.objects.filter(is_open=True)
    

    没有字段is_open。它是模型类的方法,过滤查询集时不能使用。

    您在Question 类中声明的方法可能更好地装饰为属性(@property)或缓存属性。对于以后的导入:

    from django.utils.functional import cached_property
    

    并像这样装饰方法:

    @cached_property
    def is_open(self):
        # ...
    

    这将使计算的值作为属性而不是方法可用:

    question = Question.objects.get(pk=1)
    print(question.is_open)
    

    当您为时间字段指定默认值时,您很可能希望这样:

    pub_date = models.DateTimeField('date published', default=timezone.now)
    

    注意 - 这只是timezone.now!创建条目时应调用可调用对象。否则,方法 timezone.now() 将在 django 应用程序第一次启动时被调用,并且所有条目都将保存该时间。

    如果你想为管理器添加额外的方法,你必须将你的自定义管理器分配给objects

    class Question(models.Model):
        # the fields ...
        objects = QuestionManager()
    

    之后get_expired方法将可用:

    Question.objects.get_expired()
    

    我希望这可以帮助您理解代码中的一些问题。

    【讨论】:

    • 这很有意义。我现在就试试。谢谢!
    【解决方案2】:

    看起来我在定义 Question.objects 时错过了括号

    它仍然不起作用,但我想我可以从这里解决。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多