django-find 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. django_find-1.0.1/.gitignore +6 -0
  2. django_find-1.0.1/LICENSE +17 -0
  3. django_find-1.0.1/PKG-INFO +137 -0
  4. django_find-1.0.1/README.md +102 -0
  5. django_find-1.0.1/django_find/__init__.py +4 -0
  6. django_find-1.0.1/django_find/apps.py +5 -0
  7. django_find-1.0.1/django_find/dom.py +127 -0
  8. django_find-1.0.1/django_find/handlers.py +75 -0
  9. django_find-1.0.1/django_find/model_helpers.py +14 -0
  10. django_find-1.0.1/django_find/models.py +361 -0
  11. django_find-1.0.1/django_find/parsers/__init__.py +0 -0
  12. django_find-1.0.1/django_find/parsers/json.py +64 -0
  13. django_find-1.0.1/django_find/parsers/parser.py +31 -0
  14. django_find-1.0.1/django_find/parsers/query.py +198 -0
  15. django_find-1.0.1/django_find/rawquery.py +78 -0
  16. django_find-1.0.1/django_find/refs.py +260 -0
  17. django_find-1.0.1/django_find/serializers/__init__.py +0 -0
  18. django_find-1.0.1/django_find/serializers/django.py +105 -0
  19. django_find-1.0.1/django_find/serializers/serializer.py +11 -0
  20. django_find-1.0.1/django_find/serializers/sql.py +210 -0
  21. django_find-1.0.1/django_find/serializers/util.py +15 -0
  22. django_find-1.0.1/django_find/templates/django_find/form.html +8 -0
  23. django_find-1.0.1/django_find/templates/django_find/headers.html +3 -0
  24. django_find-1.0.1/django_find/templatetags/__init__.py +0 -0
  25. django_find-1.0.1/django_find/templatetags/find_tags.py +35 -0
  26. django_find-1.0.1/django_find/tree.py +43 -0
  27. django_find-1.0.1/django_find/urls.py +7 -0
  28. django_find-1.0.1/django_find/version.py +5 -0
  29. django_find-1.0.1/pyproject.toml +70 -0
