But your example got me thinking. Is something like the following valid? (working example)
from sqlobject import *

If it is working, then it is (IMHO) valid :D
The DB structure is completely up to you of course. But think twice about the DB model. When the production data is in the database already, it is a pain to change the DB structure.

But then I should handle the inShelve bool value whenever self.sold or self.onLoan are changed.

To keep the logic in the database is up to you. Of course you can automatize it some way (directly in the DB, using DB machinery - computed columns in the Firebird for example, which is IMHO not the best way to go but it works)

Or maybe you can try something using:

def _set_soldCarefully(self, value):
    self.sold = True
    self.onLoan = False
    self.inShelve = False