django-flex 26.1.2__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.

Potentially problematic release.


This version of django-flex might be problematic. Click here for more details.

Files changed (32) hide show
  1. django_flex-26.1.2/CHANGELOG.md +42 -0
  2. django_flex-26.1.2/LICENSE +21 -0
  3. django_flex-26.1.2/MANIFEST.in +7 -0
  4. django_flex-26.1.2/PKG-INFO +300 -0
  5. django_flex-26.1.2/README.md +253 -0
  6. django_flex-26.1.2/django_flex/__init__.py +92 -0
  7. django_flex-26.1.2/django_flex/conf.py +92 -0
  8. django_flex-26.1.2/django_flex/decorators.py +182 -0
  9. django_flex-26.1.2/django_flex/fields.py +234 -0
  10. django_flex-26.1.2/django_flex/filters.py +207 -0
  11. django_flex-26.1.2/django_flex/middleware.py +143 -0
  12. django_flex-26.1.2/django_flex/permissions.py +365 -0
  13. django_flex-26.1.2/django_flex/query.py +290 -0
  14. django_flex-26.1.2/django_flex/response.py +212 -0
  15. django_flex-26.1.2/django_flex/views.py +236 -0
  16. django_flex-26.1.2/django_flex.egg-info/PKG-INFO +300 -0
  17. django_flex-26.1.2/django_flex.egg-info/SOURCES.txt +30 -0
  18. django_flex-26.1.2/django_flex.egg-info/dependency_links.txt +1 -0
  19. django_flex-26.1.2/django_flex.egg-info/requires.txt +11 -0
  20. django_flex-26.1.2/django_flex.egg-info/top_level.txt +1 -0
  21. django_flex-26.1.2/docs/api_reference.md +328 -0
  22. django_flex-26.1.2/docs/examples/basic_usage.md +392 -0
  23. django_flex-26.1.2/docs/examples/filtering.md +377 -0
  24. django_flex-26.1.2/docs/installation.md +86 -0
  25. django_flex-26.1.2/docs/permissions.md +278 -0
  26. django_flex-26.1.2/docs/quickstart.md +268 -0
  27. django_flex-26.1.2/pyproject.toml +111 -0
  28. django_flex-26.1.2/setup.cfg +4 -0
  29. django_flex-26.1.2/tests/test_fields.py +79 -0
  30. django_flex-26.1.2/tests/test_filters.py +168 -0
  31. django_flex-26.1.2/tests/test_permissions.py +77 -0
  32. django_flex-26.1.2/tests/test_response.py +97 -0
