SEBA-270 Apply xosbase timestamp code to User object
Change-Id: Iaa2015e691457d3038b807cc00039cb3677fb338
diff --git a/VERSION b/VERSION
index 8dbb0f2..a39c0b7 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.1.10
+2.1.11
diff --git a/containers/chameleon/Dockerfile.chameleon b/containers/chameleon/Dockerfile.chameleon
index 482f319..103e773 100644
--- a/containers/chameleon/Dockerfile.chameleon
+++ b/containers/chameleon/Dockerfile.chameleon
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/chameleon
-FROM xosproject/xos-base:2.1.9
+FROM xosproject/xos-base:2.1.11
# xos-base already has protoc and dependencies installed
diff --git a/containers/xos/Dockerfile.client b/containers/xos/Dockerfile.client
index 299cb90..bee6909 100644
--- a/containers/xos/Dockerfile.client
+++ b/containers/xos/Dockerfile.client
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-client
-FROM xosproject/xos-libraries:2.1.10
+FROM xosproject/xos-libraries:2.1.11
# Install XOS client
COPY xos/xos_client /tmp/xos_client
diff --git a/containers/xos/Dockerfile.libraries b/containers/xos/Dockerfile.libraries
index 1a150d9..07efd91 100644
--- a/containers/xos/Dockerfile.libraries
+++ b/containers/xos/Dockerfile.libraries
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-FROM xosproject/xos-base:2.1.10
+FROM xosproject/xos-base:2.1.11
# Add libraries
COPY lib /opt/xos/lib
diff --git a/containers/xos/Dockerfile.synchronizer-base b/containers/xos/Dockerfile.synchronizer-base
index 14913a5..aa5f6fb 100644
--- a/containers/xos/Dockerfile.synchronizer-base
+++ b/containers/xos/Dockerfile.synchronizer-base
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-synchronizer-base
-FROM xosproject/xos-client:2.1.10
+FROM xosproject/xos-client:2.1.11
COPY xos/synchronizers/new_base /opt/xos/synchronizers/new_base
COPY xos/xos/logger.py /opt/xos/xos/logger.py
diff --git a/containers/xos/Dockerfile.xos-core b/containers/xos/Dockerfile.xos-core
index 0d28235..a00c1c9 100644
--- a/containers/xos/Dockerfile.xos-core
+++ b/containers/xos/Dockerfile.xos-core
@@ -13,7 +13,7 @@
# limitations under the License.
# xosproject/xos-core
-FROM xosproject/xos-libraries:2.1.10
+FROM xosproject/xos-libraries:2.1.11
# Install XOS
ADD xos /opt/xos
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index 63510c7..7447b49 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -28,6 +28,7 @@
from django.core.exceptions import PermissionDenied
from django.core.mail import EmailMultiAlternatives
from django.db import models
+from django.utils.timezone import now
from django.db import transaction
from django.db import router
from django.db.models import F, Q
@@ -144,10 +145,13 @@
login_page = StrippedCharField(
help_text="send this user to a specific page on login", max_length=200, null=True, blank=True)
- created = models.DateTimeField(auto_now_add=True)
- updated = models.DateTimeField(auto_now=True)
- enacted = models.DateTimeField(null=True, blank = True, default=None)
- policed = models.DateTimeField(null=True, blank = True, default=None)
+ created = models.DateTimeField(help_text="Time this model was created", auto_now_add=True, null=False, blank=False)
+ updated = models.DateTimeField(help_text="Time this model was changed by a non-synchronizer", default=now, null=False,
+ blank=False)
+ enacted = models.DateTimeField(default=None, help_text="When synced, set to the timestamp of the data that was synced",
+ null=True, blank=True)
+ policed = models.DateTimeField(default=None, help_text="When policed, set to the timestamp of the data that was policed",
+ null=True, blank=True)
backend_status = StrippedCharField(max_length=1024,
default="Provisioning in progress")
backend_code = models.IntegerField( default = 0, null = False )
@@ -259,6 +263,20 @@
model.policed=None
model.save(update_fields=['enacted','deleted','policed'], silent=silent)
+ def has_important_changes(self):
+ """ Determine whether the model has changes that should be reflected in one of the changed_by_* timestamps.
+ Ignores various feedback and bookkeeping state set by synchronizers.
+ """
+ for field_name in self.changed_fields:
+ if field_name in ["policed", "updated", "enacted", "changed_by_step", "changed_by_policy"]:
+ continue
+ if field_name.startswith("backend_"):
+ continue
+ if field_name.startswith("policy_"):
+ continue
+ return True
+ return False
+
def save(self, *args, **kwargs):
if not self.leaf_model_name:
self.leaf_model_name = "User"
@@ -276,33 +294,53 @@
caller_kind = "unknown"
- if ('synchronizer' in threading.current_thread().name):
- caller_kind = "synchronizer"
-
if "caller_kind" in kwargs:
caller_kind = kwargs.pop("caller_kind")
+ update_fields = None
+ if "update_fields" in kwargs:
+ # NOTE(smbaker): modifying update_fields will cause kwargs["update_fields"] to be modified. This is
+ # intended, as kwargs will be passed to save() below.
+ update_fields = kwargs["update_fields"]
+
+ # NOTE(smbaker): always_update_timestamp still has some relevance for event_steps and pull_steps that
+ # want to cause an update. For model_policies or sync_steps it should no longer be required.
always_update_timestamp = False
if "always_update_timestamp" in kwargs:
always_update_timestamp = always_update_timestamp or kwargs.pop("always_update_timestamp")
- # SMBAKER: if an object is trying to delete itself, or if the observer
- # is updating an object's backend_* fields, then let it slip past the
- # composite key check.
- ignore_composite_key_check=False
- if "update_fields" in kwargs:
- ignore_composite_key_check=True
- for field in kwargs["update_fields"]:
- if not (field in ["backend_register", "backend_status", "deleted", "enacted", "updated"]):
- ignore_composite_key_check=False
+ is_sync_save = False
+ if "is_sync_save" in kwargs:
+ is_sync_save = kwargs.pop("is_sync_save")
+
+ is_policy_save = False
+ if "is_policy_save" in kwargs:
+ is_policy_save = kwargs.pop("is_policy_save")
if (caller_kind!="synchronizer") or always_update_timestamp:
self.updated = timezone.now()
+ else:
+ # We're not auto-setting timestamp, but let's check to make sure that the caller hasn't tried to set our
+ # timestamp backward...
+ if (self.updated != self._initial["updated"]) and ((not update_fields) or ("updated" in update_fields)):
+ log.info("Synchronizer tried to change `updated` timestamp on model %s from %s to %s. Ignored." % (self, self._initial["updated"], self.updated))
+ self.updated = self._initial["updated"]
+
+ if is_sync_save and self.has_important_changes():
+ self.changed_by_step = timezone.now()
+ if update_fields:
+ update_fields.append("changed_by_step")
+
+ if is_policy_save and self.has_important_changes():
+ self.changed_by_policy = timezone.now()
+ if update_fields:
+ update_fields.append("changed_by_policy")
if not self.username:
self.username = self.email
- self.full_clean()
+ if not self.deleted:
+ self.full_clean()
super(User, self).save(*args, **kwargs)
diff --git a/xos/core/models/xosbase.py b/xos/core/models/xosbase.py
index 8cd1d4f..5a327ac 100644
--- a/xos/core/models/xosbase.py
+++ b/xos/core/models/xosbase.py
@@ -114,8 +114,8 @@
raise Exception("Attempt to save object with deleted foreign key reference")
def has_important_changes(self):
- """ Determine whether the model has changes that should be reflected in one of the changed_by_* timestampes.
- Ignores varous feedback and bookeeeping state set by synchronizers.
+ """ Determine whether the model has changes that should be reflected in one of the changed_by_* timestamps.
+ Ignores various feedback and bookkeeping state set by synchronizers.
"""
for field_name in self.changed_fields:
if field_name in ["policed", "updated", "enacted", "changed_by_step", "changed_by_policy"]:
@@ -141,7 +141,7 @@
update_fields = None
if "update_fields" in kwargs:
- # NOTE(smbaker): modifying update_fields will cause kwargs["update_fiels"] to be modified. This is
+ # NOTE(smbaker): modifying update_fields will cause kwargs["update_fields"] to be modified. This is
# intended, as kwargs will be passed to save() below.
update_fields = kwargs["update_fields"]