@@ -0,0 +1,6 @@
1
+ *.py[co]
2
+ *.egg-info
3
+ *.swp
4
+ dist
5
+ build/
6
+ _build/
@@ -0,0 +1,17 @@
1
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2
+ of this software and associated documentation files (the "Software"), to deal
3
+ in the Software without restriction, including without limitation the rights
4
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
5
+ copies of the Software, and to permit persons to whom the Software is
6
+ furnished to do so, subject to the following conditions:
7
+
8
+ The above copyright notice and this permission notice shall be included in all
9
+ copies or substantial portions of the Software.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17
+ SOFTWARE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-find
3
+ Version: 1.0.1
4
+ Summary: Simple but powerful search/filter functionality for Django projects
5
+ Project-URL: Homepage, https://github.com/knipknap/django-find
6
+ Project-URL: Repository, https://github.com/knipknap/django-find
7
+ Project-URL: Issues, https://github.com/knipknap/django-find/issues
8
+ Author-email: Samuel Abels <knipknap@gmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: django,filter,find,json,query,search,sql
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Framework :: Django
15
+ Classifier: Framework :: Django :: 4.2
16
+ Classifier: Framework :: Django :: 5.1
17
+ Classifier: Framework :: Django :: 5.2
18
+ Classifier: Framework :: Django :: 6.0
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: License :: OSI Approved :: MIT License
21
+ Classifier: Operating System :: OS Independent
22
+ Classifier: Programming Language :: Python
23
+ Classifier: Programming Language :: Python :: 3
24
+ Classifier: Programming Language :: Python :: 3.10
25
+ Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
28
+ Classifier: Topic :: Internet :: WWW/HTTP
29
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
30
+ Classifier: Topic :: Text Processing :: Filters
31
+ Requires-Python: >=3.10
32
+ Requires-Dist: dateparser
33
+ Requires-Dist: django>=4.2
34
+ Description-Content-Type: text/markdown
35
+
36
+ # django-find
37
+
38
+ [![Build Status](https://travis-ci.org/knipknap/django-find.svg?branch=master)](https://travis-ci.org/knipknap/django-find)
39
+ [![Coverage Status](https://coveralls.io/repos/github/knipknap/django-find/badge.svg?branch=master)](https://coveralls.io/github/knipknap/django-find?branch=master)
40
+ [![Code Climate](https://lima.codeclimate.com/github/knipknap/django-find/badges/gpa.svg)](https://lima.codeclimate.com/github/knipknap/django-find)
41
+ [![Documentation Status](https://readthedocs.org/projects/django-find/badge/?version=latest)](http://django-find.readthedocs.io/en/latest/?badge=latest)
42
+
43
+ ## Summary
44
+
45
+ **django-find** is a Django app that makes it easy to add complex
46
+ search/filter functionality for the models in your project.
47
+ It supports two different ways to search your Django models:
48
+ Query-based, or JSON-based.
49
+
50
+ **django-find** is not a full text search engine, it searches the fields
51
+ of your models. In other words, it filters on your models and provides
52
+ tabular data as a result.
53
+
54
+ ## Features
55
+
56
+ ### Query-based search
57
+
58
+ By query-based, we mean that you can use statements like these
59
+ to search your models:
60
+
61
+ ```
62
+ author:"robert frost" and (title:road or chapter:2)
63
+ ```
64
+
65
+ ### Add a search field to your template using a single tag
66
+
67
+ ```
68
+ {% load find_tags %}
69
+ {% find object_list %}
70
+ {% for obj in object_list %}
71
+ {{ obj.name }}
72
+ {% endfor %}
73
+ ```
74
+
75
+ (object\_list is a queryset that is passed to the template)
76
+
77
+ ### Query in your code
78
+
79
+ Just add the Searchable mixin:
80
+
81
+ ```python
82
+ from django_find import Searchable
83
+
84
+ class Author(models.Model, Searchable):
85
+ name = models.CharField("Author Name", max_length=10)
86
+ ...
87
+ ```
88
+
89
+ And you are good to go:
90
+
91
+ ```python
92
+ # Query-based search returns a standard Django QuerySet that you
93
+ # can .filter() and work with as usual.
94
+ query = Book.by_query('author:"robert frost" and title:"the road"')
95
+
96
+ # You can also get a Django Q object for the statements.
97
+ q_obj = Book.q_from_query('author:"robert frost" and title:"the road"')
98
+ ```
99
+
100
+ ### Query using JSON
101
+
102
+ To make it easy to do complex searches spanning multiple models, JSON-based
103
+ query method is provided. It allows your to make custom searches like these:
104
+
105
+ ![Custom Search](https://raw.githubusercontent.com/knipknap/django-find/master/docs/_static/custom.png)
106
+
107
+ For this, a JSON-based search functionality is provided:
108
+
109
+ ```
110
+ {
111
+ "Author":{"name":[[["equals","test"]]]},
112
+ "Book": {"title":[[["notcontains","c"]]]},
113
+ "Chapter": {"content":[[["startswith","The "]]]}
114
+ }
115
+ ```
116
+
117
+ django-find is smart in figuring out how to join those models
118
+ together and return a useful result.
119
+ In your code, you can load the JSON and get back the search
120
+ result:
121
+
122
+ ```python
123
+ # JSON-based search exhausts what Django's ORM can do, so it does
124
+ # not return a Django QuerySet, but a row-based PaginatedRawQuerySet:
125
+ query, field_list = Book.by_json_raw('''{
126
+ "Chapter": {"title":[[["contains","foo"]]]}
127
+ }''')
128
+ print('|'.join(field_list))
129
+ for row in query:
130
+ print('|'.join(row))
131
+ ```
132
+
133
+ ## Documentation
134
+
135
+ Full documentation, including installation instructions, is here:
136
+
137
+ http://django-find.readthedocs.io
@@ -0,0 +1,102 @@
1
+ # django-find
2
+
3
+ [![Build Status](https://travis-ci.org/knipknap/django-find.svg?branch=master)](https://travis-ci.org/knipknap/django-find)
4
+ [![Coverage Status](https://coveralls.io/repos/github/knipknap/django-find/badge.svg?branch=master)](https://coveralls.io/github/knipknap/django-find?branch=master)
5
+ [![Code Climate](https://lima.codeclimate.com/github/knipknap/django-find/badges/gpa.svg)](https://lima.codeclimate.com/github/knipknap/django-find)
6
+ [![Documentation Status](https://readthedocs.org/projects/django-find/badge/?version=latest)](http://django-find.readthedocs.io/en/latest/?badge=latest)
7
+
8
+ ## Summary
9
+
10
+ **django-find** is a Django app that makes it easy to add complex
11
+ search/filter functionality for the models in your project.
12
+ It supports two different ways to search your Django models:
13
+ Query-based, or JSON-based.
14
+
15
+ **django-find** is not a full text search engine, it searches the fields
16
+ of your models. In other words, it filters on your models and provides
17
+ tabular data as a result.
18
+
19
+ ## Features
20
+
21
+ ### Query-based search
22
+
23
+ By query-based, we mean that you can use statements like these
24
+ to search your models:
25
+
26
+ ```
27
+ author:"robert frost" and (title:road or chapter:2)
28
+ ```
29
+
30
+ ### Add a search field to your template using a single tag
31
+
32
+ ```
33
+ {% load find_tags %}
34
+ {% find object_list %}
35
+ {% for obj in object_list %}
36
+ {{ obj.name }}
37
+ {% endfor %}
38
+ ```
39
+
40
+ (object\_list is a queryset that is passed to the template)
41
+
42
+ ### Query in your code
43
+
44
+ Just add the Searchable mixin:
45
+
46
+ ```python
47
+ from django_find import Searchable
48
+
49
+ class Author(models.Model, Searchable):
50
+ name = models.CharField("Author Name", max_length=10)
51
+ ...
52
+ ```
53
+
54
+ And you are good to go:
55
+
56
+ ```python
57
+ # Query-based search returns a standard Django QuerySet that you
58
+ # can .filter() and work with as usual.
59
+ query = Book.by_query('author:"robert frost" and title:"the road"')
60
+
61
+ # You can also get a Django Q object for the statements.
62
+ q_obj = Book.q_from_query('author:"robert frost" and title:"the road"')
63
+ ```
64
+
65
+ ### Query using JSON
66
+
67
+ To make it easy to do complex searches spanning multiple models, JSON-based
68
+ query method is provided. It allows your to make custom searches like these:
69
+
70
+ ![Custom Search](https://raw.githubusercontent.com/knipknap/django-find/master/docs/_static/custom.png)
71
+
72
+ For this, a JSON-based search functionality is provided:
73
+
74
+ ```
75
+ {
76
+ "Author":{"name":[[["equals","test"]]]},
77
+ "Book": {"title":[[["notcontains","c"]]]},
78
+ "Chapter": {"content":[[["startswith","The "]]]}
79
+ }
80
+ ```
81
+
82
+ django-find is smart in figuring out how to join those models
83
+ together and return a useful result.
84
+ In your code, you can load the JSON and get back the search
85
+ result:
86
+
87
+ ```python
88
+ # JSON-based search exhausts what Django's ORM can do, so it does
89
+ # not return a Django QuerySet, but a row-based PaginatedRawQuerySet:
90
+ query, field_list = Book.by_json_raw('''{
91
+ "Chapter": {"title":[[["contains","foo"]]]}
92
+ }''')
93
+ print('|'.join(field_list))
94
+ for row in query:
95
+ print('|'.join(row))
96
+ ```
97
+
98
+ ## Documentation
99
+
100
+ Full documentation, including installation instructions, is here:
101
+
102
+ http://django-find.readthedocs.io
@@ -0,0 +1,4 @@
1
+ from .models import Searchable
2
+ from .version import __version__
3
+
4
+ __all__ = ["Searchable", "__version__"]
@@ -0,0 +1,5 @@
1
+ from django.apps import AppConfig
2
+
3
+ class DjangoFindConfig(AppConfig):
4
+ name = 'django_find'
5
+ verbose_name = "Django Find"
@@ -0,0 +1,127 @@
1
+ from builtins import str
2
+ from .tree import Node
3
+
4
+ operators = [
5
+ 'contains',
6
+ 'equals',
7
+ 'startswith',
8
+ 'endswith',
9
+ 'regex',
10
+ 'gt',
11
+ 'gte',
12
+ 'lt',
13
+ 'lte',
14
+ 'any'
15
+ ]
16
+
17
+ class Group(Node):
18
+ def translate_term_names(self, name_map):
19
+ def translate(dom_obj):
20
+ dom_obj.name = name_map.get(dom_obj.name, dom_obj.name)
21
+ self.each(translate, Term)
22
+
23
+ def get_term_names(self):
24
+ """
25
+ Returns a flat list of the names of all Terms in the query, in
26
+ the order in which they appear. Filters duplicates.
27
+ """
28
+ field_names = []
29
+ def collect_field_names(dom_obj):
30
+ if not dom_obj.name in field_names:
31
+ field_names.append(dom_obj.name)
32
+ self.each(collect_field_names, Term)
33
+ return field_names
34
+
35
+ def auto_leave_scope(self):
36
+ return False
37
+
38
+ def optimize(self):
39
+ children = [c.optimize() for c in self.children]
40
+ self.children = [c for c in children if c is not None]
41
+ children = []
42
+ for child in self.children:
43
+ if type(child) == type(self):
44
+ for grandchild in child.children:
45
+ children.append(grandchild)
46
+ else:
47
+ children.append(child)
48
+ self.children = children
49
+ if not self.children and not self.is_root:
50
+ return None
51
+ if len(self.children) == 1 and not self.is_root:
52
+ return self.children[0]
53
+ return self
54
+
55
+ def serialize(self, strategy):
56
+ results = [c.serialize(strategy) for c in self.children]
57
+ if self.is_root:
58
+ return strategy.logical_root_group(self, results)
59
+ return strategy.logical_group(results)
60
+
61
+ class And(Group):
62
+ @classmethod
63
+ def is_logical(self):
64
+ return True
65
+
66
+ @classmethod
67
+ def precedence(self):
68
+ return 2
69
+
70
+ def serialize(self, strategy):
71
+ return strategy.logical_and(c.serialize(strategy)
72
+ for c in self.children)
73
+
74
+ class Or(Group):
75
+ @classmethod
76
+ def is_logical(self):
77
+ return True
78
+
79
+ @classmethod
80
+ def precedence(self):
81
+ return 1
82
+
83
+ def serialize(self, strategy):
84
+ return strategy.logical_or(c.serialize(strategy)
85
+ for c in self.children)
86
+
87
+ class Not(Group):
88
+ @classmethod
89
+ def precedence(self):
90
+ return 3
91
+
92
+ def auto_leave_scope(self):
93
+ return True
94
+
95
+ def optimize(self):
96
+ children = [c.optimize() for c in self.children]
97
+ self.children = [c for c in children if c is not None]
98
+ if not self.children and not self.is_root:
99
+ return None
100
+ return self
101
+
102
+ def serialize(self, strategy):
103
+ children = [c.serialize(strategy) for c in self.children]
104
+ return strategy.logical_not(children)
105
+
106
+ class Term(Node):
107
+ def __init__(self, name, operator, data):
108
+ assert operator in operators, "unsupported operator {}".format(operator)
109
+ Node.__init__(self)
110
+ self.name = name
111
+ self.operator = str(operator)
112
+ self.data = str(data)
113
+
114
+ def optimize(self):
115
+ return self
116
+
117
+ def each(self, func, node_type):
118
+ if node_type is None or isinstance(self, node_type):
119
+ func(self)
120
+
121
+ def dump(self, indent=0):
122
+ return [(indent * ' ')
123
+ + self.__class__.__name__ + ': '
124
+ + self.name + ' ' + self.operator + ' ' + repr(self.data)]
125
+
126
+ def serialize(self, strategy):
127
+ return strategy.term(self.name, self.operator, self.data)
@@ -0,0 +1,75 @@
1
+ from django.db import models
2
+
3
+ class FieldHandler(object):
4
+ """
5
+ Abstract base type for all field handlers.
6
+
7
+ A field handler is an object that you can use to define custom
8
+ behavior when searching a field of a model.
9
+
10
+ You might want to use a field handler if you are using a custom
11
+ model field, or if your query contains information that
12
+ requires client-side processing before being passed to
13
+ the database.
14
+ """
15
+ db_type = None
16
+
17
+ @classmethod
18
+ def handles(cls, model, field):
19
+ raise NotImplemented
20
+
21
+ @classmethod
22
+ def prepare(cls, value):
23
+ return value
24
+
25
+ class StrFieldHandler(FieldHandler):
26
+ db_type = 'STR'
27
+
28
+ @classmethod
29
+ def handles(cls, model, field):
30
+ return isinstance(field, (models.CharField, models.TextField))
31
+
32
+ class LowerCaseStrFieldHandler(StrFieldHandler):
33
+ db_type = 'LCSTR'
34
+
35
+ class IPAddressFieldHandler(LowerCaseStrFieldHandler):
36
+ @classmethod
37
+ def handles(cls, model, field):
38
+ return isinstance(field, models.GenericIPAddressField)
39
+
40
+ class BooleanFieldHandler(FieldHandler):
41
+ db_type = 'BOOL'
42
+
43
+ @classmethod
44
+ def handles(cls, model, field):
45
+ return isinstance(field, models.BooleanField)
46
+
47
+ class IntegerFieldHandler(FieldHandler):
48
+ db_type = 'INT'
49
+
50
+ @classmethod
51
+ def handles(cls, model, field):
52
+ return isinstance(field, (models.IntegerField, models.AutoField))
53
+
54
+ class DateFieldHandler(FieldHandler):
55
+ db_type = 'DATE'
56
+
57
+ @classmethod
58
+ def handles(cls, model, field):
59
+ return isinstance(field, models.DateField)
60
+
61
+ class DateTimeFieldHandler(FieldHandler):
62
+ db_type = 'DATETIME'
63
+
64
+ @classmethod
65
+ def handles(cls, model, field):
66
+ return isinstance(field, models.DateTimeField)
67
+
68
+ type_registry = [
69
+ LowerCaseStrFieldHandler,
70
+ IPAddressFieldHandler,
71
+ BooleanFieldHandler,
72
+ IntegerFieldHandler,
73
+ DateTimeFieldHandler,
74
+ DateFieldHandler,
75
+ ]
@@ -0,0 +1,14 @@
1
+ from .serializers.sql import SQLSerializer
2
+
3
+ def sql_from_dom(cls, dom, mode='SELECT', fullnames=None, extra_model=None):
4
+ if not fullnames:
5
+ fullnames = dom.get_term_names()
6
+ if not fullnames:
7
+ return 'SELECT * FROM (SELECT NULL) tbl WHERE 0', [], [] # Empty set
8
+ primary_cls = cls.get_primary_class_from_fullnames(fullnames)
9
+ serializer = SQLSerializer(primary_cls,
10
+ mode=mode,
11
+ fullnames=fullnames,
12
+ extra_model=extra_model)
13
+ sql, args = dom.serialize(serializer)
14
+ return sql, args, fullnames