0

我正在读一本关于 Django 的书,我正在尝试重新解释一些内容。(我使用的是 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 并在模型管理中使用它。我试图实施这些建议,但没有运气。在我的代码的最后一个版本中,我尝试以as_view()这种方式使用 CBV(在 CBV 之后的 views.py 中):

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

非常感谢任何帮助或建议。

4

1 回答 1

0

最后,我无法找到解决问题的方法,至少以我想要的方式。所以我试图解决这个问题,以其他方式获得相同的结果。在寻找不同的方法时,例如不使用 GenericForeignKey 或 ContentTypes 我遇到了这个链接:避免 Django 的 GenericForeignKey 它看起来清晰简单,虽然可能有点不那么优雅,所以我实现了以下解决方案。

1)我通过删除 GenericForeignKey 并将其替换为每种类型的受支持内容的 OneToOne 关系来修改 Content 类:

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']

然后我通过重写 get_queryset 函数修改了 ContentAdmin 类:

@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

通过这种方式,我可以在管理界面中加载附件,然后通过从列表中方便地选择它们来将它们与所需的内容结合起来。不幸的是,这是一个分两步的过程,我更愿意以一种形式在管理方面解决所有问题,但是......它可以工作。

于 2019-01-05T18:53:07.820 回答