Feature Request: "Abstract Model"

5 views
Skip to first unread message

Wanrong Lin

unread,
Jan 12, 2008, 2:15:32 PM1/12/08
to django-d...@googlegroups.com
Hi,

I have just started playing with Django and found it a pleasure to set
up web site (even just a toy system so far) using the frame work. Thank
you very much!

I have a data model case that I think probably is quite common to other
people too, and I did not find a way to do it with current Django, so I
wonder whether the developers can take a look of it.

My situation is:

I have quite a few data model classes that share some common fields, so
I have the following model code:

--------------------
class Common(models.Model):
# common fields
......

class Model_A(Common):
# extra fields
......

class Model_B(Common):
# extra fields
......
-------------------

That works, except that a database table will be created for "Common",
which will never be used by itself.

So I will just keep "Common" table empty, not a big deal. But, to make
it more elegant (which I suspect a lot of Python programmers are
obsessed about), can we add some kind of mechanism to tell Django that
"Common" is an "abstract model" that is not intended to be used by
itself, so no table needs to be created?

Thank you for giving it a thought.

Best regards.

Wanrong


alex....@gmail.com

unread,
Jan 12, 2008, 5:33:00 PM1/12/08
to Django developers
This seems like somehting that would be implemented after model
subclassing is implemented.

James Bennett

unread,
Jan 12, 2008, 6:49:18 PM1/12/08
to django-d...@googlegroups.com
On 1/12/08, Wanrong Lin <wanro...@gmail.com> wrote:
> I have a data model case that I think probably is quite common to other
> people too, and I did not find a way to do it with current Django, so I
> wonder whether the developers can take a look of it.

A search of the archive of this list will reveal a lot of prior
discussion and information on this topic.


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

Marc Fargas

unread,
Jan 13, 2008, 4:46:52 PM1/13/08
to django-d...@googlegroups.com
Hi Wanrong,
Maybe you could live with something like:

class common(models.Model):
...
Meta:
create_db_schema = False

class Model_B(models.Model):
...
Meta:
create_db_schema = True

But for this, you'll need to wait for #3163 [1] to be checked-in (and
it's still waitting for tests).

Cheers,
Marc

1: http://code.djangoproject.com/ticket/3163

> --~--~---------~--~----~------------~-------~--~----~
> You received this message because you are subscribed to the Google Groups "Django developers" group.
> To post to this group, send email to django-d...@googlegroups.com
> To unsubscribe from this group, send email to django-develop...@googlegroups.com
> For more options, visit this group at http://groups.google.com/group/django-developers?hl=en
> -~----------~----~----~----~------~----~------~--~---
>
--
http://www.marcfargas.com -- will be finished some day.

signature.asc

Wanrong Lin

unread,
Jan 13, 2008, 11:04:23 PM1/13/08
to django-d...@googlegroups.com

Hi, Marc,

This is great news. I am glad I have not re-written all my models with
all those duplicated stuff (I am sure that wouldn't make me feel I am
following the "DRY" principle).

Is it possible to give any prediction when this great stuff will be
checked in? Thanks a lot.

Wanrong

Marc Fargas

unread,
Jan 14, 2008, 4:11:12 AM1/14/08
to django-d...@googlegroups.com
Hi Wanrong,
It's hard to give predictions on open source projects, the last comment
on #3163 is advising that he/she cannot write the tests for some reason.
And without tests the ticket won't be checked-in.

You can always try to give a hand writting suchs tests. Once the ticket
has everything it should it doesn't take too long to get checked-in
unless: a) no committer sees it (unlikely), b) somebody has concerns
about the ticket, unlikely as it's marked "Accepted".

So, if you feel brave enought you can take a look at
http://www.djangoproject.com/documentation/contributing/ and try to get
the ticket's patches to good shape ;)

Marc.

El dom, 13-01-2008 a las 23:04 -0500, Wanrong Lin escribió:
>
> Hi, Marc,
>
> This is great news. I am glad I have not re-written all my models with
> all those duplicated stuff (I am sure that wouldn't make me feel I am
> following the "DRY" principle).
>
> Is it possible to give any prediction when this great stuff will be
> checked in? Thanks a lot.
>
> Wanrong
>
> Marc Fargas wrote:
> > Hi Wanrong,
> > Maybe you could live with something like:
> >
> > class common(models.Model):
> > ...
> > Meta:
> > create_db_schema = False
> >
> > class Model_B(models.Model):
> > ...
> > Meta:
> > create_db_schema = True
> >
> > But for this, you'll need to wait for #3163 [1] to be checked-in (and
> > it's still waitting for tests).
> >
> > Cheers,
> > Marc
> >
> > 1: http://code.djangoproject.com/ticket/3163
> >