@@ -0,0 +1,42 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Calendar Versioning](https://calver.org/) (YY.MM.MICRO).
7
+
8
+ ## [26.1.0] - 2026-01-11
9
+
10
+ ### Added
11
+
12
+ - Initial release of django-flex
13
+ - Core query engine with field selection and filtering
14
+ - Permission system with row-level, field-level, and operation-level access control
15
+ - `FlexQueryView` class-based view for easy integration
16
+ - `flex_query` decorator for function-based views
17
+ - Optional middleware for centralized endpoint
18
+ - Comprehensive documentation and examples
19
+ - Support for Django 3.2, 4.0, 4.1, 4.2, 5.0, and 6.0
20
+ - Support for Python 3.8, 3.9, 3.10, 3.11, 3.12, and 3.14
21
+
22
+ ### CRUD Action Names
23
+
24
+ - `get` - Retrieve single object by ID
25
+ - `query` - Query multiple objects with filters/pagination
26
+ - `create` - Create new objects
27
+ - `update` - Update existing objects
28
+ - `delete` - Delete objects
29
+
30
+ ### Features
31
+
32
+ - **Field Selection**: Use comma-separated field strings with dot notation for relations
33
+ - Wildcards: `*` for all fields, `customer.*` for all customer fields
34
+ - Nested relations: `customer.address.city`
35
+ - **Filtering**: Full Django ORM operator support
36
+ - Comparison: `lt`, `lte`, `gt`, `gte`, `exact`, `in`, `isnull`, `range`
37
+ - Text: `contains`, `icontains`, `startswith`, `endswith`, `regex`
38
+ - Date/Time: `date`, `year`, `month`, `day`, `hour`, `minute`, `second`
39
+ - Composable: `and`, `or`, `not` for complex conditions
40
+ - **Pagination**: Limit/offset with smart cursor-based continuation
41
+ - **Security**: Principle of least privilege with deny-by-default
42
+ - **Performance**: Automatic `select_related` for N+1 prevention
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nehemiah Jacob
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ recursive-include django_flex *.py
5
+ recursive-include docs *.md
6
+ global-exclude __pycache__
7
+ global-exclude *.py[cod]
@@ -0,0 +1,300 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-flex
3
+ Version: 26.1.2
4
+ Summary: A flexible query language for Django - enable frontends to dynamically construct database queries
5
+ Author: Nehemiah Jacob
6
+ Maintainer: Nehemiah Jacob
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/n3h3m/django-flex
9
+ Project-URL: Documentation, https://github.com/n3h3m/django-flex#readme
10
+ Project-URL: Repository, https://github.com/n3h3m/django-flex.git
11
+ Project-URL: Issues, https://github.com/n3h3m/django-flex/issues
12
+ Project-URL: Changelog, https://github.com/n3h3m/django-flex/blob/main/CHANGELOG.md
13
+ Keywords: django,query,api,flexible,dynamic,graphql-alternative,rest,orm
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Web Environment
16
+ Classifier: Framework :: Django
17
+ Classifier: Framework :: Django :: 3.2
18
+ Classifier: Framework :: Django :: 4.0
19
+ Classifier: Framework :: Django :: 4.1
20
+ Classifier: Framework :: Django :: 4.2
21
+ Classifier: Framework :: Django :: 5.0
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: License :: OSI Approved :: MIT License
24
+ Classifier: Operating System :: OS Independent
25
+ Classifier: Programming Language :: Python :: 3
26
+ Classifier: Programming Language :: Python :: 3.8
27
+ Classifier: Programming Language :: Python :: 3.9
28
+ Classifier: Programming Language :: Python :: 3.10
29
+ Classifier: Programming Language :: Python :: 3.11
30
+ Classifier: Programming Language :: Python :: 3.12
31
+ Classifier: Topic :: Internet :: WWW/HTTP
32
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
+ Requires-Python: >=3.8
34
+ Description-Content-Type: text/markdown
35
+ License-File: LICENSE
36
+ Requires-Dist: django>=3.2
37
+ Provides-Extra: dev
38
+ Requires-Dist: pytest>=7.0; extra == "dev"
39
+ Requires-Dist: pytest-django>=4.5; extra == "dev"
40
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
41
+ Requires-Dist: black>=23.0; extra == "dev"
42
+ Requires-Dist: isort>=5.12; extra == "dev"
43
+ Requires-Dist: flake8>=6.0; extra == "dev"
44
+ Requires-Dist: mypy>=1.0; extra == "dev"
45
+ Requires-Dist: django-stubs>=4.0; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ # Django-Flex
49
+
50
+ [![PyPI version](https://badge.fury.io/py/django-flex.svg)](https://pypi.org/project/django-flex/)
51
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-flex.svg)](https://pypi.org/project/django-flex/)
52
+ [![Django versions](https://img.shields.io/badge/django-3.2%20%7C%204.x%20%7C%205.x%20%7C%206.x-blue.svg)](https://www.djangoproject.com/)
53
+
54
+ **A flexible query language for Django** — Enable frontends to dynamically construct database queries with built-in security.
55
+
56
+ ## Features
57
+
58
+ - 🔍 **Dynamic Field Selection** — Request only the fields you need
59
+ - 🔬 **Rich Filtering** — Full Django ORM operator support
60
+ - 📄 **Built-in Pagination** — Limit/offset with smart cursor support
61
+ - 🔐 **Layered Security** — Row, field, filter, and operation-level access control
62
+ - ⚡ **N+1 Prevention** — Automatic `select_related` optimization
63
+ - 🎯 **Django Native** — Uses Django's built-in auth (groups, permissions)
64
+
65
+ ## Installation
66
+
67
+ ```bash
68
+ pip install django-flex
69
+ ```
70
+
71
+ ```python
72
+ # settings.py
73
+ INSTALLED_APPS = [
74
+ ...
75
+ 'django_flex',
76
+ ]
77
+ ```
78
+
79
+ ## Quick Start
80
+
81
+ Using Django's official documentation models: Blog, Author, Entry.
82
+
83
+ ### 1. Create a View
84
+
85
+ ```python
86
+ # views.py
87
+ from datetime import date
88
+ from django.db.models import Q
89
+ from django_flex import FlexQueryView
90
+ from .models import Entry
91
+
92
+ class EntryQueryView(FlexQueryView):
93
+ model = Entry
94
+
95
+ flex_permissions = {
96
+ 'staff': {
97
+ 'rows': lambda user: Q(), # All entries
98
+ 'fields': ['*', 'blog.*'],
99
+ 'filters': ['id', 'rating.gte', 'blog.id', 'headline.icontains'],
100
+ 'order_by': ['-pub_date', 'rating'],
101
+ 'ops': ['get', 'query'],
102
+ },
103
+ 'authenticated': {
104
+ 'rows': lambda user: Q(pub_date__lte=date.today()),
105
+ 'fields': ['id', 'headline', 'pub_date', 'blog.name'],
106
+ 'filters': ['id', 'headline.icontains'],
107
+ 'order_by': ['-pub_date'],
108
+ 'ops': ['get', 'query'],
109
+ },
110
+ }
111
+ ```
112
+
113
+ ### 2. Add URL Route
114
+
115
+ ```python
116
+ # urls.py
117
+ from .views import EntryQueryView
118
+
119
+ urlpatterns = [
120
+ path('api/entries/', EntryQueryView.as_view()),
121
+ ]
122
+ ```
123
+
124
+ ### 3. Query from Frontend
125
+
126
+ ```javascript
127
+ // Query entries
128
+ const response = await fetch('/api/entries/', {
129
+ method: 'POST',
130
+ headers: {'Content-Type': 'application/json'},
131
+ body: JSON.stringify({
132
+ fields: 'id, headline, pub_date, blog.name',
133
+ filters: { 'rating.gte': 4 },
134
+ order_by: '-pub_date',
135
+ limit: 20
136
+ })
137
+ });
138
+
139
+ // Response
140
+ {
141
+ "success": true,
142
+ "code": "FLEX_OK_QUERY",
143
+ "pagination": {"offset": 0, "limit": 20, "has_more": false},
144
+ "results": {
145
+ "1": {"id": 1, "headline": "Django 5.0 Released", "pub_date": "2024-01-15", "blog": {"name": "Django News"}},
146
+ "2": {"id": 2, "headline": "Getting Started Guide", "pub_date": "2024-01-14", "blog": {"name": "Tutorials"}}
147
+ }
148
+ }
149
+ ```
150
+
151
+ ## Query Language
152
+
153
+ ### Field Selection
154
+
155
+ ```javascript
156
+ fields: 'id, headline' // Specific fields
157
+ fields: '*' // All model fields
158
+ fields: '*, blog.*' // All fields + related
159
+ fields: 'id, blog.name, blog.tagline' // Nested fields
160
+ ```
161
+
162
+ ### Filtering
163
+
164
+ ```javascript
165
+ // Simple equality
166
+ filters: { rating: 5 }
167
+
168
+ // Operators
169
+ filters: { 'rating.gte': 4 } // Greater than or equal
170
+ filters: { 'rating.in': [4, 5] } // In list
171
+ filters: { 'headline.icontains': 'django' } // Case-insensitive search
172
+ filters: { 'pub_date.gte': '2024-01-01' } // Date comparison
173
+
174
+ // Composition
175
+ filters: { or: { rating: 5, 'number_of_comments.gte': 100 } }
176
+ filters: { not: { 'rating.lt': 3 } }
177
+ ```
178
+
179
+ ### Pagination
180
+
181
+ ```javascript
182
+ limit: 20 // Max results
183
+ offset: 40 // Skip first 40
184
+
185
+ // Response includes next cursor
186
+ pagination: {
187
+ offset: 40,
188
+ limit: 20,
189
+ has_more: true,
190
+ next: { fields: '...', limit: 20, offset: 60 }
191
+ }
192
+ ```
193
+
194
+ ## Security Configuration
195
+
196
+ Django-Flex uses Django's built-in auth for role resolution:
197
+
198
+ 1. `superuser` → bypasses all checks
199
+ 2. `staff` → `user.is_staff`
200
+ 3. `<group_name>` → first Django group
201
+ 4. `authenticated` → logged in, no group
202
+
203
+ ```python
204
+ # settings.py
205
+ DJANGO_FLEX = {
206
+ 'DEFAULT_LIMIT': 50,
207
+ 'MAX_LIMIT': 200,
208
+ 'MAX_RELATION_DEPTH': 2,
209
+
210
+ 'PERMISSIONS': {
211
+ 'entry': {
212
+ 'exclude': ['internal_notes'],
213
+
214
+ 'staff': {
215
+ 'rows': lambda user: Q(),
216
+ 'fields': ['*', 'blog.*'],
217
+ 'filters': ['id', 'rating.gte', 'pub_date.gte', 'blog.id'],
218
+ 'order_by': ['-pub_date', '-rating'],
219
+ 'ops': ['get', 'query', 'create', 'update', 'delete'],
220
+ },
221
+ 'authenticated': {
222
+ 'rows': lambda user: Q(pub_date__lte=date.today()),
223
+ 'fields': ['id', 'headline', 'pub_date', 'rating'],
224
+ 'filters': ['id'],
225
+ 'order_by': ['-pub_date'],
226
+ 'ops': ['get', 'query'],
227
+ },
228
+ },
229
+ },
230
+ }
231
+ ```
232
+
233
+ ## Usage Patterns
234
+
235
+ ### Class-Based View
236
+
237
+ ```python
238
+ from django_flex import FlexQueryView
239
+
240
+ class EntryQueryView(FlexQueryView):
241
+ model = Entry
242
+ flex_permissions = {...}
243
+ ```
244
+
245
+ ### Decorator
246
+
247
+ ```python
248
+ from django_flex import flex_query
249
+
250
+ @flex_query(
251
+ model=Entry,
252
+ allowed_fields=['id', 'headline', 'blog.name'],
253
+ allowed_filters=['id', 'headline.icontains'],
254
+ allowed_actions=['get', 'query'],
255
+ )
256
+ def entry_query(request, result, query_spec):
257
+ return JsonResponse(result.to_dict())
258
+ ```
259
+
260
+ ### Programmatic
261
+
262
+ ```python
263
+ from django_flex import FlexQuery
264
+
265
+ result = FlexQuery(Entry).execute({
266
+ 'fields': 'id, headline, blog.name',
267
+ 'filters': {'rating.gte': 4},
268
+ }, user=request.user)
269
+ ```
270
+
271
+ ## Supported Operators
272
+
273
+ | Category | Operators |
274
+ |----------|-----------|
275
+ | Comparison | `lt`, `lte`, `gt`, `gte`, `exact`, `in`, `isnull`, `range` |
276
+ | Text | `contains`, `icontains`, `startswith`, `endswith`, `regex` |
277
+ | Date/Time | `date`, `year`, `month`, `day`, `hour`, `minute`, `second` |
278
+ | Composition | `and`, `or`, `not` |
279
+
280
+ ## Response Codes
281
+
282
+ | Code | Description |
283
+ |------|-------------|
284
+ | `FLEX_OK` | Single object retrieved |
285
+ | `FLEX_OK_QUERY` | Query results returned |
286
+ | `FLEX_LIMIT_CLAMPED` | Results returned, limit was reduced |
287
+ | `FLEX_NOT_FOUND` | Object not found |
288
+ | `FLEX_PERMISSION_DENIED` | Access denied |
289
+ | `FLEX_INVALID_FILTER` | Invalid filter syntax |
290
+
291
+ ## Documentation
292
+
293
+ - [Installation Guide](https://github.com/n3h3m/django-flex/blob/main/docs/installation.md)
294
+ - [Quick Start](https://github.com/n3h3m/django-flex/blob/main/docs/quickstart.md)
295
+ - [Permissions Guide](https://github.com/n3h3m/django-flex/blob/main/docs/permissions.md)
296
+ - [API Reference](https://github.com/n3h3m/django-flex/blob/main/docs/api_reference.md)
297
+
298
+ ## License
299
+
300
+ MIT License - see [LICENSE](https://github.com/n3h3m/django-flex/blob/main/LICENSE) for details.
@@ -0,0 +1,253 @@
1
+ # Django-Flex
2
+
3
+ [![PyPI version](https://badge.fury.io/py/django-flex.svg)](https://pypi.org/project/django-flex/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-flex.svg)](https://pypi.org/project/django-flex/)
5
+ [![Django versions](https://img.shields.io/badge/django-3.2%20%7C%204.x%20%7C%205.x%20%7C%206.x-blue.svg)](https://www.djangoproject.com/)
6
+
7
+ **A flexible query language for Django** — Enable frontends to dynamically construct database queries with built-in security.
8
+
9
+ ## Features
10
+
11
+ - 🔍 **Dynamic Field Selection** — Request only the fields you need
12
+ - 🔬 **Rich Filtering** — Full Django ORM operator support
13
+ - 📄 **Built-in Pagination** — Limit/offset with smart cursor support
14
+ - 🔐 **Layered Security** — Row, field, filter, and operation-level access control
15
+ - ⚡ **N+1 Prevention** — Automatic `select_related` optimization
16
+ - 🎯 **Django Native** — Uses Django's built-in auth (groups, permissions)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install django-flex
22
+ ```
23
+
24
+ ```python
25
+ # settings.py
26
+ INSTALLED_APPS = [
27
+ ...
28
+ 'django_flex',
29
+ ]
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ Using Django's official documentation models: Blog, Author, Entry.
35
+
36
+ ### 1. Create a View
37
+
38
+ ```python
39
+ # views.py
40
+ from datetime import date
41
+ from django.db.models import Q
42
+ from django_flex import FlexQueryView
43
+ from .models import Entry
44
+
45
+ class EntryQueryView(FlexQueryView):
46
+ model = Entry
47
+
48
+ flex_permissions = {
49
+ 'staff': {
50
+ 'rows': lambda user: Q(), # All entries
51
+ 'fields': ['*', 'blog.*'],
52
+ 'filters': ['id', 'rating.gte', 'blog.id', 'headline.icontains'],
53
+ 'order_by': ['-pub_date', 'rating'],
54
+ 'ops': ['get', 'query'],
55
+ },
56
+ 'authenticated': {
57
+ 'rows': lambda user: Q(pub_date__lte=date.today()),
58
+ 'fields': ['id', 'headline', 'pub_date', 'blog.name'],
59
+ 'filters': ['id', 'headline.icontains'],
60
+ 'order_by': ['-pub_date'],
61
+ 'ops': ['get', 'query'],
62
+ },
63
+ }
64
+ ```
65
+
66
+ ### 2. Add URL Route
67
+
68
+ ```python
69
+ # urls.py
70
+ from .views import EntryQueryView
71
+
72
+ urlpatterns = [
73
+ path('api/entries/', EntryQueryView.as_view()),
74
+ ]
75
+ ```
76
+
77
+ ### 3. Query from Frontend
78
+
79
+ ```javascript
80
+ // Query entries
81
+ const response = await fetch('/api/entries/', {
82
+ method: 'POST',
83
+ headers: {'Content-Type': 'application/json'},
84
+ body: JSON.stringify({
85
+ fields: 'id, headline, pub_date, blog.name',
86
+ filters: { 'rating.gte': 4 },
87
+ order_by: '-pub_date',
88
+ limit: 20
89
+ })
90
+ });
91
+
92
+ // Response
93
+ {
94
+ "success": true,
95
+ "code": "FLEX_OK_QUERY",
96
+ "pagination": {"offset": 0, "limit": 20, "has_more": false},
97
+ "results": {
98
+ "1": {"id": 1, "headline": "Django 5.0 Released", "pub_date": "2024-01-15", "blog": {"name": "Django News"}},
99
+ "2": {"id": 2, "headline": "Getting Started Guide", "pub_date": "2024-01-14", "blog": {"name": "Tutorials"}}
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Query Language
105
+
106
+ ### Field Selection
107
+
108
+ ```javascript
109
+ fields: 'id, headline' // Specific fields
110
+ fields: '*' // All model fields
111
+ fields: '*, blog.*' // All fields + related
112
+ fields: 'id, blog.name, blog.tagline' // Nested fields
113
+ ```
114
+
115
+ ### Filtering
116
+
117
+ ```javascript
118
+ // Simple equality
119
+ filters: { rating: 5 }
120
+
121
+ // Operators
122
+ filters: { 'rating.gte': 4 } // Greater than or equal
123
+ filters: { 'rating.in': [4, 5] } // In list
124
+ filters: { 'headline.icontains': 'django' } // Case-insensitive search
125
+ filters: { 'pub_date.gte': '2024-01-01' } // Date comparison
126
+
127
+ // Composition
128
+ filters: { or: { rating: 5, 'number_of_comments.gte': 100 } }
129
+ filters: { not: { 'rating.lt': 3 } }
130
+ ```
131
+
132
+ ### Pagination
133
+
134
+ ```javascript
135
+ limit: 20 // Max results
136
+ offset: 40 // Skip first 40
137
+
138
+ // Response includes next cursor
139
+ pagination: {
140
+ offset: 40,
141
+ limit: 20,
142
+ has_more: true,
143
+ next: { fields: '...', limit: 20, offset: 60 }
144
+ }
145
+ ```
146
+
147
+ ## Security Configuration
148
+
149
+ Django-Flex uses Django's built-in auth for role resolution:
150
+
151
+ 1. `superuser` → bypasses all checks
152
+ 2. `staff` → `user.is_staff`
153
+ 3. `<group_name>` → first Django group
154
+ 4. `authenticated` → logged in, no group
155
+
156
+ ```python
157
+ # settings.py
158
+ DJANGO_FLEX = {
159
+ 'DEFAULT_LIMIT': 50,
160
+ 'MAX_LIMIT': 200,
161
+ 'MAX_RELATION_DEPTH': 2,
162
+
163
+ 'PERMISSIONS': {
164
+ 'entry': {
165
+ 'exclude': ['internal_notes'],
166
+
167
+ 'staff': {
168
+ 'rows': lambda user: Q(),
169
+ 'fields': ['*', 'blog.*'],
170
+ 'filters': ['id', 'rating.gte', 'pub_date.gte', 'blog.id'],
171
+ 'order_by': ['-pub_date', '-rating'],
172
+ 'ops': ['get', 'query', 'create', 'update', 'delete'],
173
+ },
174
+ 'authenticated': {
175
+ 'rows': lambda user: Q(pub_date__lte=date.today()),
176
+ 'fields': ['id', 'headline', 'pub_date', 'rating'],
177
+ 'filters': ['id'],
178
+ 'order_by': ['-pub_date'],
179
+ 'ops': ['get', 'query'],
180
+ },
181
+ },
182
+ },
183
+ }
184
+ ```
185
+
186
+ ## Usage Patterns
187
+
188
+ ### Class-Based View
189
+
190
+ ```python
191
+ from django_flex import FlexQueryView
192
+
193
+ class EntryQueryView(FlexQueryView):
194
+ model = Entry
195
+ flex_permissions = {...}
196
+ ```
197
+
198
+ ### Decorator
199
+
200
+ ```python
201
+ from django_flex import flex_query
202
+
203
+ @flex_query(
204
+ model=Entry,
205
+ allowed_fields=['id', 'headline', 'blog.name'],
206
+ allowed_filters=['id', 'headline.icontains'],
207
+ allowed_actions=['get', 'query'],
208
+ )
209
+ def entry_query(request, result, query_spec):
210
+ return JsonResponse(result.to_dict())
211
+ ```
212
+
213
+ ### Programmatic
214
+
215
+ ```python
216
+ from django_flex import FlexQuery
217
+
218
+ result = FlexQuery(Entry).execute({
219
+ 'fields': 'id, headline, blog.name',
220
+ 'filters': {'rating.gte': 4},
221
+ }, user=request.user)
222
+ ```
223
+
224
+ ## Supported Operators
225
+
226
+ | Category | Operators |
227
+ |----------|-----------|
228
+ | Comparison | `lt`, `lte`, `gt`, `gte`, `exact`, `in`, `isnull`, `range` |
229
+ | Text | `contains`, `icontains`, `startswith`, `endswith`, `regex` |
230
+ | Date/Time | `date`, `year`, `month`, `day`, `hour`, `minute`, `second` |
231
+ | Composition | `and`, `or`, `not` |
232
+
233
+ ## Response Codes
234
+
235
+ | Code | Description |
236
+ |------|-------------|
237
+ | `FLEX_OK` | Single object retrieved |
238
+ | `FLEX_OK_QUERY` | Query results returned |
239
+ | `FLEX_LIMIT_CLAMPED` | Results returned, limit was reduced |
240
+ | `FLEX_NOT_FOUND` | Object not found |
241
+ | `FLEX_PERMISSION_DENIED` | Access denied |
242
+ | `FLEX_INVALID_FILTER` | Invalid filter syntax |
243
+
244
+ ## Documentation
245
+
246
+ - [Installation Guide](https://github.com/n3h3m/django-flex/blob/main/docs/installation.md)
247
+ - [Quick Start](https://github.com/n3h3m/django-flex/blob/main/docs/quickstart.md)
248
+ - [Permissions Guide](https://github.com/n3h3m/django-flex/blob/main/docs/permissions.md)
249
+ - [API Reference](https://github.com/n3h3m/django-flex/blob/main/docs/api_reference.md)
250
+
251
+ ## License
252
+
253
+ MIT License - see [LICENSE](https://github.com/n3h3m/django-flex/blob/main/LICENSE) for details.