Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 6 commits
  • 17 files changed
  • 0 comments
  • 1 contributor
1  .hgignore
@@ -12,6 +12,7 @@ syntax: glob
12 12 *.orig
13 13 *.swp
14 14 *~
  15 +.tox
15 16
16 17 #misc files
17 18 pip-log.txt
1  .hgtags
... ... @@ -0,0 +1 @@
  1 +2e207697bf89c48d3782f2cdef087d6bd9e30f12 0.7
9 README.rst
Source Rendered
@@ -218,10 +218,17 @@ time-series data which may be extremely using in plotting data:
218 218 Testing
219 219 =======
220 220
221   -If you'd like to test ``django-qsstats`` against your local configuration, add
  221 +If you'd like to test ``django-qsstats-magic`` against your local configuration, add
222 222 ``qsstats`` to your ``INSTALLED_APPS`` and run ``./manage.py test qsstats``.
223 223 The test suite assumes that ``django.contrib.auth`` is installed.
224 224
  225 +For testing against different python, DB and django versions install tox
  226 +(pip install tox) and run 'tox' from the source checkout::
  227 +
  228 + $ tox
  229 +
  230 +Db user 'qsstats_test' with password 'qsstats_test' and a DB 'qsstats_test'
  231 +should exist.
225 232
226 233 Difference from django-qsstats
227 234 ==============================
20 qsstats/__init__.py
... ... @@ -1,15 +1,17 @@
1 1 __author__ = 'Matt Croydon, Mikhail Korobov, Pawel Tomasiewicz'
2   -__version__ = (0, 6, 2)
  2 +__version__ = (0, 7, 0)
3 3
4 4 from functools import partial
5 5 import datetime
6 6 from dateutil.relativedelta import relativedelta
7 7 from dateutil.parser import parse
  8 +
8 9 from django.db.models import Count
9 10 from django.db import DatabaseError, transaction
10 11 from django.conf import settings
11 12
12   -from qsstats.utils import get_bounds, _to_datetime, _parse_interval, get_interval_sql
  13 +from qsstats.utils import get_bounds, _to_datetime, _parse_interval, get_interval_sql, _remove_time
  14 +from qsstats import compat
