Django - Pass model name as parameter to generic view
Asked Answered
D

3

6

Let's say I have some Models that inherit from a base class, Animal. I can use generic views and route Cat/12 to a detail view and Dod/10 to the same detail view with a different context. But I would like to get the Model name from the url so that I dont have to define a route.

I have something like this:

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'),
...

Obviously, this is too much repetitive code. I would rather do something like this:

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'),
...

Can I do this?

EDIT

Here's what I ended up with, thanks to Darwin's help. It avoids the if/else to get the Model name:

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()

Next time I have a question about Inheritance, I'll consult Darwin! LoL

Dagny answered 24/4, 2013 at 18:10 Comment(0)
R
6

You can inherit from DetailView and override the dispatch method to build your own rules with something like this:

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()

and use an urlpattern like this:

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

Edit: I've make a mistake the last time because we can't instantiate a view class, just only using the 'as_view()' method. Try the new approach, I think this can help.

Rakehell answered 24/4, 2013 at 18:25 Comment(4)
Kind of an unhelpful error: This method is available only on the view class.Dagny
See the code again, handler is defined after the AnimalDetailView, not inside it.Rakehell
I have it defined outside of the AnimalDetailView class. In my views.py im importing using from animals.views import handlerDagny
Nice one, Darwin. Thanks for sticking with me on this! I'd vote you up but my reputation's not good enough. I guess Mother was right, :-(Dagny
L
1

Yes, any named parameters (i.e. ?P<my_animal>) in your urls will automatically be passed as keyword arguments to your views,:

The key part to making this [Class Based Views] work is that when class-based views are called, various useful things are stored on self; as well as the request (self.request) this includes the positional (self.args) and name-based (self.kwargs) arguments captured according to the URLconf.

so you will have access to them as self.kwargs['my_animal'] in the view.

If you look at the __init__ method of BaseDetailView (from which DetailView inherits) you'll see that all it is doing is taking the kwargs and assigning them to instance attributes, so you could easily do the following:

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

and the view should automatically assign the value passed in the URL to self.model. Of course you need to be careful and make sure to validate the input here, but it's a nice way to dynamically grab objects from models specified by the user

Leadsman answered 24/4, 2013 at 18:18 Comment(1)
Sweet, but I'm doing it wrong, I guess: I'm getting name 'my_plant' is not defined in urls.pyDagny
G
1

One other way to do it would be to pass your model directly instead of passing it through a variable

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()))
Giuseppe answered 22/10, 2018 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.