开发者

How do I create an inline editable many-to-many relationship

开发者 https://www.devze.com 2023-03-29 00:35 出处:网络
the situation In my example I want to create a Page model with a many to many relationship with a content-blocks model.

the situation

In my example I want to create a Page model with a many to many relationship with a content-blocks model.

  1. A page has a title, slug, and main content block.
  2. content blocks have a title and a content block.

What I can get:

Showing page.blocks in the admin form displays a multi select of content blocks

Creating an inline form for the content blocks on the page admin shows several selects with a + sign to add more

What I am trying to accomplish:

Full CRUD on content block on the page admin

Note: Due to the difficulty of my request, I'm beginning to believe the UX pattern im trying to accomplish is wrong. If I want a content creator to come in and create a page, pick some existing content blocks (ex: an existing sidebar content block), and then create a new custom block. I don't think i want him to have to jump all over the place to do this...

Related Question without solutions: How do I use a TabularInline with editable fields on a ManyToMany relationship?

EDIT

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)


Haven't had the opportunity to test this, but it should work:

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()

You could then actually add some javascript to show/hide the ContentBlock fields depending on whether the content_block field is set to 'New..' or an existing one.


This isn't the answer I was looking for, BUT, What I ended up going with is

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

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

and then having a regular tabular inline for ContentBlock on the page admin form.

So that way I can have page specific content blocks related to a page, AND be able to have generic content blocks able to be used wherever.

Then, I created an inclusion tag to render a content block by name that I use in my templates.


The project https://github.com/caktus/django-pagelets sounds like exactly what you are looking for. A page can have 'pagelets' and 'shared pagelets' with a nice admin for the two (pagelets are simply content blocks).

The non-shared pagelets are shown as inlines with the ability to add extra blocks directly on the page admin screen. For shared pagelets you get the drop-down with a plus-sign.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号