Add a more verbose GenericForeignKey implementation
This commit is contained in:
parent
d2256d0b3b
commit
c1d27aa019
@ -1392,8 +1392,8 @@ def rename_and_remove_object_and_target(db):
|
|||||||
new_target_column = activity_table.columns["temp_target"]
|
new_target_column = activity_table.columns["temp_target"]
|
||||||
|
|
||||||
# rename them to the old names.
|
# rename them to the old names.
|
||||||
new_object_column.alter(name="object")
|
new_object_column.alter(name="object_id")
|
||||||
new_target_column.alter(name="target")
|
new_target_column.alter(name="target_id")
|
||||||
|
|
||||||
# Commit the changes to the database.
|
# Commit the changes to the database.
|
||||||
db.commit()
|
db.commit()
|
||||||
|
@ -51,9 +51,6 @@ _log = logging.getLogger(__name__)
|
|||||||
class GenericModelReference(Base):
|
class GenericModelReference(Base):
|
||||||
"""
|
"""
|
||||||
Represents a relationship to any model that is defined with a integer pk
|
Represents a relationship to any model that is defined with a integer pk
|
||||||
|
|
||||||
NB: This model should not be used directly but through the GenericForeignKey
|
|
||||||
field provided.
|
|
||||||
"""
|
"""
|
||||||
__tablename__ = "core__generic_model_reference"
|
__tablename__ = "core__generic_model_reference"
|
||||||
|
|
||||||
@ -63,6 +60,11 @@ class GenericModelReference(Base):
|
|||||||
# This will be the tablename of the model
|
# This will be the tablename of the model
|
||||||
model_type = Column(Unicode, nullable=False)
|
model_type = Column(Unicode, nullable=False)
|
||||||
|
|
||||||
|
# Constrain it so obj_pk and model_type have to be unique
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint("obj_pk", "model_type"),
|
||||||
|
{})
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
# This can happen if it's yet to be saved
|
# This can happen if it's yet to be saved
|
||||||
if self.model_type is None or self.obj_pk is None:
|
if self.model_type is None or self.obj_pk is None:
|
||||||
@ -76,7 +78,7 @@ class GenericModelReference(Base):
|
|||||||
|
|
||||||
# Check we've been given a object
|
# Check we've been given a object
|
||||||
if not issubclass(model, Base):
|
if not issubclass(model, Base):
|
||||||
raise ValueError("Only models can be set as GenericForeignKeys")
|
raise ValueError("Only models can be set as using the GMR")
|
||||||
|
|
||||||
# Check that the model has an explicit __tablename__ declaration
|
# Check that the model has an explicit __tablename__ declaration
|
||||||
if getattr(model, "__tablename__", None) is None:
|
if getattr(model, "__tablename__", None) is None:
|
||||||
@ -89,13 +91,13 @@ class GenericModelReference(Base):
|
|||||||
|
|
||||||
# Check that the field on the model is a an integer field
|
# Check that the field on the model is a an integer field
|
||||||
pk_column = getattr(model, primary_keys[0])
|
pk_column = getattr(model, primary_keys[0])
|
||||||
if issubclass(pk_column, Integer):
|
if not isinstance(pk_column.type, Integer):
|
||||||
raise ValueError("Only models with integer pks can be set")
|
raise ValueError("Only models with integer pks can be set")
|
||||||
|
|
||||||
# Ensure that everything has it's ID set
|
if getattr(obj, pk_column.key) is None:
|
||||||
obj.save(commit=False)
|
obj.save(commit=False)
|
||||||
|
|
||||||
self.obj_pk = getattr(obj, pk_column)
|
self.obj_pk = getattr(obj, pk_column.key)
|
||||||
self.model_type = obj.__tablename__
|
self.model_type = obj.__tablename__
|
||||||
|
|
||||||
def _get_model_from_type(self, model_type):
|
def _get_model_from_type(self, model_type):
|
||||||
@ -121,66 +123,26 @@ class GenericModelReference(Base):
|
|||||||
model = type(obj)
|
model = type(obj)
|
||||||
pk = getattr(obj, "id")
|
pk = getattr(obj, "id")
|
||||||
|
|
||||||
gmr = GenericModelReference.query.filter_by(
|
gmr = cls.query.filter_by(
|
||||||
obj_pk=pk,
|
obj_pk=pk,
|
||||||
model_type=model.__tablename__
|
model_type=model.__tablename__
|
||||||
)
|
)
|
||||||
|
|
||||||
return gmr.first()
|
return gmr.first()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_or_new(cls, obj):
|
||||||
|
""" Finds an existing GMR or creates a new one for the object """
|
||||||
|
obj = cls.find_for_obj(obj)
|
||||||
|
|
||||||
class GenericForeignKey(types.TypeDecorator):
|
# If there isn't one already create one
|
||||||
"""
|
if obj is None:
|
||||||
GenericForeignKey type used to refer to objects with an integer foreign key.
|
obj = cls(
|
||||||
|
obj_pk=obj.id,
|
||||||
This will break referential integrity, only use in places where that is
|
model_type=type(obj).__tablename__
|
||||||
not important.
|
)
|
||||||
"""
|
|
||||||
|
|
||||||
impl = Integer
|
|
||||||
|
|
||||||
def process_result_value(self, value, *args, **kwargs):
|
|
||||||
""" Looks up GenericModelReference and model for field """
|
|
||||||
# If this hasn't been set yet return None
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Look up the GenericModelReference for this.
|
|
||||||
gmr = GenericModelReference.query.filter_by(id=value).first()
|
|
||||||
|
|
||||||
# If it's set to something invalid (i.e. no GMR exists return None)
|
|
||||||
if gmr is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Ask the GMR for the corresponding model
|
|
||||||
return gmr.get_object()
|
|
||||||
|
|
||||||
def process_bind_param(self, value, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Save the foreign key
|
|
||||||
|
|
||||||
There is no mechanism to put a constraint to make this only save foreign
|
|
||||||
keys to GenericModelReference. The only thing you can do is have this
|
|
||||||
method which only saves GenericModelReference.
|
|
||||||
"""
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Find the GMR if there is one.
|
|
||||||
gmr = GenericModelReference.find_for_obj(value)
|
|
||||||
|
|
||||||
# Create one if there isn't
|
|
||||||
if gmr is None:
|
|
||||||
gmr = GenericModelReference()
|
|
||||||
gmr.set_object(value)
|
|
||||||
gmr.save(commit=False)
|
|
||||||
|
|
||||||
return gmr.id
|
|
||||||
|
|
||||||
def _set_parent_with_dispatch(self, parent):
|
|
||||||
self.parent = parent
|
|
||||||
|
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
class Location(Base):
|
class Location(Base):
|
||||||
""" Represents a physical location """
|
""" Represents a physical location """
|
||||||
@ -620,6 +582,7 @@ class MediaEntry(Base, MediaEntryMixin):
|
|||||||
return import_component(self.media_type + '.models:BACKREF_NAME')
|
return import_component(self.media_type + '.models:BACKREF_NAME')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
return "<MediaEntry DEBUG>"
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
# obj.__repr__() should return a str on Python 2
|
# obj.__repr__() should return a str on Python 2
|
||||||
safe_title = self.title.encode('utf-8', 'replace')
|
safe_title = self.title.encode('utf-8', 'replace')
|
||||||
@ -1407,10 +1370,18 @@ class Activity(Base, ActivityMixin):
|
|||||||
generator = Column(Integer,
|
generator = Column(Integer,
|
||||||
ForeignKey("core__generators.id"),
|
ForeignKey("core__generators.id"),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
object = Column(GenericForeignKey(),
|
|
||||||
nullable=False)
|
# Create the generic foreign keys for the object
|
||||||
target = Column(GenericForeignKey(),
|
object_id = Column(Integer, ForeignKey(GenericModelReference.id), nullable=False)
|
||||||
nullable=True)
|
object_helper = relationship(GenericModelReference)
|
||||||
|
object = association_proxy("object_helper", "get_object",
|
||||||
|
creator=GenericModelReference.find_or_new)
|
||||||
|
|
||||||
|
# Create the generic foreign Key for the target
|
||||||
|
target_id = Column(Integer, ForeignKey(GenericModelReference), nullable=True)
|
||||||
|
target_helper = relationship(GenericModelReference)
|
||||||
|
taget = association_proxy("target_helper", "get_target",
|
||||||
|
creator=GenericModelReference.find_or_new)
|
||||||
|
|
||||||
get_actor = relationship(User,
|
get_actor = relationship(User,
|
||||||
backref=backref("activities",
|
backref=backref("activities",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user