13 15 from qsstats.exceptions import *
14 16
15 17 class QuerySetStats(object):
@@ -110,8 +112,11 @@ def _fast_time_series(self, start, end, interval='days',
110 112 filter(**kwargs).order_by().values('d').\
111 113 annotate(agg=aggregate)
112 114
113   - def to_dt(d): # leave dates as-is
114   - return parse(d, yearfirst=True) if isinstance(d, basestring) else d
  115 + today = _remove_time(compat.now())
  116 + def to_dt(d):
  117 + if isinstance(d, basestring):
  118 + return parse(d, yearfirst=True, default=today)
  119 + return d
115 120
116 121 data = dict((to_dt(item['d']), item['agg']) for item in aggregate_data)
117 122
@@ -137,13 +142,13 @@ def until(self, dt, date_field=None, aggregate=None):
137 142 return self.pivot(dt, 'lte', date_field, aggregate)
138 143
139 144 def until_now(self, date_field=None, aggregate=None):
140   - return self.pivot(datetime.datetime.now(), 'lte', date_field, aggregate)
  145 + return self.pivot(compat.now(), 'lte', date_field, aggregate)
141 146
142 147 def after(self, dt, date_field=None, aggregate=None):
143 148 return self.pivot(dt, 'gte', date_field, aggregate)
144 149
145 150 def after_now(self, date_field=None, aggregate=None):
146   - return self.pivot(datetime.datetime.now(), 'gte', date_field, aggregate)
  151 + return self.pivot(compat.now(), 'gte', date_field, aggregate)
147 152
148 153 def pivot(self, dt, operator=None, date_field=None, aggregate=None):
149 154 operator = operator or self.operator
@@ -155,7 +160,8 @@ def pivot(self, dt, operator=None, date_field=None, aggregate=None):
155 160
156 161 # Utility functions
157 162 def update_today(self):
158   - self.today = datetime.date.today()
  163 + _now = compat.now()
  164 + self.today = _remove_time(_now)
159 165 return self.today
160 166
161 167 def _aggregate(self, date_field=None, aggregate=None, filter=None):
8 qsstats/compat.py
... ... @@ -0,0 +1,8 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import absolute_import
  3 +
  4 +import datetime
  5 +try:
  6 + from django.utils.timezone import now
  7 +except ImportError:
  8 + now = datetime.datetime.now
25 qsstats/tests.py
... ... @@ -1,7 +1,11 @@
  1 +from __future__ import absolute_import
  2 +import datetime
  3 +
1 4 from django.test import TestCase
2 5 from django.contrib.auth.models import User
3 6 from qsstats import QuerySetStats, InvalidInterval, DateFieldMissing, QuerySetMissing
4   -import datetime
  7 +from qsstats import compat
  8 +from .utils import _remove_time
5 9
6 10 class QuerySetStatsTestCase(TestCase):
7 11 def test_basic_today(self):
@@ -19,8 +23,7 @@ def test_basic_today(self):
19 23 # We should only see a single user
20 24 self.assertEqual(qss.this_day(), 1)
21 25
22   - def test_time_series(self):
23   - today = datetime.date.today()
  26 + def assertTimeSeriesWorks(self, today):
24 27 seven_days_ago = today - datetime.timedelta(days=7)
25 28 for j in range(1,8):
26 29 for i in range(0,j):
@@ -32,10 +35,18 @@ def test_time_series(self):
32 35 time_series = qss.time_series(seven_days_ago, today)
33 36 self.assertEqual([t[1] for t in time_series], [0, 1, 2, 3, 4, 5, 6, 7])
34 37
  38 + def test_time_series(self):
  39 + _now = compat.now()
  40 + today = _remove_time(_now)
  41 + self.assertTimeSeriesWorks(today)
  42 +
  43 + def test_time_series_naive(self):
  44 + self.assertTimeSeriesWorks(datetime.date.today())
  45 +
35 46 def test_until(self):
36   - today = datetime.date.today()
  47 + now = compat.now()
  48 + today = _remove_time(now)
37 49 yesterday = today - datetime.timedelta(days=1)
38   - now = datetime.datetime.now()
39 50
40 51 u = User.objects.create_user('u', 'u@example.com')
41 52 u.date_joined = today
@@ -50,9 +61,9 @@ def test_until(self):
50 61 self.assertEqual(qss.until_now(), 1)
51 62
52 63 def test_after(self):
53   - today = datetime.date.today()
  64 + now = compat.now()
  65 + today = _remove_time(now)
54 66 tomorrow = today + datetime.timedelta(days=1)
55   - now = datetime.datetime.now()
56 67
57 68 u = User.objects.create_user('u', 'u@example.com')
58 69 u.date_joined = today
20 qsstats/utils.py
@@ -2,14 +2,16 @@
2 2 import re
3 3 from dateutil.relativedelta import relativedelta, MO
4 4 from qsstats.exceptions import InvalidInterval, UnsupportedEngine
  5 +from qsstats import compat
5 6
6   -def _to_date(dt):
7   - return datetime.date(dt.year, dt.month, dt.day)
  7 +def _remove_time(dt):
  8 + tzinfo = getattr(dt, 'tzinfo', compat.now().tzinfo)
  9 + return datetime.datetime(dt.year, dt.month, dt.day, tzinfo=tzinfo)
8 10
9 11 def _to_datetime(dt):
10 12 if isinstance(dt, datetime.datetime):
11 13 return dt
12   - return datetime.datetime(dt.year, dt.month, dt.day)
  14 + return _remove_time(dt)
13 15
14 16 def _parse_interval(interval):
15 17 num = 1
@@ -23,14 +25,14 @@ def _parse_interval(interval):
23 25 def get_bounds(dt, interval):
24 26 ''' Returns interval bounds the datetime is in. '''
25 27
26   - day = _to_datetime(_to_date(dt))
  28 + day = _to_datetime(_remove_time(dt))
27 29 dt = _to_datetime(dt)
28 30
29 31 if interval == 'minute':
30   - begin = datetime.datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute)
  32 + begin = datetime.datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, tzinfo=dt.tzinfo)
31 33 end = begin + relativedelta(minutes=1)
32 34 elif interval == 'hour':
33   - begin = datetime.datetime(dt.year, dt.month, dt.day, dt.hour)
  35 + begin = datetime.datetime(dt.year, dt.month, dt.day, dt.hour, tzinfo=dt.tzinfo)
