问题
What we're trying to do is populate a list of inline forms with initial values using some queryset of a different model. We have products, metrics (some category or type or rating), and a rating, which stores the actual rating and ties metrics to products.
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)
The end result we're going for is a list of all possible ratings for a Product on the Product admin page. If we have 20 Metrics in our database, when we go to the Product page we want to see 20 forms for Ratings on the page, each one tied to a different Metric. We can't use a queryset based on Ratings to populate the page, because the Rating for a particular Product/Metric combination might not yet exist.
We've been looking at all the forms and formset code in Django, and are hoping to come up with a solution as simple as this:
http://www.thenestedfloat.com/articles/limiting-inline-admin-objects-in-django
He just overrides something in BaseInlineFormSet and gives it to the inline. Maybe we can just make something like
class RatingInlineFormset(BaseInlineFormset):
With some overrides. Any ideas?
回答1:
Are you looking for an admin or front-end solution? The admin way is the following, you could reverse engineer it to get a similar front-end solution:
# 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)
回答2:
I have managed to implement similar functionality a bit like this:
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,
)
EDIT: The condition must be done in the LEFT JOIN, not the WHERE, otherwise you will have missing lines.
来源:https://stackoverflow.com/questions/1269052/inlineformset-with-queryset-of-different-model