Skip to content

Commit

Permalink
Ready to new release. Support to Django 1.7. For Django 1.7, django_l…
Browse files Browse the repository at this point in the history
…ike use the lookup API
  • Loading branch information
goinnn committed Oct 13, 2014
1 parent 6cd30ef commit c8a1f1c
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 74 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
0.2.0 (2014-10-13)
===================
* Support to Django 1.7.
* For Django 1.7, django_like use the lookup API

0.1.0 (2013-12-20)
===================

Expand Down
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ Requeriments
Installation
============


* To use the "like/ilike lookup" with Django 1.4, 1.5 or 1.6 you need to install django_like (this app make a monkey patching) or to patch your Django.
* To use the "like/ilike lookup" with Django 1.3, 1.2 or 1.1 you need to install django_like (this app make a monkey patching), You could patch your Django but I have not make a patch, but this will be very easy. You only have to see the monkey patch and to update the same, or you could see other patch and update more or less the same.
* To use the "like/ilike lookup" with Django 1.7 you only need to install django_like but now this application ***does not make monkey patching***. Now Django provide an API to do it.




In your settings.py
-------------------

Expand Down Expand Up @@ -115,4 +123,5 @@ a readily setup project that uses django-like. You can run it as usual:
::

python manage.py syncdb --noinput
python manage.py loaddata app_data
python manage.py runserver
163 changes: 98 additions & 65 deletions django_like/__init__.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,103 @@
import django

from django.db import backend
from django.db import connection
from django.db.models.fields import Field, subclassing
from django.db.models.sql.constants import QUERY_TERMS


if isinstance(QUERY_TERMS, set):
QUERY_TERMS.add('like')
QUERY_TERMS.add('ilike')
if django.VERSION[0] >= 1 and django.VERSION[1] >= 7:

from django.db.models.lookups import Contains, IContains
from django.db.models import Field

class Like(Contains):
lookup_name = 'like'

def get_rhs_op(self, connection, rhs):
return connection.operators['contains'] % rhs

def as_sql(self, qn, connection):
return super(Like, self).as_sql(qn, connection)

class ILike(IContains):
lookup_name = 'ilike'

def process_lhs(self, qn, connection):
lhs_sql, params = super(ILike, self).process_lhs(qn, connection)
backend_name = backend.__name__
if 'postgres' in backend_name or \
'oracle' in backend_name:
lhs_sql = 'UPPER(%s)' % lhs_sql
return (lhs_sql, params)

def get_rhs_op(self, connection, rhs):
return connection.operators['icontains'] % rhs

def as_sql(self, qn, connection):
return super(ILike, self).as_sql(qn, connection)

Field.register_lookup(Like)
Field.register_lookup(ILike)

else:
QUERY_TERMS['like'] = None
QUERY_TERMS['ilike'] = None

connection.operators['like'] = connection.operators['contains']
connection.operators['ilike'] = connection.operators['icontains']
NEW_LOOKUP_TYPE = ('like', 'ilike')


def get_prep_lookup(self, lookup_type, value):
try:
return self.get_prep_lookup_origin(lookup_type, value)
except TypeError as e:
if lookup_type in NEW_LOOKUP_TYPE:
return value
raise e


def get_db_prep_lookup(self, lookup_type, value, *args, **kwargs):
try:
value_returned = self.get_db_prep_lookup_origin(lookup_type,
value,
*args, **kwargs)
except TypeError as e: # Django 1.1
if lookup_type in NEW_LOOKUP_TYPE:
from django.db import connection
from django.db.models.fields import Field, subclassing
from django.db.models.sql.constants import QUERY_TERMS

if isinstance(QUERY_TERMS, set):
QUERY_TERMS.add('like')
QUERY_TERMS.add('ilike')
else:
QUERY_TERMS['like'] = None
QUERY_TERMS['ilike'] = None

connection.operators['like'] = connection.operators['contains']
connection.operators['ilike'] = connection.operators['icontains']
NEW_LOOKUP_TYPE = ('like', 'ilike')

def get_prep_lookup(self, lookup_type, value):
try:
return self.get_prep_lookup_origin(lookup_type, value)
except TypeError as e:
if lookup_type in NEW_LOOKUP_TYPE:
return value
raise e

def get_db_prep_lookup(self, lookup_type, value, *args, **kwargs):
try:
value_returned = self.get_db_prep_lookup_origin(lookup_type,
value,
*args, **kwargs)
except TypeError as e: # Django 1.1
if lookup_type in NEW_LOOKUP_TYPE:
return [value]
raise e
if value_returned is None and lookup_type in NEW_LOOKUP_TYPE: # Dj > 1.1
return [value]
raise e
if value_returned is None and lookup_type in NEW_LOOKUP_TYPE: # Dj > 1.1
return [value]
return value_returned


