How to Limit the Maximum Value of a Numeric Field in a Django Model

How to limit the maximum value of a numeric field in a Django model?

You could also create a custom model field type - see http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

In this case, you could 'inherit' from the built-in IntegerField and override its validation logic.

The more I think about this, I realize how useful this would be for many Django apps. Perhaps a IntegerRangeField type could be submitted as a patch for the Django devs to consider adding to trunk.

This is working for me:

from django.db import models

class IntegerRangeField(models.IntegerField):
def __init__(self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs):
self.min_value, self.max_value = min_value, max_value
models.IntegerField.__init__(self, verbose_name, name, **kwargs)
def formfield(self, **kwargs):
defaults = {'min_value': self.min_value, 'max_value':self.max_value}
defaults.update(kwargs)
return super(IntegerRangeField, self).formfield(**defaults)

Then in your model class, you would use it like this (field being the module where you put the above code):

size = fields.IntegerRangeField(min_value=1, max_value=50)

OR for a range of negative and positive (like an oscillator range):

size = fields.IntegerRangeField(min_value=-100, max_value=100)

What would be really cool is if it could be called with the range operator like this:

size = fields.IntegerRangeField(range(1, 50))

But, that would require a lot more code since since you can specify a 'skip' parameter - range(1, 50, 2) - Interesting idea though...

Max and min values for a Django model field, according to the values already introduced

The trick to this is a little something I just noticed in the validator docs:

class MaxValueValidator(limit_value, message=None)

Raises a ValidationError with a code of 'max_value' if value is greater than limit_value, which may be a callable.

You don't want your maximum value to be the same for every record, so you can pass in a method that queries the database to find the current max and add 1. To avoid referencing the class before it's declared you can either pull the method out of the class or wrap the call in a lambda expression. I chose to pull it out.

Try changing your models.py to look like this:

from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import Max

def get_max_myfield() -> int:
max_found = MyModel.objects.aggregate(Max('myfield'))["myfield__max"]
if max_found is None:
return 1
return max_found + 1

class MyModel(models.Model):
myfield = models.IntegerField(validators=[
MinValueValidator(1),
MaxValueValidator(get_max_myfield)])

Here's the same model code in an example that lets you run a complete Django app in one file and experiment with it. The first three calls to clean_fields() validate correctly, and the fourth one complains that myfield is too big.

# Tested with Django 3.1 and Python 3.8.
import logging
import sys

import django
from django.apps import apps
from django.apps.config import AppConfig
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import connections, models, DEFAULT_DB_ALIAS
from django.db.models import Max
from django.db.models.base import ModelBase
from django.core.validators import MaxValueValidator, MinValueValidator

NAME = 'udjango'
DB_FILE = NAME + '.db'

def main():
setup()
logger = logging.getLogger(__name__)

def get_max_myfield() -> int:
max_found = MyModel.objects.aggregate(Max('myfield'))["myfield__max"]
if max_found is None:
return 1
return max_found + 1

class MyModel(models.Model):
myfield = models.IntegerField(validators=[
MinValueValidator(1),
MaxValueValidator(get_max_myfield)])

syncdb(MyModel)

m1 = MyModel(myfield=1)
m1.clean_fields()
m1.save()

m2a = MyModel(myfield=2)
m2a.clean_fields()
m2a.save()

m2b = MyModel(myfield=2)
m2b.clean_fields()
m2b.save()

m101 = MyModel(myfield=101)
try:
m101.clean_fields()
assert False, "Should have raised ValidationError."
except ValidationError:
logger.info("Raised validation error, as expected.")

logger.info('Max allowed is %d.', get_max_myfield())

def setup():
with open(DB_FILE, 'w'):
pass # wipe the database
settings.configure(
DEBUG=True,
DATABASES={
DEFAULT_DB_ALIAS: {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': DB_FILE}},
LOGGING={'version': 1,
'disable_existing_loggers': False,
'formatters': {
'debug': {
'format': '%(asctime)s[%(levelname)s]'
'%(name)s.%(funcName)s(): %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'}},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'debug'}},
'root': {
'handlers': ['console'],
'level': 'INFO'},
'loggers': {
"django.db": {"level": "DEBUG"}}})
app_config = AppConfig(NAME, sys.modules['__main__'])
apps.populate([app_config])
django.setup()
original_new_func = ModelBase.__new__

@staticmethod
def patched_new(cls, name, bases, attrs):
if 'Meta' not in attrs:
class Meta:
app_label = NAME
attrs['Meta'] = Meta
return original_new_func(cls, name, bases, attrs)
ModelBase.__new__ = patched_new

def syncdb(model):
""" Standard syncdb expects models to be in reliable locations.

Based on https://github.com/django/django/blob/1.9.3
/django/core/management/commands/migrate.py#L285
"""
connection = connections[DEFAULT_DB_ALIAS]
with connection.schema_editor() as editor:
editor.create_model(model)

main()

Django ORM: Get maximum value of a field with corresponding other fields values

Use models.Subquery with models.OuterRef to join on cell_id field. Then use queryset.annotate() to annotate the subquery with max_traffic. Finally, use queryset.filter() to select rows that have traffic equals to max_traffic and use .distinct() to remove duplicate rows.

counters_with_traffic = Counters.objects.filter(
date_time__gte=date_start,
date_time__lte=date_end
).annotate(
# calculate the traffic for each row.
traffic=Case(
When(Q(tfnscan=0) | Q(thnscan=0), then=0),
default=Round((F('tftralacc') * 1.0 / F('tfnscan')) +
(F('thtralacc') * 1.0 / F('thnscan')), 2),
output_field=models.FloatField()
)
)

counters_with_max_traffic = counters_with_traffic.order_by('cell_id').values(
# Group by cell_id.
'cell_id'
).order_by().annotate(
# calculate the max traffic for the grouped Cells.
max_traffic=Max('traffic'),
).filter(cell_id=models.OuterRef("cell_id")).values("max_traffic")

result = counters_with_traffic.annotate(
max_traffic=models.Subquery(counters_with_max_traffic),
).filter(
traffic=models.F("max_traffic")
).values(
"cell_id", "max_traffic", "date_time"
).distinct("cell_id", "max_traffic")

Get maximum value of a field in django

Use Django Aggregations:

from django.db.models import Max

Foo.objects.all().aggregate(Max('quantity'))

# or

Foo.objects.aggregate(Max('quantity'))

# or ignore empty quantities

Foo.objects.filter(quantity__isnull=False).aggregate(Max('quantity'))

# how to get the max value

max_quantity = Foo.objects.aggregate(Max('quantity')).get('quantity__max')


Related Topics



Leave a reply



Submit