Просмотр исходного кода

Merge pull request #202 from sh4nks/datetime-utc

Convert DateTime objects to timezone aware DateTime objects
Peter Justin 8 лет назад
Родитель
Сommit
8b0f8fb23d

+ 4 - 5
flaskbb/app.py

@@ -10,7 +10,6 @@
 """
 import os
 import logging
-import datetime
 import time
 from functools import partial
 
@@ -34,10 +33,10 @@ from flaskbb.extensions import (db, login_manager, mail, cache, redis_store,
                                 debugtoolbar, migrate, themes, plugin_manager,
                                 babel, csrf, allows, limiter, celery)
 # various helpers
-from flaskbb.utils.helpers import (format_date, time_since, crop_title,
-                                   is_online, render_markup, mark_online,
+from flaskbb.utils.helpers import (time_utcnow, format_date, time_since,
+                                   crop_title, is_online, mark_online,
                                    forum_is_unread, topic_is_unread,
-                                   render_template)
+                                   render_template, render_markup)
 from flaskbb.utils.translations import FlaskBBDomain
 # permission checks (here they are used for the jinja filters)
 from flaskbb.utils.requirements import (IsAdmin, IsAtleastModerator,
@@ -238,7 +237,7 @@ def configure_before_handlers(app):
         authenticated."""
 
         if current_user.is_authenticated:
-            current_user.lastseen = datetime.datetime.utcnow()
+            current_user.lastseen = time_utcnow()
             db.session.add(current_user)
             db.session.commit()
 

+ 3 - 3
flaskbb/auth/forms.py

@@ -8,15 +8,15 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
-from datetime import datetime
-
 from flask_wtf import Form
 from wtforms import (StringField, PasswordField, BooleanField, HiddenField,
                      SubmitField, SelectField)
 from wtforms.validators import (DataRequired, InputRequired, Email, EqualTo,
                                 regexp, ValidationError)
 from flask_babelplus import lazy_gettext as _
+
 from flaskbb.user.models import User
+from flaskbb.utils.helpers import time_utcnow
 from flaskbb.utils.recaptcha import RecaptchaField
 
 USERNAME_RE = r'^[\w.+-]+$'
@@ -76,7 +76,7 @@ class RegisterForm(Form):
         user = User(username=self.username.data,
                     email=self.email.data,
                     password=self.password.data,
-                    date_joined=datetime.utcnow(),
+                    date_joined=time_utcnow(),
                     primary_group_id=4,
                     language=self.language.data)
         return user.save()

+ 24 - 23
flaskbb/forum/models.py

@@ -8,15 +8,15 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
-from datetime import datetime, timedelta
+from datetime import timedelta
 
 from flask import url_for, abort
 from sqlalchemy.orm import aliased
 
 from flaskbb.extensions import db
 from flaskbb.utils.helpers import (slugify, get_categories_and_forums,
-                                   get_forums)
-from flaskbb.utils.database import CRUDMixin
+                                   get_forums, time_utcnow)
+from flaskbb.utils.database import CRUDMixin, UTCDateTime
 from flaskbb.utils.settings import flaskbb_config
 
 
@@ -70,7 +70,7 @@ class TopicsRead(db.Model, CRUDMixin):
                          db.ForeignKey("forums.id", use_alter=True,
                                        name="fk_tr_forum_id"),
                          primary_key=True)
-    last_read = db.Column(db.DateTime, default=datetime.utcnow())
+    last_read = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
 
 
 class ForumsRead(db.Model, CRUDMixin):
@@ -82,8 +82,8 @@ class ForumsRead(db.Model, CRUDMixin):
                          db.ForeignKey("forums.id", use_alter=True,
                                        name="fk_fr_forum_id"),
                          primary_key=True)
-    last_read = db.Column(db.DateTime, default=datetime.utcnow())
-    cleared = db.Column(db.DateTime)
+    last_read = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
+    cleared = db.Column(UTCDateTime(timezone=True))
 
 
 class Report(db.Model, CRUDMixin):