> > El s�b, 12-01-2008 a las 14:15 -0500, Wanrong Lin escribi�:

signature.asc

Alex Myodov

unread,
Jan 14, 2008, 11:45:16 AM1/14/08
to Django developers
Wanrong and Marc,

I (the author of current #3163 patch) was just in the middle of the
large email here explaining what I tried to do to create the tests,
and what I could not, and where I need help,... but was suddenly
stricken with the idea how I can write those tests. And the quick
experiments shown that it indeed works.
So, expect the proper tests for that create_db_schema patch in a
couple (or three at max) days, and don't bother to spend your time
investigating it before that.
Btw, thanks for another good application of this little feature :) -
it proves me that I am going the right way.


On 14 янв, 12:11, Marc Fargas <teleni...@telenieko.com> wrote:
> Hi Wanrong,
> It's hard to give predictions on open source projects, the last comment
> on #3163 is advising that he/she cannot write the tests for some reason.
> And without tests the ticket won't be checked-in.
>
> You can always try to give a hand writting suchs tests. Once the ticket
> has everything it should it doesn't take too long to get checked-in
> unless: a) no committer sees it (unlikely), b) somebody has concerns
> about the ticket, unlikely as it's marked "Accepted".
>
> So, if you feel brave enought you can take a look athttp://www.djangoproject.com/documentation/contributing/and try to get
> --http://www.marcfargas.com-- will be finished some day.
>
>  signature.asc
> 1KЗагрузить

Wanrong Lin

unread,
Jan 14, 2008, 1:07:30 PM1/14/08
to django-d...@googlegroups.com

Alex,

Thanks a lot for this. Look forward to the new version.

Wanrong

Alex Myodov

unread,
Jan 14, 2008, 8:11:59 PM1/14/08
to Django developers
Wanrong,

Just updated the ticket with the latest patch, which includes thorough
tests of the feature. Hope it now should be enough to commit, if the
gurus consider the code sufficient.
> >> So, if you feel brave enought you can take a look athttp://www.djangoproject.com/documentation/contributing/andtry to get
> >> --http://www.marcfargas.com--will be finished some day.
>
> >>  signature.asc
> >> 1KЗагрузить

Wanrong Lin

unread,
Jan 17, 2008, 9:13:57 PM1/17/08
to django-d...@googlegroups.com

Hi, Alex,

Thanks for the update. I just tried the patch (on svn 7020), and found
two problems:

1. Even though the models with "create_db_schema=False" actually will no
longer have tables created in the database, when "syncdb.py" runs the
message still says those tables are created.
2. (More serious) if I have two models, Model_A and Model_B. Model_A
has "create_db_schema=False" and Model_B inherits from Model_A. In the
administration interface if I create a record for Model_B, it saves with
no problem, but when I click on the record name trying to access it, I
will get an exception complaining that the table for Model_A does not
exist (error code 1146). Actually, even without your patch, and the
table for Model_A does exist, I will still get the same exception. So
the problem probably is not caused by your patch, but something related
to using inherited models.

Thanks you for your work and I am hoping sometime soon you or somebody
can get this nailed down.

Wanrong

Wanrong Lin

unread,
Jan 17, 2008, 9:37:19 PM1/17/08
to django-d...@googlegroups.com

I searched the list and it seems the topic has been debated in summer
2006 but don't see any progress was made (at least from what I can see
in the mailing list). To me, model inheritance seems to be a very
important feature. I was hoping that the patch recently submitted by
Alex would solve this, but as you can see from my last post on this
topic, it seems there is something more fundamental to be addressed
here. Can anybody here update us with the status of this issue and where
it will go in the future?

Thank you very much.

Wanrong

Alex Myodov

unread,
Jan 18, 2008, 6:30:44 PM1/18/08
to Django developers
Thanks for your notices Wahrong.

1. by "syncdb.py" you do mean "manage.py syncdb", don't you? Thanks
for spotting, the table was not created anyway in fact (only the
confusing message was generated), but I corrected django/core/
management/commands/syncdb.py and will upload the latest patch soon.

2. I am quite unsure that the model inheritance is "legal" enough in
Django yet; from what I heard a serious work is being done currently
to implement a full-scale model inheritance for one-to-one relations,
but I do not keep track of it. As for me, I would not expect *any*
sensible behaviour for inherited models at all yet, just because
inheritance is never promised in model-api. Let's wait a bit and see
how things are going when the model inheritance is released
officially.
> >>>> So, if you feel brave enought you can take a look athttp://www.djangoproject.com/documentation/contributing/andtryto get
> >>>> --http://www.marcfargas.com--willbe finished some day.
>
> >>>>  signature.asc
> >>>> 1KЗагрузить

Brian Harring

