Add LocalUser and RemoteUser and migration

This commit is contained in:
Jessica Tallon 2015-06-26 15:29:44 +02:00
parent b4b04522bf
commit aa9ba3ed80
2 changed files with 223 additions and 64 deletions

View File

@ -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()

View File

@ -222,62 +222,29 @@ 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)
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
# the RFC) and because it would be a mess to implement at this
# 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)
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")
## TODO
# plugin data would be in a separate model
def __repr__(self):
return '<{0} #{1} {2} {3} "{4}">'.format(
self.__class__.__name__,
self.id,
'verified' if self.has_privilege(u'active') else 'non-verified',
'admin' if self.has_privilege(u'admin') else 'user',
self.username)
def delete(self, **kwargs):
"""Deletes a User and all related entries/comments/files/..."""
# Collections get deleted by relationships.
media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id)
for media in media_entries:
# TODO: Make sure that "MediaEntry.delete()" also deletes
# all related files/Comments
media.delete(del_orphan_tags=False, commit=False)
# Delete now unused tags
# TODO: import here due to cyclic imports!!! This cries for refactoring
from mediagoblin.db.util import clean_orphan_tags
clean_orphan_tags(commit=False)
# Delete user, pass through commit=False/True in kwargs
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
@ -309,19 +276,89 @@ class User(Base, UserMixin):
"""
return UserBan.query.get(self.id) is not None
def serialize(self, request):
published = UTC.localize(self.created)
updated = UTC.localize(self.updated)
user = {
"id": "acct:{0}@{1}".format(self.username, request.host),
"published": published.isoformat(),
"preferredUsername": self.username,
"displayName": "{0}@{1}".format(self.username, request.host),
"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
# the RFC) and because it would be a mess to implement at this
# point.
email = Column(Unicode, nullable=False)
pw_hash = Column(Unicode)
# 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)
uploaded = Column(Integer, default=0)
upload_limit = Column(Integer)
## TODO
# plugin data would be in a separate model
def __repr__(self):
return '<{0} #{1} {2} {3} "{4}">'.format(
self.__class__.__name__,
self.id,
'verified' if self.has_privilege(u'active') else 'non-verified',
'admin' if self.has_privilege(u'admin') else 'user',
self.username)
def delete(self, **kwargs):
"""Deletes a User and all related entries/comments/files/..."""
# Collections get deleted by relationships.
media_entries = MediaEntry.query.filter(MediaEntry.uploader == self.id)
for media in media_entries:
# TODO: Make sure that "MediaEntry.delete()" also deletes
# all related files/Comments
media.delete(del_orphan_tags=False, commit=False)
# Delete now unused tags
# TODO: import here due to cyclic imports!!! This cries for refactoring
from mediagoblin.db.util import clean_orphan_tags
clean_orphan_tags(commit=False)
# Delete user, pass through commit=False/True in kwargs
super(User, self).delete(**kwargs)
_log.info('Deleted user "{0}" account'.format(self.username))
def serialize(self, request):
user = {
"id": "acct:{0}@{1}".format(self.username, request.host),
"preferredUsername": self.username,
"displayName": "{0}@{1}".format(self.username, request.host),
"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):
"""