One of the things I really appreciate about Python is how easy it is to navigate from a line of code that mentions some class or function to the source where that name is defined. For example, given
headers = SortedDict()
You may ask
What's a Searching that source file for
SortedDict, you find it's defined by
from django.utils.datastructures import SortedDict
From that you can expect with some confidence that it's in a file named
django/utils/datastructures.py. Granted, that file may be in a number of places (because
sys.path), but if you have any doubt it's quick to confirm:
>>> import django.utils.datastructures >>> django.utils.datastructures.__file__ '/usr/lib/python2.7/dist-packages/django/utils/datastructures.py'
There are a number of variations that can make that slightly more complicated, but you should never have to guess or grep your entire hard drive. A competent IDE can navigate it for you in a keystroke. (Two of my most-used PyCharm shortcuts are Ctrl-q to open the docstring and Ctrl-b to take you to the definition of whatever your cursor is on.) Having fast and unambiguous access to definitions is an invaluable resource when reading code.1
I say this all by why of introduction as to why it drove me absolutely up the wall when I was reading code I could not find a definition for. It went something like this:
from django.db import models class Course(models.Model): def size(self): if self.videocourse.duration > 90: return "big" else: return "small"
Where's I asked. There's no
course.videocourse come from?
videocourse attribute defined on the
Course class or its superclass. PyCharm said it couldn't find a definition either. I searched the module for anything doing
videocourse = and found no such assignments.
I knew the Django ORM does add attributes to models for
backwards relations, but there were no instances of
ForeignKey(Course, related_name='videocourse') anywhere, or any other
ForeignKey(Course) call that would result in a relation with that name.
What there was, however, was this:
class VideoCourse(Course): duration = models.IntegerField()
But it didn't have any
ForeignKey (or other relationship fields) attributes to
Course, so surely it wouldn't implicitly add attributes to
Course instances, right?
Actually, in this case I do remember coming across the chapter on model inheritance in Two Scoops. I thought something along the lines of
this seems complicated and not relevant to any database modelling problems I've had so far, why are they introducing it so early in the book? and resolved not to use multi-table inheritance if I could avoid it. That plan doesn't work if you jump in to an existing project, though.
I think providing a way to look up foreign key relationships from both directions is a good one; it's convenient syntax for a very common use case. I also think the Python maxim of
- A foreign key adds an attribute to the target model in addition to the model where it is defined.
- The name of that attribute (the
related_name) has a default taken from the name of the source class, with some subtle transformation applied (just enough to confuse your normal use of search in a case-sensitive language).
- Subclassing a model implicitly adds a foreign key to the parent model — sometimes, depending on options set in
Metaon the parent (
abstract) or child (
and I think the end result is not a good one for maintainability.
Fortunately, it looks like we can make some implicit things explicit.
class VideoCourse(Course): course = models.OneToOneField(Course, parent_link=True, related_name="video_course") duration = models.IntegerField()
related_name lets us make items #2 and #3 above both explicit, and also makes the attribute name follow our naming conventions (
video course is two words, not a compound word). With that, I still wouldn't have seen anything explicitly assigning to
video_course, but when I started looking for matching
related_names I would have found this.
This is something I've sorely missed in my brief forays into Ruby on Rails, but that's another story.
This is also why we never use