Combinable generic CBVs

148 views
Skip to first unread message

Rainy

unread,
Aug 28, 2012, 10:44:32 PM8/28/12
to django...@googlegroups.com
When I use CBVs, I nearly always end up needing to mix different types in the same view, e.g. detail view and list view, list view and modelform view, etc. I really like CBVs but I feel this is the one shortcoming that I constantly run into that makes CBVs much less flexible and helpful than they could be.

Current CBVs don't have built-in support for this because instance variables and methods have the same name, e.g. self.model, self.object in both detail view and model form view, get_context_data in all CBVs, and so on. I think it would be very useful to modify CBVs to make them combinable by using different variable and method names and having separate unified methods that combine data from specific methods: you'd have self.detail_object, self.modelform_object, get_detail_context_data, and get_context_data would check if get_detail_context_data (and other methods) exist and get data from all of them and return combined into one dictionary.

It seems like it may be quite a bit of work, and I don't have much experience designing many levels of classes with Mixins, I'd like some advice on whether this is a good idea and how to implement it. Any suggestions / hints appreciated!

Melvyn Sopacua

unread,
Aug 29, 2012, 3:09:55 AM8/29/12
to django...@googlegroups.com
On 29-8-2012 4:44, Rainy wrote:
> When I use CBVs, I nearly always end up needing to mix different types in
> the same view, e.g. detail view and list view, list view and modelform
> view, etc. I really like CBVs but I feel this is the one shortcoming that I
> constantly run into that makes CBVs much less flexible and helpful than
> they could be.

What are your real world cases for this? If you combine several models
to make up a page, you should consider writing your own mixins that
inherit only from ContextMixin and implement get_context_data() to add
the extra information.
For example:
class OnSaleMixin(ContextMixin) :
on_sale_queryset = Products.objects.filter(on_sale=True)
def get_context_data(self, **kwargs) :
context = { 'on_sale_list': self.on_sale_queryset }
context.update(kwargs)
return super(OnSaleMixin, self).get_context_data(
**context)

class ProductList(ListView, OnSaleMixin) :
model = Products


--
Melvyn Sopacua

Rainy

unread,
Aug 29, 2012, 12:46:09 PM8/29/12
to Django users
I will give some examples at the end of the message, but first I want
to expand a bit on this idea.

Let's say you have a detail CBV, FooView(DetailView): model = Foo ;
and a list view, BarView(ListView): model = Bar .

Now, combining different types of views is an extremely common case
and you might say that the cases when you don't need to do that are
usually so simple that existing GCBVs already handle them perfectly.
Nobody ever complained that inheriting from a ListView and maybe
overriding one or two methods is hard to do. So the user case that
we need to optimize for is the one where difficulties lie and where
people have run into problems and complained about, blaming GCBVs
for being hard to use and debug.

I don't see any apparent reason why the two examples above can't
be combined exactly in the same way you inherit each separate view:
FooBarView(DetailView, ListView): detail_model=Foo; list_model=Bar.
As a bonus, you don't have to decide which mixin to use and when
overriding methods, you will know which instance variable and method
handles detail and list objects.

Here are a few examples where it would be useful, and I can find many
more in my projects:

- blog post: 3 CBVs: post, list of comments, comment form
- search: FormView, ListView
- photo album: detail view for album, listview of images

I know each of these cases can be handled with current GCBVs but
with a lot more effort and in a more verbose and error-prone way
than what I'm proposing.

Melvyn Sopacua

unread,
Aug 29, 2012, 5:24:21 PM8/29/12
to django...@googlegroups.com
On 29-8-2012 18:46, Rainy wrote:
> On Aug 29, 3:10 am, Melvyn Sopacua <m.r.sopa...@gmail.com> wrote:
>> On 29-8-2012 4:44, Rainy wrote:
>>
>>> When I use CBVs, I nearly always end up needing to mix different types in
>>> the same view, e.g. detail view and list view, list view and modelform
>>> view, etc. I really like CBVs but I feel this is the one shortcoming that I
>>> constantly run into that makes CBVs much less flexible and helpful than
>>> they could be.
>>
>> What are your real world cases for this? If you combine several models
>> to make up a page, you should consider writing your own mixins that
>> inherit only from ContextMixin and implement get_context_data() to add
>> the extra information.

<snip example>

