Skip to content

Commit

Permalink
Updated to py3 and added support for choice fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Moritz Hille committed Jul 15, 2021
1 parent 0df3298 commit 6fd9f8a
Show file tree
Hide file tree
Showing 22 changed files with 105 additions and 85 deletions.
23 changes: 12 additions & 11 deletions django_find/dom.py
@@ -1,17 +1,18 @@
from __future__ import absolute_import, print_function
from builtins import str
from .tree import Node

operators = ['contains',
'equals',
'startswith',
'endswith',
'regex',
'gt',
'gte',
'lt',
'lte',
'any']
operators = [
'contains',
'equals',
'startswith',
'endswith',
'regex',
'gt',
'gte',
'lt',
'lte',
'any'
]

class Group(Node):
def translate_term_names(self, name_map):
Expand Down
4 changes: 2 additions & 2 deletions django_find/models.py
Expand Up @@ -2,8 +2,8 @@
This module contains the Searchable mixin, the main public API of
django-find.
"""
from __future__ import absolute_import, print_function
from collections import OrderedDict
from django.apps import AppConfig
from django.db import models
from .parsers.query import QueryParser
from .parsers.json import JSONParser
Expand Down Expand Up @@ -114,7 +114,7 @@ def get_field_from_selector(cls, selector):
model = cls
while '__' in selector:
model_name, selector = selector.split('__', 1)
model = model._meta.get_field(model_name).rel.to
model = model._meta.get_field(model_name).remote_field.model

return model, model._meta.get_field(selector)

Expand Down
2 changes: 1 addition & 1 deletion django_find/parsers/json.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function

import json
from collections import OrderedDict
from ..dom import Group, And, Or, Not, Term
Expand Down
1 change: 0 additions & 1 deletion django_find/parsers/parser.py
@@ -1,4 +1,3 @@

class Parser(object):
"""
The base class for all parsers.
Expand Down
49 changes: 31 additions & 18 deletions django_find/parsers/query.py
@@ -1,5 +1,6 @@
from __future__ import absolute_import, print_function
import re
from ..refs import get_subclasses
from django_find import models
from collections import OrderedDict
from .parser import Parser
from ..dom import Group, And, Or, Not, Term
Expand All @@ -18,17 +19,19 @@
('=', 'equals'),
))

operators_str = '|'.join(operators.keys())
tokens = [('and', re.compile(r'and\b', re.I)),
('or', re.compile(r'or\b', re.I)),
('not', re.compile(r'not\b', re.I)),
('openbracket', re.compile(r'\(')),
('closebracket', re.compile(r'\)')),
('whitespace', re.compile(r'\s+')),
('field', re.compile(r'([\w\-]+)({})'.format(operators_str))),
('word', re.compile(r'"([^"]*)"')),
('word', re.compile(r'([^"\s\\\'\)]+)')),
('unknown', re.compile(r'.'))]
operators_str = '|'.join(list(operators.keys()))
tokens = [
('and', re.compile(r'and\b', re.I)),
('or', re.compile(r'or\b', re.I)),
('not', re.compile(r'not\b', re.I)),
('openbracket', re.compile(r'\(')),
('closebracket', re.compile(r'\)')),
('whitespace', re.compile(r'\s+')),
('field', re.compile(r'([\w\-]+)({})'.format(operators_str))),
('word', re.compile(r'"([^"]*)"')),
('word', re.compile(r'([^"\s\\\'\)]+)')),
('unknown', re.compile(r'.'))
]

def open_scope(scopes, scope):
scopes.append(scopes[-1].add(scope))
Expand Down Expand Up @@ -98,6 +101,15 @@ def parse_field(self, scopes, match):
except IndexError:
return

for subclass in get_subclasses(models.Searchable):
try:
for k, v in subclass._meta.get_field(field_name).choices:
if value == v:
value = k
break
except:
continue

term = get_term_from_op(field, op, value)
scopes[-1].add(term)
close_scope(scopes)
Expand Down Expand Up @@ -136,9 +148,6 @@ def parse_closebracket(self, scopes, match):
# Auto-leave the parent scope (if necessary).
close_scope(scopes)

def parse_whitespace(self, scopes, match):
pass

def parse(self, query):
self._reset()
self.input = query.strip()
Expand All @@ -147,8 +156,12 @@ def parse(self, query):
token, match = self._get_next_token()

while token != 'EOF':
parse_func = getattr(self, 'parse_'+token)
parse_func(scopes, match)
token, match = self._get_next_token()
try:
parse_func = getattr(self, 'parse_'+token)
parse_func(scopes, match)
except AttributeError:
pass
finally:
token, match = self._get_next_token()

