Django:用于更改对象外键的用户界面

时间:2014-06-14 15:38:08

标签: django forms foreign-keys django-forms inline-formset

假设我有一组与ForeignKey相关的简单Django模型:

class Author(models.Model):
    name = models.CharField('Name', max_length=50)

class Book(models.Model):
    author = models.ForeignKey(Author)
    title = models.CharField('Title', max_length=50)
    rank = models.IntegerField('Rank')

现在,在我的模板中,我想要创建一个并排有两个<ol>列表的用户界面,每个列表代表按其排名排序的给定作者的书籍列表,并且具有Book in的表单<li>节点。因此,整个页面由一个Author formset组成,每个formset都由一个内联Book formset组成。希望很清楚...

现在有了一些花哨的javascript / jQuery我允许用户做两件事:1)她可以在其<ol>中拖放一个Book表单来设置它的rank和2)她可以将一本书从一个<ol>拖放到另一个<ol>,目的是更改其作者。

从Django的角度来看,完成任务1完全没有问题。提交了表单集,保存了数据,Django不知道这种方法之间的区别,如果用户只是输入了不同的当然,书籍在输入文本字段中排名。

然而,完成任务2在我看来有点棘手。如果我将Book表单从一个Author列表拖放到另一个作者列表,然后使用一些更精美的javascript来更新其输入类ID和名称(id_1-book-0-title - &gt; id_2-book-0-title,例如),以及更新Book formset管理表单的TOTAL_FORMS和INITIAL_FORMS,以反映每行中新的Book表单数量,然后事情不太有效。

Django将列表中的拖动表单视为一种新形式的课程,并在数据库中创建一个真正的新书(如果您拥有唯一的字段,则会出现问题,因为本书已经在数据库中)。至于旧表中缺少此表单,表单DELETE不会被发送,因此Django当然不会删除旧对象。如果没有任何唯一字段,结果是您在数据库中获得了Book的两个副本,现在每个作者列表中都有一个。对于某种Book模型的独特字段(例如序列号说),您只需点击验证错误。

有谁知道设置这个的正确方法是什么?

编辑:这是我迄今为止的粗略view

def managerView(request):

if request.method == "POST":
    author_formset = AuthorFormSet(request.POST, prefix='author')
    pairs =[]
    for authorform in author_formset:
        author=authorform.instance
        tempDict={}
        tempDict['authorform'] =authorform
        tempDict['bookformset'] = BookFormSet(request.POST, prefix=str(author.pk)+'-book', instance=author)
        pairs.append(tempDict)

    if author_formset.is_valid() and all([pair['bookformset'].is_valid() for pair in pairs]):
        author_formset.save()
        for pair in pairs:
            author=pair['authorform'].instance
            #For this author, delete all current books, then repopulate
            #from forms in book list that came from request.
            old_book_pks = set([book.pk for book in author.books.all()])
            new_book_pks = set([bform.instance.pk for bform in pair['bookformset'].forms])
            pks_for_trash = old_book_pks - new_book_pks
            if len(pair['bookformset'].forms): pair['bookformset'].save()
        return HttpResponseRedirect(reverse('admin:index'))

else:
    author_formset = AuthorFormSet(prefix='author', queryset=Author.objects.order_by('rank'))
    pairs=[]
    for f in author_formset:
        author=f.instance
        #prefix the different book formsets like '1-book' etc
        pairs.append({'authorform':f, 'bookformset': BookFormSet(prefix=str(author.pk)+'-book',instance=author)})

myContext= {'authorformset': author_formset, 'pairs':pairs, 'request': request}
return myContext

现在是formsets:

AuthorFormSet = modelformset_factory(Author, extra=0)   
BookFormSet = inlineformset_factory(Author, Book, form=BookForm, extra=0, formset=BaseBookFormSet)       

除了一些自定义清理之外,BookForm和BaseBookFormSet中的内容不多,所以我暂时不会包含它们,除非有人认为它们会有用。

2 个答案:

答案 0 :(得分:1)

如果您还包含表单和查看代码,将会很有帮助。但是,作为一般概念,这似乎不应该太难实现。您使用的是基于类的视图吗?这听起来像是一种思考正在发生的事情的方法是当你更愿意触发更新逻辑时触发创建逻辑。 CBV就是为这种东西而设计的。就模型而言,您需要将对Book实例的PK的引用传递给Update View(基于类或功能视图)以及新Author的PK。

好的,如果没有实际在本地运行此代码,很难知道这是否能完全解决您的问题,但我认为关键是:

for pair in pairs:
            author=pair['authorform'].instance
            #For this author, delete all current books, then repopulate
            #from forms in book list that came from request.
            old_book_pks = set([book.pk for book in author.books.all()])
            new_book_pks = set([bform.instance.pk for bform in pair['bookformset'].forms])
            pks_for_trash = old_book_pks - new_book_pks
            if len(pair['bookformset'].forms): pair['bookformset'].save()
        return HttpResponseRedirect(reverse('admin:index'))

你有没有试过这样的事情:

for pk in new_book_pks:
    book = Book.objects.get(pk=pk)
    book.author = author
    book.save()

另外,只需注意:

if len(pair['bookformset'].forms): pair['bookformset'].save()

就个人而言,这对我来说是不可思议的。单行条件可能违反PEP8。你有没有理由使用len(pair...)?你不能只做if pair['bookformset'].forms:吗?

答案 1 :(得分:0)

我意识到自己很傻。如果不是坚持使用书籍的内联表单集,我只是发送回所有书籍的模板集(无论作者)和另一个作者,然后每本书都有一个作者下拉,这在拖动时使用javascript更新是微不足道的。将其放入新列表(此字段可能会出于演示目的隐藏)。然后一切都按照应有的方式运作。

对于在这样的设置中将正确的书籍组织到正确的作者<ol>的问题,小模板标签过滤器可以完成这项工作:

@register.filter(name='forms_for_author')
def forms_for_author(book_formset, authorid):

forms = []
for form in book_formset:
    if str(form.instance.tap_id) == str(tapid):
        forms.append(form)
return forms

在模板中用作

{% for authorform in authorformset %}
<ol class="author">
    {% for bookform in bookformset|forms_for_author:authorform.id.value %}
     <li>..</li>
     {% endfor %}
</ol>
{% endfor %}