-
diff --git a/trac/attachment.py b/trac/attachment.py
|
a
|
b
|
|
| 21 | 21 | import re |
| 22 | 22 | import shutil |
| 23 | 23 | import sys |
| 24 | | import time |
| 25 | 24 | import unicodedata |
| 26 | 25 | |
| 27 | 26 | from genshi.builder import tag |
| … |
… |
|
| 36 | 35 | from trac.resource import * |
| 37 | 36 | from trac.search import search_to_sql, shorten_result |
| 38 | 37 | from trac.util import get_reporter_id, create_unique_file |
| 39 | | from trac.util.datefmt import format_datetime, to_timestamp, utc |
| | 38 | from trac.util.datefmt import format_datetime, from_utimestamp, \ |
| | 39 | to_datetime, to_utimestamp, utc |
| 40 | 40 | from trac.util.text import exception_to_unicode, pretty_size, print_table, \ |
| 41 | 41 | unicode_quote, unicode_unquote |
| 42 | 42 | from trac.util.translation import _ |
| … |
… |
|
| 150 | 150 | self.filename = row[0] |
| 151 | 151 | self.description = row[1] |
| 152 | 152 | self.size = row[2] and int(row[2]) or 0 |
| 153 | | time = row[3] and int(row[3]) or 0 |
| 154 | | self.date = datetime.fromtimestamp(time, utc) |
| | 153 | self.date = from_utimestamp(row[3]) |
| 155 | 154 | self.author = row[4] |
| 156 | 155 | self.ipnr = row[5] |
| 157 | 156 | |
| … |
… |
|
| 199 | 198 | |
| 200 | 199 | |
| 201 | 200 | def insert(self, filename, fileobj, size, t=None, db=None): |
| 202 | | # FIXME: `t` should probably be switched to `datetime` too |
| 203 | 201 | if not db: |
| 204 | 202 | db = self.env.get_db_cnx() |
| 205 | 203 | handle_ta = True |
| … |
… |
|
| 207 | 205 | handle_ta = False |
| 208 | 206 | |
| 209 | 207 | self.size = size and int(size) or 0 |
| 210 | | timestamp = int(t or time.time()) |
| 211 | | self.date = datetime.fromtimestamp(timestamp, utc) |
| | 208 | if t is None: |
| | 209 | t = datetime.now(utc) |
| | 210 | elif not isinstance(t, datetime): # Compatibility with 0.11 |
| | 211 | t = to_datetime(t, utc) |
| | 212 | self.date = t |
| 212 | 213 | |
| 213 | 214 | # Make sure the path to the attachment is inside the environment |
| 214 | 215 | # attachments directory |
| … |
… |
|
| 232 | 233 | cursor.execute("INSERT INTO attachment " |
| 233 | 234 | "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", |
| 234 | 235 | (self.parent_realm, self.parent_id, filename, |
| 235 | | self.size, timestamp, self.description, |
| | 236 | self.size, to_utimestamp(t), self.description, |
| 236 | 237 | self.author, self.ipnr)) |
| 237 | 238 | shutil.copyfileobj(fileobj, targetfile) |
| 238 | 239 | self.resource.id = self.filename = filename |
| … |
… |
|
| 265 | 266 | attachment.filename = filename |
| 266 | 267 | attachment.description = description |
| 267 | 268 | attachment.size = size and int(size) or 0 |
| 268 | | time = time and int(time) or 0 |
| 269 | | attachment.date = datetime.fromtimestamp(time, utc) |
| | 269 | attachment.date = from_utimestamp(time or 0) |
| 270 | 270 | attachment.author = author |
| 271 | 271 | attachment.ipnr = ipnr |
| 272 | 272 | yield attachment |
| … |
… |
|
| 452 | 452 | " FROM attachment " |
| 453 | 453 | " WHERE time > %s AND time < %s " |
| 454 | 454 | " AND type = %s", |
| 455 | | (to_timestamp(start), to_timestamp(stop), realm)) |
| | 455 | (to_utimestamp(start), to_utimestamp(stop), realm)) |
| 456 | 456 | for realm, id, filename, ts, description, author in cursor: |
| 457 | | time = datetime.fromtimestamp(ts, utc) |
| | 457 | time = from_utimestamp(ts) |
| 458 | 458 | yield ('created', realm, id, filename, time, description, author) |
| 459 | 459 | |
| 460 | 460 | def get_timeline_events(self, req, resource_realm, start, stop): |
| … |
… |
|
| 502 | 502 | if 'ATTACHMENT_VIEW' in req.perm(attachment): |
| 503 | 503 | yield (get_resource_url(self.env, attachment, req.href), |
| 504 | 504 | get_resource_shortname(self.env, attachment), |
| 505 | | datetime.fromtimestamp(time, utc), author, |
| | 505 | from_utimestamp(time), author, |
| 506 | 506 | shorten_result(desc, terms)) |
| 507 | 507 | |
| 508 | 508 | # IResourceManager methods |
-
diff --git a/trac/db/api.py b/trac/db/api.py
|
a
|
b
|
|
| 83 | 83 | self._cnx_pool = None |
| 84 | 84 | |
| 85 | 85 | def init_db(self): |
| 86 | | connector, args = self._get_connector() |
| | 86 | connector, args = self.get_connector() |
| 87 | 87 | connector.init_db(**args) |
| 88 | 88 | |
| 89 | 89 | def get_connection(self): |
| 90 | 90 | if not self._cnx_pool: |
| 91 | | connector, args = self._get_connector() |
| | 91 | connector, args = self.get_connector() |
| 92 | 92 | self._cnx_pool = ConnectionPool(5, connector, **args) |
| 93 | 93 | return self._cnx_pool.get_cnx(self.timeout or None) |
| 94 | 94 | |
| … |
… |
|
| 104 | 104 | @param dest: base filename to write to. |
| 105 | 105 | Returns the file actually written. |
| 106 | 106 | """ |
| 107 | | connector, args = self._get_connector() |
| | 107 | connector, args = self.get_connector() |
| 108 | 108 | if not dest: |
| 109 | 109 | backup_dir = self.backup_dir |
| 110 | 110 | if backup_dir[0] != "/": |
| … |
… |
|
| 120 | 120 | os.makedirs(backup_dir) |
| 121 | 121 | return connector.backup(dest) |
| 122 | 122 | |
| 123 | | def _get_connector(self): ### FIXME: Make it public? |
| | 123 | def get_connector(self): |
| 124 | 124 | scheme, args = _parse_db_str(self.connection_uri) |
| 125 | 125 | candidates = [ |
| 126 | 126 | (priority, connector) |
| … |
… |
|
| 147 | 147 | args['log'] = self.log |
| 148 | 148 | return connector, args |
| 149 | 149 | |
| | 150 | _get_connector = get_connector # For 0.11 compatibility |
| | 151 | |
| 150 | 152 | |
| 151 | 153 | def _parse_db_str(db_str): |
| 152 | 154 | scheme, rest = db_str.split(':', 1) |
-
diff --git a/trac/db/mysql_backend.py b/trac/db/mysql_backend.py
|
a
|
b
|
|
| 52 | 52 | except ImportError: |
| 53 | 53 | has_mysqldb = False |
| 54 | 54 | |
| | 55 | # Mapping from "abstract" SQL types to DB-specific types |
| | 56 | _type_map = { |
| | 57 | 'int64': 'bigint', |
| | 58 | } |
| | 59 | |
| 55 | 60 | |
| 56 | 61 | class MySQLConnector(Component): |
| 57 | 62 | """MySQL database support for version 4.1 and greater. |
| … |
… |
|
| 133 | 138 | coldefs = [] |
| 134 | 139 | for column in table.columns: |
| 135 | 140 | ctype = column.type |
| | 141 | ctype = _type_map.get(ctype, ctype) |
| 136 | 142 | if column.auto_increment: |
| 137 | 143 | ctype = 'INT UNSIGNED NOT NULL AUTO_INCREMENT' |
| 138 | 144 | # Override the column type, as a text field cannot |
| … |
… |
|
| 151 | 157 | '_'.join(index.columns), table.name, |
| 152 | 158 | self._collist(table, index.columns)) |
| 153 | 159 | |
| | 160 | def alter_column_types(self, table, columns): |
| | 161 | """Yield SQL statements altering the type of one or more columns of |
| | 162 | a table. |
| | 163 | |
| | 164 | Type changes are specified as a `columns` dict mapping column names |
| | 165 | to `(from, to)` SQL type tuples. |
| | 166 | """ |
| | 167 | alterations = [] |
| | 168 | for name, (from_, to) in sorted(columns.iteritems()): |
| | 169 | to = _type_map.get(to, to) |
| | 170 | if to != _type_map.get(from_, from_): |
| | 171 | alterations.append((name, to)) |
| | 172 | if alterations: |
| | 173 | yield "ALTER TABLE %s %s" % (table, |
| | 174 | ', '.join("MODIFY %s %s" % each |
| | 175 | for each in alterations)) |
| | 176 | |
| 154 | 177 | def backup(self, dest_file): |
| 155 | 178 | try: |
| 156 | 179 | from subprocess import Popen, PIPE |
| … |
… |
|
| 238 | 261 | |
| 239 | 262 | def cursor(self): |
| 240 | 263 | return MySQLUnicodeCursor(self.cnx) |
| | 264 | |
| | 265 | def alter_column_types(cursor, table, columns): |
| | 266 | """Alter the type of one or more columns of a table. |
| | 267 | |
| | 268 | Type changes are specified as a `columns` dict mapping column names |
| | 269 | to `(from_type, to_type)` tuples. |
| | 270 | """ |
| | 271 | for name, (from_type, to_type) in columns.iteritems(): |
| | 272 | if (from_type, to_type) == ('int', 'int64'): |
| | 273 | pass |
| | 274 | else: |
| | 275 | raise NotImplementedError('Conversion from %s to %s is not ' |
| | 276 | 'implemented') |
-
diff --git a/trac/db/postgres_backend.py b/trac/db/postgres_backend.py
|
a
|
b
|
|
| 45 | 45 | |
| 46 | 46 | _like_escape_re = re.compile(r'([/_%])') |
| 47 | 47 | |
| | 48 | # Mapping from "abstract" SQL types to DB-specific types |
| | 49 | _type_map = { |
| | 50 | 'int64': 'bigint', |
| | 51 | } |
| | 52 | |
| 48 | 53 | |
| 49 | 54 | class PostgreSQLConnector(Component): |
| 50 | 55 | """PostgreSQL database support.""" |
| … |
… |
|
| 92 | 97 | coldefs = [] |
| 93 | 98 | for column in table.columns: |
| 94 | 99 | ctype = column.type |
| | 100 | ctype = _type_map.get(ctype, ctype) |
| 95 | 101 | if column.auto_increment: |
| 96 | 102 | ctype = 'SERIAL' |
| 97 | | if len(table.key) == 1 and column.name in table.key: |
| | 103 | elif len(table.key) == 1 and column.name in table.key: |
| 98 | 104 | ctype += ' PRIMARY KEY' |
| 99 | 105 | coldefs.append(' "%s" %s' % (column.name, ctype)) |
| 100 | 106 | if len(table.key) > 1: |
| … |
… |
|
| 109 | 115 | '_'.join(index.columns), table.name, |
| 110 | 116 | '","'.join(index.columns)) |
| 111 | 117 | |
| | 118 | def alter_column_types(self, table, columns): |
| | 119 | """Yield SQL statements altering the type of one or more columns of |
| | 120 | a table. |
| | 121 | |
| | 122 | Type changes are specified as a `columns` dict mapping column names |
| | 123 | to `(from, to)` SQL type tuples. |
| | 124 | """ |
| | 125 | alterations = [] |
| | 126 | for name, (from_, to) in sorted(columns.iteritems()): |
| | 127 | to = _type_map.get(to, to) |
| | 128 | if to != _type_map.get(from_, from_): |
| | 129 | alterations.append((name, to)) |
| | 130 | if alterations: |
| | 131 | yield "ALTER TABLE %s %s" % (table, |
| | 132 | ', '.join("ALTER COLUMN %s TYPE %s" % each |
| | 133 | for each in alterations)) |
| | 134 | |
| 112 | 135 | def backup(self, dest_file): |
| 113 | 136 | try: |
| 114 | 137 | from subprocess import Popen, PIPE |
-
diff --git a/trac/db/sqlite_backend.py b/trac/db/sqlite_backend.py
|
a
|
b
|
|
| 107 | 107 | return |
| 108 | 108 | yield row |
| 109 | 109 | |
| | 110 | # Mapping from "abstract" SQL types to DB-specific types |
| | 111 | _type_map = { |
| | 112 | 'int': 'integer', |
| | 113 | 'int64': 'integer', |
| | 114 | } |
| | 115 | |
| 110 | 116 | |
| 111 | 117 | def _to_sql(table): |
| 112 | 118 | sql = ["CREATE TABLE %s (" % table.name] |
| 113 | 119 | coldefs = [] |
| 114 | 120 | for column in table.columns: |
| 115 | 121 | ctype = column.type.lower() |
| | 122 | ctype = _type_map.get(ctype, ctype) |
| 116 | 123 | if column.auto_increment: |
| 117 | 124 | ctype = "integer PRIMARY KEY" |
| 118 | 125 | elif len(table.key) == 1 and column.name in table.key: |
| 119 | 126 | ctype += " PRIMARY KEY" |
| 120 | | elif ctype == "int": |
| 121 | | ctype = "integer" |
| 122 | 127 | coldefs.append(" %s %s" % (column.name, ctype)) |
| 123 | 128 | if len(table.key) > 1: |
| 124 | 129 | coldefs.append(" UNIQUE (%s)" % ','.join(table.key)) |
| … |
… |
|
| 175 | 180 | def to_sql(self, table): |
| 176 | 181 | return _to_sql(table) |
| 177 | 182 | |
| | 183 | def alter_column_types(self, table, columns): |
| | 184 | """Yield SQL statements altering the type of one or more columns of |
| | 185 | a table. |
| | 186 | |
| | 187 | Type changes are specified as a `columns` dict mapping column names |
| | 188 | to `(from, to)` SQL type tuples. |
| | 189 | """ |
| | 190 | for name, (from_, to) in sorted(columns.iteritems()): |
| | 191 | if _type_map.get(to, to) != _type_map.get(from_, from_): |
| | 192 | raise NotImplementedError('Conversion from %s to %s is not ' |
| | 193 | 'implemented' % (from_, to)) |
| | 194 | return () |
| | 195 | |
| 178 | 196 | def backup(self, dest_file): |
| 179 | 197 | """Simple SQLite-specific backup of the database. |
| 180 | 198 | |
| … |
… |
|
| 188 | 206 | raise TracError("Backup attempt failed") |
| 189 | 207 | return dest_file |
| 190 | 208 | |
| | 209 | |
| 191 | 210 | class SQLiteConnection(ConnectionWrapper): |
| 192 | 211 | """Connection wrapper for SQLite.""" |
| 193 | 212 | |
-
diff --git a/trac/db/tests/__init__.py b/trac/db/tests/__init__.py
|
a
|
b
|
|
| 1 | 1 | import unittest |
| 2 | 2 | |
| 3 | | from trac.db.tests import api |
| 4 | | from trac.db.tests import postgres_test |
| | 3 | from trac.db.tests import api, mysql_test, postgres_test |
| 5 | 4 | |
| 6 | 5 | from trac.db.tests.functional import functionalSuite |
| 7 | 6 | |
| … |
… |
|
| 9 | 8 | |
| 10 | 9 | suite = unittest.TestSuite() |
| 11 | 10 | suite.addTest(api.suite()) |
| | 11 | suite.addTest(mysql_test.suite()) |
| 12 | 12 | suite.addTest(postgres_test.suite()) |
| 13 | 13 | return suite |
| 14 | 14 | |
-
diff --git a/trac/db/tests/mysql_test.py b/trac/db/tests/mysql_test.py
new file mode 100644
|
-
|
+
|
|
| | 1 | # -*- coding: utf-8 -*- |
| | 2 | # |
| | 3 | # Copyright (C) 2009 Edgewall Software |
| | 4 | # All rights reserved. |
| | 5 | # |
| | 6 | # This software is licensed as described in the file COPYING, which |
| | 7 | # you should have received as part of this distribution. The terms |
| | 8 | # are also available at http://trac.edgewall.org/wiki/TracLicense. |
| | 9 | # |
| | 10 | # This software consists of voluntary contributions made by many |
| | 11 | # individuals. For the exact contribution history, see the revision |
| | 12 | # history and logs, available at http://trac.edgewall.org/log/. |
| | 13 | |
| | 14 | import unittest |
| | 15 | |
| | 16 | from trac.db.mysql_backend import MySQLConnector |
| | 17 | from trac.test import EnvironmentStub |
| | 18 | |
| | 19 | |
| | 20 | class MySQLTableAlterationSQLTest(unittest.TestCase): |
| | 21 | def setUp(self): |
| | 22 | self.env = EnvironmentStub() |
| | 23 | |
| | 24 | def test_alter_column_types(self): |
| | 25 | connector = MySQLConnector(self.env) |
| | 26 | sql = connector.alter_column_types('milestone', |
| | 27 | {'due': ('int', 'int64'), |
| | 28 | 'completed': ('int', 'int64')}) |
| | 29 | sql = list(sql) |
| | 30 | self.assertEqual([ |
| | 31 | "ALTER TABLE milestone " |
| | 32 | "MODIFY completed bigint, " |
| | 33 | "MODIFY due bigint", |
| | 34 | ], sql) |
| | 35 | |
| | 36 | def test_alter_column_types_same(self): |
| | 37 | connector = MySQLConnector(self.env) |
| | 38 | sql = connector.alter_column_types('milestone', |
| | 39 | {'due': ('int', 'int'), |
| | 40 | 'completed': ('int', 'int64')}) |
| | 41 | sql = list(sql) |
| | 42 | self.assertEqual([ |
| | 43 | "ALTER TABLE milestone " |
| | 44 | "MODIFY completed bigint", |
| | 45 | ], sql) |
| | 46 | |
| | 47 | def test_alter_column_types_none(self): |
| | 48 | connector = MySQLConnector(self.env) |
| | 49 | sql = connector.alter_column_types('milestone', |
| | 50 | {'due': ('int', 'int')}) |
| | 51 | self.assertEqual([], list(sql)) |
| | 52 | |
| | 53 | |
| | 54 | def suite(): |
| | 55 | suite = unittest.TestSuite() |
| | 56 | suite.addTest(unittest.makeSuite(MySQLTableAlterationSQLTest, 'test')) |
| | 57 | return suite |
| | 58 | |
| | 59 | |
| | 60 | if __name__ == '__main__': |
| | 61 | unittest.main(defaultTest='suite') |
-
diff --git a/trac/db/tests/postgres_test.py b/trac/db/tests/postgres_test.py
|
a
|
b
|
|
| 11 | 11 | class PostgresTableCreationSQLTest(unittest.TestCase): |
| 12 | 12 | def setUp(self): |
| 13 | 13 | self.env = EnvironmentStub() |
| 14 | | self.db = self.env.get_db_cnx() |
| 15 | 14 | |
| 16 | 15 | def _unroll_generator(self, generator): |
| 17 | 16 | items = [] |
| … |
… |
|
| 82 | 81 | self.assertEqual(index_sql, sql_commands[1]) |
| 83 | 82 | |
| 84 | 83 | |
| | 84 | class PostgresTableAlterationSQLTest(unittest.TestCase): |
| | 85 | def setUp(self): |
| | 86 | self.env = EnvironmentStub() |
| | 87 | |
| | 88 | def test_alter_column_types(self): |
| | 89 | connector = PostgreSQLConnector(self.env) |
| | 90 | sql = connector.alter_column_types('milestone', |
| | 91 | {'due': ('int', 'int64'), |
| | 92 | 'completed': ('int', 'int64')}) |
| | 93 | sql = list(sql) |
| | 94 | self.assertEqual([ |
| | 95 | "ALTER TABLE milestone " |
| | 96 | "ALTER COLUMN completed TYPE bigint, " |
| | 97 | "ALTER COLUMN due TYPE bigint", |
| | 98 | ], sql) |
| | 99 | |
| | 100 | def test_alter_column_types_same(self): |
| | 101 | connector = PostgreSQLConnector(self.env) |
| | 102 | sql = connector.alter_column_types('milestone', |
| | 103 | {'due': ('int', 'int'), |
| | 104 | 'completed': ('int', 'int64')}) |
| | 105 | sql = list(sql) |
| | 106 | self.assertEqual([ |
| | 107 | "ALTER TABLE milestone " |
| | 108 | "ALTER COLUMN completed TYPE bigint", |
| | 109 | ], sql) |
| | 110 | |
| | 111 | def test_alter_column_types_none(self): |
| | 112 | connector = PostgreSQLConnector(self.env) |
| | 113 | sql = connector.alter_column_types('milestone', |
| | 114 | {'due': ('int', 'int')}) |
| | 115 | self.assertEqual([], list(sql)) |
| | 116 | |
| | 117 | |
| 85 | 118 | def suite(): |
| 86 | | return unittest.makeSuite(PostgresTableCreationSQLTest, 'test') |
| | 119 | suite = unittest.TestSuite() |
| | 120 | suite.addTest(unittest.makeSuite(PostgresTableCreationSQLTest, 'test')) |
| | 121 | suite.addTest(unittest.makeSuite(PostgresTableAlterationSQLTest, 'test')) |
| | 122 | return suite |
| | 123 | |
| 87 | 124 | |
| 88 | 125 | if __name__ == '__main__': |
| 89 | 126 | unittest.main(defaultTest='suite') |
-
diff --git a/trac/db_default.py b/trac/db_default.py
|
a
|
b
|
|
| 17 | 17 | from trac.db import Table, Column, Index |
| 18 | 18 | |
| 19 | 19 | # Database version identifier. Used for automatic upgrades. |
| 20 | | db_version = 22 |
| | 20 | db_version = 23 |
| 21 | 21 | |
| 22 | 22 | def __mkreports(reports): |
| 23 | 23 | """Utility function used to create report data in same syntax as the |
| … |
… |
|
| 67 | 67 | Column('id'), |
| 68 | 68 | Column('filename'), |
| 69 | 69 | Column('size', type='int'), |
| 70 | | Column('time', type='int'), |
| | 70 | Column('time', type='int64'), |
| 71 | 71 | Column('description'), |
| 72 | 72 | Column('author'), |
| 73 | 73 | Column('ipnr')], |
| … |
… |
|
| 76 | 76 | Table('wiki', key=('name', 'version'))[ |
| 77 | 77 | Column('name'), |
| 78 | 78 | Column('version', type='int'), |
| 79 | | Column('time', type='int'), |
| | 79 | Column('time', type='int64'), |
| 80 | 80 | Column('author'), |
| 81 | 81 | Column('ipnr'), |
| 82 | 82 | Column('text'), |
| … |
… |
|
| 87 | 87 | # Version control cache |
| 88 | 88 | Table('revision', key='rev')[ |
| 89 | 89 | Column('rev'), |
| 90 | | Column('time', type='int'), |
| | 90 | Column('time', type='int64'), |
| 91 | 91 | Column('author'), |
| 92 | 92 | Column('message'), |
| 93 | 93 | Index(['time'])], |
| … |
… |
|
| 104 | 104 | Table('ticket', key='id')[ |
| 105 | 105 | Column('id', auto_increment=True), |
| 106 | 106 | Column('type'), |
| 107 | | Column('time', type='int'), |
| 108 | | Column('changetime', type='int'), |
| | 107 | Column('time', type='int64'), |
| | 108 | Column('changetime', type='int64'), |
| 109 | 109 | Column('component'), |
| 110 | 110 | Column('severity'), |
| 111 | 111 | Column('priority'), |
| … |
… |
|
| 123 | 123 | Index(['status'])], |
| 124 | 124 | Table('ticket_change', key=('ticket', 'time', 'field'))[ |
| 125 | 125 | Column('ticket', type='int'), |
| 126 | | Column('time', type='int'), |
| | 126 | Column('time', type='int64'), |
| 127 | 127 | Column('author'), |
| 128 | 128 | Column('field'), |
| 129 | 129 | Column('oldvalue'), |
| … |
… |
|
| 144 | 144 | Column('description')], |
| 145 | 145 | Table('milestone', key='name')[ |
| 146 | 146 | Column('name'), |
| 147 | | Column('due', type='int'), |
| 148 | | Column('completed', type='int'), |
| | 147 | Column('due', type='int64'), |
| | 148 | Column('completed', type='int64'), |
| 149 | 149 | Column('description')], |
| 150 | 150 | Table('version', key='name')[ |
| 151 | 151 | Column('name'), |
| 152 | | Column('time', type='int'), |
| | 152 | Column('time', type='int64'), |
| 153 | 153 | Column('description')], |
| 154 | 154 | |
| 155 | 155 | # Report system |
-
diff --git a/trac/ticket/model.py b/trac/ticket/model.py
|
a
|
b
|
|
| 26 | 26 | from trac.ticket.api import TicketSystem |
| 27 | 27 | from trac.util import embedded_numbers, partition |
| 28 | 28 | from trac.util.text import empty |
| 29 | | from trac.util.datefmt import utc, utcmax, to_timestamp |
| | 29 | from trac.util.datefmt import from_utimestamp, to_utimestamp, utc, utcmax |
| 30 | 30 | from trac.util.translation import _ |
| 31 | 31 | |
| 32 | 32 | __all__ = ['Ticket', 'Type', 'Status', 'Resolution', 'Priority', 'Severity', |
| … |
… |
|
| 111 | 111 | for i, field in enumerate(std_fields): |
| 112 | 112 | value = row[i] |
| 113 | 113 | if field in self.time_fields: |
| 114 | | self.values[field] = datetime.fromtimestamp(value or 0, utc) |
| | 114 | self.values[field] = from_utimestamp(value) |
| 115 | 115 | elif value is None: |
| 116 | 116 | self.values[field] = empty |
| 117 | 117 | else: |
| … |
… |
|
| 198 | 198 | values = dict(self.values) |
| 199 | 199 | for field in self.time_fields: |
| 200 | 200 | if field in values: |
| 201 | | values[field] = to_timestamp(values[field]) |
| | 201 | values[field] = to_utimestamp(values[field]) |
| 202 | 202 | |
| 203 | 203 | # Insert ticket record |
| 204 | 204 | std_fields = [] |
| … |
… |
|
| 248 | 248 | cursor = db.cursor() |
| 249 | 249 | if when is None: |
| 250 | 250 | when = datetime.now(utc) |
| 251 | | when_ts = to_timestamp(when) |
| | 251 | when_ts = to_utimestamp(when) |
| 252 | 252 | |
| 253 | 253 | if 'component' in self.values: |
| 254 | 254 | # If the component is changed on a 'new' ticket then owner field |
| … |
… |
|
| 328 | 328 | db = self._get_db(db) |
| 329 | 329 | cursor = db.cursor() |
| 330 | 330 | sid = str(self.id) |
| 331 | | when_ts = when and to_timestamp(when) or 0 |
| | 331 | when_ts = to_utimestamp(when) |
| 332 | 332 | if when_ts: |
| 333 | 333 | cursor.execute("SELECT time,author,field,oldvalue,newvalue," |
| 334 | 334 | "1 AS permanent FROM ticket_change " |
| … |
… |
|
| 356 | 356 | (self.id, sid, sid)) |
| 357 | 357 | log = [] |
| 358 | 358 | for t, author, field, oldvalue, newvalue, permanent in cursor: |
| 359 | | log.append((datetime.fromtimestamp(int(t), utc), author, field, |
| 360 | | oldvalue or '', newvalue or '', permanent)) |
| | 359 | log.append((from_utimestamp(t), author, field, |
| | 360 | oldvalue or '', newvalue or '', permanent)) |
| 361 | 361 | return log |
| 362 | 362 | |
| 363 | 363 | def delete(self, db=None): |
| … |
… |
|
| 389 | 389 | "WHERE ticket=%s AND time=%s", |
| 390 | 390 | (self.id, ts)) |
| 391 | 391 | fields = {} |
| 392 | | change = {'date': datetime.fromtimestamp(int(ts), utc), |
| | 392 | change = {'date': from_utimestamp(ts), |
| 393 | 393 | 'author': author, 'fields': fields} |
| 394 | 394 | for field, author, old, new in cursor: |
| 395 | 395 | fields[field] = {'author': author, 'old': old, 'new': new} |
| … |
… |
|
| 400 | 400 | scnum = str(cnum) |
| 401 | 401 | if when is None: |
| 402 | 402 | when = datetime.now(utc) |
| 403 | | when_ts = to_timestamp(when) |
| | 403 | when_ts = to_utimestamp(when) |
| 404 | 404 | |
| 405 | 405 | db, handle_ta = self._get_db_for_write(db) |
| 406 | 406 | like = db.like() |
| … |
… |
|
| 445 | 445 | (self.id, ts0, db.like_escape('_comment') + '%')) |
| 446 | 446 | for field, author, comment, ts in cursor: |
| 447 | 447 | rev = int(field[8:]) |
| 448 | | history.append((rev, datetime.fromtimestamp(int(ts0), utc), |
| 449 | | author0, comment)) |
| | 448 | history.append((rev, from_utimestamp(long(ts0)), author0, |
| | 449 | comment)) |
| 450 | 450 | ts0, author0 = ts, author |
| 451 | 451 | history.sort() |
| 452 | 452 | rev = history and (history[-1][0] + 1) or 0 |
| 453 | | history.append((rev, datetime.fromtimestamp(int(ts), utc), |
| 454 | | author, last_comment)) |
| | 453 | history.append((rev, from_utimestamp(long(ts)), author, |
| | 454 | last_comment)) |
| 455 | 455 | return history |
| 456 | 456 | |
| 457 | 457 | |
| … |
… |
|
| 758 | 758 | def _from_database(self, row): |
| 759 | 759 | name, due, completed, description = row |
| 760 | 760 | self.name = self._old_name = name |
| 761 | | self.due = due and datetime.fromtimestamp(int(due), utc) or None |
| 762 | | self.completed = completed and \ |
| 763 | | datetime.fromtimestamp(int(completed), utc) or None |
| | 761 | self.due = due and from_utimestamp(due) or None |
| | 762 | self.completed = completed and from_utimestamp(completed) or None |
| 764 | 763 | self.description = description or '' |
| 765 | 764 | |
| 766 | 765 | def delete(self, retarget_to=None, author=None, db=None): |
| … |
… |
|
| 803 | 802 | self.env.log.debug("Creating new milestone '%s'" % self.name) |
| 804 | 803 | cursor.execute("INSERT INTO milestone (name,due,completed,description) " |
| 805 | 804 | "VALUES (%s,%s,%s,%s)", |
| 806 | | (self.name, to_timestamp(self.due), to_timestamp(self.completed), |
| 807 | | self.description)) |
| | 805 | (self.name, to_utimestamp(self.due), |
| | 806 | to_utimestamp(self.completed), self.description)) |
| 808 | 807 | self._old_name = self.name |
| 809 | 808 | TicketSystem(self.env).reset_ticket_fields(db) |
| 810 | 809 | |
| … |
… |
|
| 825 | 824 | self.env.log.info('Updating milestone "%s"' % self.name) |
| 826 | 825 | cursor.execute("UPDATE milestone SET name=%s,due=%s," |
| 827 | 826 | "completed=%s,description=%s WHERE name=%s", |
| 828 | | (self.name, to_timestamp(self.due), to_timestamp(self.completed), |
| 829 | | self.description, |
| | 827 | (self.name, to_utimestamp(self.due), |
| | 828 | to_utimestamp(self.completed), self.description, |
| 830 | 829 | self._old_name)) |
| 831 | 830 | self.env.log.info('Updating milestone field of all tickets ' |
| 832 | 831 | 'associated with milestone "%s"' % self.name) |
| … |
… |
|
| 892 | 891 | raise ResourceNotFound(_('Version %(name)s does not exist.', |
| 893 | 892 | name=name)) |
| 894 | 893 | self.name = self._old_name = name |
| 895 | | self.time = row[0] and datetime.fromtimestamp(int(row[0]), utc) or None |
| | 894 | self.time = row[0] and from_utimestamp(row[0]) or None |
| 896 | 895 | self.description = row[1] or '' |
| 897 | 896 | else: |
| 898 | 897 | self.name = self._old_name = None |
| … |
… |
|
| 933 | 932 | self.env.log.debug("Creating new version '%s'" % self.name) |
| 934 | 933 | cursor.execute("INSERT INTO version (name,time,description) " |
| 935 | 934 | "VALUES (%s,%s,%s)", |
| 936 | | (self.name, to_timestamp(self.time), self.description)) |
| | 935 | (self.name, to_utimestamp(self.time), self.description)) |
| 937 | 936 | self._old_name = self.name |
| 938 | 937 | TicketSystem(self.env).reset_ticket_fields(db) |
| 939 | 938 | |
| … |
… |
|
| 955 | 954 | self.env.log.info('Updating version "%s"' % self.name) |
| 956 | 955 | cursor.execute("UPDATE version SET name=%s,time=%s,description=%s " |
| 957 | 956 | "WHERE name=%s", |
| 958 | | (self.name, to_timestamp(self.time), self.description, |
| | 957 | (self.name, to_utimestamp(self.time), self.description, |
| 959 | 958 | self._old_name)) |
| 960 | 959 | if self.name != self._old_name: |
| 961 | 960 | # Update tickets |
| … |
… |
|
| 977 | 976 | for name, time, description in cursor: |
| 978 | 977 | version = cls(env) |
| 979 | 978 | version.name = version._old_name = name |
| 980 | | version.time = time and datetime.fromtimestamp(int(time), utc) or None |
| | 979 | version.time = time and from_utimestamp(time) or None |
| 981 | 980 | version.description = description or '' |
| 982 | 981 | versions.append(version) |
| 983 | 982 | def version_order(v): |
-
diff --git a/trac/ticket/query.py b/trac/ticket/query.py
|
a
|
b
|
|
| 31 | 31 | from trac.resource import Resource |
| 32 | 32 | from trac.ticket.api import TicketSystem |
| 33 | 33 | from trac.util import Ranges |
| 34 | | from trac.util.datefmt import format_datetime, parse_date, to_timestamp, utc |
| | 34 | from trac.util.datefmt import format_datetime, from_utimestamp, parse_date, \ |
| | 35 | to_timestamp, to_utimestamp, utc |
| 35 | 36 | from trac.util.presentation import Paginator |
| 36 | 37 | from trac.util.text import empty, shorten_line, unicode_unquote |
| 37 | 38 | from trac.util.translation import _, tag_ |
| … |
… |
|
| 331 | 332 | elif val is None: |
| 332 | 333 | val = '--' |
| 333 | 334 | elif name in self.time_fields: |
| 334 | | val = datetime.fromtimestamp(int(val or 0), utc) |
| | 335 | val = from_utimestamp(val) |
| 335 | 336 | elif field and field['type'] == 'checkbox': |
| 336 | 337 | try: |
| 337 | 338 | val = bool(int(val)) |
| … |
… |
|
| 466 | 467 | def get_timestamp(date): |
| 467 | 468 | if date: |
| 468 | 469 | try: |
| 469 | | return to_timestamp(parse_date(date, req.tz)) |
| | 470 | return to_utimestamp(parse_date(date, req.tz)) |
| 470 | 471 | except TracError, e: |
| 471 | 472 | errors.append(unicode(e)) |
| 472 | 473 | return None |
-
diff --git a/trac/ticket/roadmap.py b/trac/ticket/roadmap.py
|
a
|
b
|
|
| 18 | 18 | from StringIO import StringIO |
| 19 | 19 | from datetime import datetime |
| 20 | 20 | import re |
| 21 | | from time import time |
| 22 | 21 | |
| 23 | 22 | from genshi.builder import tag |
| 24 | 23 | |
| … |
… |
|
| 30 | 29 | from trac.perm import IPermissionRequestor |
| 31 | 30 | from trac.resource import * |
| 32 | 31 | from trac.search import ISearchSource, search_to_sql, shorten_result |
| 33 | | from trac.util.datefmt import parse_date, utc, to_timestamp, to_datetime, \ |
| | 32 | from trac.util.datefmt import parse_date, utc, to_utimestamp, \ |
| 34 | 33 | get_date_format_hint, get_datetime_format_hint, \ |
| 35 | | format_date, format_datetime |
| | 34 | format_date, format_datetime, from_utimestamp |
| 36 | 35 | from trac.util.text import CRLF |
| 37 | 36 | from trac.util.translation import _ |
| 38 | 37 | from trac.ticket import Milestone, Ticket, TicketSystem, group_milestones |
| … |
… |
|
| 472 | 471 | (ticket.id,)) |
| 473 | 472 | row = cursor.fetchone() |
| 474 | 473 | if row: |
| 475 | | write_utctime('COMPLETED', to_datetime(row[0], utc)) |
| | 474 | write_utctime('COMPLETED', from_utimestamp(row[0])) |
| 476 | 475 | write_prop('END', 'VTODO') |
| 477 | 476 | write_prop('END', 'VCALENDAR') |
| 478 | 477 | |
| … |
… |
|
| 527 | 526 | # TODO: creation and (later) modifications should also be reported |
| 528 | 527 | cursor.execute("SELECT completed,name,description FROM milestone " |
| 529 | 528 | "WHERE completed>=%s AND completed<=%s", |
| 530 | | (to_timestamp(start), to_timestamp(stop))) |
| | 529 | (to_utimestamp(start), to_utimestamp(stop))) |
| 531 | 530 | for completed, name, description in cursor: |
| 532 | 531 | milestone = milestone_realm(id=name) |
| 533 | 532 | if 'MILESTONE_VIEW' in req.perm(milestone): |
| 534 | | yield('milestone', datetime.fromtimestamp(completed, utc), |
| | 533 | yield('milestone', from_utimestamp(completed), |
| 535 | 534 | '', (milestone, description)) # FIXME: author? |
| 536 | 535 | |
| 537 | 536 | # Attachments |
| … |
… |
|
| 862 | 861 | for name, due, completed, description in cursor: |
| 863 | 862 | milestone = milestone_realm(id=name) |
| 864 | 863 | if 'MILESTONE_VIEW' in req.perm(milestone): |
| | 864 | dt = (completed and from_utimestamp(completed) or |
| | 865 | due and from_utimestamp(due) or datetime.now(utc)) |
| 865 | 866 | yield (get_resource_url(self.env, milestone, req.href), |
| 866 | | get_resource_name(self.env, milestone), |
| 867 | | datetime.fromtimestamp( |
| 868 | | completed or due or time(), utc), |
| | 867 | get_resource_name(self.env, milestone), dt, |
| 869 | 868 | '', shorten_result(description, terms)) |
| 870 | 869 | |
| 871 | 870 | # Attachments |
-
diff --git a/trac/ticket/templates/report.rss b/trac/ticket/templates/report.rss
|
a
|
b
|
|
| 26 | 26 | </py:when> |
| 27 | 27 | <py:when test="col in ('time', 'changetime', 'created', 'modified')"> |
| 28 | 28 | <!-- FIXME: we end up with multiple pubDate --> |
| 29 | | <pubDate py:if="cell.value != 'None'">${http_date(fromtimestamp(int(cell.value)))}</pubDate> |
| | 29 | <pubDate py:if="cell.value != 'None'">${http_date(from_utimestamp(long(cell.value)))}</pubDate> |
| 30 | 30 | </py:when> |
| 31 | 31 | <py:when test="col == 'summary'"> |
| 32 | 32 | <title>#$row.id: $cell.value</title> |
-
diff --git a/trac/ticket/templates/report_view.html b/trac/ticket/templates/report_view.html
|
a
|
b
|
|
| 144 | 144 | |
| 145 | 145 | <!--! generic fields --> |
| 146 | 146 | <py:when test="col == 'time'"> |
| 147 | | <td class="date" py:attrs="td_attrs">${cell.value != '' and format_time(int(cell.value)) or '--'} |
| | 147 | <td class="date" py:attrs="td_attrs">${cell.value != '' and format_time(from_utimestamp(long(cell.value))) or '--'} |
| 148 | 148 | <hr py:if="fullrow"/> |
| 149 | 149 | </td> |
| 150 | 150 | </py:when> |
| 151 | 151 | |
| 152 | 152 | <py:when test="col in ('date', 'created', 'modified')"> |
| 153 | | <td class="date" py:attrs="td_attrs">${cell.value != '' and format_date(int(cell.value)) or '--'} |
| | 153 | <td class="date" py:attrs="td_attrs">${cell.value != '' and format_date(from_utimestamp(long(cell.value))) or '--'} |
| 154 | 154 | <hr py:if="fullrow"/> |
| 155 | 155 | </td> |
| 156 | 156 | </py:when> |
| 157 | 157 | |
| 158 | 158 | <py:when test="col == 'datetime'"> |
| 159 | | <td class="date" py:attrs="td_attrs">${cell.value != '' and format_datetime(int(cell.value)) or '--'} |
| | 159 | <td class="date" py:attrs="td_attrs">${cell.value != '' and format_datetime(from_utimestamp(long(cell.value))) or '--'} |
| 160 | 160 | <hr py:if="fullrow"/> |
| 161 | 161 | </td> |
| 162 | 162 | </py:when> |
-
diff --git a/trac/ticket/tests/model.py b/trac/ticket/tests/model.py
|
a
|
b
|
|
| 4 | 4 | from trac.ticket.model import Ticket, Component, Milestone, Priority, Type, Version |
| 5 | 5 | from trac.ticket.api import ITicketChangeListener |
| 6 | 6 | from trac.test import EnvironmentStub |
| 7 | | from trac.util.datefmt import utc, to_timestamp |
| | 7 | from trac.util.datefmt import utc, to_utimestamp |
| 8 | 8 | |
| 9 | 9 | from datetime import datetime |
| 10 | 10 | import unittest |
| … |
… |
|
| 300 | 300 | " description,author,ipnr) " |
| 301 | 301 | "VALUES ('ticket',%s,'file.txt',1234,%s," |
| 302 | 302 | " 'My file','mark','')", |
| 303 | | (str(tkt_id), to_timestamp(t2))) |
| | 303 | (str(tkt_id), to_utimestamp(t2))) |
| 304 | 304 | db.commit() |
| 305 | 305 | t3 = datetime(2001, 1, 1, 1, 1, 3, 0, utc) |
| 306 | 306 | ticket.save_changes('jim', 'Other', t3) |
| … |
… |
|
| 312 | 312 | sorted(log[1:3])) |
| 313 | 313 | self.assertEqual((t3, 'jim', 'comment', '', 'Other', True), log[3]) |
| 314 | 314 | |
| | 315 | def test_subsecond_change(self): |
| | 316 | """Perform two ticket changes within a second.""" |
| | 317 | tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') |
| | 318 | ticket = Ticket(self.env, tkt_id) |
| | 319 | t1 = datetime(2001, 1, 1, 1, 1, 1, 123456, utc) |
| | 320 | ticket.save_changes('jane', 'Testing', t1) |
| | 321 | t2 = datetime(2001, 1, 1, 1, 1, 1, 789012, utc) |
| | 322 | ticket.save_changes('jim', 'Other', t2) |
| | 323 | log = ticket.get_changelog() |
| | 324 | self.assertEqual(2, len(log)) |
| | 325 | self.assertEqual((t1, 'jane', 'comment', '', 'Testing', True), log[0]) |
| | 326 | self.assertEqual((t2, 'jim', 'comment', '', 'Other', True), log[1]) |
| | 327 | |
| 315 | 328 | def test_changelog_with_reverted_change(self): |
| 316 | 329 | tkt_id = self._insert_ticket('Test', reporter='joe', component='foo') |
| 317 | 330 | ticket = Ticket(self.env, tkt_id) |
| … |
… |
|
| 520 | 533 | |
| 521 | 534 | cursor = self.db.cursor() |
| 522 | 535 | cursor.execute("SELECT * FROM milestone WHERE name='Test'") |
| 523 | | self.assertEqual(('Test', to_timestamp(t1), to_timestamp(t2), 'Foo bar'), |
| | 536 | self.assertEqual(('Test', to_utimestamp(t1), to_utimestamp(t2), |
| | 537 | 'Foo bar'), |
| 524 | 538 | cursor.fetchone()) |
| 525 | 539 | |
| 526 | 540 | def test_update_milestone_without_name(self): |
-
diff --git a/trac/ticket/tests/query.py b/trac/ticket/tests/query.py
|
a
|
b
|
|
| 359 | 359 | WHERE (((%(cast_time)s>=%%s AND %(cast_time)s<%%s))) |
| 360 | 360 | ORDER BY COALESCE(t.id,0)=0,t.id""" % { |
| 361 | 361 | 'cast_time': self.env.get_db_cnx().cast('t.time', 'int')}) |
| 362 | | self.assertEqual([1217548800, 1220227200], args) |
| | 362 | self.assertEqual([1217548800000000L, 1220227200000000L], args) |
| 363 | 363 | tickets = query.execute(self.req) |
| 364 | 364 | |
| 365 | 365 | def test_constrained_by_time_range_exclusion(self): |
| … |
… |
|
| 372 | 372 | WHERE ((NOT (%(cast_time)s>=%%s AND %(cast_time)s<%%s))) |
| 373 | 373 | ORDER BY COALESCE(t.id,0)=0,t.id""" % { |
| 374 | 374 | 'cast_time': self.env.get_db_cnx().cast('t.time', 'int')}) |
| 375 | | self.assertEqual([1217548800, 1220227200], args) |
| | 375 | self.assertEqual([1217548800000000L, 1220227200000000L], args) |
| 376 | 376 | tickets = query.execute(self.req) |
| 377 | 377 | |
| 378 | 378 | def test_constrained_by_time_range_open_right(self): |
| … |
… |
|
| 385 | 385 | WHERE ((%(cast_time)s>=%%s)) |
| 386 | 386 | ORDER BY COALESCE(t.id,0)=0,t.id""" % { |
| 387 | 387 | 'cast_time': self.env.get_db_cnx().cast('t.time', 'int')}) |
| 388 | | self.assertEqual([1217548800], args) |
| | 388 | self.assertEqual([1217548800000000L], args) |
| 389 | 389 | tickets = query.execute(self.req) |
| 390 | 390 | |
| 391 | 391 | def test_constrained_by_time_range_open_left(self): |
| … |
… |
|
| 398 | 398 | WHERE ((%(cast_time)s<%%s)) |
| 399 | 399 | ORDER BY COALESCE(t.id,0)=0,t.id""" % { |
| 400 | 400 | 'cast_time': self.env.get_db_cnx().cast('t.time', 'int')}) |
| 401 | | self.assertEqual([1220227200], args) |
| | 401 | self.assertEqual([1220227200000000L], args) |
| 402 | 402 | tickets = query.execute(self.req) |
| 403 | 403 | |
| 404 | 404 | def test_constrained_by_time_range_modified(self): |
| … |
… |
|
| 411 | 411 | WHERE (((%(cast_changetime)s>=%%s AND %(cast_changetime)s<%%s))) |
| 412 | 412 | ORDER BY COALESCE(t.id,0)=0,t.id""" % { |
| 413 | 413 | 'cast_changetime': self.env.get_db_cnx().cast('t.changetime', 'int')}) |
| 414 | | self.assertEqual([1217548800, 1220227200], args) |
| | 414 | self.assertEqual([1217548800000000L, 1220227200000000L], args) |
| 415 | 415 | tickets = query.execute(self.req) |
| 416 | 416 | |
| 417 | 417 | def test_constrained_by_keywords(self): |
-
diff --git a/trac/ticket/web_ui.py b/trac/ticket/web_ui.py
|
a
|
b
|
|
| 36 | 36 | from trac.timeline.api import ITimelineEventProvider |
| 37 | 37 | from trac.util import get_reporter_id |
| 38 | 38 | from trac.util.compat import any |
| 39 | | from trac.util.datefmt import format_datetime, to_timestamp, utc |
| | 39 | from trac.util.datefmt import format_datetime, from_utimestamp, \ |
| | 40 | to_utimestamp, utc |
| 40 | 41 | from trac.util.text import exception_to_unicode, obfuscate_email_address, \ |
| 41 | 42 | shorten_line, to_unicode |
| 42 | 43 | from trac.util.presentation import separated |
| … |
… |
|
| 209 | 210 | ': ', |
| 210 | 211 | ticketsystem.format_summary(summary, status, |
| 211 | 212 | resolution, type)), |
| 212 | | datetime.fromtimestamp(ts, utc), author, |
| | 213 | from_utimestamp(ts), author, |
| 213 | 214 | shorten_result(desc, terms)) |
| 214 | 215 | |
| 215 | 216 | # Attachments |
| … |
… |
|
| 226 | 227 | yield ('ticket_details', _('Ticket updates'), False) |
| 227 | 228 | |
| 228 | 229 | def get_timeline_events(self, req, start, stop, filters): |
| 229 | | ts_start = to_timestamp(start) |
| 230 | | ts_stop = to_timestamp(stop) |
| | 230 | ts_start = to_utimestamp(start) |
| | 231 | ts_stop = to_utimestamp(stop) |
| 231 | 232 | |
| 232 | 233 | status_map = {'new': ('newticket', N_('created')), |
| 233 | 234 | 'reopened': ('reopenedticket', N_('reopened')), |
| … |
… |
|
| 263 | 264 | else: |
| 264 | 265 | return None |
| 265 | 266 | kind, verb = status_map[status] |
| 266 | | return (kind, datetime.fromtimestamp(ts, utc), author, |
| | 267 | return (kind, from_utimestamp(ts), author, |
| 267 | 268 | (ticket, verb, info, summary, status, resolution, type, |
| 268 | 269 | description, comment, cid)) |
| 269 | 270 | |
| … |
… |
|
| 1628 | 1629 | rev = int(field[8:]) |
| 1629 | 1630 | comment_history.setdefault(rev, {}).update({'comment': old}) |
| 1630 | 1631 | comment_history.setdefault(rev + 1, {}).update( |
| 1631 | | {'author': author, |
| 1632 | | 'date': datetime.fromtimestamp(int(new), utc)}) |
| | 1632 | {'author': author, 'date': from_utimestamp(long(new))}) |
| 1633 | 1633 | elif old or new: |
| 1634 | 1634 | current['fields'][field] = {'old': old, 'new': new} |
| 1635 | 1635 | if current: |
-
diff --git a/trac/upgrades/db15.py b/trac/upgrades/db15.py
|
a
|
b
|
|
| 10 | 10 | Column('authenticated', type='int'), |
| 11 | 11 | Column('var_name'), |
| 12 | 12 | Column('var_value')] |
| 13 | | db_backend, _ = DatabaseManager(env)._get_connector() |
| | 13 | db_backend, _ = DatabaseManager(env).get_connector() |
| 14 | 14 | for stmt in db_backend.to_sql(session_table): |
| 15 | 15 | cursor.execute(stmt) |
| 16 | 16 | |
-
diff --git a/trac/upgrades/db17.py b/trac/upgrades/db17.py
|
a
|
b
|
|
| 16 | 16 | Column('base_rev'), |
| 17 | 17 | Index(['rev']) |
| 18 | 18 | ] |
| 19 | | db_connector, _ = DatabaseManager(env)._get_connector() |
| | 19 | db_connector, _ = DatabaseManager(env).get_connector() |
| 20 | 20 | for stmt in db_connector.to_sql(table): |
| 21 | 21 | cursor.execute(stmt) |
| 22 | 22 | |
-
diff --git a/trac/upgrades/db18.py b/trac/upgrades/db18.py
|
a
|
b
|
|
| 29 | 29 | Index(['ticket']), |
| 30 | 30 | Index(['time'])]] |
| 31 | 31 | |
| 32 | | db_connector, _ = DatabaseManager(env)._get_connector() |
| | 32 | db_connector, _ = DatabaseManager(env).get_connector() |
| 33 | 33 | for table in tables: |
| 34 | 34 | for stmt in db_connector.to_sql(table): |
| 35 | 35 | cursor.execute(stmt) |
-
diff --git a/trac/upgrades/db19.py b/trac/upgrades/db19.py
|
a
|
b
|
|
| 13 | 13 | Column('query'), |
| 14 | 14 | Column('description') |
| 15 | 15 | ] |
| 16 | | db_connector, _ = DatabaseManager(env)._get_connector() |
| | 16 | db_connector, _ = DatabaseManager(env).get_connector() |
| 17 | 17 | for stmt in db_connector.to_sql(table): |
| 18 | 18 | cursor.execute(stmt) |
| 19 | 19 | |
-
diff --git a/trac/upgrades/db22.py b/trac/upgrades/db22.py
|
a
|
b
|
|
| 6 | 6 | Column('id'), |
| 7 | 7 | Column('generation', type='int') |
| 8 | 8 | ] |
| 9 | | db_connector, _ = DatabaseManager(env)._get_connector() |
| | 9 | db_connector, _ = DatabaseManager(env).get_connector() |
| 10 | 10 | for stmt in db_connector.to_sql(table): |
| 11 | 11 | cursor.execute(stmt) |
-
diff --git a/trac/upgrades/db23.py b/trac/upgrades/db23.py
new file mode 100644
|
-
|
+
|
|
| | 1 | from trac.db import DatabaseManager |
| | 2 | |
| | 3 | |
| | 4 | def do_upgrade(env, ver, cursor): |
| | 5 | """Convert time values from integer seconds to integer microseconds.""" |
| | 6 | tables = [ |
| | 7 | ('attachment', {'time': ('int', 'int64')}), |
| | 8 | ('wiki', {'time': ('int', 'int64')}), |
| | 9 | ('revision', {'time': ('int', 'int64')}), |
| | 10 | ('ticket', {'time': ('int', 'int64'), |
| | 11 | 'changetime': ('int', 'int64')}), |
| | 12 | ('ticket_change', {'time': ('int', 'int64')}), |
| | 13 | ('milestone', {'due': ('int', 'int64'), |
| | 14 | 'completed': ('int', 'int64')}), |
| | 15 | ('version', {'time': ('int', 'int64')}), |
| | 16 | ] |
| | 17 | |
| | 18 | db_connector, _ = DatabaseManager(env).get_connector() |
| | 19 | db = env.get_db_cnx() |
| | 20 | for table, columns in tables: |
| | 21 | # Alter column types |
| | 22 | for sql in db_connector.alter_column_types(table, columns): |
| | 23 | cursor.execute(sql) |
| | 24 | |
| | 25 | # Convert timestamps to microseconds |
| | 26 | cursor.execute("UPDATE %s SET %s" % (table, |
| | 27 | ', '.join("%s=%s*1000000" % (column, column) |
| | 28 | for column in columns))) |
| | 29 | |
| | 30 | # Convert comment edit timestamps to microseconds |
| | 31 | cursor.execute("UPDATE ticket_change SET newvalue=%s*1000000 " |
| | 32 | "WHERE field %s" % (db.cast('newvalue', 'int'), db.like()), |
| | 33 | ('_comment%',)) |
-
diff --git a/trac/util/datefmt.py b/trac/util/datefmt.py
|
a
|
b
|
|
| 61 | 61 | else: |
| 62 | 62 | return 0 |
| 63 | 63 | |
| | 64 | def to_utimestamp(dt): |
| | 65 | """Return a microsecond POSIX timestamp for the given `datetime`.""" |
| | 66 | if not dt: |
| | 67 | return 0 |
| | 68 | diff = dt - _epoc |
| | 69 | return (diff.days * 86400000000L + diff.seconds * 1000000 |
| | 70 | + diff.microseconds) |
| | 71 | |
| | 72 | def from_utimestamp(ts): |
| | 73 | """Return the `datetime` for the given microsecond POSIX timestamp.""" |
| | 74 | return _epoc + timedelta(microseconds=ts or 0) |
| 64 | 75 | |
| 65 | 76 | # -- formatting |
| 66 | 77 | |
-
diff --git a/trac/util/tests/datefmt.py b/trac/util/tests/datefmt.py
|
a
|
b
|
|
| 42 | 42 | tz.utcoffset(None)) |
| 43 | 43 | self.assertEqual('GMT +4:00', tz.zone) |
| 44 | 44 | |
| | 45 | |
| 45 | 46 | class DateFormatTestCase(unittest.TestCase): |
| 46 | 47 | |
| 47 | 48 | def test_to_datetime(self): |
| … |
… |
|
| 94 | 95 | self.assertEqual('2009-08-20', |
| 95 | 96 | datefmt.format_date(a_date, format='%Y-%m-%d')) |
| 96 | 97 | |
| | 98 | |
| | 99 | class UTimestampTestCase(unittest.TestCase): |
| | 100 | |
| | 101 | def test_sub_second(self): |
| | 102 | t = datetime.datetime(2001, 2, 3, 4, 5, 6, 123456, datefmt.utc) |
| | 103 | ts = datefmt.to_utimestamp(t) |
| | 104 | self.assertEqual(t, datefmt.from_utimestamp(ts)) |
| | 105 | |
| | 106 | |
| 97 | 107 | def suite(): |
| 98 | 108 | suite = unittest.TestSuite() |
| 99 | 109 | if PytzTestCase: |
| … |
… |
|
| 101 | 111 | else: |
| 102 | 112 | print "SKIP: utils/tests/datefmt.py (no pytz installed)" |
| 103 | 113 | suite.addTest(unittest.makeSuite(DateFormatTestCase)) |
| | 114 | suite.addTest(unittest.makeSuite(UTimestampTestCase)) |
| 104 | 115 | return suite |
| 105 | 116 | |
| 106 | 117 | if __name__ == '__main__': |
-
diff --git a/trac/versioncontrol/cache.py b/trac/versioncontrol/cache.py
|
a
|
b
|
|
| 14 | 14 | # |
| 15 | 15 | # Author: Christopher Lenz <cmlenz@gmx.de> |
| 16 | 16 | |
| 17 | | from datetime import datetime |
| 18 | 17 | import os |
| 19 | 18 | import posixpath |
| 20 | 19 | |
| 21 | 20 | from trac.core import TracError |
| 22 | | from trac.util.datefmt import utc, to_timestamp |
| | 21 | from trac.util.datefmt import from_utimestamp, to_utimestamp |
| 23 | 22 | from trac.util.translation import _ |
| 24 | 23 | from trac.versioncontrol import Changeset, Node, Repository, Authorizer, \ |
| 25 | 24 | NoSuchChangeset |
| … |
… |
|
| 67 | 66 | cursor.execute("SELECT rev FROM revision " |
| 68 | 67 | "WHERE time >= %s AND time < %s " |
| 69 | 68 | "ORDER BY time DESC, rev DESC", |
| 70 | | (to_timestamp(start), to_timestamp(stop))) |
| | 69 | (to_utimestamp(start), to_utimestamp(stop))) |
| 71 | 70 | for rev, in cursor: |
| 72 | 71 | try: |
| 73 | 72 | if self.authz.has_permission_for_changeset(rev): |
| … |
… |
|
| 80 | 79 | db = self.getdb() |
| 81 | 80 | cursor = db.cursor() |
| 82 | 81 | cursor.execute("UPDATE revision SET time=%s, author=%s, message=%s " |
| 83 | | "WHERE rev=%s", (to_timestamp(cset.date), |
| | 82 | "WHERE rev=%s", (to_utimestamp(cset.date), |
| 84 | 83 | cset.author, cset.message, |
| 85 | 84 | (str(cset.rev)))) |
| 86 | 85 | db.commit() |
| … |
… |
|
| 187 | 186 | " (rev,time,author,message) " |
| 188 | 187 | "VALUES (%s,%s,%s,%s)", |
| 189 | 188 | (str(next_youngest), |
| 190 | | to_timestamp(cset.date), |
| | 189 | to_utimestamp(cset.date), |
| 191 | 190 | cset.author, cset.message)) |
| 192 | 191 | except Exception, e: # *another* 1.1. resync attempt won |
| 193 | 192 | self.log.warning('Revision %s already cached: %s' % |
| … |
… |
|
| 346 | 345 | cursor = db.cursor() |
| 347 | 346 | cursor.execute("SELECT time,author,message FROM revision " |
| 348 | 347 | "WHERE rev=%s", (str(rev),)) |
| 349 | | row = cursor.fetchone() |
| 350 | | if row: |
| 351 | | _date, author, message = row |
| 352 | | date = datetime.fromtimestamp(_date, utc) |
| | 348 | for _date, author, message in cursor: |
| | 349 | date = from_utimestamp(_date) |
| 353 | 350 | Changeset.__init__(self, rev, message, author, date) |
| | 351 | break |
| 354 | 352 | else: |
| 355 | 353 | raise NoSuchChangeset(rev) |
| 356 | 354 | self.scope = getattr(repos, 'scope', '') |
-
diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
|
a
|
b
|
|
| 47 | 47 | import os.path |
| 48 | 48 | import weakref |
| 49 | 49 | import posixpath |
| 50 | | from datetime import datetime |
| 51 | 50 | |
| 52 | 51 | from trac.config import ListOption |
| 53 | 52 | from trac.core import * |
| … |
… |
|
| 59 | 58 | from trac.util import embedded_numbers |
| 60 | 59 | from trac.util.text import exception_to_unicode, to_unicode |
| 61 | 60 | from trac.util.translation import _ |
| 62 | | from trac.util.datefmt import utc |
| | 61 | from trac.util.datefmt import from_utimestamp |
| 63 | 62 | |
| 64 | 63 | |
| 65 | 64 | application_pool = None |
| … |
… |
|
| 781 | 780 | core.SVN_PROP_REVISION_DATE, self.pool()) |
| 782 | 781 | if not _date: |
| 783 | 782 | return None |
| 784 | | ts = core.svn_time_from_cstring(_date, self.pool()) / 1000000 |
| 785 | | return datetime.fromtimestamp(ts, utc) |
| | 783 | return from_utimestamp(core.svn_time_from_cstring(_date, self.pool())) |
| 786 | 784 | |
| 787 | 785 | def _get_prop(self, name): |
| 788 | 786 | return fs.node_prop(self.root, self._scoped_path_utf8, name, |
| … |
… |
|
| 847 | 845 | author = author and to_unicode(author, 'utf-8') |
| 848 | 846 | _date = self._get_prop(core.SVN_PROP_REVISION_DATE) |
| 849 | 847 | if _date: |
| 850 | | ts = core.svn_time_from_cstring(_date, self.pool()) / 1000000 |
| 851 | | date = datetime.fromtimestamp(ts, utc) |
| | 848 | ts = core.svn_time_from_cstring(_date, self.pool()) |
| | 849 | date = from_utimestamp(ts) |
| 852 | 850 | else: |
| 853 | 851 | date = None |
| 854 | 852 | Changeset.__init__(self, rev, message, author, date) |
-
diff --git a/trac/versioncontrol/tests/cache.py b/trac/versioncontrol/tests/cache.py
|
a
|
b
|
|
| 18 | 18 | |
| 19 | 19 | from trac.log import logger_factory |
| 20 | 20 | from trac.test import Mock, InMemoryDatabase |
| 21 | | from trac.util.datefmt import to_timestamp, utc |
| | 21 | from trac.util.datefmt import to_utimestamp, utc |
| 22 | 22 | from trac.versioncontrol import Repository, Changeset, Node, NoSuchChangeset |
| 23 | 23 | from trac.versioncontrol.cache import CachedRepository |
| 24 | 24 | |
| … |
… |
|
| 73 | 73 | |
| 74 | 74 | cursor = self.db.cursor() |
| 75 | 75 | cursor.execute("SELECT rev,time,author,message FROM revision") |
| 76 | | self.assertEquals(('0', to_timestamp(t1), '', ''), cursor.fetchone()) |
| 77 | | self.assertEquals(('1', to_timestamp(t2), 'joe', 'Import'), cursor.fetchone()) |
| | 76 | self.assertEquals(('0', to_utimestamp(t1), '', ''), cursor.fetchone()) |
| | 77 | self.assertEquals(('1', to_utimestamp(t2), 'joe', 'Import'), cursor.fetchone()) |
| 78 | 78 | self.assertEquals(None, cursor.fetchone()) |
| 79 | 79 | cursor.execute("SELECT rev,path,node_type,change_type,base_path," |
| 80 | 80 | "base_rev FROM node_change") |
| … |
… |
|
| 90 | 90 | t3 = datetime(2003, 1, 1, 1, 1, 1, 0, utc) |
| 91 | 91 | cursor = self.db.cursor() |
| 92 | 92 | cursor.execute("INSERT INTO revision (rev,time,author,message) " |
| 93 | | "VALUES (0,%s,'','')", (to_timestamp(t1),)) |
| | 93 | "VALUES (0,%s,'','')", (to_utimestamp(t1),)) |
| 94 | 94 | cursor.execute("INSERT INTO revision (rev,time,author,message) " |
| 95 | | "VALUES (1,%s,'joe','Import')", (to_timestamp(t2),)) |
| | 95 | "VALUES (1,%s,'joe','Import')", (to_utimestamp(t2),)) |
| 96 | 96 | cursor.executemany("INSERT INTO node_change (rev,path,node_type," |
| 97 | 97 | "change_type,base_path,base_rev) " |
| 98 | 98 | "VALUES ('1',%s,%s,%s,%s,%s)", |
| … |
… |
|
| 114 | 114 | |
| 115 | 115 | cursor = self.db.cursor() |
| 116 | 116 | cursor.execute("SELECT time,author,message FROM revision WHERE rev='2'") |
| 117 | | self.assertEquals((to_timestamp(t3), 'joe', 'Update'), cursor.fetchone()) |
| | 117 | self.assertEquals((to_utimestamp(t3), 'joe', 'Update'), cursor.fetchone()) |
| 118 | 118 | self.assertEquals(None, cursor.fetchone()) |
| 119 | 119 | cursor.execute("SELECT path,node_type,change_type,base_path,base_rev " |
| 120 | 120 | "FROM node_change WHERE rev='2'") |
| … |
… |
|
| 127 | 127 | t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) |
| 128 | 128 | cursor = self.db.cursor() |
| 129 | 129 | cursor.execute("INSERT INTO revision (rev,time,author,message) " |
| 130 | | "VALUES (0,%s,'','')", (to_timestamp(t1),)) |
| | 130 | "VALUES (0,%s,'','')", (to_utimestamp(t1),)) |
| 131 | 131 | cursor.execute("INSERT INTO revision (rev,time,author,message) " |
| 132 | | "VALUES (1,%s,'joe','Import')", (to_timestamp(t2),)) |
| | 132 | "VALUES (1,%s,'joe','Import')", (to_utimestamp(t2),)) |
| 133 | 133 | cursor.executemany("INSERT INTO node_change (rev,path,node_type," |
| 134 | 134 | "change_type,base_path,base_rev) " |
| 135 | 135 | "VALUES ('1',%s,%s,%s,%s,%s)", |
-
diff --git a/trac/versioncontrol/tests/svn_fs.py b/trac/versioncontrol/tests/svn_fs.py
|
a
|
b
|
|
| 145 | 145 | self.assertEqual(u'/tête', node.path) |
| 146 | 146 | self.assertEqual(Node.DIRECTORY, node.kind) |
| 147 | 147 | self.assertEqual(HEAD, node.rev) |
| 148 | | self.assertEqual(datetime(2007, 4, 30, 17, 45, 26, 0, utc), |
| | 148 | self.assertEqual(datetime(2007, 4, 30, 17, 45, 26, 234375, utc), |
| 149 | 149 | node.last_modified) |
| 150 | 150 | node = self.repos.get_node(u'/tête/README.txt') |
| 151 | 151 | self.assertEqual('README.txt', node.name) |
| 152 | 152 | self.assertEqual(u'/tête/README.txt', node.path) |
| 153 | 153 | self.assertEqual(Node.FILE, node.kind) |
| 154 | 154 | self.assertEqual(3, node.rev) |
| 155 | | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 0, utc), node.last_modified) |
| | 155 | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 234643, utc), node.last_modified) |
| 156 | 156 | |
| 157 | 157 | def test_get_node_specific_rev(self): |
| 158 | 158 | node = self.repos.get_node(u'/tête', 1) |
| … |
… |
|
| 160 | 160 | self.assertEqual(u'/tête', node.path) |
| 161 | 161 | self.assertEqual(Node.DIRECTORY, node.kind) |
| 162 | 162 | self.assertEqual(1, node.rev) |
| 163 | | self.assertEqual(datetime(2005, 4, 1, 10, 0, 52, 0, utc), node.last_modified) |
| | 163 | self.assertEqual(datetime(2005, 4, 1, 10, 0, 52, 353248, utc), node.last_modified) |
| 164 | 164 | node = self.repos.get_node(u'/tête/README.txt', 2) |
| 165 | 165 | self.assertEqual('README.txt', node.name) |
| 166 | 166 | self.assertEqual(u'/tête/README.txt', node.path) |
| 167 | 167 | self.assertEqual(Node.FILE, node.kind) |
| 168 | 168 | self.assertEqual(2, node.rev) |
| 169 | | self.assertEqual(datetime(2005, 4, 1, 13, 12, 18, 0, utc), node.last_modified) |
| | 169 | self.assertEqual(datetime(2005, 4, 1, 13, 12, 18, 216267, utc), node.last_modified) |
| 170 | 170 | |
| 171 | 171 | def test_get_dir_entries(self): |
| 172 | 172 | node = self.repos.get_node(u'/tête') |
| … |
… |
|
| 383 | 383 | self.assertEqual(0, chgset.rev) |
| 384 | 384 | self.assertEqual('', chgset.message) |
| 385 | 385 | self.assertEqual('', chgset.author) |
| 386 | | self.assertEqual(datetime(2005, 4, 1, 9, 57, 41, 0, utc), chgset.date) |
| | 386 | self.assertEqual(datetime(2005, 4, 1, 9, 57, 41, 312767, utc), chgset.date) |
| 387 | 387 | self.assertRaises(StopIteration, chgset.get_changes().next) |
| 388 | 388 | |
| 389 | 389 | def test_changeset_added_dirs(self): |
| … |
… |
|
| 391 | 391 | self.assertEqual(1, chgset.rev) |
| 392 | 392 | self.assertEqual('Initial directory layout.', chgset.message) |
| 393 | 393 | self.assertEqual('john', chgset.author) |
| 394 | | self.assertEqual(datetime(2005, 4, 1, 10, 0, 52, 0, utc), chgset.date) |
| | 394 | self.assertEqual(datetime(2005, 4, 1, 10, 0, 52, 353248, utc), chgset.date) |
| 395 | 395 | |
| 396 | 396 | changes = chgset.get_changes() |
| 397 | 397 | self.assertEqual(('branches', Node.DIRECTORY, Changeset.ADD, None, -1), |
| … |
… |
|
| 407 | 407 | self.assertEqual(3, chgset.rev) |
| 408 | 408 | self.assertEqual('Fixed README.\n', chgset.message) |
| 409 | 409 | self.assertEqual('kate', chgset.author) |
| 410 | | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 0, utc), chgset.date) |
| | 410 | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 234643, utc), chgset.date) |
| 411 | 411 | |
| 412 | 412 | changes = chgset.get_changes() |
| 413 | 413 | self.assertEqual((u'tête/README.txt', Node.FILE, Changeset.EDIT, |
| … |
… |
|
| 419 | 419 | self.assertEqual(5, chgset.rev) |
| 420 | 420 | self.assertEqual('Moved directories.', chgset.message) |
| 421 | 421 | self.assertEqual('kate', chgset.author) |
| 422 | | self.assertEqual(datetime(2005, 4, 1, 16, 25, 39, 0, utc), chgset.date) |
| | 422 | self.assertEqual(datetime(2005, 4, 1, 16, 25, 39, 658099, utc), chgset.date) |
| 423 | 423 | |
| 424 | 424 | changes = chgset.get_changes() |
| 425 | 425 | self.assertEqual((u'tête/dir1/dir2', Node.DIRECTORY, Changeset.MOVE, |
| … |
… |
|
| 433 | 433 | self.assertEqual(6, chgset.rev) |
| 434 | 434 | self.assertEqual('More things to read', chgset.message) |
| 435 | 435 | self.assertEqual('john', chgset.author) |
| 436 | | self.assertEqual(datetime(2005, 4, 1, 18, 56, 46, 0, utc), chgset.date) |
| | 436 | self.assertEqual(datetime(2005, 4, 1, 18, 56, 46, 985846, utc), chgset.date) |
| 437 | 437 | |
| 438 | 438 | changes = chgset.get_changes() |
| 439 | 439 | self.assertEqual((u'tête/README2.txt', Node.FILE, Changeset.COPY, |
| … |
… |
|
| 550 | 550 | self.assertEqual('/dir1', node.path) |
| 551 | 551 | self.assertEqual(Node.DIRECTORY, node.kind) |
| 552 | 552 | self.assertEqual(5, node.rev) |
| 553 | | self.assertEqual(datetime(2005, 4, 1, 16, 25, 39, 0, utc), node.last_modified) |
| | 553 | self.assertEqual(datetime(2005, 4, 1, 16, 25, 39, 658099, utc), node.last_modified) |
| 554 | 554 | node = self.repos.get_node('/README.txt') |
| 555 | 555 | self.assertEqual('README.txt', node.name) |
| 556 | 556 | self.assertEqual('/README.txt', node.path) |
| 557 | 557 | self.assertEqual(Node.FILE, node.kind) |
| 558 | 558 | self.assertEqual(3, node.rev) |
| 559 | | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 0, utc), node.last_modified) |
| | 559 | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 234643, utc), node.last_modified) |
| 560 | 560 | |
| 561 | 561 | def test_get_node_specific_rev(self): |
| 562 | 562 | node = self.repos.get_node('/dir1', 4) |
| … |
… |
|
| 564 | 564 | self.assertEqual('/dir1', node.path) |
| 565 | 565 | self.assertEqual(Node.DIRECTORY, node.kind) |
| 566 | 566 | self.assertEqual(4, node.rev) |
| 567 | | self.assertEqual(datetime(2005, 4, 1, 15, 42, 35, 0, utc), node.last_modified) |
| | 567 | self.assertEqual(datetime(2005, 4, 1, 15, 42, 35, 450595, utc), node.last_modified) |
| 568 | 568 | node = self.repos.get_node('/README.txt', 2) |
| 569 | 569 | self.assertEqual('README.txt', node.name) |
| 570 | 570 | self.assertEqual('/README.txt', node.path) |
| 571 | 571 | self.assertEqual(Node.FILE, node.kind) |
| 572 | 572 | self.assertEqual(2, node.rev) |
| 573 | | self.assertEqual(datetime(2005, 4, 1, 13, 12, 18, 0, utc), node.last_modified) |
| | 573 | self.assertEqual(datetime(2005, 4, 1, 13, 12, 18, 216267, utc), node.last_modified) |
| 574 | 574 | |
| 575 | 575 | def test_get_dir_entries(self): |
| 576 | 576 | node = self.repos.get_node('/') |
| … |
… |
|
| 675 | 675 | self.assertEqual(0, chgset.rev) |
| 676 | 676 | self.assertEqual('', chgset.message) |
| 677 | 677 | self.assertEqual('', chgset.author) |
| 678 | | self.assertEqual(datetime(2005, 4, 1, 9, 57, 41, 0, utc), chgset.date) |
| | 678 | self.assertEqual(datetime(2005, 4, 1, 9, 57, 41, 312767, utc), chgset.date) |
| 679 | 679 | self.assertRaises(StopIteration, chgset.get_changes().next) |
| 680 | 680 | |
| 681 | 681 | def test_changeset_added_dirs(self): |
| … |
… |
|
| 683 | 683 | self.assertEqual(4, chgset.rev) |
| 684 | 684 | self.assertEqual('More directories.', chgset.message) |
| 685 | 685 | self.assertEqual('john', chgset.author) |
| 686 | | self.assertEqual(datetime(2005, 4, 1, 15, 42, 35, 0, utc), chgset.date) |
| | 686 | self.assertEqual(datetime(2005, 4, 1, 15, 42, 35, 450595, utc), chgset.date) |
| 687 | 687 | |
| 688 | 688 | changes = chgset.get_changes() |
| 689 | 689 | self.assertEqual(('dir1', Node.DIRECTORY, 'add', None, -1), |
| … |
… |
|
| 699 | 699 | self.assertEqual(3, chgset.rev) |
| 700 | 700 | self.assertEqual('Fixed README.\n', chgset.message) |
| 701 | 701 | self.assertEqual('kate', chgset.author) |
| 702 | | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 0, utc), chgset.date) |
| | 702 | self.assertEqual(datetime(2005, 4, 1, 13, 24, 58, 234643, utc), chgset.date) |
| 703 | 703 | |
| 704 | 704 | changes = chgset.get_changes() |
| 705 | 705 | self.assertEqual(('README.txt', Node.FILE, Changeset.EDIT, |
| … |
… |
|
| 711 | 711 | self.assertEqual(5, chgset.rev) |
| 712 | 712 | self.assertEqual('Moved directories.', chgset.message) |
| 713 | 713 | self.assertEqual('kate', chgset.author) |
| 714 | | self.assertEqual(datetime(2005, 4, 1, 16, 25, 39, 0, utc), chgset.date) |
| | 714 | self.assertEqual(datetime(2005, 4, 1, 16, 25, 39, 658099, utc), chgset.date) |
| 715 | 715 | |
| 716 | 716 | changes = chgset.get_changes() |
| 717 | 717 | self.assertEqual(('dir1/dir2', Node.DIRECTORY, Changeset.MOVE, |
| … |
… |
|
| 725 | 725 | self.assertEqual(6, chgset.rev) |
| 726 | 726 | self.assertEqual('More things to read', chgset.message) |
| 727 | 727 | self.assertEqual('john', chgset.author) |
| 728 | | self.assertEqual(datetime(2005, 4, 1, 18, 56, 46, 0, utc), chgset.date) |
| | 728 | self.assertEqual(datetime(2005, 4, 1, 18, 56, 46, 985846, utc), chgset.date) |
| 729 | 729 | |
| 730 | 730 | changes = chgset.get_changes() |
| 731 | 731 | self.assertEqual(('README2.txt', Node.FILE, Changeset.COPY, |
-
diff --git a/trac/versioncontrol/web_ui/changeset.py b/trac/versioncontrol/web_ui/changeset.py
|
a
|
b
|
|
| 18 | 18 | # Christopher Lenz <cmlenz@gmx.de> |
| 19 | 19 | # Christian Boos <cboos@neuf.fr> |
| 20 | 20 | |
| 21 | | from datetime import datetime |
| 22 | 21 | from itertools import groupby |
| 23 | 22 | import os |
| 24 | 23 | import posixpath |
| … |
… |
|
| 36 | 35 | from trac.timeline.api import ITimelineEventProvider |
| 37 | 36 | from trac.util import embedded_numbers, content_disposition |
| 38 | 37 | from trac.util.compat import any |
| 39 | | from trac.util.datefmt import pretty_timedelta, utc |
| | 38 | from trac.util.datefmt import from_utimestamp, pretty_timedelta |
| 40 | 39 | from trac.util.text import exception_to_unicode, unicode_urlencode, \ |
| 41 | 40 | shorten_line, CRLF |
| 42 | 41 | from trac.util.translation import _ |
| … |
… |
|
| 991 | 990 | continue |
| 992 | 991 | yield (req.href.changeset(rev), |
| 993 | 992 | '[%s]: %s' % (rev, shorten_line(log)), |
| 994 | | datetime.fromtimestamp(ts, utc), author, |
| 995 | | shorten_result(log, terms)) |
| | 993 | from_utimestamp(ts), author, shorten_result(log, terms)) |
| 996 | 994 | |
| 997 | 995 | |
| 998 | 996 | class AnyDiffModule(Component): |
-
diff --git a/trac/web/chrome.py b/trac/web/chrome.py
|
a
|
b
|
|
| 65 | 65 | shorten_line, unicode_quote_plus, to_unicode, \ |
| 66 | 66 | javascript_quote, exception_to_unicode |
| 67 | 67 | from trac.util.datefmt import pretty_timedelta, format_datetime, format_date, \ |
| 68 | | format_time, http_date, utc |
| | 68 | format_time, from_utimestamp, http_date, utc |
| 69 | 69 | from trac.util.translation import _ |
| 70 | 70 | from trac.web.api import IRequestHandler, ITemplateStreamFilter, HTTPNotFound |
| 71 | 71 | from trac.web.href import Href |
| … |
… |
|
| 707 | 707 | 'format_time': partial(format_time, tzinfo=tzinfo), |
| 708 | 708 | 'fromtimestamp': partial(datetime.datetime.fromtimestamp, |
| 709 | 709 | tz=tzinfo), |
| | 710 | 'from_utimestamp': from_utimestamp, |
| 710 | 711 | |
| 711 | 712 | # Wiki-formatting functions |
| 712 | 713 | 'wiki_to': partial(format_to, self.env), |
-
diff --git a/trac/web/tests/session.py b/trac/web/tests/session.py
|
a
|
b
|
|
| 166 | 166 | (0,)) |
| 167 | 167 | cursor.execute("INSERT INTO session " |
| 168 | 168 | "VALUES ('987654', 0, %s)", |
| 169 | | (time.time() - PURGE_AGE - 3600,)) |
| | 169 | (int(time.time() - PURGE_AGE - 3600),)) |
| 170 | 170 | cursor.execute("INSERT INTO session_attribute VALUES " |
| 171 | 171 | "('987654', 0, 'foo', 'bar')") |
| 172 | 172 | |
-
diff --git a/trac/wiki/admin.py b/trac/wiki/admin.py
|
a
|
b
|
|
| 15 | 15 | import os.path |
| 16 | 16 | import pkg_resources |
| 17 | 17 | import sys |
| 18 | | import time |
| 19 | 18 | |
| 20 | 19 | from trac.admin import * |
| 21 | 20 | from trac.core import * |
| 22 | 21 | from trac.wiki import model |
| 23 | 22 | from trac.wiki.api import WikiSystem |
| 24 | 23 | from trac.util.compat import any |
| 25 | | from trac.util.datefmt import format_datetime, utc |
| | 24 | from trac.util.datefmt import format_datetime, from_utimestamp, \ |
| | 25 | to_utimestamp, utc |
| 26 | 26 | from trac.util.text import to_unicode, unicode_quote, unicode_unquote, \ |
| 27 | 27 | print_table, printout |
| 28 | 28 | from trac.util.translation import _ |
| … |
… |
|
| 116 | 116 | " SELECT 1+COALESCE(max(version),0),%s,%s," |
| 117 | 117 | " 'trac','127.0.0.1',%s FROM wiki " |
| 118 | 118 | " WHERE name=%s", |
| 119 | | (title, int(time.time()), data, title)) |
| | 119 | (title, to_utimestamp(datetime.now(utc)), data, title)) |
| 120 | 120 | if not old: |
| 121 | 121 | WikiSystem(self.env).pages.invalidate(db) |
| 122 | 122 | if handle_ta: |
| … |
… |
|
| 161 | 161 | cursor.execute("SELECT name, max(version), max(time) " |
| 162 | 162 | "FROM wiki GROUP BY name ORDER BY name") |
| 163 | 163 | print_table([(r[0], int(r[1]), |
| 164 | | format_datetime(datetime.fromtimestamp(r[2], utc), |
| | 164 | format_datetime(from_utimestamp(r[2]), |
| 165 | 165 | console_datetime_format)) |
| 166 | 166 | for r in cursor], |
| 167 | 167 | [_('Title'), _('Edits'), _('Modified')]) |
-
diff --git a/trac/wiki/macros.py b/trac/wiki/macros.py
|
a
|
b
|
|
| 14 | 14 | # |
| 15 | 15 | # Author: Christopher Lenz <cmlenz@gmx.de> |
| 16 | 16 | |
| 17 | | from datetime import datetime |
| 18 | 17 | from itertools import groupby |
| 19 | 18 | import inspect |
| 20 | 19 | import os |
| … |
… |
|
| 26 | 25 | |
| 27 | 26 | from trac.core import * |
| 28 | 27 | from trac.resource import Resource, get_resource_url, get_resource_summary |
| 29 | | from trac.util.datefmt import format_date, utc |
| | 28 | from trac.util.datefmt import format_date, from_utimestamp |
| 30 | 29 | from trac.util.html import escape |
| 31 | 30 | from trac.util.text import unquote, to_unicode |
| 32 | 31 | from trac.util.translation import _ |
| … |
… |
|
| 203 | 202 | for name, version, ts in cursor: |
| 204 | 203 | if not 'WIKI_VIEW' in formatter.perm('wiki', name, version): |
| 205 | 204 | continue |
| 206 | | time = datetime.fromtimestamp(ts, utc) |
| 207 | | date = format_date(time) |
| | 205 | date = format_date(from_utimestamp(ts)) |
| 208 | 206 | if date != prevdate: |
| 209 | 207 | prevdate = date |
| 210 | 208 | entries_per_date.append((date, [])) |
-
diff --git a/trac/wiki/model.py b/trac/wiki/model.py
|
a
|
b
|
|
| 20 | 20 | |
| 21 | 21 | from trac.core import * |
| 22 | 22 | from trac.resource import Resource |
| 23 | | from trac.util.datefmt import utc, to_timestamp |
| | 23 | from trac.util.datefmt import from_utimestamp, to_utimestamp, utc |
| 24 | 24 | from trac.util.translation import _ |
| 25 | 25 | from trac.wiki.api import WikiSystem |
| 26 | 26 | |
| … |
… |
|
| 68 | 68 | version, time, author, text, comment, readonly = row |
| 69 | 69 | self.version = int(version) |
| 70 | 70 | self.author = author |
| 71 | | self.time = datetime.fromtimestamp(time, utc) |
| | 71 | self.time = from_utimestamp(time) |
| 72 | 72 | self.text = text |
| 73 | 73 | self.comment = comment |
| 74 | 74 | self.readonly = readonly and int(readonly) or 0 |
| … |
… |
|
| 138 | 138 | cursor.execute("INSERT INTO wiki (name,version,time,author,ipnr," |
| 139 | 139 | "text,comment,readonly) VALUES (%s,%s,%s,%s,%s,%s," |
| 140 | 140 | "%s,%s)", (self.name, self.version + 1, |
| 141 | | to_timestamp(t), author, remote_addr, |
| | 141 | to_utimestamp(t), author, remote_addr, |
| 142 | 142 | self.text, comment, self.readonly)) |
| 143 | 143 | self.version += 1 |
| 144 | 144 | self.resource = self.resource(version=self.version) |
| … |
… |
|
| 174 | 174 | "WHERE name=%s AND version<=%s " |
| 175 | 175 | "ORDER BY version DESC", (self.name, self.version)) |
| 176 | 176 | for version, ts, author, comment, ipnr in cursor: |
| 177 | | time = datetime.fromtimestamp(ts, utc) |
| 178 | | yield version, time, author, comment, ipnr |
| | 177 | yield version, from_utimestamp(ts), author, comment, ipnr |
-
diff --git a/trac/wiki/tests/model.py b/trac/wiki/tests/model.py
|
a
|
b
|
|
| 3 | 3 | |
| 4 | 4 | from trac.core import * |
| 5 | 5 | from trac.test import EnvironmentStub |
| 6 | | from trac.util.datefmt import utc, to_timestamp |
| | 6 | from trac.util.datefmt import utc, to_utimestamp |
| 7 | 7 | from trac.wiki import WikiPage, IWikiChangeListener |
| 8 | 8 | |
| 9 | 9 | |
| … |
… |
|
| 49 | 49 | t = datetime(2001, 1, 1, 1, 1, 1, 0, utc) |
| 50 | 50 | cursor = self.db.cursor() |
| 51 | 51 | cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)", |
| 52 | | ('TestPage', 0, to_timestamp(t), 'joe', '::1', 'Bla bla', |
| | 52 | ('TestPage', 0, to_utimestamp(t), 'joe', '::1', 'Bla bla', |
| 53 | 53 | 'Testing', 0)) |
| 54 | 54 | |
| 55 | 55 | page = WikiPage(self.env, 'TestPage') |
| … |
… |
|
| 71 | 71 | cursor = self.db.cursor() |
| 72 | 72 | cursor.execute("SELECT version,time,author,ipnr,text,comment," |
| 73 | 73 | "readonly FROM wiki WHERE name=%s", ('TestPage',)) |
| 74 | | self.assertEqual((1, to_timestamp(t), 'joe', '::1', 'Bla bla', 'Testing', 0), |
| | 74 | self.assertEqual((1, to_utimestamp(t), 'joe', '::1', 'Bla bla', 'Testing', 0), |
| 75 | 75 | cursor.fetchone()) |
| 76 | 76 | |
| 77 | 77 | listener = TestWikiChangeListener(self.env) |
| … |
… |
|
| 82 | 82 | t = datetime(2001, 1, 1, 1, 1, 1, 0, utc) |
| 83 | 83 | t2 = datetime(2002, 1, 1, 1, 1, 1, 0, utc) |
| 84 | 84 | cursor.execute("INSERT INTO wiki VALUES(%s,%s,%s,%s,%s,%s,%s,%s)", |
| 85 | | ('TestPage', 1, to_timestamp(t), 'joe', '::1', 'Bla bla', |
| | 85 | ('TestPage', 1, to_utimestamp(t), 'joe', '::1', 'Bla bla', |
| 86 | 86 | 'Testing', 0)) |
| 87 | 87 | |
| 88 | 88 | page = WikiPage(self.env, 'TestPage') |
| … |
… |
|
| 92 | 92 | |
| 93 | 93 | cursor.execute("SELECT version,time,author,ipnr,text,comment," |
| 94 | 94 | "readonly FROM wiki WHERE name=%s", ('TestPage',)) |
| 95 | | self.assertEqual((1, to_timestamp(t), 'joe', '::1', 'Bla bla', 'Testing', 0), |
| | 95 | self.assertEqual((1, to_utimestamp(t), 'joe', '::1', 'Bla bla', 'Testing', 0), |
| 96 | 96 | cursor.fetchone()) |
| 97 | | self.assertEqual((2, to_timestamp(t2), 'kate', '192.168.0.101', 'Bla', |
| | 97 | self.assertEqual((2, to_utimestamp(t2), 'kate', '192.168.0.101', 'Bla', |
| 98 | 98 | 'Changing', 0), cursor.fetchone()) |
| 99 | 99 | |
| 100 | 100 | listener = TestWikiChangeListener(self.env) |
-
diff --git a/trac/wiki/web_ui.py b/trac/wiki/web_ui.py
|
a
|
b
|
|
| 16 | 16 | # Author: Jonas Borgström <jonas@edgewall.com> |
| 17 | 17 | # Christopher Lenz <cmlenz@gmx.de> |
| 18 | 18 | |
| 19 | | from datetime import datetime |
| 20 | 19 | import pkg_resources |
| 21 | 20 | import re |
| 22 | 21 | |
| … |
… |
|
| 31 | 30 | from trac.search import ISearchSource, search_to_sql, shorten_result |
| 32 | 31 | from trac.timeline.api import ITimelineEventProvider |
| 33 | 32 | from trac.util import get_reporter_id |
| 34 | | from trac.util.datefmt import to_timestamp, utc |
| | 33 | from trac.util.datefmt import from_utimestamp, to_utimestamp |
| 35 | 34 | from trac.util.text import shorten_line |
| 36 | 35 | from trac.util.translation import _ |
| 37 | 36 | from trac.versioncontrol.diff import get_diff_options, diff_blocks |
| … |
… |
|
| 608 | 607 | cursor = db.cursor() |
| 609 | 608 | cursor.execute("SELECT time,name,comment,author,version " |
| 610 | 609 | "FROM wiki WHERE time>=%s AND time<=%s", |
| 611 | | (to_timestamp(start), to_timestamp(stop))) |
| | 610 | (to_utimestamp(start), to_utimestamp(stop))) |
| 612 | 611 | for ts, name, comment, author, version in cursor: |
| 613 | 612 | wiki_page = wiki_realm(id=name, version=version) |
| 614 | 613 | if 'WIKI_VIEW' not in req.perm(wiki_page): |
| 615 | 614 | continue |
| 616 | | yield ('wiki', datetime.fromtimestamp(ts, utc), author, |
| | 615 | yield ('wiki', from_utimestamp(ts), author, |
| 617 | 616 | (wiki_page, comment)) |
| 618 | 617 | |
| 619 | 618 | # Attachments |
| … |
… |
|
| 664 | 663 | if 'WIKI_VIEW' in req.perm(page): |
| 665 | 664 | yield (get_resource_url(self.env, page, req.href), |
| 666 | 665 | '%s: %s' % (name, shorten_line(text)), |
| 667 | | datetime.fromtimestamp(ts, utc), author, |
| | 666 | from_utimestamp(ts), author, |
| 668 | 667 | shorten_result(text, terms)) |
| 669 | 668 | |
| 670 | 669 | # Attachments |