Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django - Pass model name as parameter to generic view

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

like image 710
Rob L Avatar asked Mar 25 '23 04:03

Rob L


2 Answers

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.

like image 187
Darwin Avatar answered Apr 06 '23 02:04

Darwin


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

like image 29
Timmy O'Mahony Avatar answered Apr 06 '23 04:04

Timmy O'Mahony