Django/Python DRY:使用 Q 对象和模型属性时如何避免重复代码

Posted

技术标签:

【中文标题】Django/Python DRY:使用 Q 对象和模型属性时如何避免重复代码【英文标题】:Django/Python DRY: how to avoid repeat code when working with Q objects and model attributes 【发布时间】:2013-06-17 21:29:19 【问题描述】:

我正在尝试构建一个搜索页面,该页面将允许用户查找满足特定阈值条件的模型的任何实例,并且在避免严重冗余代码时遇到了麻烦。我希望有更好的方法来做到这一点。这是一个稍微做作的示例,应该说明我正在尝试做的事情,并在最后调整了相关代码。用户将使用复选框与搜索进行交互。

models.py:

class Icecream(models.Model()):
    name = models.CharField()
    bad_threshold = models.IntegerField()
    okay_threshold = models.IntegerField()
    tasty_threshold = models.IntegerField()
    delicious_threshold = models.IntegerField()

views.py:

def search_icecreams(request):
    user = request.user
    q_search = None

    if 'taste_search' in request.GET: 
        q_search = taste_threshold_set(request, user, q_search)

    if q_search == None:
        icecream_list = Icecream.objects.order_by('name')
    else:
        icecream_list = College.objects.filter(q_search)

    context =  'icecream_list' : icecream_list 
    return render(request, '/icecream/icecreamsearch.html', context)

我要删减的相关代码如下,这几乎是直接来自我的项目,只是名称已更改。

def taste_threshold_set(request, user, q_search):
    threshold = request.GET.getlist('taste_search')
    user_type_tolerance = user.profile.get_tolerance_of(icea

    # 1-5 are the various thresholds. They are abbreviated to cut down on the
    # length of the url.
    if '1' in threshold:
        new_q = Q(bad_threshold__gt = user.profile.taste_tolerance)        

        if q_search == None:
            q_search = new_q
        else:
            q_search = (q_search) | (new_q)

    if '2' in threshold:
        new_q = Q(bad_threshold__lte=user.profile.taste_tolerance) & \
            ~Q(okay_threshold__lte=user.profile.taste_tolerance)

        if q_search == None:
            q_search = new_q
        else:
            q_search = (q_search) | (new_q)

    if '3' in threshold:
        new_q = Q(okay_threshold_3__lte=user.profile.taste_tolerance) & \
            ~Q(tasty_threshold__lte=user.profile.taste_tolerance)

        if q_search == None:
            q_search = new_q
        else:
            q_search = (q_search) | (new_q)

    if '4' in threshold:
        new_q = Q(tasty_threshold__lte=user.profile.taste_tolerance) & \
            ~Q(delicious_threshold__lte=user.profile.taste_tolerance)

        if q_search == None:
            q_search = new_q
        else:
            q_search = (q_search) | (new_q)

    if '5' in threshold:
        new_q = Q(delicious_threshold__lte = user.profile.taste_tolerance)        

        if q_search == None:
            q_search = new_q
        else:
            q_search = (q_search) | (new_q)

    return q_search

基本上我希望用户能够找到满足给定阈值级别的某个对象的所有实例。例如,所有他们认为不好的冰淇淋和所有他们认为好吃的冰淇淋。

我对这段代码有很多不满意的地方。我不喜欢检查 Q 对象是否尚未针对每个可能的阈值实例化,但看不到绕过它的方法。此外,如果这是一个非 django 问题,我会使用一个循环来检查每个给定的阈值,而不是把每个阈值都写出来。但同样,我不知道该怎么做。

最后,最大的问题是,我需要检查模型可能有 20 个不同属性的阈值。就目前而言,我必须为每个都编写一个新的阈值检查器,每个都与另一个略有不同(他们正在检查的属性的名称)。我希望能够编写一个通用检查器,然后将其传递给特定属性。有什么办法可以解决这个问题,或者我的其他两个问题?

谢谢!

【问题讨论】:

这段代码有效吗? (q_search) 看起来无效,除非参数 q_searchQ 对象 你说的是第三个代码块吗?它从我的代码(正在运行)中稍作修改,所以我可能破坏了某些东西,但我认为它有效。 q_search 参数将是 None 或 Q 对象,但它总是检查它是否为 None。如果是,则将 new_q 命名为 q_search 并继续,如果不是,则将旧的和新的,保存结果。嗯,至少这是我们的目标。 【参考方案1】:

您应该为模型使用自己的QuerySet 而不是def taste_threshold_set(...)

例子:

models.py:
...
from django.db.models.query import QuerySet
...

class IcecreamManager(models.Manager):

    def get_query_set(self):
        return self.model.QuerySet(self.model)

    def __getattr__(self, attr, *args):
        try:
            return getattr(self.__class__, attr, *args)
        except AttributeError:
            return getattr(self.get_query_set(), attr, *args)


class Icecream(models.Model()):
    name = models.CharField()
    bad_threshold = models.IntegerField()
    okay_threshold = models.IntegerField()
    tasty_threshold = models.IntegerField()
    delicious_threshold = models.IntegerField()

    objects = IcecreamManager()

    class QuerySet(QuerySet):
        def name_custom_method(self, arg1, argN):
            # you must rewrite for you solution
            return self.exclude(
                            time_end__gt=now()
                        ).filter(
                            Q(...) | Q(...)
                        )

        def name_custom_method2(...)
            ...

这些应该使您能够针对您的问题构建链查询。

【讨论】:

感谢@Abbasov,这可能是放置逻辑而不是视图的更好位置。虽然我有点困惑如何与自定义 QuerySet 交互。你能详细说明一下吗? 在 Django Icecream.objects.name_custom_method2(...).name_custom_method(...).filter(...)Icecream.objects.filter(...).name_custom_method2(...).name_custom_method(...) 中一样简单 我不是必须定义一个自定义管理器来做到这一点吗?就目前而言,我收到错误AttributeError: 'Manager' object has no attribute 'custom_method' 对不起,我忘记了最初的IcecreamManager。我更新了上面的代码 感谢您的建议。它与 Wonil 的回答相结合,为我的所有三个问题提供了一个非常优雅的解决方案。如果可以的话,我会给你一个 +1。【参考方案2】:

这种方法怎么样?

query_arg = ['bad_threshold__lte', 'bad_threshold__lte', 'okay_threshold_3__lte', 'tasty_threshold__lte', 'delicious_threshold__lte']

Q(**query_arg[int(threshold) - 1]: user.profile.taste_tolerance)

【讨论】:

非常好!这与我正在寻找的非常接近。我猜我也应该能够传入user.profile.taste_tolerance 部分,这应该允许它在所有属性中进行推广。所以我想这意味着attribute_name__lte 是所有 Q 对象和查询集的关键字参数?这很有趣。谢谢!

以上是关于Django/Python DRY:使用 Q 对象和模型属性时如何避免重复代码的主要内容,如果未能解决你的问题,请参考以下文章

MVC 中用于相似/继承对象的 DRY 主体与轻/薄视图

javascript对象,切换不同属性值的方法并努力编写DRY代码

以 DRY 方式创建活动/存档模型 (Django)

设计模式:面向对象的设计原则下(ISP、DIP、KISS、DRY、LOD)

Django / Python - 在对象创建中使用ManyToMany字段

DRY准则