重构Django类视图,清理18个重复类。

4 投票
3 回答
742 浏览
提问于 2025-04-18 10:07

https://github.com/AnthonyBRoberts/fcclincoln/blob/master/apps/story/views.py

我有点不好意思承认这个是我的代码,但确实是。

class FrontpageView(DetailView):
    template_name = "welcome_content.html"
    def get_object(self):
        return get_object_or_404(Article, slug="front-page")
    def get_context_data(self, **kwargs):
        context = super(FrontpageView, self).get_context_data(**kwargs)
        context['slug'] = "front-page"
        events = Article.objects.filter(slug="events")
        context['events'] = events
        return context

这是一个在Django框架中很常见的基于类的详细视图。

它在指定一个模板,获取一个文章对象,并且往上下文数据中添加一些内容。

然后我把这个类复制了17遍。每次都有不同的模板、不同的标识符(slug),以及不同的内容添加到上下文数据中。

这个设计的目的是为了给管理员提供一个所见即所得(WYSIWYG)的编辑器,让他们可以修改网站内容,还有一个用户认证系统,允许多个用户访问网站内容。基本上就是一个超级简单的内容管理系统(CMS),这样就不需要有人去编辑HTML来更新网站了。

但我真的希望能重构一下这个代码,这样就不需要有这18个几乎一模一样的类了。如果有人能给我一些建议,从哪里开始改进会非常欢迎。

3 个回答

0

我很少能找到需要使用CBD的地方。

你可以这样重构它:

def editable_page(slug):
    return {
        'context': {
            'slug': slug
        }
        'template': 'mysupertemplates/{0}.html'.format(slug)
    }

def frontpage(req):
    return editable_page('frontpage')

def chat(req):
    return editable_page('char')

def about(req):
    return editable_page('about')
1

我想的一个简单方法是:在模型里加一个模板字段,里面放一些预定义的模板选项(这些可以动态创建)。然后,重写默认的DetailView方法,特别是get_template_names这个方法,用来给视图分配合适的模板(如果没有找到合适的模板,可以用try: except:来处理)。

除此之外,你还可以通过一些模型标志来改变视图的行为。这样的话,你就可以为一个模型设置一个统一的入口,而不是到处定义重复的视图。

不过,我通常会把FrontPageView独立出来,因为这样更简单,而且它的作用不同。

如果你需要重复使用的上下文条目,可以考虑使用上下文处理器;如果你需要为特定视图重复使用上下文条目,可以考虑使用Mixins。

6

把你所有的类合并成一个类,这个类要继承自 TemplateResponseMixin,就像 DetailView 那样(你也可以看看 SingleObjectTemplateResponseMixin)。然后重写它的 get_template_names() 方法,让它返回适合当前情况的模板。

一个很好的例子可以在 django-blog-zinnia 项目中找到。

def get_template_names(self):
    """
    Return a list of template names to be used for the view.
    """
    model_type = self.get_model_type()
    model_name = self.get_model_name()

    templates = [
        'zinnia/%s/%s/entry_list.html' % (model_type, model_name),
        'zinnia/%s/%s_entry_list.html' % (model_type, model_name),
        'zinnia/%s/entry_list.html' % model_type,
        'zinnia/entry_list.html']

    if self.template_name is not None:
        templates.insert(0, self.template_name)

    return templates

Django 会拿这个名字列表,逐个检查这些名字在模板文件夹里是否存在。如果存在,就会使用那个模板。

更新

在仔细看了你的代码后,或许可以这样做:

在你的主 urls.py 文件中

# convert each url
url(r'^$', FrontpageView.as_view()),
url(r'^history/$', HistoryView.as_view()),
url(r'^calendar/$', CalendarView.as_view()),
url(r'^news/$', NewsView.as_view()),
url(r'^visitors/$', VisitorsView.as_view()),
...
# to just
url(r'^(?P<slug>[\w\d/-]+)/$', SuperSpecialAwesomeView.as_view()),
# but, put this at the end of urls list after any routes that don't use this view

DetailView 在设置了类属性 model 后,会检查 slug 是否在 url 的 kwargs 中。如果在,它会用这个 slug 来查找模型,就像你现在做的那样:Article.ojects.get(slug=self.kwargs['slug'])

models.py 文件

你可以在 Article 模型中添加一个 type 字段。这个类型字段会说明文章的类型。例如,你的 ChildrenViewYouthViewAdultView 都可以有一个类型为 music(因为这些模板都是音乐的,我猜它们是这样关联的)。

ARTICLE_TYPE_CHOICES = (
    (0, 'music'),
    (1, 'weddings'),
    (2, 'outreach'),
    ...
)

class Article(models.Model):
     ...
     type = models.IntegerField(choices=ARTICLE_TYPE_CHOICES)
     ...

然后,在你的 views.py 文件中

class SuperSpecialAwesomeView(DetailView):
    template_name = None
    model = Article
    def get_template_names(self):
        slug = self.kwargs.get('slug', '')
        templates = [
            # create a template based on just the slug
            '{0}.html'.format(slug),
            # create a template based on the model's type
            '{0}.html'.format(self.object.get_type_display()),
        ]
        # Allow for template_name overrides in subclasses
        if self.template_name is not None:
            templates.insert(0, self.template_name)

        return templates

假设有一个类型为 music 的文章实例,slug 为 ministry/children,Django 会寻找一个名为 ministry/children.html 的模板和一个名为 music.html 的模板。

如果你需要为其他视图做一些特别的事情(比如你可能需要为 SermonsView 做的),那么可以继承 SuperSpecialAwesomeView

class SermonsView(SuperSpecialAwesomeView):
    paginate_by = 2
    queryset = Article.objects.order_by('-publish_date')

撰写回答