Django - передать имя модели в качестве параметра в общий вид

Допустим, у меня есть несколько моделей, которые наследуются от базового класса Animal. Я могу использовать общие представления и направлять Cat/12 в подробное представление, а Dod/10 в то же самое подробное представление с другим контекстом. Но я хотел бы получить имя модели из URL-адреса, чтобы мне не нужно было определять маршрут.

У меня есть что-то вроде этого:

url(r'^cat/(?P<slug>[-\w]+)/$',
    DetailView.as_view(
        queryset=Cat.objects.filter(),
        model=Cat,
        context_object_name='animal',
        template_name='animal/detail.html'),
    name='detail'),
url(r'^dog/(?P<slug>[-\w]+)/$',
    DetailView.as_view(
        queryset=Dog.objects.filter(),
        model=Dog,
        context_object_name='animal',
        template_name='animal/detail.html'),
    name='detail'),
...

Очевидно, что это слишком много повторяющегося кода. Я бы предпочел сделать что-то вроде этого:

url(r'^?P<my_animal>\w+/(?P<slug>[-\w]+)/$',
    DetailView.as_view(
        queryset=my_animal.objects.filter(),
        model=my_animal,
        context_object_name='animal',
        template_name='animal/detail.html'),
    name='detail'),
...

Я могу сделать это?

ИЗМЕНИТЬ

Вот что у меня получилось благодаря помощи Дарвина. Это позволяет избежать if/else для получения имени модели:

class AnimalDetailView(DetailView):
    context_object_name='animal'
    template_name='animals/detail.html'

    def dispatch(self, request, *args, **kwargs):
        my_animal = kwargs.get('my_animal', None)
        self.model = get_model('animals',my_animal.capitalize())
        try:
            ret = super(AnimalDetailView, self).dispatch(request, *args, **kwargs)
        except AttributeError:
            raise Http404
        return ret

    def get_queryset(self):
        return self.model.objects.filter()

В следующий раз, когда у меня возникнет вопрос о наследовании, я проконсультируюсь с Дарвином! Смешной


person Rob L    schedule 24.04.2013    source источник


Ответы (3)


Вы можете наследовать от DetailView и переопределить метод dispatch, чтобы создать свои собственные правила примерно так:

class AnimalDetailView(DetailView):
    context_object_name='animal'
    template_name='animal/detail.html'

    def dispatch(self, request, *args, **kwargs):
        my_animal = kwargs.get('my_animal', None)
        if my_animal == 'dog':
            self.model = Dog
        elif my_animal == 'cat':
            self.model = Cat

        return super(AnimalDetailView, self).dispatch(request, *args, **kwargs)

    def get_queryset(self):
        return self.model.objects.filter()

и используйте такой шаблон URL:

url(r'^?P<my_animal>\w+/(?P<slug>[-\w]+)/$', AnimalDetailView.as_view())

Изменить: в прошлый раз я сделал ошибку, потому что мы не можем создать экземпляр класса представления, просто используя метод as_view(). Попробуйте новый подход, я думаю, это может помочь.

