Как CRUD ContentType на сайте администратора Django? - PullRequest
0 голосов
/ 12 ноября 2018

Я читаю книгу о Джанго и пытаюсь переосмыслить некоторый контент.(Я использую Django 2.1 и Python 3.6). В книге различные типы контента связаны с модулем, например:

class Module(models.Model):
    course = models.ForeignKey(Course,
                        related_name='modules',
                        on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    order = OrderField(blank=True, for_fields=['course'])

    class Meta:
        ordering = ['order']

    def __str__(self):
        return '{}. {}'.format(self.order, self.title)

class Content(models.Model):
    module = models.ForeignKey(Module,
                        related_name='contents',
                        on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType,
                                on_delete=models.CASCADE,
                                limit_choices_to={'model__in': (
                                    'text',
                                    'video',
                                    'image',
                                    'file')})
    object_id = models.PositiveIntegerField()
    item = GenericForeignKey('content_type', 'object_id')
    order = OrderField(blank=True, for_fields=['module'])

    class Meta:
        ordering = ['order']

class ItemBase(models.Model):
    owner = models.ForeignKey(User,
                        related_name='%(class)s_related',
                        on_delete=models.CASCADE)
    title = models.CharField(max_length=250)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

    def __str__(self):
        return self.title

    def render(self):
        return render_to_string(
             'courses/content/{}.html'.format(self._meta.model_name),
             {'item': self})

class Text(ItemBase):
    content = models.TextField()

class File(ItemBase):
    file = models.FileField(upload_to='files')

class Image(ItemBase):
    file = models.FileField(upload_to='images')

class Video(ItemBase):
    url = models.URLField()

В книге есть CBV для создания правильной формы для типов контента.:

class ContentCreateUpdateView(TemplateResponseMixin, View):
    module = None
    model = None
    obj = None
    template_name = 'courses/manage/content/form.html'

    def get_model(self, model_name):
        if model_name in ['text', 'video', 'image', 'file']:
            return apps.get_model(app_label='courses',
                                model_name=model_name)
        return None

    def get_form(self, model, *args, **kwargs):
        Form = modelform_factory(model, exclude=['owner',
                                             'order',
                                             'created',
                                             'updated'])
        return Form(*args, **kwargs)

    def dispatch(self, request, module_id, model_name, id=None):
        self.module = get_object_or_404(Module,
                                   id=module_id,
                                   course__owner=request.user)
        self.model = self.get_model(model_name)
        if id:
            self.obj = get_object_or_404(self.model,
                                     id=id,
                                     owner=request.user)
        return super(ContentCreateUpdateView,
           self).dispatch(request, module_id, model_name, id)

    def get(self, request, module_id, model_name, id=None):
        form = self.get_form(self.model, instance=self.obj)
        return self.render_to_response({'form': form,
                                    'object': self.obj})

    def post(self, request, module_id, model_name, id=None):
        form = self.get_form(self.model,
                         instance=self.obj,
                         data=request.POST,
                         files=request.FILES)
        if form.is_valid():
            obj = form.save(commit=False)
            obj.owner = request.user
            obj.save()
            if not id:
                # new content
                Content.objects.create(module=self.module,
                                   item=obj)
            return redirect('module_content_list', self.module.id)
        return self.render_to_response({'form': form,
                                        'object': self.obj})

И содержимое предоставляется пользователями со специальными разрешениями.Хорошо, и теперь вопрос: я хочу, чтобы содержимое управлялось только администратором на сайте администратора.Я пробовал таким образом:

class ContentInline(GenericStackedInline):
    model = Content
    extra = 0

@admin.register(Module)
class ModuleAdmin(admin.ModelAdmin):
    list_display = ['title', 'order', 'course']
    list_filter = ['course']
    search_fields = ['title', 'description']
    inlines = [ContentInline]

@admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
    list_display = ['object_id', 'module', 'content_type', 'order']

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

страница модуля на сайте администратора

В то время как страница содержимого это:

страница содержимого на сайте администратора

Я какое-то время искал решение, но не смог найти.Здесь есть похожая тема: ссылка , но предлагаемые решения предполагают использование javascript и / или дополнительных пакетов, хотя я бы хотел сделать это только с Python и Django.Я также читал, что возможное решение для отображения пользовательских представлений на сайте администратора состоит в том, чтобы написать представление и затем добавить его в URL-адреса администратора, а затем добавить его в модель администратора.Кто-то еще предложил использовать CBV из книги и использовать его в модели администратора.Я попытался реализовать эти предложения без удачи.В последней версии моего кода я пытаюсь использовать CBV с as_view() следующим образом (в views.py сразу после CBV):

content_create_update_view = ContentCreateUpdateView.as_view()

, а затем в admin.py:

@admin.register(Content)
class ContentAdmin(admin.ModelAdmin):
    list_display = ['object_id', 'module', 'content_type', 'order']

    def get_urls(self):
        urls = super().get_urls()
        my_urls = [
            path('content/<int:object_id>/change/', self.admin_site.admin_view(content_create_update_view)),
        ]
        return my_urls + urls

любая помощь или предложение с благодарностью.

1 Ответ

0 голосов
/ 05 января 2019

В конце концов я не смог найти решение своей проблемы, по крайней мере так, как я хотел.Поэтому я попытался обойти проблему, чтобы получить тот же результат другими способами.При поиске различных методов, таких как не использование GenericForeignKey или ContentTypes, я натолкнулся на эту ссылку: избегать GenericForeignKey Django Это казалось ясным и простым, хотя, возможно, немного «менее элегантным», поэтому я реализовал следующее решение.

1) Я изменил класс Content, удалив GenericForeignKey и заменив его отношением OneToOne для каждого типа поддерживаемого содержимого:

text = models.OneToOneField(Text, null=True, blank=True, on_delete=models.CASCADE)
file = models.OneToOneField(File, null=True, blank=True, on_delete=models.CASCADE)
image = models.OneToOneField(Image, null=True, blank=True, on_delete=models.CASCADE)
video = models.OneToOneField(Video, null=True, blank=True, on_delete=models.CASCADE)

и убедившись, что только одно вложение былоПодбирая за раз для каждого содержимого, я добавил проверку, перезаписывая функцию сохранения:

def save(self, **kwargs):
    assert [self.text, self.file, self.image, self.video].count(None) == 3
    return super().save(**kwargs)

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

@property
def target(self):
    if self.text_id is not None:
        return self.text
    if self.file_id is not None:
        return self.file
    if self.image_id is not None:
        return self.image
    if self.video_id is not None:
        return self.video
    raise AssertionError("You have to set content!")

2) Я изменил файл admin.py, добавив все ожидаемые типы содержимого:

@admin.register(Text)
class TextAdmin(admin.ModelAdmin):
    list_display = ['title', 'created', 'updated']

@admin.register(File)
class FileAdmin(admin.ModelAdmin):
    list_display = ['title', 'created', 'updated']

@admin.register(Image)
class ImageAdmin(admin.ModelAdmin):
    list_display = ['title', 'created', 'updated']

@admin.register(Video)
class VideoAdmin(admin.ModelAdmin):
    list_display = ['title', 'created', 'updated']

Затем я изменил класс ContentAdmin, переопределив функцию get_queryset:

@admin.register(Content)
class ContentAdmin(admin.ModelAdmin):

    def get_queryset(self, request):
        qs = super(ContentAdmin, self).get_queryset(request)
        qs = qs.select_related('text',
                               'file',
                               'image',
                               'video')
        return qs

ВТаким образом, я могу загрузить вложения в интерфейсе администратора, а затем объединить их с нужным Cнамеренно выбирая их из списка.К сожалению, это процедура в 2 этапа, я бы предпочел решить все в одной форме на стороне администратора, но ... это работает.

...