Рефакторинг представлений на основе классов Django, очистка 18 повторяющихся классов.

#python #django #refactoring #django-class-based-views

#python #django #рефакторинг #представления на основе классов django

Вопрос:

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.

Это назначение шаблона, получение объекта Article и добавление некоторых вещей в context_data .

Затем я скопировал этот класс 17 раз. Каждый раз в context_data добавляется другой шаблон, другой slug и другой материал.

Идея заключается в том, что есть редактор WYSIWYG, позволяющий администраторам изменять веб-контент, и система аутентификации пользователей, позволяющая нескольким людям получать доступ к содержимому сайта. По сути, суперпростая CMS, поэтому никому не нужно редактировать html для обновления сайта.

Но я действительно хотел бы провести рефакторинг, чтобы у меня не было этих почти идентичных 18 классов. Любые предложения о том, с чего мне следует начать, будут приветствоваться.

Ответ №1:

Сведите все ваши классы к одному классу, который наследует от TemplateResponseMixin , as DetailView does (также проверьте 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 возьмет этот список имен и попробует каждый элемент, чтобы увидеть, существует ли он в папке templates . Если это происходит, используется этот шаблон.

Обновить

После более пристального изучения вашего кода, возможно, что-то вроде этого:

В вашем основном 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>[wd/-] )/$', SuperSpecialAwesomeView.as_view()),
# but, put this at the end of urls list after any routes that don't use this view
  

DetailView после установки атрибута class model проверит, есть ли slug в kwargs url, и если это так, он будет использовать slug для выполнения поиска модели точно так же, как то, что вы уже делаете: Article.ojects.get(slug=self.kwargs['slug'])

models.py

Вы можете добавить type поле в свою Article модель. Тип будет указывать, к какому типу относится статья. Например, ваши 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 и ministry/children фрагментом , Django будет искать шаблон с именем ministry/children.html и шаблон с именем music.html .

И если вам нужно сделать что-то особенное для других представлений (например, вам, вероятно, понадобится для SermonsView ), тогда подкласс SuperSpecialAwesomeView

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

Комментарии:

1. Это фантастика, и работает точно так, как описано. Я внес некоторые изменения в функцию view, чтобы включить оператор return и get_type_display() для поиска имени типа article вместо целого числа в методе get_template_names, а также комментарий о том, куда мне нужно поместить новое регулярное выражение urls.py

Ответ №2:

Я бы подумал, что быстрый подход: добавьте поле шаблона в модель со списком предопределенных вариантов шаблонов (их можно создавать динамически). Переопределите методы DetailView по умолчанию, переопределите метод get_template_names, чтобы назначить правильный шаблон для представления (если запасной вариант недоступен, это можно сделать с помощью try: except:). Кроме того, вы можете изменить поведение представления с помощью любых флагов модели. Таким образом, вы можете иметь единую точку входа для модели, а не определять повторяющиеся представления повсюду. Я стараюсь сохранять FrontPageView независимым от других представлений, хотя для простоты и потому, что он служит другой цели. Если вам нужны повторяющиеся записи контекста, рассмотрите контекстный процессор, если вам нужны повторяющиеся записи контекста для определенных представлений, рассмотрите миксины.

Ответ №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')
  

Комментарии:

1. Почему представление на основе функций (которое не является DRY) считается рефакторингом? это подход, но не рефакторинг.

2. Это всего лишь предложение.