Как создать встроенное редактируемое отношение «многие ко многим» - PullRequest
1 голос
/ 18 августа 2011

ситуация

В моем примере я хочу создать модель страницы с отношением «многие ко многим» с моделью блоков контента.

  1. Страница имеет заголовок, слаг и блок основного содержимого.
  2. блоки контента имеют заголовок и блок контента.

Что я могу получить:

Отображение страниц.блоков в форме администратора отображает множественный выбор блоков контента

При создании встроенной формы для блоков контента на странице администратор показывает несколько вариантов выбора со знаком +, чтобы добавить больше

Что я пытаюсь сделать:

Полный CRUD для блока контента на странице admin

Примечание: Из-за сложности моего запроса я начинаю верить, что шаблон UX, который я пытаюсь выполнить, неверен. Если я хочу, чтобы создатель контента пришел и создал страницу, выберите несколько существующих блоков контента (например, существующий блок контента боковой панели), а затем создайте новый пользовательский блок. Я не думаю, что я хочу, чтобы он прыгал повсюду, чтобы сделать это ...

Вопрос по теме без решений: Как использовать TabularInline с редактируемыми полями в отношении ManyToMany?

EDIT

my admin.py</p> <pre><code>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 ]

1 голос
/ 18 августа 2011

У меня не было возможности протестировать это, но это должно работать:

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установить «Новый ..» или существующий.

0 голосов
/ 22 марта 2012

Проект https://github.com/caktus/django-pagelets звучит как то, что вы ищете.Страница может иметь «pagelets» и «shared pagelets» с хорошим администратором для двух (pagelets - просто блоки содержимого).

Неиспользуемые постлеты отображаются в виде строк с возможностью непосредственного добавления дополнительных блоков.на странице администратора.Для общих страниц вы получите раскрывающийся список со знаком плюс.

0 голосов
/ 02 сентября 2011

Это не тот ответ, который я искал, НО, что я закончил с тем, что

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

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

и затем наличие обычной табличной строки для ContentBlock в форме администратора страницы.

Таким образом, я могу иметь блоки содержимого, относящиеся к странице, и иметь общие блоки контента, которые можно использовать везде.

Затем я создал тег включения для отображения блока содержимого по имени, которое я использую в своих шаблонах.

...