There has been much discussion recently about replacing parts of Identity or
updating Identity-related tables, et cetera.
The following was my solution to the problem and I've tried to make it as
simple as possible by only replacing the model the standard SOProvider uses.
For the most part this replacement model works beautifully. By having a
function search "byUserId", and having a function override retrieval of the
"userId", I can easily swap my current username-based system for an e-mail
address-based one.
Unfortunately soprovider doesn't understand having a model of None. That
would be too easy. ;^) So, instead, I just have a Group model that is never
used. (I will likely update this in production to merge user roles and group
roles, and allow for role subtraction. (I.e. "global" rules are included,
then group roles are added or removed from the global ones, then user roles
from those. Etc.)
Enjoy!
P.s. as long as your DB classes act in an SQLObject way (i.e. .get(id) returns
a filled instance, .column is the named column, etc.) than you should be able
to trick soprovider into using almost any DB back-end. Just implement the
table creation function and/or silently drop (empty function).
--- topfloor.config ---
visit.on=True
identity.on=True
identity.failure_url="/authenticate"
identity.soprovider.model.user="content.model.Account"
identity.soprovider.model.group="content.model.Group"
identity.soprovider.model.permission="content.model.Role"
identity.soprovider.encryption_algorithm="md5"
cms.on = True
--- content.model ---
class Account(SQLObject):
class sqlmeta:
table = "accounts"
registry = "content"
Username = UnicodeCol(length=64, alternateID=True)
FullName = UnicodeCol(length=255)
Password = UnicodeCol(length=48, default=None)
EMail = UnicodeCol(length=255, alternateID=True)
Location = UnicodeCol(length=255, default=None)
Language = ForeignKey('Language', default=None)
Biography = UnicodeCol(default=None)
Searchable = BoolCol(default=True)
Portrait = BoolCol(default=False)
Roles = RelatedJoin('Role')
Groups = RelatedJoin('Group')
Created = DateTimeCol(default=datetime.now)
Updated = DateTimeCol(default=datetime.now)
def byUserId(id): return Account.byUsername(id)
byUserId = staticmethod(byUserId)
def _get_userId(self): return self.Username
def _get_permissions(self): return self.Roles
def _get_groups(self): return list()
def _get_password(self): return self.Password
def jsonify_account(obj):
result = jsonify_sqlobject(obj)
del result['Password']
if "Language" in result:
result['Language'] = jsonify(obj.Language.Code)
result['Roles'] = jsonify([p.Role for p in obj.Roles])
return result
jsonify_account = jsonify.when('isinstance(obj, Account)')(jsonify_account)
class Role(SQLObject):
class sqlmeta:
table = "roles"
registry = "content"
Role = UnicodeCol(length=64, alternateID=True)
Description = UnicodeCol(default=None)
Users = RelatedJoin('Account')
def byPermissionId(id): return Role.byRole(id)
byPermissionId = staticmethod(byPermissionId)
def _get_permissionId(self): return self.Role
# Never used. Implemented due to a lack of support for None in cfg.
class Group(SQLObject):
class sqlmeta:
table = "groups"
registry = "content"
groupId = UnicodeCol(length=16, alternateID=True)
displayName = UnicodeCol(length=255)
created = DateTimeCol(default=datetime.now)
users = list()
def _get_permissions(self): return list()
class User(SQLObject):
user = StringCol(length=15, alternateID=True)
pass = StringCol(length=32)
def _get_userId(self):
return self.user
User.byUserId = User.byUser
class FalseSQLMeta(type):
def createTable(cls, *args, **kw):
return True
class FalseSQLObject(object):
__metaclass__ = FalseSQLMeta
class Group(FalseSQLObject):
pass
class Perm(FalseSQLObject):
pass
Ciao
Michele