Skip to content

Commit 514023e

Browse files
committed
Django 2.0 compatibility (closes #1582)
Thanks to @mpauly and @timgraham for working on this and @dani0805, @andrewbenedictwallace, @RabidCicada, @webtweakers, @nadimtuhin, and @JonLevischi for testing.
2 parents 4b7681c + 40cbee5 commit 514023e

20 files changed

+118
-186
lines changed

.travis.yml

+10-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ python:
55
- 2.7
66
- 3.4
77
- 3.5
8+
- 3.6
89
- pypy
910

1011
cache:
@@ -58,17 +59,19 @@ script:
5859

5960
env:
6061
matrix:
61-
- DJANGO_VERSION=">=1.8,<1.9" VERSION_ES=">=1.0.0,<2.0.0"
62-
- DJANGO_VERSION=">=1.9,<1.10" VERSION_ES=">=1.0.0,<2.0.0"
63-
- DJANGO_VERSION=">=1.10,<1.11" VERSION_ES=">=1.0.0,<2.0.0"
64-
- DJANGO_VERSION=">=1.8,<1.9" VERSION_ES=">=2.0.0,<3.0.0"
65-
- DJANGO_VERSION=">=1.9,<1.10" VERSION_ES=">=2.0.0,<3.0.0"
66-
- DJANGO_VERSION=">=1.10,<1.11" VERSION_ES=">=2.0.0,<3.0.0"
67-
- DJANGO_VERSION=">=1.11,<1.12" VERSION_ES=">=2.0.0,<3.0.0"
62+
- DJANGO_VERSION=">=1.11,<2.0" VERSION_ES=">=1.0.0,<2.0.0"
63+
- DJANGO_VERSION=">=2.0,<2.1" VERSION_ES=">=1.0.0,<2.0.0"
64+
- DJANGO_VERSION=">=1.11,<2.0" VERSION_ES=">=2.0.0,<3.0.0"
65+
- DJANGO_VERSION=">=2.0,<2.1" VERSION_ES=">=2.0.0,<3.0.0"
6866

6967
matrix:
7068
allow_failures:
7169
- python: 'pypy'
70+
exclude:
71+
- python: 2.7
72+
env: DJANGO_VERSION=">=2.0,<2.1" VERSION_ES=">=2.0.0,<3.0.0"
73+
- python: 2.7
74+
env: DJANGO_VERSION=">=2.0,<2.1" VERSION_ES=">=1.0.0,<2.0.0"
7275

7376
notifications:
7477
irc: "irc.freenode.org#haystack"

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,4 @@ Thanks to
117117
* Morgan Aubert (@ellmetha) for Django 1.10 support
118118
* João Junior (@joaojunior) and Bruno Marques (@ElSaico) for Elasticsearch 2.x support
119119
* Alex Tomkins (@tomkins) for various patches
120+
* Martin Pauly (@mpauly) for Django 2.0 support

haystack/backends/elasticsearch_backend.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_of
296296
for field, direction in sort_by:
297297
if field == 'distance' and distance_point:
298298
# Do the geo-enabled sort.
299-
lng, lat = distance_point['point'].get_coords()
299+
lng, lat = distance_point['point'].coords
300300
sort_kwargs = {
301301
"_geo_distance": {
302302
distance_point['field']: [lng, lat],
@@ -455,7 +455,7 @@ def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_of
455455
filters.append(within_filter)
456456

457457
if dwithin is not None:
458-
lng, lat = dwithin['point'].get_coords()
458+
lng, lat = dwithin['point'].coords
459459

460460
# NB: the 1.0.0 release of elasticsearch introduce an
461461
# incompatible change on the distance filter formating

haystack/backends/solr_backend.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_of
173173
if sort_by is not None:
174174
if sort_by in ['distance asc', 'distance desc'] and distance_point:
175175
# Do the geo-enabled sort.
176-
lng, lat = distance_point['point'].get_coords()
176+
lng, lat = distance_point['point'].coords
177177
kwargs['sfield'] = distance_point['field']
178178
kwargs['pt'] = '%s,%s' % (lat, lng)
179179

@@ -290,7 +290,7 @@ def build_search_kwargs(self, query_string, sort_by=None, start_offset=0, end_of
290290

291291
if dwithin is not None:
292292
kwargs.setdefault('fq', [])
293-
lng, lat = dwithin['point'].get_coords()
293+
lng, lat = dwithin['point'].coords
294294
geofilt = '{!geofilt pt=%s,%s sfield=%s d=%s}' % (lat, lng, dwithin['field'], dwithin['distance'].km)
295295
kwargs['fq'].append(geofilt)
296296

haystack/indexes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def should_skip_field(self, field):
443443
return True
444444

445445
# Ignore certain fields (AutoField, related fields).
446-
if field.primary_key or getattr(field, 'rel'):
446+
if field.primary_key or field.is_relation:
447447
return True
448448

449449
return False

haystack/management/commands/rebuild_index.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# encoding: utf-8
2-
32
from __future__ import absolute_import, division, print_function, unicode_literals
43

54
from django.core.management import call_command
@@ -33,5 +32,11 @@ def add_arguments(self, parser):
3332
)
3433

3534
def handle(self, **options):
36-
call_command('clear_index', **options)
37-
call_command('update_index', **options)
35+
clear_options = options.copy()
36+
update_options = options.copy()
37+
for key in ('batchsize', 'workers'):
38+
del clear_options[key]
39+
for key in ('interactive', ):
40+
del update_options[key]
41+
call_command('clear_index', **clear_options)
42+
call_command('update_index', **update_options)

haystack/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ def _get_distance(self):
133133
if location_field is None:
134134
return None
135135

136-
lf_lng, lf_lat = location_field.get_coords()
136+
lf_lng, lf_lat = location_field.coords
137137
self._distance = Distance(km=geopy_distance.distance((po_lat, po_lng), (lf_lat, lf_lng)).km)
138138

139139
# We've either already calculated it or the backend returned it, so

haystack/utils/geo.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def ensure_wgs84(point):
4343

4444
if not new_point.srid:
4545
# It has no spatial reference id. Assume WGS-84.
46-
new_point.set_srid(WGS_84_SRID)
46+
new_point.srid = WGS_84_SRID
4747
elif new_point.srid != WGS_84_SRID:
4848
# Transform it to get to the right system.
4949
new_point.transform(WGS_84_SRID)
@@ -72,7 +72,7 @@ def generate_bounding_box(bottom_left, top_right):
7272
7373
The two-tuple is in the form ``((min_lat, min_lng), (max_lat, max_lng))``.
7474
"""
75-
west, lat_1 = bottom_left.get_coords()
76-
east, lat_2 = top_right.get_coords()
75+
west, lat_1 = bottom_left.coords
76+
east, lat_2 = top_right.coords
7777
min_lat, max_lat = min(lat_1, lat_2), max(lat_1, lat_2)
7878
return ((min_lat, west), (max_lat, east))

setup.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from setuptools import setup
1313

1414
install_requires = [
15-
'Django>=1.8,<1.12',
15+
'Django>=1.11',
1616
]
1717

1818
tests_require = [
@@ -54,6 +54,8 @@
5454
'Development Status :: 5 - Production/Stable',
5555
'Environment :: Web Environment',
5656
'Framework :: Django',
57+
'Framework :: Django :: 1.11',
58+
'Framework :: Django :: 2.0',
5759
'Intended Audience :: Developers',
5860
'License :: OSI Approved :: BSD License',
5961
'Operating System :: OS Independent',

test_haystack/core/models.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class MockModel(models.Model):
1717
author = models.CharField(max_length=255)
1818
foo = models.CharField(max_length=255, blank=True)
1919
pub_date = models.DateTimeField(default=datetime.datetime.now)
20-
tag = models.ForeignKey(MockTag)
20+
tag = models.ForeignKey(MockTag, models.CASCADE)
2121

2222
def __unicode__(self):
2323
return self.author
@@ -108,4 +108,4 @@ class OneToManyLeftSideModel(models.Model):
108108

109109

110110
class OneToManyRightSideModel(models.Model):
111-
left_side = models.ForeignKey(OneToManyLeftSideModel, related_name='right_side')
111+
left_side = models.ForeignKey(OneToManyLeftSideModel, models.CASCADE, related_name='right_side')

test_haystack/core/urls.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515
urlpatterns = [
16-
url(r'^admin/', include(admin.site.urls)),
16+
url(r'^admin/', admin.site.urls),
1717

1818
url(r'^$', SearchView(load_all=False), name='haystack_search'),
1919
url(r'^faceted/$',
@@ -23,5 +23,5 @@
2323
]
2424

2525
urlpatterns += [
26-
url(r'', include('test_haystack.test_app_without_models.urls', namespace='app-without-models')),
26+
url(r'', include(('test_haystack.test_app_without_models.urls', 'app-without-models'))),
2727
]

test_haystack/settings.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,11 @@
5151
},
5252
]
5353

54-
MIDDLEWARE_CLASSES = [
54+
MIDDLEWARE = [
5555
'django.middleware.common.CommonMiddleware',
5656
'django.contrib.sessions.middleware.SessionMiddleware',
5757
'django.middleware.csrf.CsrfViewMiddleware',
5858
'django.contrib.auth.middleware.AuthenticationMiddleware',
59-
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
6059
'django.contrib.messages.middleware.MessageMiddleware',
6160
]
6261

test_haystack/solr_tests/test_admin.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.contrib.auth.models import User
77
from django.test import TestCase
88
from django.test.utils import override_settings
9-
from django.core.urlresolvers import reverse
9+
from django.urls import reverse
1010

1111
from haystack import connections, reset_search_queries
1212
from haystack.utils.loading import UnifiedIndex

test_haystack/solr_tests/test_solr_management_commands.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def test_build_schema_wrong_backend(self):
194194
'PATH': mkdtemp(prefix='dummy-path-'), }
195195

196196
connections['whoosh']._index = self.ui
197-
self.assertRaises(ImproperlyConfigured, call_command, 'build_solr_schema', using='whoosh', interactive=False)
197+
self.assertRaises(ImproperlyConfigured, call_command, 'build_solr_schema', using='whoosh')
198198

199199
def test_build_schema(self):
200200

@@ -276,38 +276,38 @@ def test_app_model_variations(self):
276276
call_command('clear_index', interactive=False, verbosity=0)
277277
self.assertEqual(self.solr.search('*:*').hits, 0)
278278

279-
call_command('update_index', 'core', interactive=False, verbosity=0)
279+
call_command('update_index', 'core', verbosity=0)
280280
self.assertEqual(self.solr.search('*:*').hits, 25)
281281

282282
call_command('clear_index', interactive=False, verbosity=0)
283283
self.assertEqual(self.solr.search('*:*').hits, 0)
284284

285285
with self.assertRaises(ImproperlyConfigured):
286-
call_command('update_index', 'fake_app_thats_not_there', interactive=False)
286+
call_command('update_index', 'fake_app_thats_not_there')
287287

288-
call_command('update_index', 'core', 'discovery', interactive=False, verbosity=0)
288+
call_command('update_index', 'core', 'discovery', verbosity=0)
289289
self.assertEqual(self.solr.search('*:*').hits, 25)
290290

291291
call_command('clear_index', interactive=False, verbosity=0)
292292
self.assertEqual(self.solr.search('*:*').hits, 0)
293293

294-
call_command('update_index', 'discovery', interactive=False, verbosity=0)
294+
call_command('update_index', 'discovery', verbosity=0)
295295
self.assertEqual(self.solr.search('*:*').hits, 0)
296296

297297
call_command('clear_index', interactive=False, verbosity=0)
298298
self.assertEqual(self.solr.search('*:*').hits, 0)
299299

300-
call_command('update_index', 'core.MockModel', interactive=False, verbosity=0)
300+
call_command('update_index', 'core.MockModel', verbosity=0)
301301
self.assertEqual(self.solr.search('*:*').hits, 23)
302302

303303
call_command('clear_index', interactive=False, verbosity=0)
304304
self.assertEqual(self.solr.search('*:*').hits, 0)
305305

306-
call_command('update_index', 'core.MockTag', interactive=False, verbosity=0)
306+
call_command('update_index', 'core.MockTag', verbosity=0)
307307
self.assertEqual(self.solr.search('*:*').hits, 2)
308308

309309
call_command('clear_index', interactive=False, verbosity=0)
310310
self.assertEqual(self.solr.search('*:*').hits, 0)
311311

312-
call_command('update_index', 'core.MockTag', 'core.MockModel', interactive=False, verbosity=0)
312+
call_command('update_index', 'core.MockTag', 'core.MockModel', verbosity=0)
313313
self.assertEqual(self.solr.search('*:*').hits, 25)

test_haystack/spatial/test_spatial.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_ensure_wgs84(self):
3737
self.assertEqual(std_pnt.y, 38.97127105172941)
3838

3939
orig_pnt = Point(-95.23592948913574, 38.97127105172941)
40-
orig_pnt.set_srid(2805)
40+
orig_pnt.srid = 2805
4141
std_pnt = ensure_wgs84(orig_pnt)
4242
self.assertEqual(orig_pnt.srid, 2805)
4343
self.assertEqual(std_pnt.srid, 4326)
@@ -96,8 +96,8 @@ def test_indexing(self):
9696
self.assertEqual(sqs.count(), 1)
9797
self.assertEqual(sqs[0].username, first.username)
9898
# Make sure we've got a proper ``Point`` object.
99-
self.assertAlmostEqual(sqs[0].location.get_coords()[0], first.longitude)
100-
self.assertAlmostEqual(sqs[0].location.get_coords()[1], first.latitude)
99+
self.assertAlmostEqual(sqs[0].location.coords[0], first.longitude)
100+
self.assertAlmostEqual(sqs[0].location.coords[1], first.latitude)
101101

102102
# Double-check, to make sure there was nothing accidentally copied
103103
# between instances.
@@ -106,8 +106,8 @@ def test_indexing(self):
106106
sqs = self.sqs.models(Checkin).filter(django_id=second.pk)
107107
self.assertEqual(sqs.count(), 1)
108108
self.assertEqual(sqs[0].username, second.username)
109-
self.assertAlmostEqual(sqs[0].location.get_coords()[0], second.longitude)
110-
self.assertAlmostEqual(sqs[0].location.get_coords()[1], second.latitude)
109+
self.assertAlmostEqual(sqs[0].location.coords[0], second.longitude)
110+
self.assertAlmostEqual(sqs[0].location.coords[1], second.latitude)
111111

112112
def test_within(self):
113113
self.assertEqual(self.sqs.all().count(), 10)

test_haystack/test_app_loading.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from types import GeneratorType, ModuleType
55

6-
from django.core.urlresolvers import reverse
6+
from django.urls import reverse
77
from django.test import TestCase
88

99
from haystack.utils import app_loading

test_haystack/test_indexes.py

+7
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,11 @@ def read_queryset(self, using=None):
581581
return self.get_model().objects.complete_set()
582582

583583

584+
class ModelWithManyToManyFieldModelSearchIndex(indexes.ModelSearchIndex):
585+
def get_model(self):
586+
return ManyToManyLeftSideModel
587+
588+
584589
class ModelSearchIndexTestCase(TestCase):
585590
def setUp(self):
586591
super(ModelSearchIndexTestCase, self).setUp()
@@ -590,6 +595,7 @@ def setUp(self):
590595
self.emsi = ExcludesModelSearchIndex()
591596
self.fwomsi = FieldsWithOverrideModelSearchIndex()
592597
self.yabmsi = YetAnotherBasicModelSearchIndex()
598+
self.m2mmsi = ModelWithManyToManyFieldModelSearchIndex()
593599

594600
def test_basic(self):
595601
self.assertEqual(len(self.bmsi.fields), 4)
@@ -623,6 +629,7 @@ def test_excludes(self):
623629
self.assertTrue(isinstance(self.emsi.fields['pub_date'], indexes.DateTimeField))
624630
self.assertTrue('text' in self.emsi.fields)
625631
self.assertTrue(isinstance(self.emsi.fields['text'], indexes.CharField))
632+
self.assertNotIn('related_models', self.m2mmsi.fields)
626633

627634
def test_fields_with_override(self):
628635
self.assertEqual(len(self.fwomsi.fields), 3)

test_haystack/test_management_commands.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def test_rebuild_index_using(self, m1, m2):
6666
m2.assert_called_with("eng")
6767
m1.assert_any_call("core", "eng")
6868

69-
@patch('haystack.management.commands.update_index.Command.handle')
70-
@patch('haystack.management.commands.clear_index.Command.handle')
69+
@patch('haystack.management.commands.update_index.Command.handle', return_value='')
70+
@patch('haystack.management.commands.clear_index.Command.handle', return_value='')
7171
def test_rebuild_index(self, mock_handle_clear, mock_handle_update):
7272
call_command('rebuild_index', interactive=False)
7373

@@ -87,9 +87,9 @@ def test_rebuild_index_nocommit(self, *mocks):
8787
self.assertIn('commit', kwargs)
8888
self.assertEqual(False, kwargs['commit'])
8989

90-
@patch('haystack.management.commands.update_index.Command.handle')
91-
@patch('haystack.management.commands.clear_index.Command.handle')
92-
def test_rebuild_index_nocommit(self, *mocks):
90+
@patch('haystack.management.commands.clear_index.Command.handle', return_value='')
91+
@patch('haystack.management.commands.update_index.Command.handle', return_value='')
92+
def test_rebuild_index_nocommit(self, update_mock, clear_mock):
9393
"""
9494
Confirm that command-line option parsing produces the same results as using call_command() directly,
9595
mostly as a sanity check for the logic in rebuild_index which combines the option_lists for its
@@ -99,13 +99,15 @@ def test_rebuild_index_nocommit(self, *mocks):
9999

100100
Command().run_from_argv(['django-admin.py', 'rebuild_index', '--noinput', '--nocommit'])
101101

102-
for m in mocks:
102+
for m in (clear_mock, update_mock):
103103
self.assertEqual(m.call_count, 1)
104104

105105
args, kwargs = m.call_args
106106

107107
self.assertIn('commit', kwargs)
108108
self.assertEqual(False, kwargs['commit'])
109109

110-
self.assertIn('interactive', kwargs)
111-
self.assertEqual(False, kwargs['interactive'])
110+
args, kwargs = clear_mock.call_args
111+
112+
self.assertIn('interactive', kwargs)
113+
self.assertIs(kwargs['interactive'], False)

test_haystack/test_views.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
from threading import Thread
77

88
from django import forms
9-
from django.core.urlresolvers import reverse
109
from django.http import HttpRequest, QueryDict
1110
from django.test import TestCase, override_settings
1211
from django.utils.six.moves import queue
12+
from django.urls import reverse
1313
from test_haystack.core.models import AnotherMockModel, MockModel
1414

1515
from haystack import connections, indexes

0 commit comments

Comments
 (0)