Add LocalUser and RemoteUser and migration
This commit is contained in:
parent
b4b04522bf
commit
aa9ba3ed80
@ -1429,3 +1429,123 @@ def remove_activityintermediator(db):
|
|||||||
|
|
||||||
# Commit the changes
|
# Commit the changes
|
||||||
db.commit()
|
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()
|
||||||
|
|
||||||
|
@ -222,62 +222,29 @@ class Location(Base):
|
|||||||
|
|
||||||
class User(Base, UserMixin):
|
class User(Base, UserMixin):
|
||||||
"""
|
"""
|
||||||
TODO: We should consider moving some rarely used fields
|
Base user that is common amongst LocalUser and RemoteUser.
|
||||||
into some sort of "shadow" table.
|
|
||||||
|
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"
|
__tablename__ = "core__users"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
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)
|
url = Column(Unicode)
|
||||||
bio = Column(UnicodeText) # ??
|
bio = Column(UnicodeText)
|
||||||
uploaded = Column(Integer, default=0)
|
name = Column(Unicode)
|
||||||
upload_limit = Column(Integer)
|
|
||||||
|
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"))
|
location = Column(Integer, ForeignKey("core__locations.id"))
|
||||||
|
|
||||||
|
# Lazy getters
|
||||||
get_location = relationship("Location", lazy="joined")
|
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):
|
def has_privilege(self, privilege, allow_admin=True):
|
||||||
"""
|
"""
|
||||||
This method checks to make sure a user has all the correct privileges
|
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
|
return UserBan.query.get(self.id) is not None
|
||||||
|
|
||||||
|
|
||||||
def serialize(self, request):
|
def serialize(self, request):
|
||||||
published = UTC.localize(self.created)
|
published = UTC.localize(self.created)
|
||||||
|
updated = UTC.localize(self.updated)
|
||||||
user = {
|
user = {
|
||||||
"id": "acct:{0}@{1}".format(self.username, request.host),
|
|
||||||
"published": published.isoformat(),
|
"published": published.isoformat(),
|
||||||
"preferredUsername": self.username,
|
"updated": updated.isoformat(),
|
||||||
"displayName": "{0}@{1}".format(self.username, request.host),
|
|
||||||
"objectType": self.object_type,
|
"objectType": self.object_type,
|
||||||
"pump_io": {
|
"pump_io": {
|
||||||
"shared": False,
|
"shared": False,
|
||||||
"followed": 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": {
|
"links": {
|
||||||
"self": {
|
"self": {
|
||||||
"href": request.urlgen(
|
"href": request.urlgen(
|
||||||
@ -347,21 +384,23 @@ class User(Base, UserMixin):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.bio:
|
user.update(super(LocalUser, self).serialize(request))
|
||||||
user.update({"summary": self.bio})
|
|
||||||
if self.url:
|
|
||||||
user.update({"url": self.url})
|
|
||||||
if self.location:
|
|
||||||
user.update({"location": self.get_location.serialize(request)})
|
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def unserialize(self, data):
|
class RemoteUser(User):
|
||||||
if "summary" in data:
|
""" User that is on another (remote) instance """
|
||||||
self.bio = data["summary"]
|
__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):
|
class Client(Base):
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user