Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/django-1.6'
Browse files Browse the repository at this point in the history
Conflicts:
	johnny/backends/memcached.py
	johnny/cache.py
	johnny/tests/cache.py
	johnny/tests/testapp/urls.py
	johnny/tests/web.py
	johnny/transaction.py
	manage.py
	settings.py
  • Loading branch information
BertrandBordage committed Feb 5, 2014
2 parents 112e640 + 77d8ea5 commit e5ac7ca
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 15 deletions.
44 changes: 43 additions & 1 deletion johnny/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,45 @@ def invalidate(*tables, **kwargs):


def get_tables_for_query(query):
"""
Takes a Django 'query' object and returns all tables that will be used in
that query as a list. Note that where clauses can have their own
querysets with their own dependent queries, etc.
"""
from django.db.models.sql.where import WhereNode, SubqueryConstraint
from django.db.models.query import QuerySet
tables = [v[0] for v in getattr(query,'alias_map',{}).values()]

def get_sub_query_tables(node):
query = node.query_object
if not hasattr(query, 'field_names'):
query = query.values(*node.targets)
else:
query = query._clone()
query = query.query
return [v[0] for v in getattr(query, 'alias_map',{}).values()]

def get_tables(node, tables):
if isinstance(node, SubqueryConstraint):
return get_sub_query_tables(node)
for child in node.children:
if isinstance(child, WhereNode): # and child.children:
tables = get_tables(child, tables)
elif not hasattr(child, '__iter__'):
continue
else:
for item in (c for c in child if isinstance(c, QuerySet)):
tables += get_tables_for_query(item.query)
return tables

if query.where and query.where.children:
where_nodes = [c for c in query.where.children if isinstance(c, (WhereNode, SubqueryConstraint))]
for node in where_nodes:
tables += get_tables(node, tables)

return list(set(tables))

def get_tables_for_query_pre_16(query):
"""
Takes a Django 'query' object and returns all tables that will be used in
that query as a list. Note that where clauses can have their own
Expand Down Expand Up @@ -300,7 +339,10 @@ def newfun(cls, *args, **kwargs):
key, val = None, NotInCache()
# check the blacklist for any of the involved tables; if it's not
# there, then look for the value in the cache.
tables = get_tables_for_query(cls.query)
if django.VERSION[:2] < (1, 6):
tables = get_tables_for_query_pre_16(cls.query)
else:
tables = get_tables_for_query(cls.query)
# if the tables are blacklisted, send a qc_skip signal
blacklisted = disallowed_table(*tables)

Expand Down
2 changes: 2 additions & 0 deletions johnny/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ class CommittingTransactionMiddleware(trans_middleware.TransactionMiddleware):
transactions, even if they aren't dirty.
"""
def process_response(self, request, response):
autocommit = transaction.get_autocommit()
if transaction.is_managed():
try:
transaction.commit()
except:
pass
transaction.leave_transaction_management()
transaction.set_autocommit(autocommit)
return response
33 changes: 25 additions & 8 deletions johnny/tests/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@
from Queue import Queue

from django.conf import settings
from django.db import connection, connections
from django.db import connection, connections, transaction
from johnny import middleware
from johnny import settings as johnny_settings
from . import base


if django.VERSION[:2] < (1, 6):
def atomic(f):
return f
else:
from django.db.transaction import atomic


# put tests in here to be included in the testing suite
__all__ = ['MultiDbTest', 'SingleModelTest', 'MultiModelTest', 'TransactionSupportTest', 'BlackListTest', 'TransactionManagerTestCase']

Expand Down Expand Up @@ -46,6 +54,8 @@ def _post_teardown(self):
from django.db import transaction
_post_teardown(self)
super(TransactionQueryCacheBase, self)._post_teardown()
if transaction.is_dirty():
transaction.rollback()
if transaction.is_managed():
transaction.managed(False)

Expand All @@ -70,7 +80,7 @@ def test_basic_blacklist(self):

class MultiDbTest(TransactionQueryCacheBase):
multi_db = True
fixtures = ['genres.json', 'genres.second.json']
fixtures = ['genres.json', 'genres2.json']

def _run_threaded(self, query, queue, data):
"""Runs a query (as a string) from testapp in another thread and
Expand Down Expand Up @@ -658,6 +668,11 @@ def miss(*args, **kwargs):
t.start()
t.join()

def setUp(self):
super(TransactionSupportTest, self).setUp()
if django.VERSION[:2] >= (1, 6):
transaction.set_autocommit(True)

def tearDown(self):
from django.db import transaction
if transaction.is_managed():
Expand Down Expand Up @@ -773,18 +788,20 @@ def test_transaction_rollback(self):
transaction.managed(False)
transaction.leave_transaction_management()

@atomic
def test_savepoint_rollback(self):
"""Tests rollbacks of savepoints"""
from django.db import transaction
from .testapp.models import Genre
from johnny import cache
if not connection.features.uses_savepoints:
return
self.assertFalse(transaction.is_managed())
self.assertFalse(transaction.is_dirty())
if django.VERSION[:2] < (1, 6):
self.assertFalse(transaction.is_managed())
self.assertFalse(transaction.is_dirty())
cache.local.clear()
transaction.enter_transaction_management()
transaction.managed()
if django.VERSION[:2] < (1,6):
transaction.enter_transaction_management()
g = Genre.objects.get(pk=1)
start_title = g.title
g.title = "Adventures in Savepoint World"
Expand All @@ -802,8 +819,8 @@ def test_savepoint_rollback(self):
transaction.rollback()
g = Genre.objects.get(pk=1)
self.assertEqual(g.title, start_title)
transaction.managed(False)
transaction.leave_transaction_management()
if django.VERSION[:2] < (1, 6):
transaction.leave_transaction_management()

def test_savepoint_commit(self):
"""Tests a transaction commit (release)
Expand Down
26 changes: 26 additions & 0 deletions johnny/tests/testapp/fixtures/genres2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"model" : "testapp.genre",
"pk" : 1,
"fields" : {
"title" : "Architecture",
"slug" : "architecture"
}
},
{
"model" : "testapp.genre",
"pk" : 2,
"fields" : {
"title" : "Americana",
"slug" : "americana"
}
},
{
"model" : "testapp.genre",
"pk" : 3,
"fields" : {
"title" : "Historical",
"slug" : "historical"
}
}
]
123 changes: 123 additions & 0 deletions johnny/tests/testapp/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Django settings for proj project.

