重构Django类视图,清理18个重复类。
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 个回答
我很少能找到需要使用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')
我想的一个简单方法是:在模型里加一个模板字段,里面放一些预定义的模板选项(这些可以动态创建)。然后,重写默认的DetailView方法,特别是get_template_names这个方法,用来给视图分配合适的模板(如果没有找到合适的模板,可以用try: except:来处理)。
除此之外,你还可以通过一些模型标志来改变视图的行为。这样的话,你就可以为一个模型设置一个统一的入口,而不是到处定义重复的视图。
不过,我通常会把FrontPageView独立出来,因为这样更简单,而且它的作用不同。
如果你需要重复使用的上下文条目,可以考虑使用上下文处理器;如果你需要为特定视图重复使用上下文条目,可以考虑使用Mixins。
把你所有的类合并成一个类,这个类要继承自 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
字段。这个类型字段会说明文章的类型。例如,你的 ChildrenView
、YouthView
和 AdultView
都可以有一个类型为 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')