From aa9ba3ed8010362466d14037e2c9950ce3bc2cb2 Mon Sep 17 00:00:00 2001 From: Jessica Tallon Date: Fri, 26 Jun 2015 15:29:44 +0200 Subject: [PATCH] Add LocalUser and RemoteUser and migration --- mediagoblin/db/migrations.py | 120 +++++++++++++++++++++++++++++++++ mediagoblin/db/models.py | 155 +++++++++++++++++++++++++++---------------- 2 files changed, 217 insertions(+), 58 deletions(-) diff --git a/mediagoblin/db/migrations.py b/mediagoblin/db/migrations.py index 4f2f8915..02dc996c 100644 --- a/mediagoblin/db/migrations.py +++ b/mediagoblin/db/migrations.py @@ -1429,3 +1429,123 @@ def remove_activityintermediator(db): # Commit the changes db.commit() + +## +# Migrations for converting the User model into a Local and Remote User +# setup. +## + +class LocalUser_V0(declarative_base()): + __tablename__ = "core__local_users" + + id = Column(Integer, ForeignKey(User.id), primary_key=True) + username = Column(Unicode, nullable=False, unique=True) + email = Column(Unicode, nullable=False) + pw_hash = Column(Unicode) + + wants_comment_notification = Column(Boolean, default=True) + wants_notifications = Column(Boolean, default=True) + license_preference = Column(Unicode) + uploaded = Column(Integer, default=0) + upload_limit = Column(Integer) + +class RemoteUser_V0(declarative_base()): + __tablename__ = "core__remote_users" + + id = Column(Integer, ForeignKey(User.id), primary_key=True) + webfinger = Column(Unicode, unique=True) + +@RegisterMigration(32, MIGRATIONS) +def federation_user_create_tables(db): + """ + Create all the tables + """ + # Create tables needed + LocalUser_V0.__table__.create(db.bind) + RemoteUser_V0.__table__.create(db.bind) + db.commit() + + # Create the fields + metadata = MetaData(bind=db.bind) + user_table = inspect_table(metadata, "core__users") + + updated_column = Column( + "updated", + DateTime, + default=datetime.datetime.utcnow + ) + updated_column.create(user_table) + + name_column = Column( + "name", + Unicode + ) + name_column.create(user_table) + + db.commit() + +@RegisterMigration(33, MIGRATIONS) +def federation_user_migrate_data(db): + """ + Migrate the data over to the new user models + """ + metadata = MetaData(bind=db.bind) + + user_table = inspect_table(metadata, "core__users") + local_user_table = inspect_table(metadata, "core__local_users") + + for user in db.execute(user_table.select()): + db.execute(local_user_table.insert().values( + id=user.id, + username=user.username, + email=user.email, + pw_hash=user.pw_hash, + wants_comment_notification=user.wants_comment_notification, + wants_notifications=user.wants_notifications, + license_preference=user.license_preference, + uploaded=user.uploaded, + upload_limit=user.upload_limit + )) + + db.execute(user_table.update().where(user_table.c.id==user.id).values( + updated=user.created + )) + + db.commit() + +@RegisterMigration(34, MIGRATIONS) +def federation_remove_fields(db): + """ + This removes the fields from User model which aren't shared + """ + metadata = MetaData(bind=db.bind) + + user_table = inspect_table(metadata, "core__users") + + # Remove the columns moved to LocalUser from User + username_column = user_table.columns["username"] + username_column.drop() + + email_column = user_table.columns["email"] + email_column.drop() + + pw_hash_column = user_table.columns["pw_hash"] + pw_hash_column.drop() + + wcn_column = user_table.columns["wants_comment_notification"] + wcn_column.drop() + + wants_notifications_column = user_table.columns["wants_notifications"] + wants_notifications_column.drop() + + license_preference_column = user_table.columns["license_preference"] + license_preference_column.drop() + + uploaded_column = user_table.columns["uploaded"] + uploaded_column.drop() + + upload_limit_column = user_table.columns["upload_limit"] + upload_limit_column.drop() + + db.commit() + diff --git a/mediagoblin/db/models.py b/mediagoblin/db/models.py index 054e1677..ef37aef8 100644 --- a/mediagoblin/db/models.py +++ b/mediagoblin/db/models.py @@ -222,12 +222,92 @@ class Location(Base): class User(Base, UserMixin): """ - TODO: We should consider moving some rarely used fields - into some sort of "shadow" table. + Base user that is common amongst LocalUser and RemoteUser. + + This holds all the fields which are common between both the Local and Remote + user models. + + NB: ForeignKeys should reference this User model and NOT the LocalUser or + RemoteUser models. """ __tablename__ = "core__users" id = Column(Integer, primary_key=True) + url = Column(Unicode) + bio = Column(UnicodeText) + name = Column(Unicode) + + created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + updated = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + + location = Column(Integer, ForeignKey("core__locations.id")) + + # Lazy getters + get_location = relationship("Location", lazy="joined") + + def has_privilege(self, privilege, allow_admin=True): + """ + This method checks to make sure a user has all the correct privileges + to access a piece of content. + + :param privilege A unicode object which represent the different + privileges which may give the user access to + content. + + :param allow_admin If this is set to True the then if the user is + an admin, then this will always return True + even if the user hasn't been given the + privilege. (defaults to True) + """ + priv = Privilege.query.filter_by(privilege_name=privilege).one() + if priv in self.all_privileges: + return True + elif allow_admin and self.has_privilege(u'admin', allow_admin=False): + return True + + return False + + def is_banned(self): + """ + Checks if this user is banned. + + :returns True if self is banned + :returns False if self is not + """ + return UserBan.query.get(self.id) is not None + + def serialize(self, request): + published = UTC.localize(self.created) + updated = UTC.localize(self.updated) + user = { + "published": published.isoformat(), + "updated": updated.isoformat(), + "objectType": self.object_type, + "pump_io": { + "shared": False, + "followed": False, + }, + } + + if self.bio: + user.update({"summary": self.bio}) + if self.url: + user.update({"url": self.url}) + if self.location: + user.update({"location": self.get_location.serialize(request)}) + + def unserialize(self, data): + if "summary" in data: + self.bio = data["summary"] + + if "location" in data: + Location.create(data, self) + +class LocalUser(User): + """ This represents a user registered on this instance """ + __tablename__ = "core__local_users" + + id = Column(Integer, ForeignKey("core__users.id"), primary_key=True) username = Column(Unicode, nullable=False, unique=True) # Note: no db uniqueness constraint on email because it's not # reliable (many email systems case insensitive despite against @@ -235,18 +315,14 @@ class User(Base, UserMixin): # point. email = Column(Unicode, nullable=False) pw_hash = Column(Unicode) - created = Column(DateTime, nullable=False, default=datetime.datetime.utcnow) + # Intented to be nullable=False, but migrations would not work for it # set to nullable=True implicitly. wants_comment_notification = Column(Boolean, default=True) wants_notifications = Column(Boolean, default=True) license_preference = Column(Unicode) - url = Column(Unicode) - bio = Column(UnicodeText) # ?? uploaded = Column(Integer, default=0) upload_limit = Column(Integer) - location = Column(Integer, ForeignKey("core__locations.id")) - get_location = relationship("Location", lazy="joined") ## TODO # plugin data would be in a separate model @@ -278,50 +354,11 @@ class User(Base, UserMixin): super(User, self).delete(**kwargs) _log.info('Deleted user "{0}" account'.format(self.username)) - def has_privilege(self, privilege, allow_admin=True): - """ - This method checks to make sure a user has all the correct privileges - to access a piece of content. - - :param privilege A unicode object which represent the different - privileges which may give the user access to - content. - - :param allow_admin If this is set to True the then if the user is - an admin, then this will always return True - even if the user hasn't been given the - privilege. (defaults to True) - """ - priv = Privilege.query.filter_by(privilege_name=privilege).one() - if priv in self.all_privileges: - return True - elif allow_admin and self.has_privilege(u'admin', allow_admin=False): - return True - - return False - - def is_banned(self): - """ - Checks if this user is banned. - - :returns True if self is banned - :returns False if self is not - """ - return UserBan.query.get(self.id) is not None - - def serialize(self, request): - published = UTC.localize(self.created) user = { "id": "acct:{0}@{1}".format(self.username, request.host), - "published": published.isoformat(), "preferredUsername": self.username, "displayName": "{0}@{1}".format(self.username, request.host), - "objectType": self.object_type, - "pump_io": { - "shared": False, - "followed": False, - }, "links": { "self": { "href": request.urlgen( @@ -347,21 +384,23 @@ class User(Base, UserMixin): }, } - if self.bio: - user.update({"summary": self.bio}) - if self.url: - user.update({"url": self.url}) - if self.location: - user.update({"location": self.get_location.serialize(request)}) - + user.update(super(LocalUser, self).serialize(request)) return user - def unserialize(self, data): - if "summary" in data: - self.bio = data["summary"] +class RemoteUser(User): + """ User that is on another (remote) instance """ + __tablename__ = "core__remote_users" + + id = Column(Integer, ForeignKey("core__users.id"), primary_key=True) + webfinger = Column(Unicode, unique=True) + + def __repr__(self): + return "<{0} #{1} {2}>".format( + self.__class__.__name__, + self.id, + self.webfinger + ) - if "location" in data: - Location.create(data, self) class Client(Base): """ -- 2.11.4.GIT