Как скрестить view и template_tag?
Случалась ли у Вас ситуация когда у вас на страничке есть некий блок. При загрузке страницы он реализуется например как рендер template_tag'а:
Все замечательно но у нас получается есть 2 вещи (template_tag и view) которые по сути делают одно и тоже - возвращают один и тот же блок.
Как уйти от повторения кода?
Самое просто решение которое приходит в голову - сделать во views импорт нужного template_tag'а и возвращать в view результат работы этого самого template_tag'a:
С виду все хорошо но давайте посчитаем что у нас есть. Есть view и есть tag(есть даже библиотека тэгов - а это уже само по себе отдельная папка и пару файлов), есть записи в urls.py для каждого ajax-запроса:
А самое главное у нас одно и тоже по сути логическое действие для одного блока на странице разнесено как минимум по 2м отдельным файлам - my_tags.py и views.py.
ViewTags
Дальше пойдет интересное решение этой проблемы. Оговорюсь сразу, идея не моя, реализация тоже, но этим механизмом я пользовался и мягко говоря впечатлился:
Итак, задача скрестить view и template_tag.
Создаем файл utils/viewtags.py
В urls.py единоразово(!) делаем запись
Теперь к шаблонам.
Что-бы указать блок на странице будем писать так:
Ну и пейджинация.
Если раньше у нас был url для каждой ajax-вьюшки отдельно:
По-моему симпатично. Лично у меня количество кода значительно уменьшилось и все стало более понятным.
Случалась ли у Вас ситуация когда у вас на страничке есть некий блок. При загрузке страницы он реализуется например как рендер template_tag'а:
@register.inclusion_tag('my_block.html', takes_context=True) def my_block_tag(context): return { # some additional context }В шаблоне как обычно:
{% load my_tags %} {% my_block_tag %}Дальше, этот блок имеет pagination, который реализуется Ajax-запросом на сервер, где соответствующий view рендерит тот же самый кусок страницы:
def my_block_ajax(request, ...): #формируем нужные данные для нужной страницы return JsonResponse("my_block.html", { # какие-то данные })
Все замечательно но у нас получается есть 2 вещи (template_tag и view) которые по сути делают одно и тоже - возвращают один и тот же блок.
Как уйти от повторения кода?
Самое просто решение которое приходит в голову - сделать во views импорт нужного template_tag'а и возвращать в view результат работы этого самого template_tag'a:
from myapp.templatetags.my_tags import my_block_tag def my_block_ajax(request, ...): #формируем нужные данные для нужной страницы return my_block_tag(some_context)
С виду все хорошо но давайте посчитаем что у нас есть. Есть view и есть tag(есть даже библиотека тэгов - а это уже само по себе отдельная папка и пару файлов), есть записи в urls.py для каждого ajax-запроса:
url(r'ajax/my_block/$', 'my_block', name='my_block'),Сильно много у нас всего. А если у нас таких блоков 10? 20? это просто огромная масса лишнего повторяющегося кода.
А самое главное у нас одно и тоже по сути логическое действие для одного блока на странице разнесено как минимум по 2м отдельным файлам - my_tags.py и views.py.
ViewTags
Дальше пойдет интересное решение этой проблемы. Оговорюсь сразу, идея не моя, реализация тоже, но этим механизмом я пользовался и мягко говоря впечатлился:
Итак, задача скрестить view и template_tag.
Создаем файл utils/viewtags.py
- Делаем декоратор
import inspect from django import template from django.template.loader import get_template from django.http import Http404 from django.shortcuts import render_to_response from django.utils.importlib import import_module from django.conf import settings register = template.Library() template.builtins.append(register) _viewtags_registry = {} def viewtag(template, args=[]): def decorator(func): _register_tag(func, template) _viewtags_registry[func.__name__] = (func, template, args) return func return decorator
Этим декоратором мы будем оборачивать вьюшки.
- Вьюшки будем обрабатывать вот так
def view(request, viewtag_name): try: func, template_, arg_processors = _viewtags_registry[viewtag_name] except KeyError: raise Http404() argnames = inspect.getargspec(func).args kwargs = {} for argname in argnames: if argname in request.REQUEST and argname != 'request': kwargs[argname] = request.REQUEST[argname] for proc in arg_processors: kwargs = proc(request, kwargs) return render_to_response(template_, func(request, **kwargs), template.RequestContext(request))
- Все наши viewtags будем хранить в app/viewtags.py
Поэтому при старте сервера загружаем все наши viewtags
def autodiscover(): for app in settings.INSTALLED_APPS: try: import_module('%s.viewtags' % app) except ImportError, e: pass
- Авторегистрируем template_tags для наших viewtags
def _register_tag(func, template): def tag(parser, token): parts = token.split_contents() args, kwargs = [], [] for part in parts[1:]: if '=' in part: kwargs.append(part.split('=', 1)) else: args.append(part) return TempNode(func, template, args, dict(kwargs)) register.tag('vt:' + func.__name__, tag) class TempNode(template.Node): def __init__(self, func, template_, args, kwargs): self.func = func self.template = get_template(template_) self.args = [template.Variable(arg) for arg in args] self.kwargs = dict([(k, template.Variable(v)) for (k,v) in kwargs.items()]) def render(self, context): args = [context['request']] + [arg.resolve(context) for arg in self.args] kwargs = dict([ (key, val.resolve(context)) for (key, val) in self.kwargs.items() ]) context.update(self.func(*args, **kwargs)) res = self.template.render(context) context.pop() return res
В urls.py единоразово(!) делаем запись
url(r'^viewtag/([-\w]+)/$', 'my_project.utils.viewtags.view', name='viewtag'),В viewtags.py импортируем наш декоратор viewtag и оборачиваем вьюшку:
from my_project.utils.viewtags import viewtag @viewtag('my_block.html') def my_block(request, page=0): # делаем что нам там нужно return { #context }Благодаря пункту 3 мы автоматически будем иметь в системе "запись" в urls.py для всех функций описанных в файлах viewtags.py
Теперь к шаблонам.
Что-бы указать блок на странице будем писать так:
{% vt:my_block params %}В пункте 1 у нас зарегистрированы функции для тэгов которые называются "vt". То есть если есть функция обернутая в viewtag то можно писать в шаблоне {% vt:имя_функции %} и это будет работать.
Ну и пейджинация.
Если раньше у нас был url для каждой ajax-вьюшки отдельно:
$.get('{% url my_block_ajax %}', {page: 2}, function(data) { $('body').append(data); });То теперь мы делаем url для вьюшки с названием "viewtag" и параметром передаем конкретное название нужного viewtag:
$.get('{% url viewtag "my_block" %}', {page: 2}, function(data) { $('body').append(data); });Считаем еще раз. Библиотек своих template_tags у нас вообще нет. Кучи однотипных записей в urls.py для каждого отдельного ajax-запроса у нас тоже нет. Код для каждого блока представлен одной общей функцией в viewtags.py
По-моему симпатично. Лично у меня количество кода значительно уменьшилось и все стало более понятным.
Комментариев нет:
Отправить комментарий