使用不同模型的queryset的InlineFormSet

1 投票
2 回答
558 浏览
提问于 2025-04-15 13:33

我们想做的是用其他模型的一些查询结果来填充一系列的内联表单,并给它们设置初始值。我们有产品、指标(某种类别、类型或评分)和评分,评分用来存储实际的评分,并将指标和产品关联起来。

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.IntegerField(max_length=6)

class Metric(models.Model):
    name = models.CharField(max_length=80)
    description = models.TextField()


class Rating(models.Model)
    rating = models.IntegerField(max_length=3)

    metric = models.ForeignKey(Metric)
    product = models.ForeignKey(Product)

我们最终想要的结果是在产品管理页面上显示所有可能的产品评分。如果我们的数据库里有20个指标,当我们打开产品页面时,希望能看到20个评分的表单,每个表单都对应一个不同的指标。我们不能用基于评分的查询结果来填充页面,因为某个产品和指标的组合可能还没有评分。

我们一直在研究Django中的所有表单和表单集代码,希望能找到一个像这样简单的解决方案:

http://www.thenestedfloat.com/articles/limiting-inline-admin-objects-in-django

他只是重写了BaseInlineFormSet中的某些内容,然后把它应用到内联表单上。也许我们也可以做一些类似的事情:

class RatingInlineFormset(BaseInlineFormset):

加上一些重写的内容。有什么想法吗?

2 个回答

0

我已经成功实现了类似的功能,差不多是这样的:

from django.forms.models import BaseInlineFormSet
from django.forms.models import inlineformset_factory    

class RawQueryAdapter(object):
    """
    Implement some extra methods to make a RawQuery
    compatible with FormSet, which is expecting a QuerySet
    """
    ordered = True

    def __init__(self, qs):
        self.qs = qs
        self.db = qs.db

    def filter(self, *args, **kwargs):
        return self

    def __len__(self):
        return len(list(self.qs))

    def __getitem__(self, key):
        return self.qs[key]

class BaseRatingFormSet(BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        sql = """
            SELECT r.id, %s as product_id, m.id as metric_id
            FROM myapp_metric m
            LEFT JOIN myapp_rating r ON m.id = r.metric_id
            AND r.product_id = %s
        """
        id = kwargs['instance'].id or 'NULL'
        qs = RawQueryAdapter(Rating.objects.raw(sql % (id, id)))
        super(BaseRatingFormSet, self).__init__(queryset=qs, *args, **kwargs)

    def _construct_form(self, i, **kwargs):
        pk_key = "%s-%s" % (self.add_prefix(i), self.model._meta.pk.name)
        if self.data.get(pk_key) == '':
            # Skip parent (BaseModelFormSet) implementation as it won't work
            # with our injected raw data
            if i < self.initial_form_count() and not kwargs.get('instance'):
                kwargs['instance'] = self.get_queryset()[i]
            return super(BaseModelFormSet, self)._construct_form(i, **kwargs)
        return super(BaseRatingFormSet, self)._construct_form(i, **kwargs)

RatingFormSet = inlineformset_factory(
    Product, Rating,
    can_delete=False,
    max_num=0,
    formset=BaseRatingFormSet,
)

补充一下:这个条件要放在LEFT JOIN里,而不是WHERE里,不然会导致有些行缺失。

0

你是在寻找一个管理后台的解决方案,还是前端的解决方案呢?如果你想要管理后台的方式,可以按照下面的方法进行,你也可以通过逆向工程来得到一个类似的前端解决方案:

# admin.py

class RatingInline(admin.StackedInline):
    model = Rating

class ProductAdmin(admin.ModelAdmin):
    inlines = [ 
        RatingInline
    ]

class MetricAdmin(admin.ModelAdmin):
    pass

class RatingAdmin(admin.ModelAdmin):
    pass

admin.site.register(Product, ProductAdmin)
admin.site.register(Metric, MetricAdmin)
admin.site.register(Rating, RatingAdmin)

撰写回答