@@ -92,9 +92,9 @@ class Report(db.Model, CRUDMixin):
     id = db.Column(db.Integer, primary_key=True)
     reporter_id = db.Column(db.Integer, db.ForeignKey("users.id"),
                             nullable=False)
-    reported = db.Column(db.DateTime, default=datetime.utcnow())
+    reported = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
     post_id = db.Column(db.Integer, db.ForeignKey("posts.id"), nullable=False)
-    zapped = db.Column(db.DateTime)
+    zapped = db.Column(UTCDateTime(timezone=True))
     zapped_by = db.Column(db.Integer, db.ForeignKey("users.id"))
     reason = db.Column(db.Text)
 
@@ -121,7 +121,7 @@ class Report(db.Model, CRUDMixin):
 
         if post and user:
             self.reporter_id = user.id
-            self.reported = datetime.utcnow()
+            self.reported = time_utcnow()
             self.post_id = post.id
 
         db.session.add(self)
@@ -142,8 +142,8 @@ class Post(db.Model, CRUDMixin):
     user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
     username = db.Column(db.String(200), nullable=False)
     content = db.Column(db.Text, nullable=False)
-    date_created = db.Column(db.DateTime, default=datetime.utcnow())
-    date_modified = db.Column(db.DateTime)
+    date_created = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
+    date_modified = db.Column(UTCDateTime(timezone=True))
     modified_by = db.Column(db.String(200))
 
     # Properties
@@ -180,7 +180,7 @@ class Post(db.Model, CRUDMixin):
 
         # Adding a new post
         if user and topic:
-            created = datetime.utcnow()
+            created = time_utcnow()
             self.user_id = user.id
             self.username = user.username
             self.topic_id = topic.id
@@ -270,8 +270,8 @@ class Topic(db.Model, CRUDMixin):
     title = db.Column(db.String(255), nullable=False)
     user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
     username = db.Column(db.String(200), nullable=False)
-    date_created = db.Column(db.DateTime, default=datetime.utcnow())
-    last_updated = db.Column(db.DateTime, default=datetime.utcnow())
+    date_created = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
+    last_updated = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
     locked = db.Column(db.Boolean, default=False)
     important = db.Column(db.Boolean, default=False)
     views = db.Column(db.Integer, default=0)
