首页 > 解决方案 > 使用 Model @property 装饰器来检查 manytomany 的计数

问题描述

你如何设置模型装饰器(在 Django 中)来计算 ManyToManyFields(和其他可验证的条件)?

姜戈模型:

class Cake(models.Model):
    cake_layer = models.ManyToManyField(CakeLayer, related_name="cake_layer")
    cream_layer = models.ManyToManyField(CreamLayer, related_name="cream_layer")

    @property
    def at_least_one_cake_layer(self):
        if self.cake_layer_set.count = 0:
            raise AssertionError("At last one cake layer is needed to be a cake")

    @property
    def at_least_two_layers(self):
        total_layer_count = 0
        total_layer_count += self.cream_layer_set.count
        total_layer_count += self.cream_layer_set.count
        if total_layer_count <= 1:
            raise AssertionError("A proper Cake needs two layers, even if they are just two cake layers!")

是否像能够在模型本身中进行查询一样简单?(我只在模板和视图中使用查询,如果它们是另一个模型,我不确定如何正确(如果有的话)访问模型的属性。

请注意:如果此代码可以工作,请告诉我,但你知道我来自哪里:

随时澄清我的假设:) 谢谢!

标签: pythondjangopython-decorators

解决方案


一些明显的错误和一些改进建议:

class Cake(models.Model):
    # 1. change related_name to sth that makes semantic sense!
    # I'd also suggest plural names for m2m fields. Makes code more readable.
    cake_layers = models.ManyToManyField(CakeLayer, related_name="cakes")
    cream_layers = models.ManyToManyField(CreamLayer, related_name="cakes")

    def at_least_one_cake_layer(self):
        # 2. no _set suffix with forward m2m rels, just use the name of the field
        # 3. count is a method, needs parentheses to be actually called
        # 4. "=" is an assignment, use "==" for comparisons 
        if self.cake_layers.count() == 0:
            raise AssertionError("At last one cake layer is needed to be a cake")
        # 5. a property suggests to the caller that they are accessing a simple attribute which
        # imho should not raise an exception. A boolean return value seems better.
        # if you want to raise an error, I'd keep it a method.

    @property
    def at_least_two_layers(self):
        # 6. seems you mean to add both counts here, so one should be cake_layers
        # minor: adding two ints should not deserve 3 lines of code 
        total_layer_count = self.cake_layers.count() + self.cream_layers.count()
        if total_layer_count < 2:  
            # minor: make code match output ;)
            # even if <=1 and <2 are equivalent here, it is just good practice
            # and you avoid pitfalls in more complicated cases, e.g. with floats
            raise AssertionError("A proper Cake needs two layers, even if they are just two cake layers!")

就装饰器而言,它们只是应用于它们装饰的函数/类对象的普通 Python 函数,并且受到与任何其他函数(例如范围)相同的限制。你想用它们做什么是有意义的还是 Pythonic 取决于具体情况。

您可以在装饰器中执行数据库操作,但将其限制为装饰器返回的函数。装饰器函数本身(在大多数情况下,例如函数内部的类定义除外)将在模块加载时调用,此时模型可能尚未加载。您应该避免模块级代码中的数据库命中。

此外,您count()用于检查是否存在。当您不使用实际数字时,使用exists()哪个性能更高:

if not self.cake_layers.exists():
    # raise hell / return False

if not (self.cake_layers.exists() and self.cream_layers.exists()):
    # raise hell / return False

推荐阅读