Virtual Methods?

24 views
Skip to first unread message

DrMeers

unread,
Oct 13, 2008, 7:16:42 AM10/13/08
to Django users
I am writing a Blog-style django site which needs to render a
collection of Item objects. Item is the root Super class for a whole
bunch of sub-classes -- Photo, Video, Quote, News, etc. I want to be
able to iterate through Item.objects.all() and ask each Item to
render() itself based on which sub-class it is. I'm from a C++
background, and my virtual-method approach isn't working -- it just
calls the Item.render() method instead of the appropriate sub-class
one.

#Simplified example classes:
class Item(models.Model):
group = models.ForeignKey(ItemGroup)
order = models.IntegerField()
when = models.DateTimeField(null=True, blank=True, help_text="When
the item occured; can be auto-extracted from photos")
added = models.DateTimeField(default=datetime.datetime.now,
editable=False)
def render(self):
return "Plain 'ol item"

class News(Item):
html = models.TextField()
def render(self):
return "<div class='news'>" + self.html + "</div>"

class Photo(Item):
original = models.FileField(upload_to='%Y/%m')
def render(self):
return "<img src='%s'>" % (self.original.url)

class Quote(Item):
#...

class Video(Item):
#...

# Now I'd like to be able to iterate through all items in a given
group
# (eg. for i in exampleGroup.item_set.order_by("order") )
# and call the render function for each Item, regardless of whether it
# is a News, Photo, Quote, Video, etc. Like a virtual function in C++.
# But i.render() just calls the Item.render() method; how can I figure
out
# which subclass the item is, and call the appropriate subclass
method?

I know this is a bit ugly having HTML in the models, and I should
probably use "inclusion" template tags. Can anyone please point show
me how this should be done?

Daniel Roseman

unread,
Oct 13, 2008, 9:27:58 AM10/13/08
to Django users
There's nothing wrong with the approach of iterating through and
asking each object to call its own render method - this is the whole
point of Python's concept of 'duck-typing'. However, the issue is with
how you're getting the objects in the first place. Due to the way
model inheritance is implemented, parent objects don't know about
their subclasses. So if you just do group.item_set.all() you will just
get a series of Item objects.

Unfortunately there's no easy way round this. One approach is to give
the parent class an item_type field with choices that determine which
type it is. Or you can get all the individual subclasses separately
and combine them into a list for iterating:
list(group.news_set.all()) + list(group.photo_set.all()) etc

On the HTML issue, I don't think template tags will necessarily help
here. I would stay with the render() on each class, but instead of
just returning a literal HTML string I would get the methods to render
a template to a string and return that.

--
DR.

James Bennett

unread,
Oct 13, 2008, 9:30:37 AM10/13/08
to django...@googlegroups.com
On Mon, Oct 13, 2008 at 6:16 AM, DrMeers <DrM...@gmail.com> wrote:
> I am writing a Blog-style django site which needs to render a
> collection of Item objects. Item is the root Super class for a whole
> bunch of sub-classes -- Photo, Video, Quote, News, etc. I want to be
> able to iterate through Item.objects.all() and ask each Item to
> render() itself based on which sub-class it is. I'm from a C++
> background, and my virtual-method approach isn't working -- it just
> calls the Item.render() method instead of the appropriate sub-class
> one.

This is a quirk of the way inheritance works in the ORM; when you ask
for a QuerySet of Item instances, you get Item instances. Not
Item-but-really-some-other-subclass instances, but actual genuine Item
instances, whose methods are the methods defined on the Item class.

This happens because inheritance, under the hood, is simply a
one-to-one relation between a pair of objects; one is the "parent" and
one is the "child". And they really are *two* objects, not one, so to
get the methods of the "child" you have to be working with the "child"
object (which is accessible as described in the documentation).
They're also pulling from two different tables.

The longer answer is that inheritance in a model hierarchy -- because
it's dealing with the impedance mismatch of needing to map onto a
relational database -- doesn't behave the way inheritance behaves in
other situations, and generally it should be a last, rather than a
first, resort when you're modelling your data (unless you're just
doing things like using an abstract model class to avoid repetitive
field declarations -- note that an abstract model class cannot be
directly instantiated, so switching your setup to use one still
wouldn't get the behavior you're aiming for).


--
"Bureaucrat Conrad, you are technically correct -- the best kind of correct."

DrMeers

unread,
Oct 13, 2008, 7:20:41 PM10/13/08
to Django users
Thank you Daniel (and James) for your prompt responses. At least now I
know I wasn't missing something obvious. Your comment about storing
the type in each object sparked an idea:

from django.contrib.contenttypes.models import ContentType # very
handy that Django stores this information...
#...
class Item(models.Model): #generic super class
# [other fields go here]
content_type = models.ForeignKey(ContentType) # each item should
store its own class/type
def save(self):
self.content_type = ContentType.objects.get_for_model(self) # when a
decendant object is saved, this correctly stores the subclass type
super(Item,self).save()
def render(self):
subclass =
self.content_type.model_class().objects.get(item_ptr=self.id) # this
returns the object with the correct class/type
return subclass.render()

#Now if I say, for example:
for i in Item.objects.all():
i.render()

This works beautifully -- i.render() calls the render method of the
true subclass. Hopefully others might find this helpful also.

Thanks again for your help guys.
Reply all
Reply to author
Forward
0 new messages