@@ -340,7 +340,7 @@ class Topic(db.Model, CRUDMixin):
         """
         read_cutoff = None
         if flaskbb_config['TRACKER_LENGTH'] > 0:
-            read_cutoff = datetime.utcnow() - timedelta(
+            read_cutoff = time_utcnow() - timedelta(
                 days=flaskbb_config['TRACKER_LENGTH'])
 
         # The tracker is disabled - abort
@@ -391,7 +391,7 @@ class Topic(db.Model, CRUDMixin):
         # A new post has been submitted that the user hasn't read.
         # Updating...
         if topicsread:
-            topicsread.last_read = datetime.utcnow()
+            topicsread.last_read = time_utcnow()
             topicsread.save()
             updated = True
 
@@ -402,7 +402,7 @@ class Topic(db.Model, CRUDMixin):
             topicsread.user_id = user.id
             topicsread.topic_id = self.id
             topicsread.forum_id = self.forum_id
-            topicsread.last_read = datetime.utcnow()
+            topicsread.last_read = time_utcnow()
             topicsread.save()
             updated = True
 
@@ -471,9 +471,9 @@ class Topic(db.Model, CRUDMixin):
         self.username = user.username
 
         # Set the last_updated time. Needed for the readstracker
-        self.last_updated = datetime.utcnow()
+        self.last_updated = time_utcnow()
 
-        self.date_created = datetime.utcnow()
+        self.date_created = time_utcnow()
 
         # Insert and commit the topic
         db.session.add(self)
@@ -577,7 +577,8 @@ class Forum(db.Model, CRUDMixin):
     last_post_title = db.Column(db.String(255))
     last_post_user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
     last_post_username = db.Column(db.String(255))
-    last_post_created = db.Column(db.DateTime, default=datetime.utcnow())
+    last_post_created = db.Column(UTCDateTime(timezone=True),
+                                  default=time_utcnow)
 
     # One-to-many
     topics = db.relationship(
@@ -681,7 +682,7 @@ class Forum(db.Model, CRUDMixin):
 
         read_cutoff = None
         if flaskbb_config['TRACKER_LENGTH'] > 0:
-            read_cutoff = datetime.utcnow() - timedelta(
+            read_cutoff = time_utcnow() - timedelta(
                 days=flaskbb_config['TRACKER_LENGTH'])
 
         # fetch the unread posts in the forum
@@ -708,7 +709,7 @@ class Forum(db.Model, CRUDMixin):
             # has been submitted and has read everything (obviously, else the
             # unread_count would be useless).
             elif forumsread:
-                forumsread.last_read = datetime.utcnow()
+                forumsread.last_read = time_utcnow()
                 forumsread.save()
                 return True
 
@@ -716,7 +717,7 @@ class Forum(db.Model, CRUDMixin):
             forumsread = ForumsRead()
             forumsread.user_id = user.id
             forumsread.forum_id = self.id
-            forumsread.last_read = datetime.utcnow()
+            forumsread.last_read = time_utcnow()
             forumsread.save()
             return True
 

+ 9 - 12
flaskbb/forum/views.py

@@ -9,25 +9,22 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
-import datetime
-
 from sqlalchemy import asc, desc
 from flask import Blueprint, redirect, url_for, current_app, request, flash
 from flask_login import login_required, current_user
 from flask_babelplus import gettext as _
 from flask_allows import Permission, And
+
 from flaskbb.extensions import db, allows
 from flaskbb.utils.settings import flaskbb_config
-from flaskbb.utils.helpers import (get_online_users, time_diff, format_quote,
-                                   render_template, do_topic_action)
-
+from flaskbb.utils.helpers import (get_online_users, time_diff, time_utcnow,
+                                   format_quote, render_template,
+                                   do_topic_action)
 from flaskbb.utils.requirements import (CanAccessForum, CanAccessTopic,
                                         CanDeletePost, CanDeleteTopic,
                                         CanEditPost, CanPostReply,
                                         CanPostTopic,
                                         IsAtleastModeratorInForum)
-
-
 from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
                                   TopicsRead)
 from flaskbb.forum.forms import (NewTopicForm, QuickreplyForm, ReplyForm,
@@ -444,7 +441,7 @@ def edit_post(post_id):
             )
         else:
             form.populate_obj(post)
-            post.date_modified = datetime.datetime.utcnow()
+            post.date_modified = time_utcnow()
             post.modified_by = current_user.username
             post.save()
             return redirect(post.topic.url)
@@ -516,8 +513,8 @@ def markread(forum_id=None, slug=None):
             forumsread.user_id = current_user.id
             forumsread.forum_id = forum_instance.id
 
-        forumsread.last_read = datetime.datetime.utcnow()
-        forumsread.cleared = datetime.datetime.utcnow()
+        forumsread.last_read = time_utcnow()
+        forumsread.cleared = time_utcnow()
 
         db.session.add(forumsread)
         db.session.commit()
@@ -537,8 +534,8 @@ def markread(forum_id=None, slug=None):
         forumsread = ForumsRead()
         forumsread.user_id = current_user.id
         forumsread.forum_id = forum_instance.id
-        forumsread.last_read = datetime.datetime.utcnow()
-        forumsread.cleared = datetime.datetime.utcnow()
+        forumsread.last_read = time_utcnow()
+        forumsread.cleared = time_utcnow()
         forumsread_list.append(forumsread)
 
     db.session.add_all(forumsread_list)

+ 5 - 5
flaskbb/management/views.py

@@ -9,7 +9,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import sys
-from datetime import datetime
 
 from flask import (Blueprint, current_app, request, redirect, url_for, flash,
                    jsonify, __version__ as flask_version)
@@ -26,7 +25,8 @@ from flaskbb.utils.requirements import (IsAtleastModerator, IsAdmin,
                                         CanBanUser, CanEditUser,
                                         IsAtleastSuperModerator)
 from flaskbb.extensions import db, allows
-from flaskbb.utils.helpers import render_template, time_diff, get_online_users
+from flaskbb.utils.helpers import (render_template, time_diff, time_utcnow,
+                                   get_online_users)
 from flaskbb.user.models import Guest, User, Group
 from flaskbb.forum.models import Post, Topic, Forum, Category, Report
 from flaskbb.management.models import Setting, SettingsGroup
@@ -398,7 +398,7 @@ def report_markread(report_id=None):
 
         for report in Report.query.filter(Report.id.in_(ids)).all():
             report.zapped_by = current_user.id
-            report.zapped = datetime.utcnow()
+            report.zapped = time_utcnow()
             report.save()
             data.append({
                 "id": report.id,
@@ -424,7 +424,7 @@ def report_markread(report_id=None):
             return redirect(url_for("management.reports"))
 
         report.zapped_by = current_user.id
-        report.zapped = datetime.utcnow()
+        report.zapped = time_utcnow()
         report.save()
         flash(_("Report %(id)s marked as read.", id=report.id), "success")
         return redirect(url_for("management.reports"))
@@ -434,7 +434,7 @@ def report_markread(report_id=None):
     report_list = []
     for report in reports:
         report.zapped_by = current_user.id
-        report.zapped = datetime.utcnow()
+        report.zapped = time_utcnow()
         report_list.append(report)
 
     db.session.add_all(report_list)

+ 6 - 7
flaskbb/message/models.py

@@ -8,12 +8,11 @@
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
-from datetime import datetime
-
 from sqlalchemy_utils import UUIDType
 
 from flaskbb.extensions import db
-from flaskbb.utils.database import CRUDMixin
+from flaskbb.utils.helpers import time_utcnow
+from flaskbb.utils.database import CRUDMixin, UTCDateTime
 
 
 class Conversation(db.Model, CRUDMixin):
@@ -25,7 +24,7 @@ class Conversation(db.Model, CRUDMixin):
     to_user_id = db.Column(db.Integer, db.ForeignKey("users.id"))
     shared_id = db.Column(UUIDType, nullable=False)
     subject = db.Column(db.String(255))
-    date_created = db.Column(db.DateTime, default=datetime.utcnow())
+    date_created = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
     trash = db.Column(db.Boolean, nullable=False, default=False)
     draft = db.Column(db.Boolean, nullable=False, default=False)
     unread = db.Column(db.Boolean, nullable=False, default=True)
@@ -63,7 +62,7 @@ class Conversation(db.Model, CRUDMixin):
         """
         if message is not None:
             # create the conversation
