django-nx
Lightweight Django field utilities and extensions that reduce boilerplate and enforce sensible defaults.
Installation
pip install django-nx
Requires Python >= 3.9, Django >= 3.2, and Django REST Framework >= 3.12.4.
Quick Start
from nx import nx
class Product(nx.Model):
name = nx.CharField('Name') # Default max_length=128
price = nx.MoneyField('Price')
status = nx.IntChoiceField('Status', choices=ProductStatus)
tags = nx.ArrayField('Tags')
metadata = nx.ObjectField('Metadata')
Table of Contents
Model Fields
All fields automatically use verbose_name as help_text when help_text is not explicitly provided.
Character Fields
| Field | Default | Description |
|---|---|---|
CharField |
default="", blank=True, max_length=128 |
String field with empty string default |
TextField |
default="", blank=True |
Long text field with empty string default |
TextChoiceField |
max_length=64, defaults to first choice |
CharField backed by choices enum |
name = nx.CharField('Name', max_length=255)
description = nx.TextField('Description')
priority = nx.TextChoiceField('Priority', choices=PriorityLevel)
Numeric Fields
| Field | Default | Description |
|---|---|---|
IntegerField |
— | Standard integer with auto help_text |
MoneyField |
max_digits=18, decimal_places=2, default=Decimal('0') |
Decimal field for monetary values |
IntChoiceField |
default=first_choice |
SmallIntegerField backed by choices enum |
quantity = nx.IntegerField('Quantity')
price = nx.MoneyField('Price') # DECIMAL(18,2)
discount = nx.MoneyField('Discount', max_digits=5, decimal_places=4)
status = nx.IntChoiceField('Status', choices=OrderStatus)
Boolean & Temporal Fields
| Field | Default | Description |
|---|---|---|
BooleanField |
default=False, blank=True |
Boolean flag |
DateField |
null=True, blank=True |
Date picker |
DateTimeField |
null=True, blank=True |
DateTime picker |
is_active = nx.BooleanField('Is Active')
published_at = nx.DateTimeField('Published At')
birth_date = nx.DateField('Birth Date')
Relationship Fields
| Field | Default | Description |
|---|---|---|
ForeignKey |
null=True, blank=True, on_delete=CASCADE |
Standard FK with nullable defaults |
OneToOne |
null=True, blank=True, on_delete=CASCADE |
One-to-one with nullable defaults |
ManyToMany |
blank=True |
Many-to-many relation |
ShadowForeignKey |
db_constraint=False |
FK without database constraint |
ShadowOneToOne |
db_constraint=False |
One-to-one without DB constraint |
ShadowManyToMany |
db_constraint=False |
Many-to-many without DB constraint |
user = nx.ForeignKey('auth.User', 'User')
profile = nx.OneToOne('accounts.Profile', 'Profile')
tags = nx.ManyToMany('products.Tag', 'Tags')
# Soft / logical foreign key (no DB-level constraint)
legacy_id = nx.ShadowForeignKey('legacy.Model', 'Legacy Ref')
JSON & UUID Fields
| Field | Default | Description |
|---|---|---|
ObjectField |
default=dict, blank=True |
JSONField defaulting to {} |
ArrayField |
default=list, blank=True |
JSONField defaulting to [] |
ShortUUIDField |
max_length=22, auto-generated |
URL-safe concise UUID |
config = nx.ObjectField('Config')
items = nx.ArrayField('Items')
code = nx.ShortUUIDField('Code')
Base Model
nx.Model is an abstract base model that provides:
created_at– auto_now_add timestampupdated_at– auto_now timestampis_deleted– soft-delete flag (defaultFalse)- Auto-generated
db_table–{app_label}_{snake_case_model_name}viahumps.decamelize
import nx
class Product(nx.Model):
name = nx.CharField('Name', max_length=255)
class Meta:
app_label = 'shop'
# db_table = 'shop_product' # auto-generated if omitted
Explicitly set
Meta.db_tableto skip auto-naming.
QuerySet
nx.QuerySet adds soft-delete aware query methods. It reads deleted_field from Model.Meta (defaults to is_deleted).
from nx.models.querysets import QuerySet
class ProductQuerySet(QuerySet):
pass
class Product(nx.Model):
...
class Meta:
deleted_field = 'is_deleted'
Product.objects.valid() # is_deleted=False / 0
Product.objects.invalid() # is_deleted=True / 1
DRF Serializers
| Class | Description |
|---|---|
nx.drf.MoneyField |
DecimalField(max_digits=18, decimal_places=2) |
nx.drf.QuantityField |
IntegerField(min_value=0) |
nx.drf.MethodField |
Alias for SerializerMethodField |
nx.drf.AutoInstanceLookupMixin |
Mixin that auto-looks up instance by id on save |
from rest_framework import serializers
import nx
class ProductSerializer(nx.drf.AutoInstanceLookupMixin, serializers.ModelSerializer):
price = nx.drf.MoneyField()
stock = nx.drf.QuantityField()
category_name = nx.drf.MethodField()
class Meta:
model = Product
fields = ['id', 'price', 'stock', 'category_name']
def get_category_name(self, obj):
return obj.category.name if obj.category else None
DRF Views
ListMetadataMixin
Inject a top-level meta object (or any custom root key) into list responses.
from rest_framework import viewsets
import nx
class ProductViewSet(nx.drf.ListMetadataMixin, viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
list_metadata_root = "meta" # optional; omit to merge at top-level
def get_list_metadata(self, request, queryset, response):
return {
"total": queryset.count(),
"timestamp": timezone.now().isoformat(),
}
Response shape with list_metadata_root = "meta":
{
"count": 100,
"results": [...],
"meta": {
"total": 100,
"timestamp": "2024-01-15T09:30:00Z"
}
}
Response shape without list_metadata_root:
{
"count": 100,
"results": [...],
"total": 100,
"timestamp": "2024-01-15T09:30:00Z"
}
DRF Router
nx.Router wraps DRF's DefaultRouter. Routes have no trailing slash, and
register() derives the default basename from the singular form of prefix.
An explicitly supplied basename is left unchanged.
from nx import nx
router = nx.Router()
router.register("products", ProductViewSet)
# Route: /products (not /products/)
# Basename: product
Utilities
get_stat_datetime_range
Returns today, week, month, and year datetime ranges respecting USE_TZ.
from nx.utils import get_stat_datetime_range
ranges = get_stat_datetime_range()
# ranges.today -> (2024-01-15 00:00:00, 2024-01-15 23:59:59.999999)
# ranges.week -> (Mon 00:00:00, Sun 23:59:59.999999)
# ranges.month -> (1st 00:00:00, last_day 23:59:59.999999)
# ranges.year -> (Jan 1 00:00:00, Dec 31 23:59:59.999999)
Development
# Install dependencies
uv sync
# Run tests
pytest
# Lint
ruff check .
# Build
uv build
License
MIT License — see LICENSE for details.
Release files for django-nx 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_nx-0.6.0.tar.gz | 12.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_nx-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 26.1 kB
Release files / django_nx-0.6.0.tar.gz
| Download URL | django_nx-0.6.0.tar.gz |
|---|---|
| Size | 12.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ec2d6a3bb98a03a137dd7883e2bd487345e848eb45cfe80a43df77ffff4c9d94
|
|
BLAKE2b-256 checksum How to use checksums |
dd99b2531e93a5f36cada93fdd64a33c3957889908f5badd3fa2abef8dcca5a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / django_nx-0.6.0-py3-none-any.whl
| Download URL | django_nx-0.6.0-py3-none-any.whl |
|---|---|
| Size | 13.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3548f652b6f2107a3be4636bddd14d68c5fed22593753c90859e7f5af29e341
|
|
BLAKE2b-256 checksum How to use checksums |
65351b7385d6de2b39837a5680884df0b3b46843b287c9ddbb6d66f17c709e65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|