Repository: getredash/redash

Issue:
Timestamp fields created as "TIMESTAMP without time zone" resulting in wrong timestamp in UI
We need to either convert it to "TIMESTAMP with time zone" or make sure we send UTC times to the database (probably better the later?).



Discussion:

Resolving patch:
diff --git a/migrations/0002_fix_timestamp_fields.py b/migrations/0002_fix_timestamp_fields.py
new file mode 100644
--- /dev/null
+++ b/migrations/0002_fix_timestamp_fields.py
@@ -0,0 +1,21 @@
+from redash.models import db
+
+if __name__ == '__main__':
+    db.connect_db()
+    columns = (
+        ('activity_log', 'created_at'),
+        ('dashboards', 'created_at'),
+        ('data_sources', 'created_at'),
+        ('events', 'created_at'),
+        ('groups', 'created_at'),
+        ('queries', 'created_at'),
+        ('widgets', 'created_at'),
+        ('query_results', 'retrieved_at')
+    )
+
+    with db.database.transaction():
+        for column in columns:
+            db.database.execute_sql("ALTER TABLE {} ALTER COLUMN {} TYPE timestamp with time zone;".format(*column))
+
+    db.close_db(None)
+
diff --git a/redash/models.py b/redash/models.py
--- a/redash/models.py
+++ b/redash/models.py
@@ -9,7 +9,7 @@
 
 import peewee
 from passlib.apps import custom_app_context as pwd_context
-from playhouse.postgres_ext import ArrayField
+from playhouse.postgres_ext import ArrayField, DateTimeTZField, PostgresqlExtDatabase
 from flask.ext.login import UserMixin, AnonymousUserMixin
 
 from redash import utils, settings
@@ -18,8 +18,9 @@
 class Database(object):
     def __init__(self):
         self.database_config = dict(settings.DATABASE_CONFIG)
+        self.database_config['register_hstore'] = False
         self.database_name = self.database_config.pop('name')
-        self.database = peewee.PostgresqlDatabase(self.database_name, **self.database_config)
+        self.database = PostgresqlExtDatabase(self.database_name, **self.database_config)
         self.app = None
         self.pid = os.getpid()
 
@@ -96,7 +97,7 @@ class Group(BaseModel):
     name = peewee.CharField(max_length=100)
     permissions = ArrayField(peewee.CharField, default=DEFAULT_PERMISSIONS)
     tables = ArrayField(peewee.CharField)
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     class Meta:
         db_table = 'groups'
@@ -173,7 +174,7 @@ class ActivityLog(BaseModel):
     user = peewee.ForeignKeyField(User)
     type = peewee.IntegerField()
     activity = peewee.TextField()
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     class Meta:
         db_table = 'activity_log'
@@ -198,7 +199,7 @@ class DataSource(BaseModel):
     options = peewee.TextField()
     queue_name = peewee.CharField(default="queries")
     scheduled_queue_name = peewee.CharField(default="queries")
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     class Meta:
         db_table = 'data_sources'
@@ -222,7 +223,7 @@ class QueryResult(BaseModel):
     query = peewee.TextField()
     data = peewee.TextField()
     runtime = peewee.FloatField()
-    retrieved_at = peewee.DateTimeField()
+    retrieved_at = DateTimeTZField()
 
     class Meta:
         db_table = 'query_results'
@@ -297,7 +298,7 @@ class Query(BaseModel):
     user_email = peewee.CharField(max_length=360, null=True)
     user = peewee.ForeignKeyField(User)
     is_archived = peewee.BooleanField(default=False, index=True)
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     class Meta:
         db_table = 'queries'
@@ -441,7 +442,7 @@ class Dashboard(BaseModel):
     layout = peewee.TextField()
     dashboard_filters_enabled = peewee.BooleanField(default=False)
     is_archived = peewee.BooleanField(default=False, index=True)
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     class Meta:
         db_table = 'dashboards'
@@ -552,7 +553,7 @@ class Widget(BaseModel):
     width = peewee.IntegerField()
     options = peewee.TextField()
     dashboard = peewee.ForeignKeyField(Dashboard, related_name='widgets', index=True)
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     # unused; kept for backward compatability:
     type = peewee.CharField(max_length=100, null=True)
@@ -586,13 +587,14 @@ def delete_instance(self, *args, **kwargs):
         self.dashboard.save()
         super(Widget, self).delete_instance(*args, **kwargs)
 
+
 class Event(BaseModel):
     user = peewee.ForeignKeyField(User, related_name="events")
     action = peewee.CharField()
     object_type = peewee.CharField()
     object_id = peewee.CharField(null=True)
     additional_properties = peewee.TextField(null=True)
-    created_at = peewee.DateTimeField(default=datetime.datetime.now)
+    created_at = DateTimeTZField(default=datetime.datetime.now)
 
     class Meta:
         db_table = 'events'


Test patch: