Twistar is an Object Relational Mapper (ORM).
It therefore defines ways of interacting with objects and other objects that have relationships with them. The code
here is not necessarily great Twisted code; where typically you would expect to see DeferredList
s and
inlineCallbacks
there are none to provide clarity to those new to Twisted.
More information on relationships can be found in the Relationship
class.
Has One
Perhaps the simplest relationship is the Has One
relationship. In this example, we will
be using a User
object that Has One
Avatar
object.
1 2 3 4 5 6 7 8 9 10
from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASONE = ['avatar'] class Avatar(DBObject): pass Registry.register(User, Avatar)
In this example, we specify that each user can have one avatar. The database at this point should have two tables:
- A
users
table with at least a columnid
. - A
avatars
table with at least two columns:id
anduser_id
.
For the HASONE
class variable we can optionally give a dictionary that provides additional information:
1 2 3 4 5 6 7 8 9 10
from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASONE = [{'name': 'avatar', 'class_name': 'Avatar', foreign_key: 'user_id'}] class Avatar(DBObject): pass Registry.register(User, Avatar)
There are additional options as well: see the Relationships
class
for more information.
At this point we can use the relationship and assign user's avatar:
1 2 3 4 5 6 7
def onPicSave(picture, user): user.picture.set(picture) def onUserSave(user): Avatar(file="somewhere").save().addCallback(onPicSave, user) User(first_name="Bob").save().addCallback(onUserSave)
We can then get it:
1 2 3 4 5 6 7
def foundPicture(picture): print picture.file def foundUser(user): user.picture.get().addCallback(foundPicture) User.find(where=['first_name = ?', "Bob"], limit=1).addCallback(foundUser)
Has Many
Another relationship is used when one object "has many" of another. For instance, a User
may have many Pictures
.
1 2 3 4 5 6 7 8 9 10
from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASMANY = ['pictures'] class Picture(DBObject): pass Registry.register(User, Picture)
Again, the list assigned to HASMANY
can have dictionaries in it just like HASONE
that
contain additional configuration options. For this relationship, the database should at this point should have two tables:
- A
users
table with at least a columnid
. - A
pictures
table with at least two columns:id
anduser_id
.
pictures
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
from twisted.internet.defer import inlineCallbacks @inlineCallbacks def addPictures(): # set some pics user = yield User(first_name="Bob").save() picone = yield Picture(file="somewhere").save() pictwo = yield Picture(file="elsewhere").save() pictures = [picone, pictwo] yield user.pictures.set(pictures) # now get them pictures = yield user.pictures.get() print pictures[0].file print pictures[1].file addPictures()
Additionally, there is a clear()
method that will clear all
objects in a given has many relationship.
Belongs To
A good example of the "belongs to" property is the reverse of the "has many":
1 2 3 4 5 6 7 8 9 10
from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HASMANY = ['pictures'] class Picture(DBObject): BELONGSTO = ['user'] Registry.register(User, Picture)
Assuming the DB structure is the same as in the "has many" example, you can now get and set the user that
a particular picture belongs to (using get()
and set()
methods on picture.user
). Additionally, there is a clear()
method that will clear all
objects in a given "belongs to" relationship.
Has and Belongs To Many
In a "has and belongs to many" relationship two objects have a many to many relationship. For instance, users and favorite colors.
A user can have many favorite colors and a favorite color could belong to many users. In this example, there should be three tables —
a users
table, a favorite_colors
table, and a favorite_colors_users
table. The last on the list
is a special table that stores the relationships between the colors and users. It has no primary key, and two columns: one for
user_id
s and one for favorite_color_id
s. The table's name by convention should be the combination of the other
two table names, joined with an underscore, and arranged alphabetically. The classes would look like:
1 2 3 4 5 6 7 8 9 10
from twistar.dbobject import DBObject from twistar.registry import Registry class User(DBObject): HABTM = ['favorite_colors'] class FavoriteColor(DBObject): HABTM = ['users'] Registry.register(User, FavoriteColor)
You can now get and set the users of favorite colors and the favorite colors of users
(using get()
and set()
methods). Additionally, there is a clear()
method that will clear all
objects in a given relationship.
Polymorphic Relationships
With a polymorhpic relationship, a model can belong to more than one other model using a single relationship. For instance, take the example
where two models (say, Boy
and Girl
) both have many nicknames. Each Nickname
can belong to either a Boy
or a Girl
. In this example, the tables for boys and girls may have whatever
attributes you would like; the nicknames table, though, needs two columns in addition to an id and a value column. These columns are used to identify the id and
type of the other class. In this case, they will be called nicknameable_id
and nicknameable_type
.
The classes look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13
from twistar.dbobject import DBObject from twistar.registry import Registry class Boy(DBObject): HASMANY = [{'name': 'nicknames', 'as': 'nicknameable'}] class Girl(DBObject): HASMANY = [{'name': 'nicknames', 'as': 'nicknameable'}] class Nickname(DBObject): BELONGSTO = [{'name': 'nicknameable', 'polymorphic': True}] Registry.register(Boy, Girl, Nickname)
The use is pretty simple, and follows the same form as you would expect with a traditional "belongs to" relationship.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
def sayHi(nicknameable): print "Hello, my name is %s and I am a %s" % (nicknameable.name, nicknameable.__class__.__name__) def getPerson(nickname): nickname.nicknameable.get().addCallback(sayHi) def setNickname(nickname, boyOrGirl): boyOrGirl.nicknames.set([nickname]).addCallback(lambda _: getPerson(nickname)) def boySaved(boy): Nickname(value="Bob").save().addCallback(setNickname, boy) def girlSaved(girl): Nickname(value="Susie").save().addCallback(setNickname, girl) Boy(name="Robert").save().addCallback(boySaved) Girl(name="Susan").save().addCallback(girlSaved)
You can see that the nicknames for the boy are set via boy.nicknames.set
; they are fetched as you would expect via boy.nicknames.get
(with the same form for girls). For each nickname, by calling nickname.nicknameable.get
you could end up with either a Boy
or a
Girl
.