Обращение имен с именами в Django: несколько экземпляров одного и того же приложения

Я работал с Django некоторое время (сейчас на версии 1.2), но совсем недавно начал работать над приложением, которое должно поддерживать несколько экземпляров. Например, файл проекта urls.py будет включать его дважды в двух разных пространствах имен, например:

urlpatterns = patterns('',
    (r'^instance1/', include('myapp.urls', namespace='instance1')),
    (r'^instance2/', include('myapp.urls', namespace='instance2')),
)

Я хорошо разбирался, пока не понял, что мне нужно выяснить, что делать со всеми внутренними вызовами reverse() (или вызовы шаблонов для фильтра {% url %}). Например, скажем, я делаю что-то вроде следующего в одном из моих представлений:

return HttpResponseRedirect(reverse('view_name'))

или что-то вроде этого в одном из моих шаблонов:

<a href="{% url view_name %}">link text</a>

... где view_name - это имя шаблона URL, содержащегося в myapp.urls. Поскольку я использую пространства имен, это вызовет ошибку: нет представления под названием view_name. Скорее, я должен сказать это либо instance1:view_name, либо instance2:view_name. Но делать это динамически меня бросает.

Я немного посмотрел, и похоже, что аргумент current_app, переданный либо в Context, либо RequestContext, был разработан, чтобы помочь с этим, но не совсем ясно, как динамически передавать правильное имя приложения current_app. Итак, какой правильный способ сказать Django, какое пространство имен использовать?

EDIT: Моим вариантом использования является использование одной установки приложения несколько раз. То есть, он существует только один раз на диске, но включается несколько раз в корень проекта urls.py (каждый раз под другим пространством имен, как в моем примере выше). Имея это в виду, есть ли хороший способ отслеживать, какое пространство имен вызывается из вида/шаблона, и использовать любое использование reverse() или {% url %} в том же пространстве имен? Я знаю, что Django 1.3 предоставит некоторые дополнительные функции, которые могут помочь в этом (а именно новый и улучшенный resolve()), но, безусловно, там хороший способ сделать это сейчас...

Ответ 1

Не очень хорошее решение, но поскольку вы используете тот же текст для своего пространства имен и начальной части пути URL, вы можете извлечь этот элемент из request.path (request.path.split('/')[1]) и установить его как current_app в контекст запроса или просто использовать его как пространство имен в представлениях.

http://docs.djangoproject.com/en/dev/topics/http/urls/#url-namespaces пункт 2.

Вы можете сделать это, например. в обработчике контекста (если вы хотите использовать пространство имен в шаблоне).

Для представлений вы можете написать декоратор, который передает вашу функцию в дополнительное пространство имен kwarg и использует ее как:

@feed_namespace
def view1(request, *args, **kwargs):
    ns = kwargs['namespace']

или просто напишите функцию reverse_namespaced с дополнительным параметром (запросом), где функция получает пространство имен и использует его вместо обратного.

Конечно, если вы это сделаете, вам всегда придется использовать путь запроса/пространство имен для этого приложения.

Ответ 2

С момента публикации вопроса многое изменилось, но для будущих googlers (например, я) было бы полезно указать, что request теперь имеет пространство имен (по крайней мере с 1.7, как показано в этот пример.

Из того, что я понял, мы должны просто передать current_app позиционный аргумент на reverse/redirect, но я не смог заставить его работать, поэтому я создал метод помощи для этой цели:

def __redirect(request, viewname, *args, **kwargs):
    to = viewname

    if callable(to):
        to = viewname.__module__ + '.' + viewname.__name__
    if request.resolver_match.namespace:
        to = '%s:%s' % (request.resolver_match.namespace, to)

    return redirect(
        to,
        *args,
        **kwargs
    )

Я использую redirect здесь, но все аргументы передаются на reverse, поэтому он тот же.

Ответ 4

Переменная current_app - это то, что вам нужно, чтобы установить что-то, что вам нравится.

Лично я бы рекомендовал установить его как-то вроде __name__.rsplit('.', 1)[0], чтобы вы получили spam в spam/views.py.

Но вы можете определить его как угодно, если ваше имя приложения соответствует тому, что вы определяете в файле urls.

Ответ 5

Если вы не слишком привязаны к пространствам имен, вы можете вместо этого написать urls.py, чтобы выглядеть как:

urlpatterns = patterns('',
    (r'^(P<instance>/)', 'controllers.route_me'),
)

Это вызовет функцию controllers.route_me и передаст ему запрос, а также строку "instance", с которой вы могли бы справиться следующим образом:

# (in controllers.py)
def route_me(request, instance):
    # you now have complete control, and do what you need to do with the 'instance' and 'request' vars
    pass