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.0.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.0-py3-none-any.whl (24.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: django_flex-26.1.0.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.0.tar.gz
Algorithm Hash digest
SHA256 27b0c73c2ae92cbc75eaf0d0f3119b9f6d2cb7b7cf885ec6408ad63f1a4c0a84
MD5 a289f75e7a1531836a7e95ef1e0e5097
BLAKE2b-256 bc8e5d665ff4e654134b0c57f91dd634eb58a7af665447297e74fd6ceb8fc281

See more details on using hashes here.

File details

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

File metadata

  • Download URL: django_flex-26.1.0-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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 161afd145c438143f150c510dc3a249d03b6aea11cfa076200107fc00f1b759f
MD5 b3c9e351f1455751299e93f9aaacf79a
BLAKE2b-256 c0d697d8cd4a5ac743a37e6f08d682b3851e7d92fad283ea6ed5781ccf00e438

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