-            self.date_created = datetime.utcnow()
+            self.date_created = time_utcnow()
             db.session.add(self)
             db.session.commit()
 
@@ -86,7 +85,7 @@ class Message(db.Model, CRUDMixin):
     # the user who wrote the message
     user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
     message = db.Column(db.Text, nullable=False)
-    date_created = db.Column(db.DateTime, default=datetime.utcnow())
+    date_created = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
 
     user = db.relationship("User", lazy="joined")
 
@@ -98,7 +97,7 @@ class Message(db.Model, CRUDMixin):
         """
         if conversation is not None:
             self.conversation_id = conversation.id
-            self.date_created = datetime.utcnow()
+            self.date_created = time_utcnow()
 
         db.session.add(self)
         db.session.commit()

+ 2 - 3
flaskbb/message/views.py

@@ -9,7 +9,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import uuid
-from datetime import datetime
 
 from flask import Blueprint, redirect, request, url_for, flash, abort
 from flask_login import login_required, current_user
@@ -17,7 +16,7 @@ from flask_babelplus import gettext as _
 
 from flaskbb.extensions import db
 from flaskbb.utils.settings import flaskbb_config
-from flaskbb.utils.helpers import render_template, format_quote
+from flaskbb.utils.helpers import render_template, format_quote, time_utcnow
 from flaskbb.message.forms import ConversationForm, MessageForm
 from flaskbb.message.models import Conversation, Message
 from flaskbb.user.models import User
@@ -228,7 +227,7 @@ def edit_conversation(conversation_id):
             # Move the message from ``Drafts`` to ``Sent``.
             conversation.draft = False
             conversation.to_user = to_user
-            conversation.date_created = datetime.utcnow()
+            conversation.date_created = time_utcnow()
             conversation.save()
 
             flash(_("Message sent."), "success")

+ 8 - 8
flaskbb/user/models.py

@@ -9,7 +9,6 @@
     :license: BSD, see LICENSE for more details.
 """
 import os