person Darwin    schedule 24.04.2013
comment
Какая-то бесполезная ошибка: This method is available only on the view class. - person Rob L; 24.04.2013
comment
Посмотрите код еще раз, обработчик определяется после AnimalDetailView, а не внутри него. - person Darwin; 24.04.2013
comment
Я определил его вне класса AnimalDetailView. В моем views.py я импортирую, используя from animals.views import handler - person Rob L; 24.04.2013
comment
Красавчик, Дарвин. Спасибо, что остаетесь со мной в этом! Я бы проголосовал за тебя, но моя репутация недостаточно хороша. Я думаю, мама была права, :-( - person Rob L; 25.04.2013

Да, любые именованные параметры (например, ?P<my_animal>) в ваших URL-адресах будут автоматически передаваться вашим представлениям в качестве аргументов ключевого слова:

Ключевой частью работы этого [представления на основе классов] является то, что при вызове представлений на основе классов различные полезные вещи сохраняются в себе; а также запрос (self.request), который включает позиционные (self.args) и основанные на имени (self.kwargs) аргументы, захваченные в соответствии с URLconf.

поэтому у вас будет доступ к ним как self.kwargs['my_animal'] в представлении.

Если вы посмотрите на __init__ метод BaseDetailView (от которого наследуется DetailView), вы увидите, что все, что он делает, это берет kwargs и назначает их атрибутам экземпляра, поэтому вы можете легко сделать следующее:

url(r'^?P<model>\w+/(?P<slug>[-\w]+)/$',...

и представление должно автоматически присвоить значение, переданное в URL-адресе, self.model. Конечно, вам нужно быть осторожным и обязательно проверять ввод здесь, но это хороший способ динамического захвата объектов из моделей, указанных пользователем.

person Timmy O'Mahony    schedule 24.04.2013
comment
Мило, но я думаю, что я делаю это неправильно: я получаю name 'my_plant' is not defined в urls.py - person Rob L; 24.04.2013

Еще один способ сделать это - передать вашу модель напрямую, а не передавать ее через переменную.

url.py

from django.urls import path
from . import views
# Common url
urlpatterns = [
    path('', views.MoneyIndex.as_view(), name='money_index'),
    path('cat', views.CatListView.as_view(), name='cat_list'),
    path('dog', views.DogListView.as_view(), name='dog_list'),
    ]

# Dynamic url
MYMODELS = ['Cat','Dog',]
for modelX in MYMODELS:
    urlpatterns = urlpatterns + [
        path('{0}/add'.format(modelX.lower()), views.MyCreateView.as_view(model=modelX), name='{0}_create'.format(modelX.lower())),
        path('{0}/<pk>'.format(modelX.lower()), views.MyDetailView.as_view(model=modelX), name='{0}_detail'.format(modelX.lower())),
        path('{0}/<pk>/upd'.format(modelX.lower()), views.MyUpdateView.as_view(model=modelX), name='{0}_update'.format(modelX.lower())),
        path('{0}/<pk>/del'.format(modelX.lower()), views.MyDeleteView.as_view(model=modelX), name='{0}_delete'.format(modelX.lower())),
    ]

view.py

from django.views import generic
from .models import Cat, Dog
from .forms import CatForm, DogForm

class MyDetailView(generic.DetailView):     
        def __init__(self, *args, **kwargs):
            super(MyDetailView, self).__init__(*args, **kwargs)
            modeltxt = self.model
            self.model = eval(modeltxt)


 class MyCreateView(generic.CreateView):
    def __init__(self, *args, **kwargs):
        super(MyCreateView, self).__init__(*args, **kwargs)
        modeltxt = self.model
        self.model = eval(modeltxt)
        self.form_class = eval('{0}Form'.format(modeltxt))
        self.success_url = reverse_lazy('{0}_list'.format(modeltxt.lower()))    


class MyUpdateView(generic.UpdateView):
    def __init__(self, *args, **kwargs):
        super(MyUpdateView, self).__init__(*args, **kwargs)
        modeltxt = self.model
        self.model = eval(modeltxt)
        self.form_class = eval('{0}Form'.format(modeltxt))
        self.success_url = reverse_lazy('{0}_list'.format(modeltxt.lower())) 


class MyDeleteView(generic.DeleteView):
    def __init__(self, *args, **kwargs):
        super(MyDeleteView, self).__init__(*args, **kwargs)
        modeltxt = self.model
        self.model = eval(modeltxt)
        # Must be done because default points to modelname_confirm_delete.html
        self.template_name = 'appname/{0}_detail.html'.format(modeltxt.lower())
        self.success_url = reverse_lazy('{0}_list'.format(modeltxt.lower()))
person openHBP    schedule 22.10.2018