Нужен Formset для модели отношений с формами для всех экземпляров одного ForeignKey - PullRequest
0 голосов
/ 14 октября 2010

У меня есть поле ManyToMany с моделью отношений.Мне нужен набор форм, отфильтрованный по одному из ключей, который показывает форму для каждого из остальных ключей.

Я предполагаю, что пользовательский менеджер в модели отношений является ключом к решению этой проблемы.Менеджер будет возвращать «фантомные» экземпляры, инициализированные с помощью соответствующего ForeignKey, когда в базе данных нет реального экземпляра.Я просто не знаю, как заставить менеджера добавлять «фантомные» экземпляры, когда он, кажется, предназначен для фильтрации существующих.

Я надеюсь, что пример стоит 1 КБ слов.* Скажем, я хочу, чтобы мои пользователи могли оценивать альбомы.Я хотел бы отобразить набор форм с формой для всех альбомов выбранной группы.Примеры моделей и представлений

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 голосов
/ 10 ноября 2010

Я вернулся к этой проблеме после повторного поиска в Интернете. Мое представление о том, что нужен специальный менеджер, было неверным. Мне нужен был собственный встроенный набор форм, который берет два набора запросов: один для поиска, а другой с упорядоченным списком элементов для отображения.

Проблема с этой техникой состоит в том, что model_formsets действительно хотели бы, чтобы за существующими экземплярами следовали дополнительные экземпляры. Решение состоит в том, чтобы создать два списка экземпляров для отображения: существующие записи и дополнительные записи. Затем, после того как model_formsets создаст формы, отсортируйте их обратно в порядке отображения.

Чтобы отсортировать формы набора форм, вам нужно применить мой патч 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))
...