-from datetime import datetime
 
 from werkzeug.security import generate_password_hash, check_password_hash
 from flask import url_for
@@ -18,8 +17,9 @@ from flask_login import UserMixin, AnonymousUserMixin
 from flaskbb._compat import max_integer
 from flaskbb.extensions import db, cache
 from flaskbb.exceptions import AuthenticationError
+from flaskbb.utils.helpers import time_utcnow
 from flaskbb.utils.settings import flaskbb_config
-from flaskbb.utils.database import CRUDMixin
+from flaskbb.utils.database import CRUDMixin, UTCDateTime
 from flaskbb.forum.models import (Post, Topic, topictracker, TopicsRead,
                                   ForumsRead)
 from flaskbb.message.models import Conversation
@@ -82,9 +82,9 @@ class User(db.Model, UserMixin, CRUDMixin):
     username = db.Column(db.String(200), unique=True, nullable=False)
     email = db.Column(db.String(200), unique=True, nullable=False)
     _password = db.Column('password', db.String(120), nullable=False)
-    date_joined = db.Column(db.DateTime, default=datetime.utcnow())
-    lastseen = db.Column(db.DateTime, default=datetime.utcnow())
-    birthday = db.Column(db.DateTime)
+    date_joined = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
+    lastseen = db.Column(UTCDateTime(timezone=True), default=time_utcnow)
+    birthday = db.Column(UTCDateTime(timezone=True))
     gender = db.Column(db.String(10))
     website = db.Column(db.String(200))
     location = db.Column(db.String(100))
@@ -92,7 +92,7 @@ class User(db.Model, UserMixin, CRUDMixin):
     avatar = db.Column(db.String(200))
     notes = db.Column(db.Text)
 
-    last_failed_login = db.Column(db.DateTime)
+    last_failed_login = db.Column(UTCDateTime(timezone=True))
     login_attempts = db.Column(db.Integer, default=0)
     activated = db.Column(db.Boolean, default=False)
 
@@ -174,7 +174,7 @@ class User(db.Model, UserMixin, CRUDMixin):
     @property
     def days_registered(self):
         """Returns the amount of days the user is registered."""
-        days_registered = (datetime.utcnow() - self.date_joined).days
+        days_registered = (time_utcnow() - self.date_joined).days
         if not days_registered:
             return 1
         return days_registered
@@ -247,7 +247,7 @@ class User(db.Model, UserMixin, CRUDMixin):
 
             # user exists, wrong password
             user.login_attempts += 1
-            user.last_failed_login = datetime.utcnow()
+            user.last_failed_login = time_utcnow()
             user.save()
 
         # protection against account enumeration timing attacks

+ 24 - 0
flaskbb/utils/database.py

