I have the following models. How do I get access to the unicode of the inheriting tables (Team and Athete) from the Entity table? I'm trying to display a list of all the Entities that displays 'name' if Team and 'firstname' and 'lastname' if Athlete.
class Entity(models.Model):
entity_type_list = (('T', 'Team'), ('A', 'Athlete'))
type = models.CharField(max_length=2, choices=entity_type_list,default='P')
pictureurl = models.URLField('Picture Url', verify_exists=False, max_length=255, null=True, blank=True)
class Team(Entity):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class Athlete(Entity):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
def __unicode__(self):
return '%s %s' % (self.firstname, self.lastname)
This answer from Carl Meyer to the question mentioned earlier by Paul McMillan might be what your looking for. A subtlety to this problem not captured in some of the answers is how to get at derived class instances from a QuerySet on Entity.
The Problem
for entity in Entity.objects.all()
print unicode(entity) # Calls the Entity class unicode, which is not what you want.
A Solution
Use the InheritanceCastModel mixin in the answer linked above as a base class for Entity. You can then cast from Entity instances to the actual derived class instances. This is particularly handy when you want to use querysets on your parent class (Entity) but access the derived class instances.
class Entity(InheritanceCastModel):
# your model definition. You can get rid of the entity_type_list and type, as the
# real_type provided by InheritanceCastModel provides this info
class Athlete(Entity):
# unchanged
class Team(Entity):
# unchanged
for entity in Entity.objects.all():
actual_entity = entity.cast()
print unicode(actual_entity) # actual entity is a a Team or Athlete
From pure Python, you can use the isinstance function:
class Entity:
def __init__(self):
if isinstance(self, Team):
print 'is team'
elif isinstance(self, Athlete):
print 'is athlete'
class Team(Entity):
def __unicode__(self):
return 'Team'
class Athlete(Entity):
def __unicode__(self):
return 'Athlete'
I am not very clear what you want to do, but in any case you can add a criteria in dervied class instead of checking unicode method of derived classes
e.g. you can ask the class isTypeA ? or why don't you check the type?
Loop over all the entities... if entity.class == 'Athlete' and entity.firstname and entity.lastname: blah
Hope this helps.
Edit: hmmm looks like I forgot about actually getting the combined list of both entities. Not sure I know of a slick way to do that.
I answered a similar question a while ago. Have a look, I think one of the answers probably solves your problem as well.
How do I access the child classes of an object in django without knowing the name of the child class?
My answer from there was to add this to the parent class:
def get_children(self):
rel_objs = self._meta.get_all_related_objects()
return [getattr(self, x.get_accessor_name()) for x in rel_objs if x.model != type(self)]
Then you can call that function to get the children objects (in your case you will only have one) and then call the unicode function from that object.
This is a little ugly, but I think it should work:
entities = Entity.objects.all()
for entity in entities:
try:
print entity.team
except:
print entity.athlete
Check out http://docs.djangoproject.com/en/1.0/topics/db/models/#id7 for more on multi-table inheritance. Just be careful, because the Django ORM is inevitably a leaky abstraction and things you might normally do with objects can get you in trouble, or do unexpected things.
If I undestand correctly, you are simply asking how to call the __unicode__ method of a given object.
Use unicode(instance) and depending on the type of entity, the appropriate implementation will be called polymorphically.
I don't believe you have to do anything. If Entity is never instantiated directly, you will never call the non-existent Entity.__unicode__ method. However, if you'd like to play it save, you could add a stub method in your Entity class:
class Entity(models.Model):
def __unicode__(self):
pass
class Team(Entity):
def __unicode__(self):
return self.name
class Athlete(Entity):
def __unicode__(self):
return '%s %s' % (self.firstname, self.lastname)
You are now assured that any class which inherits from Entity will have a __unicode__ method, and you can simply traverse them:
for thing in [TeamA(), AthleteA(), TeamB(), AthleteB()]:
print unicode(thing)
Related
I have plenty of Hardware models which have a HardwareType with various characteristics. Like so:
# models.py
from django.db import models
class HardwareType(model.Models):
name = models.CharField(max_length=32, unique=True)
# some characteristics of this particular piece of hardware
weight = models.DecimalField(max_digits=12, decimal_places=3)
# and more [...]
class Hardware(models.Model):
type = models.ForeignKey(HardwareType)
# some attributes
is_installed = models.BooleanField()
location_installed = models.TextField()
# and more [...]
If I wish to add a new Hardware object, I first have to retrieve the HardwareType every time, which is not very DRY:
tmp_hd_type = HardwareType.objects.get(name='NG35001')
new_hd = Hardware.objects.create(type=tmp_hd_type, is_installed=True, ...)
Therefore, I have tried to override the HardwareManager.create() method to automatically import the type when creating new Hardware like so:
# models.py
from django.db import models
class HardwareType(model.Models):
name = models.CharField(max_length=32, unique=True)
# some characteristics of this particular piece of hardware
weight = models.DecimalField(max_digits=12, decimal_places=3)
# and more [...]
class HardwareManager(models.Manager):
def create(self, *args, **kwargs):
if 'type' in kwargs and kwargs['type'] is str:
kwargs['type'] = HardwareType.objects.get(name=kwargs['type'])
super(HardwareManager, self).create(*args, **kwargs)
class Hardware(models.Model):
objects = HardwareManager()
type = models.ForeignKey(HardwareType)
# some attributes
is_installed = models.BooleanField()
location_installed = models.TextField()
# and more [...]
# so then I should be able to do:
new_hd = Hardware.objects.create(type='ND35001', is_installed=True, ...)
But I keep getting errors and really strange behaviors from the ORM (I don't have them right here, but I can post them if needed). I've searched in the Django documentation and the SO threads, but mostly I end up on solutions where:
the Hardware.save() method is overridden (should I get the HardwareType there ?) or,
the manager defines a new create_something method which calls self.create().
I also started digging into the code and saw that the Manager is some special kind of QuerySet but I don't know how to continue from there. I'd really like to replace the create method in place and I can't seem to manage this. What is preventing me from doing what I want to do?
The insight from Alasdair's answer helped a lot to catch both strings and unicode strings, but what was actually missing was a return statement before the call to super(HardwareManager, self).create(*args, **kwargs) in the HardwareManager.create() method.
The errors I was getting in my tests yesterday evening (being tired when coding is not a good idea :P) were ValueError: Cannot assign None: [...] does not allow null values. because the subsequent usage of new_hd that I had create()d was None because my create() method didn't have a return. What a stupid mistake !
Final corrected code:
class HardwareManager(models.Manager):
def create(self, *args, **kwargs):
if 'type' in kwargs and isinstance(kwargs['type'], basestring):
kwargs['type'] = HardwareType.objects.get(name=kwargs['type'])
return super(HardwareManager, self).create(*args, **kwargs)
Without seeing the traceback, I think the problem is on this line.
if 'type' in kwargs and kwargs['type'] is str:
This is checking whether kwargs['type'] is the same object as str, which will always be false.
In Python 3, to check whether `kwargs['type'] is a string, you should do:
if 'type' in kwargs and isinstance(kwargs['type'], str):
If you are using Python 2, you should use basestring, to catch byte strings and unicode strings.
if 'type' in kwargs and isinstance(kwargs['type'], basestring):
I was researching the same problem as you and decided not to use an override.
In my case making just another method made more sense given my constraints.
class HardwareManager(models.Manager):
def create_hardware(self, type):
_type = HardwareType.objects.get_or_create(name=type)
return self.create(type = _type ....)
I'm making Django like ORM for my study project and because we are not allowed to use existing ORMs (If you want to use one you have to code it yourself) and just for educating myself, i thought that the same kind of ORM like in Django would be nice.
In the ORM I wan't to make model definitions in same style that they are implemented in Django. ie.
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Django uses metaclasses and in my project I'm using too, but I have problem with the fact that metaclasses construct classes not instances and so all attributes are class attributes and shared between all instances.
This is generic example what I tried but because of what I earlier said, it won't work:
def getmethod(attrname):
def _getmethod(self):
return getattr(self, "__"+attrname).get()
return _getmethod
def setmethod(attrname):
def _setmethod(self, value):
return getattr(self, "__"+attrname).set(value)
return _setmethod
class Metaclass(type):
def __new__(cls, name, based, attrs):
ndict = {}
for attr in attrs:
if isinstance(attrs[attr], Field):
ndict['__'+attr] = attrs[attr]
ndict[attr] = property(getmethod(attr), setmethod(attr))
return super(Metaclass, cls).__new__(cls, name, based, ndict)
class Field:
def __init__(self):
self.value = 0;
def set(self, value):
self.value = value
def get(self):
return self.value
class Mainclass:
__metaclass__ = Metaclass
class Childclass(Mainclass):
attr1 = Field()
attr2 = Field()
a = Childclass()
print "a, should be 0:", a.attr1
a.attr1 = "test"
print "a, should be test:", a.attr1
b = Childclass()
print "b, should be 0:", b.attr1
I tried to lookup from Djangos source but it is too complicated for me to understand and the "magic" seems to be hidden somewhere.
Question is simple, how Django does this in very simplificated example?
The answer is quite simple really, once you check the right code. The metaclass used by Django adds all fields to <model>._meta.fields (well, kinda), but the field attribute is removed from the actual class. The only exception to this is a RelatedField subclass, in which case an object descriptor is added (similar to a property - in fact, a propery is an object descriptor, just with a native implementation).
Then, in the __init__ method of the model, the code iterates over all fields, and either sets the provided value in *args or **kwargs, or sets a default value on the instance.
In your example, this means that the class Person will never have attributes named first_name and last_name, but both fields are stored in Person._meta.fields. However, an instance of Person will always have attributes named first_name and last_name, even if they are not provided as arguments.
With an instance of Concert I get: unbound method do_stuff() must be called with Concert instance as first argument (got ModelBase instance instead)
models.py:
class Event(models.Model):
def do_stuff(self):
response self.do_specific_stuff(self)
class Concert(Event):
def do_specific_stuff(self):
...
class Party(Event):
def do_specific_stuff(self):
...
views:
def index(request):
x = Concert.objects.get(name='Jack White # Oakland')
output = x.do_stuff()
return HttpResponse(output)
My goal is to loop trough all the events and execute the do_specific_stuff child class method based on what kind of event it is.
In Django, inheritance triggers multi-table inheritance, but you don't get the polymorphism in Python. It's just an instance of the ORM not providing a perfect correspondence between the data schema and the object model.
In other words, when you query Event, you get back a whole bunch of Event objects, regardless of whether some of them are actually Concert or Party objects. You have to manually downcast. If an Event is a Concert, it will have an attribute called concert, which points to the corresponding Concert subclass. Likewise for Party. If it is just a normal Event, it will have neither attribute.
You could use the following property on Event to automatically downcast your object:
#property
def as_child_class(self):
"""Casts this object to its subclass, if possible"""
if hasattr(self, 'concert'):
return self.concert
elif hasattr(self, 'party'):
return self.party
else:
return self
Then you could do something like:
for event in Event.objects.all()
event.as_child_class.do_specific_stuff()
Similar questions have come up before:
Polymorphism in Django
How do I access the child classes of an object in django without knowing the name of the child class?
And this link has some other ideas:
http://jeffelmore.org/2010/11/11/automatic-downcasting-of-inherited-models-in-django/
It seems to me that your Event model is ONLY for inheritance, so you should abstract it:
class Event(models.Model):
class Meta:
absract = True
def do_stuff(self):
response self.do_specific_stuff()
def do_specific_stuff(self):
raise NotImplemented
class Concert(Event):
def do_specific_stuff(self):
...
class Party(Event):
def do_specific_stuff(self):
...
I may be wrong about your usage of Event, but if it was, abstracting your Event model will have it to be like a plain class, I mean no database actions will be taken for such model.
Hope this helps! :)
First of all, see Template method
Secondly, the Event class should be abstract.
class Event:
def __init__:
raise NotImplemented('This class is abstract')
Thirdly, see Single Table Inheritance and Class Table Inheritance patterns.
And django-ORM realisation of the patterns there
Good luck =)
I'm writing a Django app that works like a newspaper. I have articles and then I have customized versions of those articles that appear in certain contexts. So, I could have a version of an article that appears on the front page of the newspaper that has a shorter version of the article's original headline. So I have:
class Article(models.Model):
""" A newspaper article with lots of fields """
title = models.CharField(max_length=255)
content = models.CharField(max_length=255)
# Lots of fields...
I'd like to have a CustomArticlè object that is a proxy for the Articlè, but with an optional alternative headline:
class CustomArticle(Article):
""" An alternate version of a article """
alternate_title = models.CharField(max_length=255)
#property
def title(self):
""" use the alternate title if there is one """
if self.custom_title:
return self.alternate_title
else:
return self.title
class Meta:
proxy = True
# Other fields and methods
Unfortunately, I can't add new fields to a proxy:
>>> TypeError: Abstract base class containing model fields not permitted for proxy model 'CustomArticle'
So, I could do something like this:
class CustomArticle(models.Model):
# Other methods...
original = models.ForeignKey('Article')
def __getattr__(self, name):
if hasattr(self.original):
return getattr(self.original, name)
else:
return super(self, CustomArticle).__getattr__(name)
But unfortunately, __getattr__ doesn't seem to work with Django models. The fields in the Article class could change, so it isn't practical to create a #property method for each one in CustomArticle. What is the right way to do this?
It looks like this might work for the __getattr__:
def __getattr__(self, key):
if key not in ('original', '_ original_cache'):
return getattr(self.original, key)
raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, key))
What about making CustomArticle a subclass of Article? Django models do support inheritance! Have a look at: https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance
try something like this:
class CustomArticle(models.Model):
# Other methods...
original = models.ForeignKey('Article')
def __getattr__(self, name):
return getattr(self.original, name)
My application uses class inheritance to minimize repetition across my models. My models.py looks kind of like this:
class BaseModel(models.Model):
title = models.CharField(max_length=100)
pub_date = models.DateField()
class Child(BaseModel):
foo = models.CharField(max_length=20)
class SecondChild(BaseModel):
bar = models.CharField(max_length=20)
Now most of the time, my views and templates only deal with instances of Child or SecondChild. Once in a while, however, I have a situation where I have an instance of BaseModel, and need to figure out which class is inheriting from that instance.
Given an instance of BaseModel, let's call it base, Django's ORM offers base.child and base.secondchild. Currently, I have a method that loops through all of them to figure it out. It would look something like this:
class BaseModel(models.Model):
...
def get_absolute_url(self):
url = None
try:
self.child
url = self.child.get_absolute_url()
except Child.DoesNotExist:
pass
if not url:
try:
self.secondchild
url = self.secondchild.get_absolute_url()
except SecondChild.DoesNotExist:
pass
if not url:
url = '/base/%i' % self.id
return url
That is hopelessly ugly, and gets uglier with every additional child class I have. Does anybody have any ideas on a better, more pythonic way to go about this?
Various forms of this question pop up here regularly. This answer demonstrates a generic way to "cast" a parent type to its proper subtype without having to query every subtype table. That way you wouldn't need to define a monster get_absolute_url on the parent which covers all the cases, you'd just convert to the child type and call get_absolute_url normally.
I haven't messed with Django inheitance much, so I suppose you can't override get_absolute_url() in the model classes?
Perhaps the visitor pattern could help if there are lot of functions that need this in many different places.
I haven't tested this, but it might be worth tinkering with:
def get_absolute_url(self):
subclasses = ('child', 'secondchild', )
for subclass in subclasses:
if hasattr(self, subclass):
return getattr(self, subclass).get_absolute_url()
return '/base/%i' % self.id