34 36 end = begin + relativedelta(hours=1)
35 37 elif interval == 'day':
36 38 begin = day
@@ -39,11 +41,11 @@ def get_bounds(dt, interval):
39 41 begin = day - relativedelta(weekday=MO(-1))
40 42 end = begin + datetime.timedelta(days=7)
41 43 elif interval == 'month':
42   - begin = datetime.datetime(dt.year, dt.month, 1)
  44 + begin = datetime.datetime(dt.year, dt.month, 1, tzinfo=dt.tzinfo)
43 45 end = begin + relativedelta(months=1)
44 46 elif interval == 'year':
45   - begin = datetime.datetime(dt.year, 1, 1)
46   - end = datetime.datetime(dt.year+1, 1, 1)
  47 + begin = datetime.datetime(dt.year, 1, 1, tzinfo=dt.tzinfo)
  48 + end = datetime.datetime(dt.year+1, 1, 1, tzinfo=dt.tzinfo)
47 49 else:
48 50 raise InvalidInterval('Inverval not supported.')
49 51 end = end - relativedelta(microseconds=1)
21 runtests.py
... ... @@ -1,21 +0,0 @@
1   -#!/usr/bin/env python
2   -# -*- coding: utf-8 -*-
3   -
4   -from django.conf import settings
5   -from django.core.management import call_command
6   -import sys
7   -
8   -engine = sys.argv[1]
9   -
10   -settings.configure(
11   - INSTALLED_APPS=('qsstats', 'django.contrib.auth', 'django.contrib.contenttypes'),
12   - DATABASES = {
13   - 'default': {
14   - 'ENGINE': 'django.db.backends.' + engine,
15   - 'NAME': 'test'
16   - }
17   - }
18   -)
19   -
20   -if __name__ == "__main__":
21   - call_command('test', 'qsstats')
17 setup.py
@@ -8,12 +8,25 @@
8 8
9 9 setup(
10 10 name='django-qsstats-magic',
11   - version='0.6.2',
  11 + version='0.7',
12 12 description='A django microframework that eases the generation of aggregate data for querysets.',
13 13 long_description = open('README.rst').read(),
14 14 author='Matt Croydon, Mikhail Korobov',
15 15 author_email='mcroydon@gmail.com, kmike84@gmail.com',
16 16 url='http://bitbucket.org/kmike/django-qsstats-magic/',
17 17 packages=['qsstats'],
18   - requires=['dateutil(>=1.4.1, < 2.0)']
  18 + requires=['dateutil(>=1.4.1, < 2.0)'],
  19 + classifiers=[
  20 + 'Development Status :: 4 - Beta',
  21 + 'Environment :: Web Environment',
  22 + 'Framework :: Django',
  23 + 'Intended Audience :: Developers',
  24 + 'License :: OSI Approved :: MIT License',
  25 + 'Programming Language :: Python',
  26 + 'Programming Language :: Python :: 2',
  27 + 'Programming Language :: Python :: 2.5',
  28 + 'Programming Language :: Python :: 2.6',
  29 + 'Programming Language :: Python :: 2.7',
  30 + 'Topic :: Software Development :: Libraries :: Python Modules',
  31 + ],
19 32 )
2  test_settings/__init__.py
... ... @@ -0,0 +1,2 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import absolute_import
16 test_settings/mysql.py
... ... @@ -0,0 +1,16 @@
  1 +INSTALLED_APPS = (
  2 + 'qsstats',
  3 + 'django.contrib.auth',
  4 + 'django.contrib.contenttypes'
  5 +)
  6 +
  7 +DATABASES = {
  8 + 'default': {
  9 + 'ENGINE': 'django.db.backends.mysql',
  10 + 'NAME': 'qsstats_test',
  11 + 'USER': 'qsstats_test',
  12 + 'PASSWORD': 'qsstats_test',
  13 + }
  14 +}
  15 +
  16 +SECRET_KEY = 'foo'
2  test_settings/mysql_tz.py
... ... @@ -0,0 +1,2 @@
  1 +from .mysql import *
  2 +USE_TZ = True
16 test_settings/postgres.py
... ... @@ -0,0 +1,16 @@
  1 +INSTALLED_APPS = (
  2 + 'qsstats',
  3 + 'django.contrib.auth',
  4 + 'django.contrib.contenttypes'
  5 +)
  6 +
  7 +DATABASES = {
  8 + 'default': {
  9 + 'ENGINE': 'django.db.backends.postgresql_psycopg2',
  10 + 'NAME': 'qsstats_test',
  11 + 'USER': 'qsstats_test',
  12 + 'PASSWORD': 'qsstats_test',
  13 + }
  14 +}
  15 +
  16 +SECRET_KEY = 'foo'
