【问题标题】:How do I create an inline editable many-to-many relationship如何创建内联可编辑的多对多关系
【发布时间】:2011-10-29 06:08:32
【问题描述】:

情况

在我的示例中,我想创建一个与内容块模型具有多对多关系的页面模型。

  1. 一个页面有一个标题、slug 和主要内容块。
  2. 内容块有一个标题和一个内容块。

我能得到什么:

在管理表单中显示 page.blocks 会显示多选的内容块

在页面管理员上为内容块创建内联表单会显示几个带有 + 号的选择以添加更多内容

我想要完成的工作:

页面管理上的内容块上的完整 CRUD

注意:由于我的请求很困难,我开始相信我试图完成的 UX 模式是错误的。如果我希望内容创建者进来并创建一个页面,请选择一些现有的内容块(例如:现有的侧边栏内容块),然后创建一个新的自定义块。我不认为我希望他必须跳遍整个地方才能做到这一点......

没有解决方案的相关问题: How do I use a TabularInline with editable fields on a ManyToMany relationship?

编辑

my admin.py

from django.contrib import admin
from django.contrib.flatpages.admin import FlatpageForm, FlatPageAdmin
from django.contrib.flatpages.models import FlatPage
from my_flatpages.models import ExtendedFlatPage, ContentBlock
from mptt.admin import MPTTModelAdmin
from django import forms
import settings

"""
Extended Flatpage Form
"""
class ExtendedFlatPageForm(FlatpageForm):
    class Meta:
        model = ExtendedFlatPage


"""
Page Content Block inline form
"""
class ContentBlockInlineAdminForm(forms.ModelForm):
    # Add form field for selecting an existing content block
    content_block_choices = [('', 'New...')]
    content_block_choices.extend([(c.id, c) for c in ContentBlock.objects.all()])
    content_blocks = forms.ChoiceField(choices=content_block_choices, label='Content Block')

    def __init(self, *args, **kwargs):
        super(ContentBlockInlineAdminForm, self).__init__(*args, **kwargs)

        # Show as existing content block if it already exists
        if self.instance.pk:
            self.fields['content_block'].initial = self.instance.pk
            self.fields['title'].initial = ''
            self.fields['content'].initial = ''

        # Make title and content not required so user can opt to select existing content block
        self.fields['title'].required = False
        self.fields['content'].required = False

    def clean(self):
        content_block = self.cleaned_data.get('content_block')
        title = self.cleaned_data.get('title')
        content = self.cleaned_data.get('content')

        # Validate that either user has selected existing content block or entered info for new content block
        if not content_block and not title and not content:
            raise forms.ValidationError('You must either select an existing content block or enter the title and content for a new content block')


"""
Content Block Inline Admin
"""
class ContentBlockInlineAdmin(admin.TabularInline):
    form = ContentBlockInlineAdminForm

    class Meta:
        model = ContentBlock
        extra = 1


"""
Extended Flatpage Admin
"""
class ExtendedFlatPageAdmin(FlatPageAdmin, MPTTModelAdmin):
    form = ExtendedFlatPageForm
    fieldsets = (
        (
            None, 
            {
                'fields': ('url', 'title', 'content', ('parent', 'sites'))
            }
        ),
        (
            'SEO Fields', 
            {
                'fields': ('seo_title', 'seo_keywords', 'seo_description'), 
            'classes': ('collapse', )
            }
        ),
        (
            'Advanced options', 
            {
                'fields': ('enable_comments', 'registration_required', 'template_name'), 
            'classes': ('collapse', )
            }
        ),
    )
    inlines = (ContentBlockInlineAdmin,)


    class Media:
        js = (
            'https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js',
            settings.MEDIA_URL + 'js/tinymce/jquery.tinymce.js',
            settings.MEDIA_URL + 'js/init_tinymce.js'
        )

admin.site.unregister(FlatPage)
admin.site.register(ExtendedFlatPage, ExtendedFlatPageAdmin)