> I will give some examples at the end of the message, but first I want
> to expand a bit on this idea.
>
> Let's say you have a detail CBV, FooView(DetailView): model = Foo ;
> and a list view, BarView(ListView): model = Bar .
>
> Now, combining different types of views is an extremely common case
> and you might say that the cases when you don't need to do that are
> usually so simple that existing GCBVs already handle them perfectly.

The list and detail view don't bite each other in many aspects. However,
as you will list in examples below, you usually don't have just one list
view per page. See below.

> I don't see any apparent reason why the two examples above can't
> be combined exactly in the same way you inherit each separate view:
> FooBarView(DetailView, ListView): detail_model=Foo; list_model=Bar.

Because how would you declare a second list model? And how would you
pick these up? Aside from making the routing through the model
difficult, this will also be a declaration nightmare.

> As a bonus, you don't have to decide which mixin to use and when
> overriding methods, you will know which instance variable and method
> handles detail and list objects.

Except when you need more than one list or detail or both.

> Here are a few examples where it would be useful, and I can find many
> more in my projects:
>
> - blog post: 3 CBVs: post, list of comments, comment form
> - search: FormView, ListView
> - photo album: detail view for album, listview of images
>
> I know each of these cases can be handled with current GCBVs but
> with a lot more effort and in a more verbose and error-prone way
> than what I'm proposing.

I don't see that:
class BlogView(CommentListMixin, CommentFormMixin, DetailView) :
model = BlogPosts

versus:
class BlogView(DetailView, ListView, CreateView) :
detail_model = BlogPost
list_model = Comments
form_model = Comments

I find the second version harder to read, more verbose to type and when
you want to support multiple models you will have to support some sort
of sequence or mapping. Not to mention how you'd handle template-coder
friendly names through context_object_name and similar.

Yes, the first version requires more work to code the mixins, but I
don't find them harder to debug either. In fact, pretty much only one
point where things can go wrong: get_context_data().

I can see your point and some of my issues with it may well be personal
preference, but I'm not sure if the Generic CBV's are a good base for
what you ask of them. I'm currently working on a RelatedModelMixin and
subsequently a FormsetMixin and I already feel like I'm coloring outside
the lines.
It's made me consider the fact that function based views are more
natural. A view really is a process where you hand it some info and it
returns what you asked for. It's less natural to think of it as a
collection of parts (objects) in a machine that work together to produce
your result.

However, once you know the order in which things are flowing through the
magic machine, debugging them isn't hard and things make a lot of sense
so I think part of the blame here is in the documentation which is in
fact improving a lot.

Last but not least, I think you should start writing if you feel
confident it can be done and is an improvement. It's the best way to
find out if your ideas can work or if another approach should be sought.

--
Melvyn Sopacua

Rainy

unread,
Aug 30, 2012, 12:11:04 PM8/30/12
to Django users


On Aug 29, 5:24 pm, Melvyn Sopacua <m.r.sopa...@gmail.com> wrote:
> On 29-8-2012 18:46, Rainy wrote:
>
> > On Aug 29, 3:10 am, Melvyn Sopacua <m.r.sopa...@gmail.com> wrote:
> >> On 29-8-2012 4:44, Rainy wrote:
>
> >>> When I use CBVs, I nearly always end up needing to mix different types in
> >>> the same view, e.g. detail view and list view, list view and modelform
> >>> view, etc. I really like CBVs but I feel this is the one shortcoming that I
> >>> constantly run into that makes CBVs much less flexible and helpful than
> >>> they could be.
>
> >> What are your real world cases for this? If you combine several models
> >> to make up a page, you should consider writing your own mixins that
> >> inherit only from ContextMixin and implement get_context_data() to add
> >> the extra information.
>
> <snip example>
>
> > I will give some examples at the end of the message, but first I want
> >  to expand a bit on this idea.
>
> > Let's say you have a detail CBV, FooView(DetailView): model = Foo ;
> >  and a list view, BarView(ListView): model = Bar .
>
> > Now, combining different types of views is an extremely common case
> > and you might say that the cases when you don't need to do that are
> > usually so simple that existing GCBVs already handle them perfectly.
>
> The list and detail view don't bite each other in many aspects. However,
> as you will list in examples below, you usually don't have just one list
> view per page. See below.


Actually, I've never had more than one list per view, either in these
examples or in any other I've worked with. In fact, I've never had
a view where more than one instance of any type of CBV was
needed, as far as I can remember. The only common case I
can think of is having two forms - let's say a search and login.
I think it should be handled as a special case, maybe allowing
form_model to be a list and updating all CBVs to process it
as either a single item or as a list.