2  test_settings/postgres_tz.py
... ... @@ -0,0 +1,2 @@
  1 +from .postgres import *
  2 +USE_TZ = True
14 test_settings/sqlite.py
... ... @@ -0,0 +1,14 @@
  1 +INSTALLED_APPS = (
  2 + 'qsstats',
  3 + 'django.contrib.auth',
  4 + 'django.contrib.contenttypes'
  5 +)
  6 +
  7 +DATABASES = {
  8 + 'default': {
  9 + 'ENGINE': 'django.db.backends.sqlite3',
  10 + 'NAME': 'test'
  11 + }
  12 +}
  13 +
  14 +SECRET_KEY = 'foo'
2  test_settings/sqlite_tz.py
... ... @@ -0,0 +1,2 @@
  1 +from .sqlite import *
  2 +USE_TZ = True
104 tox.ini
... ... @@ -0,0 +1,104 @@
  1 +[tox]
  2 +envlist = py25, py26, py27, pypy, py_django13, py_django12, postgres, mysql, postgres_tz, mysql_tz, sqlite_tz, mysql_tz_nopytz, postgres_tz_nopytz, sqlite_tz_nopytz
  3 +
  4 +[testenv]
  5 +deps=
  6 + python-dateutil == 1.5
  7 + django == 1.4
  8 +
  9 +commands=
  10 + django-admin.py test qsstats --settings=test_settings.sqlite []
  11 +
  12 +
  13 +
  14 +[testenv:py_django13]
  15 +deps=
  16 + python-dateutil == 1.5
  17 + django == 1.3.1
  18 +
  19 +[testenv:py_django12]
  20 +deps=
  21 + python-dateutil == 1.5
  22 + django == 1.2.7
  23 +
  24 +
  25 +
  26 +[testenv:postgres]
  27 +deps=
  28 + python-dateutil == 1.5
  29 + django == 1.4
  30 + psycopg2
  31 +
  32 +commands=
  33 + django-admin.py test qsstats --settings=test_settings.postgres []
  34 +
  35 +[testenv:postgres_tz]
  36 +deps=
  37 + python-dateutil == 1.5
  38 + django == 1.4
  39 + psycopg2
  40 + pytz
  41 +
  42 +commands=
  43 + django-admin.py test qsstats --settings=test_settings.postgres_tz []
  44 +
  45 +[testenv:postgres_tz_nopytz]
  46 +deps=
  47 + python-dateutil == 1.5
  48 + django == 1.4
  49 + psycopg2
  50 +
  51 +commands=
  52 + django-admin.py test qsstats --settings=test_settings.postgres_tz []
  53 +
  54 +
  55 +
  56 +
  57 +[testenv:mysql]
  58 +deps=
  59 + python-dateutil == 1.5
  60 + django == 1.4
  61 + mysql-python
  62 +
  63 +commands=
  64 + django-admin.py test qsstats --settings=test_settings.mysql []
  65 +
  66 +[testenv:mysql_tz]
  67 +deps=
  68 + python-dateutil == 1.5
  69 + django == 1.4
  70 + pytz
  71 + mysql-python
  72 +
  73 +commands=
  74 + django-admin.py test qsstats --settings=test_settings.mysql_tz []
  75 +
  76 +[testenv:mysql_tz_nopytz]
  77 +deps=
  78 + python-dateutil == 1.5
  79 + django == 1.4
  80 + mysql-python
  81 +
  82 +commands=
  83 + django-admin.py test qsstats --settings=test_settings.mysql_tz []
  84 +
  85 +
  86 +
  87 +
  88 +[testenv:sqlite_tz]
  89 +deps=
  90 + python-dateutil == 1.5
  91 + django == 1.4
  92 + pytz
  93 +
  94 +commands=
  95 + django-admin.py test qsstats --settings=test_settings.sqlite_tz []
  96 +
  97 +[testenv:sqlite_tz_nopytz]
  98 +deps=
  99 + python-dateutil == 1.5
  100 + django == 1.4
  101 +
  102 +commands=
  103 + django-admin.py test qsstats --settings=test_settings.sqlite_tz []
  104 +

No commit comments for this range

Something went wrong with that request. Please try again.