Relationships¶
Creating relationships in Saffier is as simple as importing the fields and apply them into the models.
There are currently two types, the ForeignKey and the OneToOneField.
When declaring a foreign key, you can pass the value in two ways, as a string or as a model object. Internally Saffier lookups up inside the registry and maps your fields.
When declaring a model you can have one or more ForeignKey pointing to different tables or multiple foreign keys pointing to the same table as well.
Tip
Have a look at the related name documentation to understand how you can leverage reverse queries with foreign keys.
ForeignKey¶
Let us define the following models User
and Profile
.
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class User(saffier.Model):
is_active = saffier.BooleanField(default=True)
first_name = saffier.CharField(max_length=50, null=True)
last_name = saffier.CharField(max_length=50, null=True)
email = saffier.EmailField(max_lengh=100)
password = saffier.CharField(max_length=1000, null=True)
class Meta:
registry = models
class Profile(saffier.Model):
user = saffier.ForeignKey(User, on_delete=saffier.CASCADE)
class Meta:
registry = models
Now let us create some entries for those models.
user = await User.query.create(first_name="Foo", email="foo@bar.com")
await Profile.query.create(user=user)
user = await User.query.create(first_name="Bar", email="bar@foo.com")
await Profile.query.create(user=user)
Multiple foreign keys pointing to the same table¶
What if you want to have multiple foreign keys pointing to the same model? This is also easily possible to achieve.
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class User(saffier.Model):
is_active = saffier.BooleanField(default=True)
first_name = saffier.CharField(max_length=50)
last_name = saffier.CharField(max_length=50)
email = saffier.EmailField(max_lengh=100)
password = saffier.CharField(max_length=1000)
class Meta:
registry = models
class Thread(saffier.Model):
sender = saffier.ForeignKey(
User,
on_delete=saffier.CASCADE,
related_name="sender",
)
receiver = saffier.ForeignKey(
User,
on_delete=saffier.CASCADE,
related_name="receiver",
)
message = saffier.TextField()
class Meta:
registry = models
Tip
Have a look at the related name documentation to understand how you can leverage reverse queries with foreign keys withe the related_name.
Load an instance without the foreign key relationship on it¶
profile = await Profile.query.get(id=1)
# We have an album instance, but it only has the primary key populated
print(profile.user) # User(id=1) [sparse]
print(profile.user.pk) # 1
print(profile.user.email) # Raises AttributeError
Load an instance with the foreign key relationship on it¶
profile = await Profile.query.get(user__id=1)
await profile.user.load() # loads the foreign key
Load an instance with the foreign key relationship on it with select related¶
profile = await Profile.query.select_related("user").get(id=1)
print(profile.user) # User(id=1) [sparse]
print(profile.user.pk) # 1
print(profile.user.email) # foo@bar.com
Access the foreign key values directly from the model¶
Note
This is only possible since the version 1.3.0 of Saffier, before this version, the only way was by using the select_related or using the load().
You can access the values of the foreign keys of your model directly via model instance without using the select_related or the load().
Let us see an example.
Create a user and a profile
user = await User.query.create(first_name="Foo", email="foo@bar.com")
await Profile.query.create(user=user)
Accessing the user data from the profile
profile = await Profile.query.get(user__email="foo@bar.com")
print(profile.user.email) # "foo@bar.com"
print(profile.user.first_name) # "Foo"
ForeignKey constraints¶
As mentioned in the foreign key field, you can specify constraints in a foreign key.
The available values are CASCADE
, SET_NULL
, RESTRICT
and those can also be imported
from saffier
.
from saffier import CASCADE, SET_NULL, RESTRICT
When declaring a foreign key or a one to one key, the on_delete must be provided or an
AssertationError
is raised.
Looking back to the previous example.
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class User(saffier.Model):
is_active = saffier.BooleanField(default=True)
first_name = saffier.CharField(max_length=50, null=True)
last_name = saffier.CharField(max_length=50, null=True)
email = saffier.EmailField(max_lengh=100)
password = saffier.CharField(max_length=1000, null=True)
class Meta:
registry = models
class Profile(saffier.Model):
user = saffier.ForeignKey(User, on_delete=saffier.CASCADE)
class Meta:
registry = models
Profile
model defines a saffier.ForeignKey
to the User
with on_delete=saffier.CASCADE
which
means that whenever a User
is deleted from the database, all associated Profile
instances will
also be removed.
Delete options¶
- CASCADE - Remove all referencing objects.
- RESTRICT - Restricts the removing referenced objects.
- SET_NULL - This will make sure that when an object is deleted, the associated referencing
instances pointing to that object will set to null. When this
SET_NULL
is true, thenull=True
must be also provided or anAssertationError
is raised.
OneToOneField¶
Creating an OneToOneField
relationship between models is basically the same as the
ForeignKey with the key difference that it uses unique=True
on the foreign key
column.
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class User(saffier.Model):
is_active = saffier.BooleanField(default=True)
first_name = saffier.CharField(max_length=50)
last_name = saffier.CharField(max_length=50)
email = saffier.EmailField(max_lengh=100)
password = saffier.CharField(max_length=1000)
class Meta:
registry = models
class Profile(saffier.Model):
user = saffier.OneToOneField(User, on_delete=saffier.CASCADE)
class Meta:
registry = models
The same rules for this field are the same as the ForeignKey as this derives from it.
Let us create a User
and a Profile
.
user = await User.query.create(email="foo@bar.com")
await Profile.query.create(user=user)
Now creating another Profile
with the same user will fail and raise an exception.
await Profile.query.create(user=user)