#12030 closed Bug (fixed)
PositiveIntegerField in model form does not validate max value
Reported by: | Owned by: | ||
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | aliaselbarto@…, Simon Charette | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Entering in a value of more than 4294967295 for a PositiveIntegerField in a model form causes an error to be thrown trying to insert the value into the database. Ideally this would get validated by the model form.
Attachments (2)
Change History (31)
comment:1 by , 15 years ago
Component: | Forms → Database layer (models, ORM) |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 14 years ago
Owner: | changed from | to
---|
comment:3 by , 14 years ago
Has patch: | set |
---|
by , 14 years ago
Attachment: | positive_integer.diff added |
---|
comment:4 by , 14 years ago
Easy pickings: | unset |
---|---|
Patch needs improvement: | set |
Severity: | → Normal |
Type: | → Bug |
As pointed out by Russell, the maximum number would need to be determined by each backend -- not hardcoded in the PositiveInteger class.
comment:6 by , 11 years ago
How would the following be handled?
if form.is_valid(): instance = form.save(commit=False) instance.save(using='nowforsomethingcompletelydifferent')
I can't think of a generic way solve this. However I am working on a patch that consults the backend's features to get the correct min and max value.
comment:7 by , 11 years ago
Cc: | added |
---|
comment:8 by , 11 years ago
I tried to change the owner but I don't know how to do that.
Please, reassign it.
comment:9 by , 11 years ago
Yes, there's a small glitch in Trac since we simplified the workflow. You'll have to use the "assign to myself" option, save changes, and then that option will turn into "unassign". Sorry for the inconvenience.
comment:11 by , 11 years ago
Owner: | removed |
---|---|
Status: | assigned → new |
comment:13 by , 11 years ago
The pull request completely ignores the backend-specific limits, so I think it doesn't resolve anything, unfortunately.
comment:14 by , 11 years ago
I went ahead and gathered data about backend-specific limits based on datatypes used.
Field | Documented range | PostgreSQL | SQLite3* | MySQL | Oracle† |
---|---|---|---|---|---|
SmallIntegerField | [-32768; 32767] | As documented | No limits | As documented | [-99999999999; 99999999999] |
IntegerField | [-2147483648; 2147483647] | As documented | No limits | As documented | [-99999999999; 99999999999] |
BigIntegerField | [-9223372036854775808; 9223372036854775807] | As documented | No limits | As documented | [-9999999999999999999; 9999999999999999999] |
PositiveSmallIntegerField | [0; 32767] | As documented | No limits | [0; 4294967295] | [0; 99999999999] |
PositiveIntegerField | [0; 2147483647] | As documented | No limits | [0; 18446744073709551615] | [0; 99999999999] |
* Based on local testing SQLite3 doesn't enforce any limits.
† I also assumed Oracle limits based on documented values of NUMBER(11)
and NUMBER(19)
.
What if we added a BaseDatabaseOperations.field_integer_range(self, internal_type)
that returns a tuple of the (min_value, max_value)
form?
This method could then be called from django.db.fields.IntegerField.__init__
(or stored in a cached_property
to avoid accessing django.db.connection
at import time) in order to pass along default values when formfield
is called. Database field level validation could also be added at this point.
follow-up: 18 comment:15 by , 11 years ago
Thanks Simon for this research. Very nice.
However, as shown in comment:6, the database is not necessarily known at the time of the form instanciation. Considering that probably 98% of Django sites use only one database (and 99.5% only one database engine simultaneously), we could just skip the check if more than one database engine is in use.
comment:16 by , 11 years ago
I'm no expert, but what if it is not a Field itself should bother about the limits?
I mean the formfields returned by modelfields are not aware of the limits.
But ModelForm maybe can find out with which backend it has to interact and set the limits of its integer fields.
It is simple when 'instance' kwarg is provided. Anyway ModelForm knows which backend will be used when save() is called, except something interesting happens between form.is_valid() and form.save().
So is_valid() may update the limits before validating.
In that case first time rendered html will lack HTML5 attributes.
And it is intuitive that is_valid just do checks and does not alter the state.
So my scenario is rather weird than useful, but maybe it makes a little sense.
comment:17 by , 11 years ago
Skipping the check if more than one engine is in use seems good to me. Especially so if we go with only one database engine in use rule. Alternatively we could just collapse the limits of all DB engines in use by doing intersection of all the limit ranges. But even that seems a bit complicated for this problem.
comment:18 by , 11 years ago
Cc: | added |
---|---|
Version: | 1.1 → master |
Replying to claudep:
Thanks Simon for this research. Very nice.
However, as shown in comment:6, the database is not necessarily known at the time of the form instanciation. Considering that probably 98% of Django sites use only one database (and 99.5% only one database engine simultaneously), we could just skip the check if more than one database engine is in use.
I'd add that of this 0.5% using many database engine simultaneously not many of them are retrieving an object from an engine and saving to another. IMHO comment:6 is really an edge case and I assume many other things might also break since constraints checks are all ran against the wrong database here (foreign keys, uniques, ...).
Just to be safe we could add a database aware check in django.db.models.fields.IntegerField.validate
based on model_instance._state.db
or router.db_for_write(model_instance.__class__, instance=model_instance)
by referring directly to connections[db].ops.field_integer_range(self.get_internal_type())
.
Both limits intersection and skips of formfield
default kwargs are suitable since the validation should be enforced at the model level anyway.
comment:19 by , 11 years ago
I agree with @charettes' last statement. This should be in the model validation, not form.
comment:20 by , 11 years ago
Patch needs improvement: | unset |
---|
Created a PR that assumes only one database engine: https://github.com/django/django/pull/2464
It was locally tested against (Py2, Py3) X (SQLite3, PostgreSQL, MySQL) (only missing Oracle).
It's missing a release note since we might want to merge this bugfix into the 1.7.x instead?
comment:21 by , 11 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
comment:22 by , 11 years ago
Owner: | set to |
---|---|
Resolution: | → fixed |
Status: | new → closed |
I don't think it's as simple as checking against 232-1; it will depend on how the backend defines PositiveIntegerField. It's also something that we can now integrate into the model validators.