【问题标题】:Django: user interface for changing objects foreign keyDjango:用于更改对象外键的用户界面
【发布时间】:2014-08-04 23:16:56
【问题描述】:

假设我有一组简单的 Django 模型,通过 ForeignKey 关联:

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> 列表,每个列表代表给定作者的书籍列表,按其等级排序,并且在 @ 中有一个表单用于书籍987654324@ 节点。因此,整个页面由一个 Author 表单集组成,每个表单集都由一个内联 Book 表单集组成。希望这很清楚......

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

从 Django 的角度来看,form me 完成任务 1 绝对没有问题。formset 提交,数据保存,Django 不知道这种方法和用户输入不同的区别当然,书籍在输入文本字段中排名。

但是,在我看来,完成任务 2 有点棘手。如果我将 Book 表单从一个 Author 列表拖放到另一个,然后使用一些更花哨的 javascript 来更新其输入类 id 和名称(例如id_1-book-0-title --> id_2-book-0-title),以及更新Book formset Management 表单的 TOTAL_FORMS 和 INITIAL_FORMS 以反映每行中 Book 表单的新数量,然后事情就不太正常了。

Django 将列表中拖动的表单视为一个新表单,并在数据库中创建一个真正的新书(如果你有唯一的字段,因为这本书已经在数据库中,这是有问题的)。至于 oldlist 中缺少此表单,则不会发送表单 DELETE,因此 Django 当然不会最终删除旧对象。如果没有任何唯一字段,结果是您在数据库中获得两份图书副本,现在每个作者的列表中都有一份。使用某种 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

现在是表单集:

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

除了一些自定义清理之外,BookForm 和 BaseBookFormSet 中没有发生太多事情,所以我暂时不会包括它们,除非有人认为它们有用。

【问题讨论】:

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


    【解决方案1】:

    如果您还包含表单和视图代码,将会很有帮助。然而,作为一个一般概念,这似乎并不难实现。您是否使用基于类的视图?这听起来像是一种思考正在发生的事情的方法是,当您希望触发更新逻辑时,会触发创建逻辑。 CBV 正是为这类事情而设计的。就您的模型而言,您需要将对 Book 实例的 PK 的引用与新作者的 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:吗?

    【讨论】:

    • 感谢您的回复!我已经更新以包含我的观点(我并没有真正使用作者/书籍,但除了一些细节之外,我的问题完美地映射到那个问题)。我从来没有玩过 CBV,所以我会读一读,也许我可以使用它们。
    • 目前我只是验证我拖到作者列表的表单,删除他所有的旧书,然后用我从 request.POST 中的表单中抓取的那些重新填充他的书。似乎有点疯狂。我使用前缀来处理多个表单集,并且配对是为了确保在模板中正确的书籍被放入正确的作者<ol>
    【解决方案2】:

    我意识到我很傻。如果不是坚持对书籍使用内联表单集,我只是发回所有书籍(无论作者)和所有作者中的另一个的表单集,那么每本书都有一个作者下拉列表,在拖动和更新时使用 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 %}
    

    【讨论】:

      猜你喜欢
      • 2018-09-02
      • 1970-01-01
      • 2021-03-22
      • 1970-01-01
      • 1970-01-01
      • 2021-06-30
      • 2016-05-08
      • 1970-01-01
      • 2016-10-27
      相关资源
      最近更新 更多