unread,
Feb 4, 2008, 7:50:11 AM2/4/08
to django-d...@googlegroups.com

This is a dirty hack mind you, but a rather effective one- I
personally use it for when I need to create common structures w/in
tables and need to able to change the structure definitions in a
single spot. If you did the following-

def add_common_fields(local_scope):
local_scope['field1'] = models.IntegerField(blank=True,
null=True)
local_scope['field2'] = models.CharField(maxlength=255,
blank=True, null=True)
# other common definitions, same thing, updating the passed in
# dict

you could then just do

class Model_A(models.Model):
add_common_fields(locals())
# other fields

class Model_B(models.Model):
add_common_fields(locals())
# other fields.

Pros of the approach:
1) you're easily able to add common fields to model definitions, and
it Just Works (TM)
2) since the class scope is executed in order, via shifting around the
add_common_fields invocation you can shift the sql column definition
as needed.

Cons:
1) exploits the fact locals() in class scope is a mutable dict- I've
yet to see commentary indicating this will change anytime soon for
cpython, but I've no idea if this works in ironpython/jython (assume
so due to metaclass semantics, but I've not tested it).
2) if you've never seen that trick before and come across it in code,
it's likely going to confuse the hell out of the person examining it.
Comments likely warranted to combat that.
3) inheritance would be a bit more pythonic (although inheritance
requires some funkyness to be able to control field ordering).

You probably could fold the approach above into a metaclass if desired
also- would be a bit more pythonic possibly.

Either way, it's a useful trick, so hopefully it helps-
~brian

George Vilches

unread,
Feb 4, 2008, 8:32:17 AM2/4/08
to django-d...@googlegroups.com
On Feb 4, 2008, at 7:50 AM, Brian Harring wrote:

This is a dirty hack mind you, but a rather effective one- I
personally use it for when I need to create common structures w/in
tables and need to able to change the structure definitions in a
single spot.  If you did the following-

def add_common_fields(local_scope):
   local_scope['field1'] = models.IntegerField(blank=True,
      null=True)
   local_scope['field2'] = models.CharField(maxlength=255,
      blank=True, null=True)
   # other common definitions, same thing, updating the passed in
   # dict

you could then just do

class Model_A(models.Model):
  add_common_fields(locals())
  # other fields

class Model_B(models.Model):
  add_common_fields(locals())
  # other fields.

...

You probably could fold the approach above into a metaclass if desired
also- would be a bit more pythonic possibly.

Since we've been using a metaclass for doing a similar task, seems appropriate to paste it now:

class ModelMixinBase(ModelBase):
    def __new__(cls, name, bases, attrs):
        new_attrs = attrs.copy()
        for attr,value in cls.__dict__.iteritems():
            if isinstance(value, models.Field):
                new_attrs.setdefault(attr,value)
            elif attr == 'methods':
                for v in value:
                    if callable(v):
                        new_attrs.setdefault(v.__name__, v)
                    elif isinstance(v, str):
                        new_attrs.setdefault(v, getattr(cls, v))
        return super(ModelMixinBase, cls).__new__(cls, name, bases, new_attrs)


class MixinBaseA(ModelMixinBase):
    common_1 = models.IntegerField()
    common_2 = models.CharField(max_length=255)

class ResultModel(models.Model):
    __metaclass__ = MixinBaseA
    specific_1 = models.IntegerField()

The end result should give you as far as we have been able to tell a perfectly okay Django model instance (we've been using it for months and haven't seen any weird behavior yet).  We know it does a touch more than what yours does, but it could easily be stripped down to just be the equivalent of what you've got above.

gav

Brian Harring

unread,
Feb 4, 2008, 10:40:29 AM2/4/08
to django-d...@googlegroups.com

I see only one particular fault with this approach- if you ever add
functionality to allow N inheriting parents, instead of a single line
of inheritance. Django internally has a rather voodoo-rific
creation_counter in the Field class namespace, that serves as an
instance count for each Field derivative instantiated, and it uses
that creation_counter to determine where to insert the Field
derivative into the Models fields list (which maps out to the sql
column ordering).

If you ever try to extend your metaclass approach to allow mixing
multiple parents (which makes sense, imo), the sql column ordering
would be dependant on the order of imports.

Aside from that, nifty approach although I think I'd try to mangle the
ModelMixinBase instance so it was invokable, and then use it like so-

class ResultModel(MixinBaseA(), MixinBaseB()):
specific_1 = models.IntegerField()

The reason I'd try that direction is due to the creation_counter
voodoo- if you could slightly bastardize ModelMixinBase.__call__ so
that it was able return clones of the fields (with the
creation_counter incremented), it would solve the sql field order
issue I mentioned above while enabling N parent inheritance.

Just a thought.
~brian

Reply all
Reply to author
Forward
0 new messages