【问题讨论】:

    标签: django django-admin many-to-many


    【解决方案1】:

    还没有机会对此进行测试,但它应该可以工作:

    class ContentBlockInlineAdminForm(forms.ModelForm):
        # Add form field for selecting an existing content block
        content_block_choices = [('', 'New...')]
        content_block_choices.extend([(c.id, c) for c in ContentBlock.objects.all()])
        content_blocks = forms.ChoiceField(choices=content_block_choices, label='Content Block')
    
        def __init(self, *args, **kwargs):
            super(ContentBlockInlineAdminForm, self).__init__(*args, **kwargs)
    
            # Show as existing content block if it already exists
            if self.instance.pk:
                self.fields['content_block'].initial = self.instance.pk
                self.fields['title'].initial = ''
                self.fields['content'].initial = ''
    
            # Make title and content not required so user can opt to select existing content block
            self.fields['title'].required = False
            self.fields['content'].required = False
    
        def clean(self):
            content_block = self.cleaned_data.get('content_block')
            title = self.cleaned_data.get('title')
            content = self.cleaned_data.get('content')
    
            # Validate that either user has selected existing content block or entered info for new content block
            if not content_block and not title and not content:
                raise forms.ValidationError('You must either select an existing content block or enter the title and content for a new content block')
    
    class ContentBlockInlineAdmin(admin.TabularInline):
        form = ContentBlockInlineAdminForm
    
        class Meta:
            model = ContentBlock
            extra = 1
    
    class PageAdmin(admin.ModelAdmin):
        inlines = [
            ContentBlockInlineAdmin,
        ]
    
        """
        Override saving of formset so that if a form has an existing content block selected, it
        sets the form instance to have the pk of that existing object (resulting in update rather
        than create). Also need to set all the fields on ContentType so the update doesn't change
        the existing obj.
        """
        def save_formset(self, request, form, formset, change):
            for form in formset:
                if form.cleaned_data.get('content_block'):
                    content_block = ContentBlock.objects.get(pk=form.cleaned_data.get('content_block'))
                    instance = form.save(commit=False)
                    instance.pk = content_block.pk
                    instance.title = content_block.title
                    instance.content = content_block.content
                    instance.save()
                else:
                    form.save()
    

    然后,您实际上可以添加一些 javascript 来显示/隐藏 ContentBlock 字段,具体取决于 content_block 字段设置为“新建..”还是现有字段。

    【讨论】:

    • 这看起来很有希望,我得到一个“异常值:'model' 是 'ExtendedFlatPageAdmin.inlines[0]' 的必需属性。”
    • 好的,我通过在 ContentBlockInlineAdmin 类中添加:model = ExtendedFlatPage.blocks.through 来实现这一点。它看起来像这样:tinypic.com/r/2uyg6yd/7,这并不是我真正想要的。也许我错过了什么/不明白?
    • 查看我对您提到的另一个问题的回答。它也适用于此。
    【解决方案2】:

    这不是我想要的答案,但是,我最终得到的是

    class Page(models.Model):
        ....
    
    class ContentBlock(models.Model):
        page = models.ForeignKey(
            Page, 
            blank = True,
            null = True,
        )
        ....
    

    然后在页面管理表单上有一个用于 ContentBlock 的常规表格内联。

    这样我就可以拥有与页面相关的页面特定内容块,并且能够拥有能够在任何地方使用的通用内容块。

    然后,我创建了一个包含标记来按名称呈现我在模板中使用的内容块。

    【讨论】:

      【解决方案3】:

      https://github.com/caktus/django-pagelets 项目听起来正是您正在寻找的。一个页面可以有“pagelets”和“shared pagelets”,两者都有一个很好的管理员(pagelet 只是内容块)。

      非共享小页面显示为内联,可以直接在页面管理屏幕上添加额外的块。对于共享的 pagelet,您会看到带有加号的下拉菜单。

      【讨论】:

      • 感谢您的回复。我最终选择了上面发布的解决方案。我喜欢尽可能自己动手。
      猜你喜欢
      • 1970-01-01
      • 2010-12-15
      • 2012-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-25
      • 2018-03-28
      • 1970-01-01
      相关资源
      最近更新 更多