diff --git a/.travis.yml b/.travis.yml index 3e60bbc..0103790 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: - "3.4" - "3.5" - "3.6" - - "pypy" install: - pip install coveralls tox-travis script: diff --git a/README.md b/README.md index 3f54b48..0f02027 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This repository is part of a larger project. To read about it, please see ## Requirements This library requires -* Python 2.7 (including PyPy), 3.4, 3.5, or 3.6 +* Python 2.7, 3.4, 3.5, or 3.6 * Django 1.8, 1.9, 1.10, or 1.11 ## API Docs diff --git a/regcore_pgsql/tests/views_tests.py b/regcore_pgsql/tests/views_tests.py index 93e8de4..13ac423 100644 --- a/regcore_pgsql/tests/views_tests.py +++ b/regcore_pgsql/tests/views_tests.py @@ -1,4 +1,5 @@ import pytest +from django.db.models import Q from mock import call, Mock pytest.importorskip('django', minversion='1.10') # noqa @@ -32,22 +33,80 @@ def test_matching_sections(monkeypatch): assert call(documentindex__doc_root='rrr') in filters -def test_transform_results(): - """Verify conversion to a dict.""" - sect1, sect2 = Mock(title='Section 111'), Mock(title='Section 222') - query1, query2 = make_queryset_mock(), make_queryset_mock() - sect1.get_descendants.return_value = query1 - sect2.get_descendants.return_value = query2 - query1.first.return_value = doc_recipe.prepare( - text='sect1', label_string='root-111', version='vvv', - title='Section 111') - query2.first.return_value = doc_recipe.prepare( - text='subpar', label_string='root-222-a-3', version='vvv') - - assert views.transform_results([sect1, sect2], 'my terms') == [ - dict(text='sect1', label=['root', '111'], version='vvv', - regulation='root', label_string='root-111', title='Section 111'), - dict(text='subpar', label=['root', '222', 'a', '3'], version='vvv', - regulation='root', label_string='root-222-a-3', - title='Section 222'), - ] +@pytest.mark.django_db +def test_transform_results(monkeypatch): + """If there's a text match inside a section, we should convert it to a + dictionary.""" + monkeypatch.setattr( # __search isn't supported by sqlite + views, 'Q', Mock(return_value=Q(text__contains='matching'))) + sect = doc_recipe.make(label_string='root-11', title='Sect 111', + version='vvv') + par_a = doc_recipe.make(label_string='root-11-a', parent=sect) + doc_recipe.make(text='matching text', label_string='root-11-a-3', + parent=par_a, title="Match's title") + + results = views.transform_results([sect], 'this is a query') + assert results == [{ + 'text': 'matching text', + 'label': ['root', '11', 'a', '3'], + 'version': 'vvv', + 'regulation': 'root', + 'label_string': 'root-11-a-3', + 'match_title': "Match's title", + 'paragraph_title': "Match's title", + 'section_title': 'Sect 111', + 'title': 'Sect 111', + }] + + +@pytest.mark.django_db +def test_transform_title_match(monkeypatch): + """If there's a title match with no text, we should conver to the correct + dictionary.""" + monkeypatch.setattr( # __search isn't supported by sqlite + views, 'Q', Mock(return_value=Q(title__contains='matching'))) + sect = doc_recipe.make(label_string='root-11', title='Sect 111', + version='vvv') + par_a = doc_recipe.make(label_string='root-11-a', parent=sect, text='', + title='matching title') + doc_recipe.make(label_string='root-11-a-3', parent=par_a, + text='inner text', title='inner title') + + results = views.transform_results([sect], 'this is a query') + assert results == [{ + 'text': 'inner text', + 'label': ['root', '11', 'a'], + 'version': 'vvv', + 'regulation': 'root', + 'label_string': 'root-11-a', + 'match_title': 'matching title', + 'paragraph_title': 'inner title', + 'section_title': 'Sect 111', + 'title': 'Sect 111', + }] + + +@pytest.mark.django_db +def test_transform_no_exact_match(monkeypatch): + """If text is searched text is broken across multiple paragraphs, we + should just graph the first text node we can find.""" + monkeypatch.setattr( # __search isn't supported by sqlite + views, 'Q', Mock(return_value=Q(text=None))) # will have no results + sect = doc_recipe.make(label_string='root-11', text='', title='Sect 111', + version='vvv') + par_a = doc_recipe.make(label_string='root-11-a', parent=sect, + text='has some text', title='nonmatching title') + doc_recipe.make(label_string='root-11-a-3', parent=par_a) + + results = views.transform_results([sect], 'this is a query') + assert results == [{ + 'text': 'has some text', + 'label': ['root', '11'], + 'version': 'vvv', + 'regulation': 'root', + 'label_string': 'root-11', + 'match_title': 'Sect 111', + 'paragraph_title': 'nonmatching title', + 'section_title': 'Sect 111', + 'title': 'Sect 111', + }] diff --git a/regcore_pgsql/views.py b/regcore_pgsql/views.py index ed6d6e2..967e5ec 100644 --- a/regcore_pgsql/views.py +++ b/regcore_pgsql/views.py @@ -40,17 +40,25 @@ def transform_results(sections, search_terms): serialization.""" final_results = [] for section in sections: - first_match = section.get_descendants(include_self=True)\ + # TODO: n+1 problem; hypothetically these could all be performed via + # subqueries and annotated on the sections queryset + match_node = section.get_descendants(include_self=True)\ .filter(Q(text__search=search_terms) | Q(title__search=search_terms))\ + .first() or section + text_node = match_node.get_descendants(include_self=True)\ + .exclude(text='')\ .first() final_results.append({ - 'text': first_match.text, - 'label': first_match.label_string.split('-'), - 'version': first_match.version, - 'regulation': first_match.label_string.split('-')[0], - 'label_string': first_match.label_string, + 'text': text_node.text if text_node else '', + 'label': match_node.label_string.split('-'), + 'version': section.version, + 'regulation': section.label_string.split('-')[0], + 'label_string': match_node.label_string, + 'match_title': match_node.title, + 'paragraph_title': text_node.title if text_node else '', + 'section_title': section.title, 'title': section.title, }) return final_results diff --git a/setup.py b/setup.py index 3704746..9016147 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="regcore", - version="4.0.0", + version="4.1.0", license="public domain", packages=find_packages(), include_package_data=True, diff --git a/tox.ini b/tox.ini index cceb3d1..2565465 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = clean,py{27,34,35,36,py}-django{18,19,110,111}-{elastic,haystack},py{27,34,35,36}-django{110,111}-pgsql,lint,docs +envlist = clean,py{27,34,35,36}-django{18,19,110,111}-{elastic,haystack},py{27,34,35,36}-django{110,111}-pgsql,lint,docs [testenv] deps =