def monkey_get_db_prep_lookup(cls):
cls.get_db_prep_lookup_origin = cls.get_db_prep_lookup
cls.get_db_prep_lookup = get_db_prep_lookup
if hasattr(subclassing, 'call_with_connection_and_prepared'): # Dj > 1.1
setattr(cls, 'get_db_prep_lookup',
subclassing.call_with_connection_and_prepared(cls.get_db_prep_lookup))
for new_cls in cls.__subclasses__():
monkey_get_db_prep_lookup(new_cls)


def lookup_cast(self, lookup_type):
lookup = '%s'
if lookup_type == 'ilike':
return 'UPPER(%s)' % lookup
return self.lookup_cast_origin(lookup_type)


def monkey_ilike():
backend_name = backend.__name__
if 'postgres' in backend_name or \
'oracle' in backend_name:
connection.ops.__class__.lookup_cast_origin = connection.ops.lookup_cast
connection.ops.__class__.lookup_cast = lookup_cast

monkey_get_db_prep_lookup(Field)
monkey_ilike()
if hasattr(Field, 'get_prep_lookup'):
Field.get_prep_lookup_origin = Field.get_prep_lookup
Field.get_prep_lookup = get_prep_lookup
return value_returned

def monkey_get_db_prep_lookup(cls):
cls.get_db_prep_lookup_origin = cls.get_db_prep_lookup
cls.get_db_prep_lookup = get_db_prep_lookup
if hasattr(subclassing, 'call_with_connection_and_prepared'): # Dj > 1.1
setattr(cls, 'get_db_prep_lookup',
subclassing.call_with_connection_and_prepared(cls.get_db_prep_lookup))
for new_cls in cls.__subclasses__():
monkey_get_db_prep_lookup(new_cls)

def lookup_cast(self, lookup_type):
lookup = '%s'
if lookup_type == 'ilike':
return 'UPPER(%s)' % lookup
return self.lookup_cast_origin(lookup_type)

def monkey_ilike():
backend_name = backend.__name__
if 'postgres' in backend_name or \
'oracle' in backend_name:
connection.ops.__class__.lookup_cast_origin = connection.ops.lookup_cast
connection.ops.__class__.lookup_cast = lookup_cast

monkey_get_db_prep_lookup(Field)
monkey_ilike()
if hasattr(Field, 'get_prep_lookup'):
Field.get_prep_lookup_origin = Field.get_prep_lookup
Field.get_prep_lookup = get_prep_lookup
File renamed without changes.
17 changes: 13 additions & 4 deletions example/example/app/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
import django

import datetime

from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import FieldError
from django.core.management import call_command
from django.core.urlresolvers import reverse
from django.db.models.sql.constants import QUERY_TERMS
Expand All @@ -31,6 +34,8 @@

class DjangoLikeTestCase(TestCase):

fixtures = ['app_data.json']

def test_like(self):
users_like = User.objects.filter(username__like="u%r%")
users_regex = User.objects.filter(username__regex="^u.*r.$")
Expand All @@ -54,11 +59,15 @@ def test_lookup_error(self):
try:
User.objects.filter(username__error="u%r%")
raise AssertionError("The before query should have failed")
except TypeError:
pass
except TypeError as e:
if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
raise e
except FieldError as e:
if django.VERSION[0] == 1 and django.VERSION[1] < 7:
raise e

def test_benchmark_like(self):
if not 'django_like' in settings.INSTALLED_APPS:
if 'django_like' not in settings.INSTALLED_APPS:
return # Running the tests with Django patched
call_command('benchmark_like', num_users=10, num_queries=10)

Expand All @@ -67,7 +76,7 @@ def test_index_example(self):
self.assertEqual(response.status_code, 200)