return result.optimize()
4 changes: 2 additions & 2 deletions django_find/refs.py
Expand Up @@ -47,14 +47,14 @@ def parent_classes(cls):
parents = []
for field in cls._meta.get_fields():
if isinstance(field, (models.ForeignKey, models.ManyToManyField)):
parents.append(field.rel.to)
parents.append(field.remote_field.model)
return parents

def get_field_to(cls, target_cls):
for field in cls._meta.get_fields():
if not isinstance(field, (models.ForeignKey, models.ManyToManyField)):
continue
if field.rel.to is target_cls:
if field.remote_field.model is target_cls:
return field
return None

Expand Down
2 changes: 1 addition & 1 deletion django_find/serializers/django.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function

from functools import reduce
from django.db.models import Q
from .serializer import Serializer
Expand Down
36 changes: 21 additions & 15 deletions django_find/serializers/sql.py
@@ -1,24 +1,29 @@
from __future__ import absolute_import, print_function, unicode_literals
from builtins import str
from collections import defaultdict, OrderedDict
from MySQLdb import escape_string
from ..refs import get_join_for
from .serializer import Serializer
from .util import parse_date, parse_datetime

int_op_map = {'equals': 'equals',
'contains': 'equals',
'startswith': 'gte',
'endswith': 'lte'}

str_op_map = {'gt': 'startswith',
'gte': 'startswith',
'lt': 'endswith',
'lte': 'endswith'}

date_op_map = {'contains': 'equals',
'startswith': 'gte',
'endswith': 'lte'}
int_op_map = {
'equals': 'equals',
'contains': 'equals',
'startswith': 'gte',
'endswith': 'lte'
}

str_op_map = {
'gt': 'startswith',
'gte': 'startswith',
'lt': 'endswith',
'lte': 'endswith'
}

date_op_map = {
'contains': 'equals',
'startswith': 'gte',
'endswith': 'lte'
}

operator_map = {
'equals': "='{}'",
Expand All @@ -30,7 +35,8 @@
'startswith': " LIKE '{}%%'",
'endswith': " LIKE '%%{}'",
'contains': " LIKE '%%{}%%'",
'regex': " REGEXP '%%{}%%'"}
'regex': " REGEXP '%%{}%%'"
}

def _mkcol(tbl, name, alias):
return tbl+'.'+name+' '+tbl+'_'+alias
Expand Down
2 changes: 1 addition & 1 deletion django_find/templatetags/find_tags.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function

from django import template
from django.template.loader import render_to_string

Expand Down
2 changes: 1 addition & 1 deletion django_find/tree.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function


class Node(object):
def __init__(self, children=None, is_root=False):
Expand Down
24 changes: 12 additions & 12 deletions docs/conf.py
Expand Up @@ -51,8 +51,8 @@
master_doc = 'index'

# General information about the project.
project = u'django-find'
copyright = u'2018, Samuel Abels'
project = 'django-find'
copyright = '2018, Samuel Abels'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
Expand Down Expand Up @@ -194,8 +194,8 @@
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'django-find.tex', u'django-find Documentation',
u'Samuel Abels', 'manual'),
('index', 'django-find.tex', 'django-find Documentation',
'Samuel Abels', 'manual'),
]

# The name of an image file (relative to this directory) to place at the top of
Expand Down Expand Up @@ -224,8 +224,8 @@
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'django-find', u'django-find Documentation',
[u'Samuel Abels'], 1)
('index', 'django-find', 'django-find Documentation',
['Samuel Abels'], 1)
]

# If true, show URL addresses after external links.
Expand All @@ -238,8 +238,8 @@
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'django-find', u'django-find Documentation',
u'Samuel Abels', 'django-find', 'Query your models using human-friendly query language',
('index', 'django-find', 'django-find Documentation',
'Samuel Abels', 'django-find', 'Query your models using human-friendly query language',
'Miscellaneous'),
]

Expand All @@ -256,10 +256,10 @@
# -- Options for Epub output ---------------------------------------------------

# Bibliographic Dublin Core info.
epub_title = u'django-find'
epub_author = u'Samuel Abels'
epub_publisher = u'Samuel Abels'
epub_copyright = u'2018, Samuel Abels'
epub_title = 'django-find'
epub_author = 'Samuel Abels'
epub_publisher = 'Samuel Abels'
epub_copyright = '2018, Samuel Abels'

