CORD-1519 Prevent saving models that link to deleted models
Change-Id: I78e5f6c9250f518e76308aec0525315995c575b6
diff --git a/xos/core/models/attic/xosbase_model.py b/xos/core/models/attic/xosbase_model.py
index 8a45a05..98746c6 100644
--- a/xos/core/models/attic/xosbase_model.py
+++ b/xos/core/models/attic/xosbase_model.py
@@ -53,6 +53,44 @@
model.policed=None
model.save(update_fields=['enacted','deleted','policed'], silent=silent)
+def verify_live_keys(self, update_fields):
+ """ Check the fields to be updated, if they contain foreign keys, that the foreign keys only point
+ to live objects in the database.
+
+ This is to catch races between model policies where an object is being deleted while a model policy is
+ still operating on it.
+ """
+
+ if getattr(self, "deleted", False):
+ # If this model is already deleted, then no need to check anything. We only need to check for live
+ # models that point to dead models. If a dead model points to other dead models, then we could
+ # be updating something else in the dead model (backend_status, etc)
+ return
+
+ for field in self._meta.fields:
+ try:
+ f = getattr(self, field.name)
+ except Exception, e:
+ # Exception django.db.models.fields.related.RelatedObjectDoesNotExist
+ # is thrown by django when you're creating an object that has a base and the base doesn't exist yet
+ continue
+
+ if f is None:
+ # If field hold a null value, we don't care
+ continue
+
+ ftype = field.get_internal_type()
+ if (ftype != "ForeignKey"):
+ # If field isn't a foreign key, we don't care
+ continue
+
+ if (update_fields) and (field.name not in update_fields):
+ # If update_fields is nonempty, and field is not to be updated, we don't care.
+ continue
+
+ if getattr(f, "deleted", False):
+ raise Exception("Attempt to save object with deleted foreign key reference")
+
def save(self, *args, **kwargs):
# let the user specify silence as either a kwarg or an instance varible
@@ -85,9 +123,9 @@
if (caller_kind!="synchronizer") or always_update_timestamp:
self.updated = timezone.now()
-
- super(XOSBase, self).save(*args, **kwargs)
-
+ with transaction.atomic():
+ self.verify_live_keys(update_fields = kwargs.get("update_fields"))
+ super(XOSBase, self).save(*args, **kwargs)
self.push_redis_event()