the situation
In my example I want to create a Page model with a many to many relationship with a content-blocks model.
- A page has a title, slug, and main content block.
- 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.
精彩评论