@@ -8,6 +8,8 @@
     :copyright: (c) 2015 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
 """
+import pytz
+
 from flaskbb.extensions import db
 
 
@@ -26,3 +28,25 @@ class CRUDMixin(object):
         db.session.delete(self)
         db.session.commit()
         return self
+
+
+class UTCDateTime(db.TypeDecorator):
+    impl = db.DateTime
+
+    def process_bind_param(self, value, dialect):
+        """Way into the database."""
+        if value is not None:
+            # store naive datetime for sqlite and mysql
+            if dialect.name in ("sqlite", "mysql"):
+                return value.replace(tzinfo=None)
+
+            return value.astimezone(pytz.UTC)
+
+    def process_result_value(self, value, dialect):
+        """Way out of the database."""
+        # convert naive datetime to non naive datetime
+        if dialect.name in ("sqlite", "mysql") and value is not None:
+            return value.replace(tzinfo=pytz.UTC)
+
+        # other dialects are already non-naive
+        return value

+ 10 - 4
flaskbb/utils/helpers.py

@@ -15,6 +15,7 @@ import operator
 import struct
 from io import BytesIO
 from datetime import datetime, timedelta
+from pytz import UTC
 
 import requests
 import unidecode
@@ -203,7 +204,7 @@ def forum_is_unread(forum, forumsread, user):
     if not user.is_authenticated:
         return False
 
-    read_cutoff = datetime.utcnow() - timedelta(
+    read_cutoff = time_utcnow() - timedelta(
         days=flaskbb_config["TRACKER_LENGTH"])
 
     # disable tracker if TRACKER_LENGTH is set to 0
@@ -250,7 +251,7 @@ def topic_is_unread(topic, topicsread, user, forumsread=None):
     if not user.is_authenticated:
         return False
 
-    read_cutoff = datetime.utcnow() - timedelta(
+    read_cutoff = time_utcnow() - timedelta(
         days=flaskbb_config["TRACKER_LENGTH"])
 
     # disable tracker if read_cutoff is set to 0
@@ -349,11 +350,16 @@ def is_online(user):
     return user.lastseen >= time_diff()
 
 
+def time_utcnow():
+    """Returns a timezone aware utc timestamp."""
+    return datetime.now(UTC)
+
+
 def time_diff():
     """Calculates the time difference between now and the ONLINE_LAST_MINUTES
     variable from the configuration.
     """
-    now = datetime.utcnow()
+    now = time_utcnow()
     diff = now - timedelta(minutes=flaskbb_config['ONLINE_LAST_MINUTES'])
     return diff
 
@@ -386,7 +392,7 @@ def time_since(time):  # pragma: no cover
 
     :param time: A datetime object
     """
-    delta = time - datetime.utcnow()
+    delta = time - time_utcnow()
     return format_timedelta(delta, add_direction=True)
 
 

+ 1 - 1
migrations/versions/514ca0a3282c_private_messages.py

@@ -56,7 +56,7 @@ def downgrade():
     sa.Column('to_user_id', sa.INTEGER(), nullable=True),
     sa.Column('subject', sa.VARCHAR(length=255), nullable=True),
     sa.Column('message', sa.TEXT(), nullable=True),
-    sa.Column('date_created', sa.DATETIME(), nullable=True),
+    sa.Column('date_created', sa.DateTime(), nullable=True),
     sa.Column('trash', sa.BOOLEAN(), nullable=False),
     sa.Column('draft', sa.BOOLEAN(), nullable=False),
     sa.Column('unread', sa.BOOLEAN(), nullable=False),

+ 71 - 0
migrations/versions/d9530a529b3f_add_timezone_awareness_for_datetime.py

