Skip to main content

A flexible query language for Django - enable frontends to dynamically construct database queries

Project description

Django-Flex

PyPI version Python versions Django versions

A flexible query language for Django — Enable frontends to dynamically construct database queries with built-in security.

Features

  • 🔍 Dynamic Field Selection — Request only the fields you need
  • 🔬 Rich Filtering — Full Django ORM operator support
  • 📄 Built-in Pagination — Limit/offset with smart cursor support
  • 🔐 Layered Security — Row, field, filter, and operation-level access control
  • N+1 Prevention — Automatic select_related optimization
  • 🎯 Django Native — Uses Django's built-in auth (groups, permissions)

Installation

pip install django-flex
# settings.py
INSTALLED_APPS = [
    ...
    'django_flex',
]

Quick Start

Using Django's official documentation models: Blog, Author, Entry.

1. Create a View

# views.py
from datetime import date
from django.db.models import Q
from django_flex import FlexQueryView
from .models import Entry

class EntryQueryView(FlexQueryView):
    model = Entry

    flex_permissions = {
        'staff': {
            'rows': lambda user: Q(),  # All entries
            'fields': ['*', 'blog.*'],
            'filters': ['id', 'rating.gte', 'blog.id', 'headline.icontains'],
            'order_by': ['-pub_date', 'rating'],
            'ops': ['get', 'query'],
        },
        'authenticated': {
            'rows': lambda user: Q(pub_date__lte=date.today()),
            'fields': ['id', 'headline', 'pub_date', 'blog.name'],
            'filters': ['id', 'headline.icontains'],
            'order_by': ['-pub_date'],
            'ops': ['get', 'query'],
        },
    }

2. Add URL Route

# urls.py
from .views import EntryQueryView

urlpatterns = [
    path('api/entries/', EntryQueryView.as_view()),
]

3. Query from Frontend

// Query entries
const response = await fetch('/api/entries/', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
        fields: 'id, headline, pub_date, blog.name',
        filters: { 'rating.gte': 4 },
        order_by: '-pub_date',
        limit: 20
    })
});

// Response
{
    "success": true,
    "code": "FLEX_OK_QUERY",
    "pagination": {"offset": 0, "limit": 20, "has_more": false},
    "results": {
        "1": {"id": 1, "headline": "Django 5.0 Released", "pub_date": "2024-01-15", "blog": {"name": "Django News"}},
        "2": {"id": 2, "headline": "Getting Started Guide", "pub_date": "2024-01-14", "blog": {"name": "Tutorials"}}
    }
}

Query Language

Field Selection

fields: 'id, headline'              // Specific fields
fields: '*'                          // All model fields
fields: '*, blog.*'                  // All fields + related
fields: 'id, blog.name, blog.tagline'  // Nested fields

Filtering

// Simple equality
filters: { rating: 5 }

// Operators
filters: { 'rating.gte': 4 }           // Greater than or equal
filters: { 'rating.in': [4, 5] }       // In list
filters: { 'headline.icontains': 'django' }  // Case-insensitive search
filters: { 'pub_date.gte': '2024-01-01' }    // Date comparison

// Composition
filters: { or: { rating: 5, 'number_of_comments.gte': 100 } }
filters: { not: { 'rating.lt': 3 } }

Pagination

limit: 20       // Max results
offset: 40      // Skip first 40

// Response includes next cursor
pagination: {
    offset: 40,
    limit: 20,
    has_more: true,
    next: { fields: '...', limit: 20, offset: 60 }
}

Security Configuration

Django-Flex uses Django's built-in auth for role resolution:

  1. superuser → bypasses all checks
  2. staffuser.is_staff
  3. <group_name> → first Django group
  4. authenticated → logged in, no group
# settings.py
DJANGO_FLEX = {
    'DEFAULT_LIMIT': 50,
    'MAX_LIMIT': 200,
    'MAX_RELATION_DEPTH': 2,

    'PERMISSIONS': {
        'entry': {
            'exclude': ['internal_notes'],

            'staff': {
                'rows': lambda user: Q(),
                'fields': ['*', 'blog.*'],
                'filters': ['id', 'rating.gte', 'pub_date.gte', 'blog.id'],
                'order_by': ['-pub_date', '-rating'],
                'ops': ['get', 'query', 'create', 'update', 'delete'],
            },
            'authenticated': {
                'rows': lambda user: Q(pub_date__lte=date.today()),
                'fields': ['id', 'headline', 'pub_date', 'rating'],
                'filters': ['id'],
                'order_by': ['-pub_date'],
                'ops': ['get', 'query'],
            },
        },
    },
}

Usage Patterns

Class-Based View

from django_flex import FlexQueryView

class EntryQueryView(FlexQueryView):
    model = Entry
    flex_permissions = {...}

Decorator

from django_flex import flex_query

@flex_query(
    model=Entry,
    allowed_fields=['id', 'headline', 'blog.name'],
    allowed_filters=['id', 'headline.icontains'],
    allowed_actions=['get', 'query'],
)
def entry_query(request, result, query_spec):
    return JsonResponse(result.to_dict())

Programmatic

from django_flex import FlexQuery

result = FlexQuery(Entry).execute({
    'fields': 'id, headline, blog.name',
    'filters': {'rating.gte': 4},
}, user=request.user)

Supported Operators

Category Operators
Comparison lt, lte, gt, gte, exact, in, isnull, range
Text contains, icontains, startswith, endswith, regex
Date/Time date, year, month, day, hour, minute, second
Composition and, or, not

Response Codes

Code Description
FLEX_OK Single object retrieved
FLEX_OK_QUERY Query results returned
FLEX_LIMIT_CLAMPED Results returned, limit was reduced
FLEX_NOT_FOUND Object not found
FLEX_PERMISSION_DENIED Access denied
FLEX_INVALID_FILTER Invalid filter syntax

Documentation

License

MIT License - see LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_flex-26.1.1.tar.gz (34.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_flex-26.1.1-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

Details for the file django_flex-26.1.1.tar.gz.

File metadata

  • Download URL: django_flex-26.1.1.tar.gz
  • Upload date:
  • Size: 34.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for django_flex-26.1.1.tar.gz
Algorithm Hash digest
SHA256 0d074a44a76237ce58fbf3777bf92f2c7dffd1ee01326087766c1a5e64634c0f
MD5 772805f86d12f51d8020d13079d67f1d
BLAKE2b-256 e711a3c08229f1d6e8f685f46e571d10f4128727d9406cce124cb92565b87e90

See more details on using hashes here.

File details

Details for the file django_flex-26.1.1-py3-none-any.whl.

File metadata

  • Download URL: django_flex-26.1.1-py3-none-any.whl
  • Upload date:
  • Size: 24.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for django_flex-26.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 05626bdbdaf0b6fb47eade4c7cfe5375adc727b062a79055f19b36e7370a9bf7
MD5 34207a27fd90ed8f7d9a2d94e30cbd11
BLAKE2b-256 566aba766ba76c3cb5c78aa22092366fa1703c73b09e854b9facaded01b5b9ff

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page