# The language of the text. It defaults to the language option
# or en if the language is not set.
Expand Down
3 changes: 2 additions & 1 deletion runtests.py
Expand Up @@ -44,6 +44,7 @@
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.contrib.messages.context_processors.messages',
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
Expand All @@ -58,7 +59,7 @@
TEMPLATE_DEBUG=False,
ALLOWED_HOSTS=[],
INSTALLED_APPS=ALWAYS_INSTALLED_APPS + CUSTOM_INSTALLED_APPS,
MIDDLEWARE_CLASSES=ALWAYS_MIDDLEWARE_CLASSES,
MIDDLEWARE=ALWAYS_MIDDLEWARE_CLASSES,
TEMPLATES=TEMPLATES,
ROOT_URLCONF='tests.urls',
DATABASES={
Expand Down
2 changes: 1 addition & 1 deletion tests/parsers/test_json.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from django.test import TestCase
from django_find.parsers.json import JSONParser

Expand Down
2 changes: 1 addition & 1 deletion tests/parsers/test_query.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from django.test import TestCase
from django_find.parsers.query import QueryParser

Expand Down
2 changes: 1 addition & 1 deletion tests/serializers/test_django.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function

from datetime import date, datetime
import datetime
from django.test import TestCase
Expand Down
2 changes: 1 addition & 1 deletion tests/serializers/test_sql.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function

from django.test import TestCase
from django_find.parsers.json import JSONParser
from django_find.serializers.sql import SQLSerializer
Expand Down
2 changes: 1 addition & 1 deletion tests/test_handlers.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from copy import copy
from django.test import TestCase
from django_find.handlers import type_registry, LowerCaseStrFieldHandler
Expand Down
2 changes: 1 addition & 1 deletion tests/test_rawquery.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from django.test import TestCase
from django_find.rawquery import PaginatedRawQuerySet
from .models import Author
Expand Down
20 changes: 10 additions & 10 deletions tests/test_refs.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import, print_function

from django.test import TestCase
from django_find import Searchable
from django_find.refs import get_subclasses, child_classes, parent_classes, \
Expand Down Expand Up @@ -56,15 +56,15 @@ def testGetObjectVectorTo(self):
(Author, DerivedAuthor, Book, SecondAuthor)])

def testGetJoinFor(self):
expected = [(u'search_tests_author', None, None),
(u'search_tests_book', u'author_id', u'search_tests_author.id'),
(u'search_tests_chapter_book', u'book_id', u'search_tests_book.id'),
(u'search_tests_chapter', u'id', u'search_tests_chapter_book.chapter_id')]
expected = [('search_tests_author', None, None),
('search_tests_book', 'author_id', 'search_tests_author.id'),
('search_tests_chapter_book', 'book_id', 'search_tests_book.id'),
('search_tests_chapter', 'id', 'search_tests_chapter_book.chapter_id')]
self.assertEqual(get_join_for((Author, Book, Chapter)), expected)

expected = [(u'search_tests_chapter', None, None),
(u'search_tests_chapter_book', u'chapter_id', u'search_tests_chapter.id'),
(u'search_tests_book', u'id', u'search_tests_chapter_book.book_id'),
(u'search_tests_author', u'id', u'search_tests_book.author_id'),
(u'search_tests_secondauthor', u'author_id', u'search_tests_author.id')]
expected = [('search_tests_chapter', None, None),
('search_tests_chapter_book', 'chapter_id', 'search_tests_chapter.id'),
('search_tests_book', 'id', 'search_tests_chapter_book.book_id'),
('search_tests_author', 'id', 'search_tests_book.author_id'),
('search_tests_secondauthor', 'author_id', 'search_tests_author.id')]
self.assertEqual(get_join_for((Chapter, Book, Author, SecondAuthor)), expected)
2 changes: 1 addition & 1 deletion tests/test_searchable.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from django.test import TestCase
from django_find.handlers import LowerCaseStrFieldHandler, IntegerFieldHandler
from .models import Author, DerivedAuthor, SecondAuthor, Book, Chapter, \
Expand Down
2 changes: 1 addition & 1 deletion tests/test_tags.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from django.test import TestCase
from django.test.client import RequestFactory
from django.template import Template, Context
Expand Down
2 changes: 1 addition & 1 deletion tests/test_templates.py
@@ -1,4 +1,4 @@
from __future__ import absolute_import

from django.test import TestCase
from django.test.client import RequestFactory
from django.template import Template, Context
Expand Down

0 comments on commit 6fd9f8a

Please sign in to comment.