import django

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = ()

MANAGERS = ADMINS

if django.VERSION[:2] < (1, 3):
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = 'johnny-db.db' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
else:
DATABASES = {
'default' : {
'ENGINE' : 'django.db.backends.sqlite3',
'NAME' : 'johnny-db.db',
}
}

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

if django.VERSION[:2] < (1, 3):
#CACHE_BACKEND="johnny.backends.locmem://"
CACHE_BACKEND="johnny.backends.memcached://localhost:11211/"
#CACHE_BACKEND="johnny.backends.filebased:///tmp/johnny_cache.cc"
else:
#CACHES = { 'default' : { 'BACKEND': 'johnny.backends.locmem.LocMemCache' }}
CACHES = {
'default' : {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
#'BACKEND': 'johnny.backends.memcached.MemcachedCache',
'LOCATION': ['localhost:11211'],
'JOHNNY_CACHE': True,
}
}


# Make this unique, and don't share it with anybody.
SECRET_KEY = '_vpn1a^j(6&+3qip2me4f#&8#m#*#icc!%=x=)rha4k=!4m8s4'

# List of callables that know how to import templates from various sources.
if django.VERSION[:2] < (1, 3):
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
# 'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
)
else:
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
# 'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)

ROOT_URLCONF = 'proj.urls'

TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)

INSTALLED_APPS = (
#'django.contrib.auth',
#'django.contrib.sessions',
#'django.contrib.sites',
'johnny',
)

LOGGING_CONFIG=None

try:
from local_settings import *
except ImportError:
pass

# set up a multi-db router if there are multiple databases set
lcls = locals()
if 'DATABASES' in lcls and len(lcls['DATABASES']) > 1:
DATABASE_ROUTERS = ['routers.MultiSyncedRouter']

1 change: 1 addition & 0 deletions johnny/tests/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from johnny.tests import *
12 changes: 10 additions & 2 deletions johnny/tests/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"""Tests for the QueryCache functionality of johnny."""

from django.db import connection
from django.db import connection, transaction
import django
from . import base

Expand Down Expand Up @@ -56,9 +56,17 @@ class TestJohnnyTransactionMiddleware(base.TransactionJohnnyWebTestCase):
'django.middleware.locale.LocaleMiddleware',
'django.middleware.gzip.GZipMiddleware',
'django.middleware.http.ConditionalGetMiddleware',
'johnny.middleware.CommittingTransactionMiddleware',
)

def setUp(self):
super(TestJohnnyTransactionMiddleware, self).setUp()
if django.VERSION < (1, 6):
self.middleware += (
'johnny.middleware.CommittingTransactionMiddleware',
)
else:
transaction.set_autocommit(True)

def test_queries_from_templates(self):
"""Verify that doing the same request w/o a db write twice *does*
populate the cache properly."""
Expand Down
5 changes: 4 additions & 1 deletion johnny/transaction.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import django
from django.db import transaction, connection, DEFAULT_DB_ALIAS

from johnny import settings as johnny_settings
from johnny.decorators import wraps, available_attrs


Expand Down Expand Up @@ -72,6 +73,7 @@ def _get_from_savepoints(self, key, using=None):
def _trunc_using(self, using):
if using is None:
using = DEFAULT_DB_ALIAS
using = johnny_settings.DB_CACHE_KEYS[using]
if len(using) > 100:
using = using[0:68] + self.keygen.gen_key(using[68:])
return using
Expand Down Expand Up @@ -152,7 +154,8 @@ def _create_savepoint(self, sid, using=None):
self._clear(using)
#append the key to the savepoint stack
sids = self._get_sid(using)
sids.append(key)
if key not in sids:
sids.append(key)

def _rollback_savepoint(self, sid, using=None):
sids = self._get_sid(using)
Expand Down
2 changes: 1 addition & 1 deletion manage.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python

from django.core.management import execute_from_command_line
import os
import sys
from django.core.management import execute_from_command_line

if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
Expand Down
10 changes: 8 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
[tox]
envlist=py27,py26,py25,dj14,dj13
envlist=py27,py26,py25,dj16,dj15,dj14,dj13

[base]
deps =
python-memcached
ipdb

[testenv]
[testenv:dj16]
commands={toxinidir}/manage.py test johnny
deps=
django>=1.6,<1.7
{[base]deps}

[testenv:dj15]
commands={toxinidir}/manage.py test johnny
deps=
django>=1.5,<1.6
Expand Down

0 comments on commit e5ac7ca

Please sign in to comment.