>
> > I don't see any apparent reason why the two examples above can't
> > be combined exactly in the same way you inherit each separate view:
> > FooBarView(DetailView, ListView): detail_model=Foo; list_model=Bar.
>
> Because how would you declare a second list model? And how would you
> pick these up? Aside from making the routing through the model
> difficult, this will also be a declaration nightmare.


I showed examples below, doesn't seem like a nightmare, I think..

>
> > As a bonus, you don't have to decide which mixin to use and when
> > overriding methods, you will know which instance variable and method
> > handles detail and list objects.
>
> Except when you need more than one list or detail or both.
>
> > Here are a few examples where it would be useful, and I can find many
> > more in my projects:
>
> >   - blog post: 3 CBVs: post, list of comments, comment form
> >  - search: FormView, ListView
> >  - photo album: detail view for album, listview of images
>
> > I know each of these cases can be handled with current GCBVs but
> > with a lot more effort and in a more verbose and error-prone way
> > than what I'm proposing.
>
> I don't see that:
> class BlogView(CommentListMixin, CommentFormMixin, DetailView) :
>         model = BlogPosts
>
> versus:
> class BlogView(DetailView, ListView, CreateView) :
>         detail_model = BlogPost
>         list_model = Comments
>         form_model = Comments
>
> I find the second version harder to read, more verbose to type and when
> you want to support multiple models you will have to support some sort
> of sequence or mapping.


It's ok that it's more verbose, in fact it's a good thing becase here
verbosity is moved from method level to class attribute level,
I want my methods to be as short and clear as possible, to make
it easier to follow the logic.

> Not to mention how you'd handle template-coder
> friendly names through context_object_name and similar.


Of course, this would be done with declarations like
context_detail_object_name, etc, only for objects
that need to be accessed in template. In this case,
context_create_object_name is probably not needed
and can be left as None.
>
> Yes, the first version requires more work to code the mixins, but I
> don't find them harder to debug either. In fact, pretty much only one
> point where things can go wrong: get_context_data().

No, the issue is the clash between self.object, context_object_name,
self.model. You have to look through the entire tree of CBVs and
make sure all of them refer to the correct objects everywhere
they're referenced. For example, self.object is created in both
get() and post(), if you want to change the default, you have to
override both of them.

The only reason you would use self.object for both detail
and modelform, imho, is to make GCBVs code shorter and
clearer, inheriting from SingleObjectMixin and reusing all
of its code. As a user of GCBVs, it really doesn't matter
much to me if they're a little longer. What does matter to
me is that as soon as I want to combine different types
of CBVs, it's a major pain even though there is no inherent
reason why it needs to be so.
>
> I can see your point and some of my issues with it may well be personal
> preference, but I'm not sure if the Generic CBV's are a good base for
> what you ask of them. I'm currently working on a RelatedModelMixin and


Is related model the case where one is a detail model and the other a
list model? I think that's what you'd run into in 95% of cases, and
it's
made much easier with my proposal.

> subsequently a FormsetMixin and I already feel like I'm coloring outside
> the lines.

> It's made me consider the fact that function based views are more
> natural.


I couldn't disagree more. Whatever the argument may be about current
GCBVs, a flat CBV inherited from View is always 100 times better than
the equivalent FBV. Even the argument about logic flow does not apply
-
you can arrange methods in the order of logic flow if you prefer.

Even though GCBVs weren't a very good design (I've often heard that
the biggest issue is documentation, but I suspect that's what made it
hard to write the docs - if the design isn't very good, how do you
explain
or excuse it in the docs? Why are simple things often so hard and
complex
to implement?); the basic implementation of CBVs is by far the best
thing
that ever happened to Django.


A view really is a process where you hand it some info and it
> returns what you asked for. It's less natural to think of it as a
> collection of parts (objects) in a machine that work together to produce
> your result.
>
> However, once you know the order in which things are flowing through the
> magic machine, debugging them isn't hard and things make a lot of sense
> so I think part of the blame here is in the documentation which is in
> fact improving a lot.
>
> Last but not least, I think you should start writing if you feel
> confident it can be done and is an improvement. It's the best way to
> find out if your ideas can work or if another approach should be sought.


That's what I thought, too, but I was wondering if I'm missing some
obvious problems with my approach.

Thanks for the feedback.
Reply all
Reply to author
Forward
0 new messages