需要Formset用于关系模型以及一个ForeignKey的所有实例的表单

时间:2010-10-14 07:26:54

标签: django django-models many-to-many django-forms

我有一个带有关系模型的ManyToMany字段。我想要一个formset,过滤其中一个 键,显示每个其他键的表单。

我的猜测是关系模型上的自定义管理器是解决此问题的关键。当数据库中没有真实实例时,管理器将返回使用适当的ForeignKey初始化的“幻像”实例。我只是不知道如何让管理员添加“幻影”实例,因为它似乎是为了过滤掉现有的实例。

我希望一个例子值得1K字。

说我希望我的用户能够为专辑评分。我想显示一个formset 所选乐队的所有专辑的表格。示例模型&图

from django.contrib.auth.models import User
from django.db import models

class Band(models.Model):
    name = models.CharField(max_length=30)

    def __unicode__(self):
        return self.name

class Album(models.Model):
    name = models.CharField(max_length=30)
    band = models.ForeignKey(Band)
    ratings = models.ManyToManyField(User, through="Rating")

    def __unicode__(self):
        return self.name


class Rating(models.Model):
    user = models.ForeignKey(User)
    album = models.ForeignKey(Album)
    rating = models.IntegerField()

    def __unicode__(self):
        return "%s: %s" % (self.user, self.album)

# views.py
from django.forms.models import modelformset_factory
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from models import Band, Rating


RatingFormSet = modelformset_factory(Rating, exclude=('user',), extra=1)

def update(request):
    user = request.user
    band = Band.objects.all()[0]
    formset = RatingFormSet(request.POST or None,
                      queryset=Rating.objects.filter(album__band=band, 
                                                     user=user))

    if formset.is_valid():
        objects = formset.save(commit=False)
        print "saving %d objects" % len(objects)

        for obj in objects:
            obj.user = user
            obj.save()    
        return HttpResponseRedirect("/update/")

    return render_to_response("rating/update.html", 
                              {'formset': formset, 'band':band},
                              context_instance=RequestContext(request))

问题是它只显示现有关系实例的表单。如何获取所有专辑的条目。

感谢。

1 个答案:

答案 0 :(得分:0)

我再次搜索网络后又回到了这个问题。我认为自定义经理需要的直觉是错误的。我需要的是一个自定义内联表单集,它采用两个查询集:一个用于搜索,另一个用于显示项目的有序列表。

这种技术的问题在于,model_formsets 确实喜欢让现有的实例跟随额外的实例。解决方案是两个显示两个实例列表:现有记录和额外记录。然后,在model_formsets创建表单后,将它们排序回显示顺序。

要对formset表单进行排序,您需要应用我的django补丁[14655]来制作可迭代的表单集&然后创建一个排序迭代器。

结果视图如下所示:


from django.contrib.auth.models import User
from django.forms.models import inlineformset_factory, BaseInlineFormSet, \
    BaseModelFormSet, _get_foreign_key
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template.context import RequestContext
from models import Band, Rating

class list_qs(list):
    """a list pretending to be a queryset"""
    def __init__(self, queryset):
        self.qs = queryset
    def __getattr__(self, attr):
        return getattr(self.qs, attr)

class BaseSparseInlineFormSet(BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        self.display_set = kwargs.pop('display_set')
        self.instance_class = kwargs.pop('instance_class', self.model)
        # extra is limited by max_num in baseformset
        self.max_num = self.extra = len(self.display_set)
        super(BaseSparseInlineFormSet, self).__init__(*args, **kwargs)

    def __iter__(self):
        if not hasattr(self, '_display_order'):
            order = [(i, obj._display_order)
                      for i, obj in enumerate(self._instances)]
            order.sort(cmp=lambda x,y: x[1]-y[1])
            self._display_order = [i[0] for i in order]
        for i in self._display_order:
            yield self.forms[i]

    def get_queryset(self):
        if not hasattr(self, '_queryset'):
            # generate a list of instances to display & note order
            existing = list_qs(self.queryset) 
            extra = []
            dk = _get_foreign_key(self.display_set.model, self.model)
            for i, item in enumerate(self.display_set):
                params = {dk.name: item, self.fk.name: self.instance}
                try:
                    obj = self.queryset.get(**params)
                    existing.append(obj)
                except self.model.DoesNotExist:
                    obj = self.instance_class(**params)
                    extra.append(obj)
                obj._display_order = i
            self._instances = existing + extra
            self._queryset = existing
        return self._queryset

    def _construct_form(self, i, **kwargs):
        # make sure "extra" forms have an instance
        if not hasattr(self, '_instances'):
            self.get_queryset()
        kwargs['instance'] = self._instances[i]
        return super(BaseSparseInlineFormSet, self)._construct_form(i, **kwargs)


RatingFormSet = inlineformset_factory(User, Rating, formset=BaseSparseInlineFormSet)

def update(request):  
    band = Band.objects.all()[0]
    formset = RatingFormSet(request.POST or None, 
                            display_set=band.album_set.all(),
                            instance=request.user)
    if formset.is_valid():
        objects = formset.save(commit=False)
        print "saving %d objects" % len(objects)

        for obj in objects:
            obj.save()

        return HttpResponseRedirect("/update/")

    return render_to_response("rating/update.html", 
                              {'formset': formset, 'band':band},
                              context_instance=RequestContext(request))