def test_total_seconds(self):
if not 'django_like' in settings.INSTALLED_APPS or not hasattr(datetime.timedelta, 'total_seconds'):
if 'django_like' not in settings.INSTALLED_APPS or not hasattr(datetime.timedelta, 'total_seconds'):
return # Running the tests with Django patched or python 2.6
from django_like.management.commands.benchmark_like import total_seconds, total_seconds_backward_compatible
tds = [datetime.timedelta(1),
Expand Down
6 changes: 3 additions & 3 deletions example/example/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,10 @@

import django

if django.VERSION[0] >= 1 and django.VERSION[1] >= 4:
if django.VERSION[0] == 1 and django.VERSION[1] >= 4:
TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.tz',)

if django.VERSION[0] >= 1 and django.VERSION[1] >= 3:
if django.VERSION[0] == 1 and django.VERSION[1] >= 3:
INSTALLED_APPS += ('django.contrib.staticfiles',)
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
Expand Down Expand Up @@ -194,7 +194,7 @@
TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.static',)


if django.VERSION[0] >= 1 and django.VERSION[1] >= 2:
if django.VERSION[0] == 1 and django.VERSION[1] >= 2:
INSTALLED_APPS += ('django.contrib.messages',)
MIDDLEWARE_CLASSES += ('django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.messages.middleware.MessageMiddleware')
Expand Down
4 changes: 4 additions & 0 deletions example/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
else:
os.environ[ENVIRONMENT_VARIABLE] = sys.argv[1]

if django.VERSION[0] == 1 and django.VERSION[1] >= 7:
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

if django.VERSION[0] == 1 and django.VERSION[1] <= 5:
management.call_command('test', 'app')
else:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def read(*rnames):

setup(
name="django-like",
version="0.1.0",
version="0.2.0",
author="Pablo Martin",
author_email="goinnn@gmail.com",
description="Django application that provider the like and ilike lookups for the querysets",
Expand Down
56 changes: 55 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py27-dj16,py27-dj15,py27-dj14,py27-dj13,py27-dj12,py27-dj11,py33-dj16,py33-dj15,py26-dj16,py26-dj15,py26-dj14,py26-dj13,py26-dj12,py26-dj11
envlist = py27-dj17,py27-dj16,py27-dj15,py27-dj14,py27-dj13,py27-dj12,py27-dj11,py33-dj17,py33-dj16,py33-dj15,py34-dj17,py26-dj16,py26-dj15,py26-dj14,py26-dj13,py26-dj12,py26-dj11


[testenv]
Expand Down Expand Up @@ -119,6 +119,24 @@ deps =
psycopg2==2.4.1
MySQL-python==1.2.5

[testenv:py27-dj17]
basepython = python2.7
commands =
python {envbindir}/coverage run -p example/run_tests.py
python {envbindir}/coverage run -p example/run_tests.py example.settings.no_debug
python {envbindir}/coverage run -p example/run_tests.py example.settings.postgres
python {envbindir}/coverage run -p example/run_tests.py example.settings.postgres_no_debug
python {envbindir}/coverage run -p example/run_tests.py example.settings.mysql
python {envbindir}/coverage run -p example/run_tests.py example.settings.mysql_no_debug
deps =
django==1.7
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
psycopg2==2.5.1
MySQL-python==1.2.5


[testenv:py27-dj16]
basepython = python2.7
commands =
Expand Down Expand Up @@ -224,6 +242,24 @@ deps =
psycopg2==2.4.1
MySQL-python==1.2.5

[testenv:py33-dj17]
basepython = python3.3
commands =
python {envbindir}/coverage run -p example/run_tests.py
python {envbindir}/coverage run -p example/run_tests.py example.settings.no_debug
python {envbindir}/coverage run -p example/run_tests.py example.settings.postgres
python {envbindir}/coverage run -p example/run_tests.py example.settings.postgres_no_debug
python {envbindir}/coverage run -p example/run_tests.py example.settings.mysql
python {envbindir}/coverage run -p example/run_tests.py example.settings.mysql_no_debug
deps =
django==1.7
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
psycopg2==2.5.1
git+https://github.com/clelland/MySQL-for-Python-3#egg=MySQL-python


[testenv:py33-dj16]
basepython = python3.3
commands =
Expand Down Expand Up @@ -275,3 +311,21 @@ deps =
coveralls==0.3
psycopg2==2.5.1
git+https://github.com/clelland/MySQL-for-Python-3#egg=MySQL-python


[testenv:py34-dj17]
basepython = python3.4
commands =
python {envbindir}/coverage run -p example/run_tests.py
python {envbindir}/coverage run -p example/run_tests.py example.settings.no_debug
python {envbindir}/coverage run -p example/run_tests.py example.settings.postgres
python {envbindir}/coverage run -p example/run_tests.py example.settings.postgres_no_debug
python {envbindir}/coverage run -p example/run_tests.py example.settings.mysql
python {envbindir}/coverage run -p example/run_tests.py example.settings.mysql_no_debug
deps =
django==1.7
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
psycopg2==2.5.1
git+https://github.com/clelland/MySQL-for-Python-3#egg=MySQL-python

0 comments on commit c8a1f1c

Please sign in to comment.