Posts Tagged django
Related_Name attribute in Django Models
Two Many-To-One Foreign Key Relationships to the Same Object
See Django’s official documentation on the subject: Backward Related Objects.
The database has a relationship between Film and Language. Each film has an explicitly defined many-to-one attribute called language via the attribute film.language, and inversely, Django creates an implicit set of each Language’s films via the attribute film.language_set. A simple model is defined below:
from django.db import models
# Create your models here.
class Film(models.Model):
title = models.CharField(max_length=255, db_index = True)
language = models.ForeignKey('Language')
def __unicode__(self):
return self.title
class Language(models.Model):
name = models.CharField(max_length=20, unique=True)
def __unicode__(self):
return u'%s' (self.name)
This validates fine, and we can create a few films and languages using the following code:
#!/usr/bin/python
from mysite.sakila.models import *
#truncate the two tables
Film.objects.all().delete()
Language.objects.all().delete()
english = Language(name='English')
spanish = Language(name='Spansih')
english.save()
spanish.save()
print '-------Languages-------'
for l in Language.objects.all():
print l
film_1 = Film(title='film_1', language=english)
film_2 = Film(title='film_2', language=english)
film_3 = Film(title='film_3', language=spanish)
film_4 = Film(title='film_4', language=spanish)
print '--------Films---------'
for f in [film_1, film_2, film_3, film_4]:
f.save()
print f
print '----------English Films--------'
for ef in english.film_set.all():
print ef
print '----------Spanish Films--------'
for sf in spanish.film_set.all():
print sf
Running this script gives the following output:
-------Languages------- English Spansih --------Films--------- film_1 in English film_2 in English film_3 in Spansih film_4 in Spansih ----------English Films-------- film_1 in English film_2 in English ----------Spanish Films-------- film_3 in Spansih film_4 in Spansih
Notice Django’s automatic creation of the implicit one-to-many relationship between Language and Film. When the app is loaded, Django initially searches through each model object and creates in memory the implicit inverse relationships defined by many-to-one foreign keys. For example, the model above does not explicitly define the one-to-many relationship between language and films. Nonetheless, I can pull up any language from the database, and get a list of its films via the automatically created attribute setof films. Though not explicitly defined, I can still get a list of both Spanish and English films using the query manager called film_set.
Now let’s say we want to keep track of not only the language of the film, but also the original language of the film. Although it may be the same video, the language of the film is dubbed, and other attributes like title, description, etc. are all modified in order to localize better in the foreign market. For example, If we have the English film Ghostbusters, we could obtain the language object by accessing the attribute language, as in ghostbusters.language. Also, we have the Spanish language version of the film, this time called Assaseinos de Fantasmas defined by the variable aseseino_de_fantasmas, which is essentially a Spanish dub of the film Ghostbusters. This film would have an original language of English, and we could access this attribute as asesino_de_fantasmas.original_language.
The Language model is still the same as before, while the new Film model has another many-to-one foreign key relationship to the Language table:
from django.db import models
class Film(models.Model):
title = models.CharField(max_length=255, db_index = True)
language = models.ForeignKey('Language')
original_language = models.ForeignKey('Language')
def __unicode__(self):
return %s in %s (original language: %s) % (self.title, self.language, self.original_language)
class Language(models.Model):
name = models.CharField(max_length=20, unique=True)
def __unicode__(self):
return self.name
When we try to validate the app using Django’s manage.py tool with the validate command, we unfortunately get an error:
Error: One or more models did not validate: sakila.film: Accessor for field ‘language’ clashes with related field ‘Language.film_set’. Add a related_name argument to the definition for ‘language’. sakila.film: Accessor for field ‘original_language’ clashes with related field ‘Language.film_set’. Add a related_name argument to the definition for ‘original_language’.
The Problem with Multiple Foreign Keys to the same Table
The clue to the problem is the term related_name within the error output, and is caused by the Film model having two many-to-one foreign key relationships to the same table – in this case both the language and original_language attributes. When Django attempts to create the implicit one-to-many relationship to the Film table, it discovers that there are actually two of these relationships. If one were to use, for example, english.film_set, would this set refer to the set of languages or original_languages?
The Solution: related_name argument
To remedy this problem, one only needs to add a related_name argument to the model definition either of the two many-to-one Language relationships of Film. I’ll just give original_language the related name of original_language_film. Now one can access the set of films in English using the original english.film_set; likewise, we can obtain all the films that were originally in English using english.orig_lang_films.
from django.db import models
class Film(models.Model):
title = models.CharField(max_length=255, db_index = True)
language = models.ForeignKey('Language')
original_language = models.ForeignKey('Language', related_name='orig_lang_film')
def __unicode__(self):
return u'%s in %s (original language: %s)' % (self.title, self.language, self.original_language)
class Language(models.Model):
name = models.CharField(max_length=20, unique=True)
def __unicode__(self):
return self.name
Don’t forget to recreate your database since we’ve modified the model and will now need to have Django resync it!
Our model now validates successfully! Now, let’s change the script to take into account our new original language attribute.
-------Languages------- English Spansih --------Films--------- film_1 in English (original language: English) film_2 in English (original language: Spansih) film_3 in Spansih (original language: English) film_4 in Spansih (original language: English) ----------English Films-------- film_1 in English (original language: English) film_2 in English (original language: Spansih) ----------Spanish Films-------- film_3 in Spansih (original language: English) film_4 in Spansih (original language: English) ----------Oringal Language English Films-------- film_4 in Spansih (original language: English) film_4 in Spansih (original language: English) film_4 in Spansih (original language: English) ----------Oringal Spanish English Films-------- film_4 in Spansih (original language: English)