# 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()
+
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
# 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
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(
},
}
- 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):
"""