Django - Pass model name as parameter to generic view

django generic views tutorial
django detail view multiple models
django listview multiple models
django class-based views url parameters
django class-based views post
django views
django get_context_data
django listview pagination

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

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.

If your urlconf looks something like this: url(r'^(?P<slug>[a-zA-Z0-9-]+)/$', MyView.​as_view(), name = 'my_named_view'). then the slug will be  In this case we use '<int:pk>' to capture the book id, which must be a specially formatted string and pass it to the view as a parameter named pk (short for primary key). This is the id that is being used to store the book uniquely in the database, as defined in the Book Model.

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

This error happens because you are using the UpdateView generic view. This class expects a single model and form. As a solution, you could override the  Every parameter that’s passed to the as_view method is an instance variable of the View class. That means to add slug as a parameter you have to create it as an instance variable in your sub-class:

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

Then the model in question can be passed as an extra argument to the URLconf. view functions with a bewildering array of options; now, rather than passing in a absence of an explicit template Django will infer one from the object's name. Built-in class-based generic views¶ Writing Web applications can be monotonous, because we repeat certain patterns again and again. Django tries to take away some of that monotony at the model and template layers, but Web developers also experience this boredom at the view level. Django’s generic views were developed to ease that pain. They

For Django class-based views we access an appropriate view function model, or you might want to use a different template variable name if book_list We might also override get_context_data() in order to pass additional  from django.utils import timezone from django.views.generic.list import ListView from articles.models import Article class ArticleListView (ListView): model = Article paginate_by = 100 # if pagination is desired def get_context_data (self, ** kwargs): context = super (). get_context_data (** kwargs) context ['now'] = timezone. now return context

Listing 9-9 Django class-based view with CreateView to create model records class-based views, using the standard reverse() method to resolve url names /​page/1/,/page/2/) to pass the url page parameter value to the class-based view. Class-based views¶ A view is a callable which takes a request and returns a response. This can be more than just a function, and Django provides an example of some classes which can be used as views. These allow you to structure your views and reuse code by harnessing inheritance and mixins.

Django's *generic views* were developed to ease that pain. just view functions with a bewildering array of options; now, rather than passing in a large amount __str__(self): # __unicode__ on Python 2 return self.name class Book(models. In addition to the context provided by django.views.generic.list.MultipleObjectMixin (via django.views.generic.dates.BaseDateListView), the template’s context will be: date_list : A QuerySet object containing all months that have objects available according to queryset , represented as datetime.datetime objects, in ascending order.

Comments
  • Kind of an unhelpful error: This method is available only on the view class.
  • See the code again, handler is defined after the AnimalDetailView, not inside it.
  • I have it defined outside of the AnimalDetailView class. In my views.py im importing using from animals.views import handler
  • 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, :-(
  • glad to help. Regards.
  • Sweet, but I'm doing it wrong, I guess: I'm getting name 'my_plant' is not defined in urls.py