@@ -0,0 +1,71 @@
+"""add timezone awareness for datetime objects
+
+Revision ID: d9530a529b3f
+Revises: 221d918aa9f0
+Create Date: 2016-06-21 09:39:38.348519
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'd9530a529b3f'
+down_revision = '221d918aa9f0'
+
+from alembic import op
+import sqlalchemy as sa
+import flaskbb
+
+
+def upgrade():
+    connection = op.get_bind()
+
+    # Having a hard time with ALTER TABLE/COLUMN stuff in SQLite.. and because DateTime objects are stored as strings anyway,
+    # we can simply skip those migrations for SQLite
+    if connection.engine.dialect.name != "sqlite":
+        # user/models.py
+        op.alter_column('users', 'date_joined', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('users', 'lastseen', existing_type=sa.DateTime(), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('users', 'birthday', existing_type=sa.DateTime(), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('users', 'last_failed_login', existing_type=sa.DateTime(), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+
+        # message/models.py
+        op.alter_column('conversations', 'date_created', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('messages', 'date_created', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+
+        # forum/models.py
+        op.alter_column('topicsread', 'last_read', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('forumsread', 'last_read', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('forumsread', 'cleared', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('reports', 'reported', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('reports', 'zapped', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('posts', 'date_created', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('posts', 'date_modified', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('topics', 'date_created', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('topics', 'last_updated', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('forums', 'last_post_created', existing_type=sa.DateTime(timezone=False), type_=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+
+
+def downgrade():
+    connection = op.get_bind()
+
+    if connection.engine.dialect.name != "sqlite":
+        # user/models.py
+        op.alter_column('users', 'date_joined', type_=sa.DateTime(), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('users', 'lastseen', type_=sa.DateTime(), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('users', 'birthday', type_=sa.DateTime(), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('users', 'last_failed_login', type_=sa.DateTime(), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+
+        # message/models.py
+        op.alter_column('conversations', 'date_created', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('messages', 'date_created', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+
+        # forum/models.py
+        op.alter_column('topicsread', 'last_read', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('forumsread', 'last_read', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('forumsread', 'cleared', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('reports', 'reported', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('reports', 'zapped', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('posts', 'date_created', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('posts', 'date_modified', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('topics', 'date_created', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('topics', 'last_updated', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)
+        op.alter_column('forums', 'last_post_created', type_=sa.DateTime(timezone=False), existing_type=flaskbb.utils.database.UTCDateTime(timezone=True), existing_nullable=True)

+ 1 - 1
requirements.txt

@@ -22,6 +22,7 @@ Flask-Redis==0.1.0
 Flask-Script==2.0.5
 Flask-SQLAlchemy==2.1
 Flask-Themes2==0.1.4
+Flask-WhooshAlchemy==0.56
 Flask-WTF==0.12
 itsdangerous==0.24
 Jinja2==2.8
@@ -44,4 +45,3 @@ Unidecode==0.4.19
 Werkzeug==0.11.10
 Whoosh==2.7.4
 WTForms==2.1
-https://github.com/jshipley/Flask-WhooshAlchemy/archive/master.zip#egg=Flask-Whooshalchemy

+ 1 - 3
setup.py

@@ -84,6 +84,7 @@ setup(
         'Flask-Script',
         'Flask-SQLAlchemy',
         'Flask-Themes2',
+        'Flask-WhooshAlchemy',
         'Flask-WTF',
         'itsdangerous',
         'Jinja2',
@@ -114,9 +115,6 @@ setup(
         'pytest-cov',
         'pytest-random'
     ],
-    dependency_links=[
-        'https://github.com/jshipley/Flask-WhooshAlchemy/archive/master.zip#egg=Flask-WhooshAlchemy',
-    ],
     classifiers=[
         'Development Status :: 4 - Beta',
         'Environment :: Web Environment',

+ 0 - 1
tests/fixtures/user.py

@@ -1,4 +1,3 @@
-import datetime
 import pytest
 from flaskbb.user.models import User, Guest
 

+ 4 - 4
tests/unit/utils/test_helpers.py

@@ -2,7 +2,7 @@
 import datetime
 from flaskbb.utils.helpers import slugify, forum_is_unread, topic_is_unread, \
     crop_title, render_markup, is_online, format_date, format_quote, \
-    get_image_info, check_image
+    get_image_info, check_image, time_utcnow
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.forum.models import Forum
 
@@ -59,8 +59,8 @@ def test_topic_is_unread(guest, user, forum, topic, topicsread, forumsread):
     assert topic_is_unread(topic, topicsread=None, user=user, forumsread=forumsread)
 
     # lets mark the forum as read
-    forumsread.cleared = datetime.datetime.utcnow()
-    forumsread.last_read = datetime.datetime.utcnow()
+    forumsread.cleared = time_utcnow()
+    forumsread.last_read = time_utcnow()
     forumsread.save()
     assert not topic_is_unread(topic, topicsread=None, user=user, forumsread=forumsread)
 
@@ -69,7 +69,7 @@ def test_topic_is_unread(guest, user, forum, topic, topicsread, forumsread):
     assert not topic_is_unread(topic, None, user, None)
 
     # post is older than tracker length
-    time_posted = datetime.datetime.utcnow() - datetime.timedelta(days=2)
+    time_posted = time_utcnow() - datetime.timedelta(days=2)
     flaskbb_config["TRACKER_LENGTH"] = 1
     topic.last_post.date_created = time_posted
     topic.save()