如何创建内联可编辑的多对多关系

时间:2011-08-17 20:13:43

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

情况

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

  1. 页面有标题,slug和主要内容块。
  2. 内容块具有标题和内容块。
  3. 我能得到什么:

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

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

    我想要完成的任务:

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

    注意:由于我的请求有困难,我开始相信我试图完成的用户体验模式是错误的。如果我希望内容创建者进入并创建页面,请选择一些现有内容块(例如:现有侧边栏内容块),然后创建新的自定义块。我不认为我希望他不得不跳过这个地方来做这件事......

    没有解决方案的相关问题: 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)
    

    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)

3 个答案:

答案 0 :(得分: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字段是设置为“新..”还是现有字段。

答案 1 :(得分:0)

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

class Page(models.Model):
    ....

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

然后在页面管理表单上为ContentBlock添加常规表格内联。

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

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

答案 2 :(得分:0)

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

非共享pagelet显示为内联,可以直接在页面管理屏幕上添加额外的块。对于共享的pagelet,您可以通过加号获得下拉列表。