Bagaimana cara membatasi nilai maksimum bidang numerik dalam model Django?

169

Django memiliki berbagai bidang numerik yang tersedia untuk digunakan dalam model, misalnya DecimalField dan PositiveIntegerField . Meskipun yang pertama dapat dibatasi pada jumlah tempat desimal yang disimpan dan jumlah keseluruhan karakter yang disimpan, adakah cara untuk membatasinya agar hanya menyimpan angka dalam rentang tertentu, misalnya 0,0-5,0?

Gagal, apakah ada cara untuk membatasi PositiveIntegerField hanya menyimpan, misalnya, nomor hingga 50?

Pembaruan: sekarang Bug 6845 telah ditutup , pertanyaan StackOverflow ini mungkin dapat diperdebatkan. - sampablokuper

sampablokuper
sumber
Saya seharusnya mengatakan bahwa saya juga ingin pembatasan diterapkan di admin Django. Untuk mendapatkan itu, setidaknya, dokumentasi memiliki ini untuk mengatakan: docs.djangoproject.com/en/dev/ref/contrib/admin/…
sampablokuper
Sebenarnya, pra-1.0 Django tampaknya memiliki solusi yang sangat elegan: cotellese.net/2007/12/11/… . Saya bertanya-tanya apakah ada cara yang sama-sama elegan dalam melakukan ini dalam rilis Django.
sampablokuper
Saya kecewa mengetahui bahwa sepertinya tidak ada cara yang elegan untuk melakukan ini dengan Django svn saat ini. Lihat utas diskusi ini untuk lebih jelasnya: groups.google.com/group/django-users/browse_thread/thread/…
sampablokuper
Gunakan validator pada model, dan validasi akan berfungsi di antarmuka admin dan di ModelForms: docs.djangoproject.com/en/dev/ref/validators/…
guettli

Jawaban:

133

Anda juga dapat membuat jenis bidang model khusus - lihat http://docs.djangoproject.com/en/dev/howto/custom-model-fields/#howto-custom-model-fields

Dalam hal ini, Anda bisa 'mewarisi' dari IntegerField bawaan dan mengganti logika validasinya.

Semakin saya memikirkan hal ini, saya menyadari betapa berharganya hal ini bagi banyak aplikasi Django. Mungkin tipe IntegerRangeField dapat dikirimkan sebagai tambalan untuk pengembang Django untuk dipertimbangkan menambah trunk.

Ini bekerja untuk saya:

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)

Kemudian di kelas model Anda, Anda akan menggunakannya seperti ini (bidang menjadi modul tempat Anda meletakkan kode di atas):

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

ATAU untuk rentang negatif dan positif (seperti rentang osilator):

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

Apa yang akan sangat keren adalah jika bisa dipanggil dengan operator jarak seperti ini:

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

Tapi, itu akan membutuhkan lebih banyak kode karena sejak Anda dapat menentukan parameter 'lewati' - rentang (1, 50, 2) - Namun, ide yang menarik ...

NathanD
sumber
Ini berfungsi tetapi ketika dalam metode clean model, nilai integer selalu adalah None yang membuatnya jadi saya tidak dapat memberikan pembersihan tambahan apa pun untuknya. Adakah yang tahu mengapa ini dan bagaimana cara memperbaikinya?
KrisF
2
Anda dapat meningkatkan bidang khusus Anda dengan menambahkan MinValueValidator(min_value) and MaxValueValidator(max_value)sebelum menelepon super().__init__ ...(snippet: gist.github.com/madneon/147159f46ed478c71d5ee4950a9d697d )
madneon
349

Anda dapat menggunakan validator bawaan Django -

from django.db.models import IntegerField, Model
from django.core.validators import MaxValueValidator, MinValueValidator

class CoolModelBro(Model):
    limited_integer_field = IntegerField(
        default=1,
        validators=[
            MaxValueValidator(100),
            MinValueValidator(1)
        ]
     )

Sunting : Saat bekerja langsung dengan model, pastikan untuk memanggil metode model full_clean sebelum menyimpan model untuk memicu validator. Ini tidak diperlukan saat menggunakan ModelFormkarena formulir akan melakukannya secara otomatis.

pengguna1569050
sumber
4
Saya kira Anda harus menulis validator Anda sendiri jika Anda ingin limited_integer_field juga opsional? (Hanya rentang Validasi jika tidak kosong) null = Benar, kosong = Benar tidak melakukannya ..
radtek
2
Dalam Django 1.7 pengaturan null=Truedan blank=Trueberfungsi seperti yang diharapkan. Bidang ini opsional dan jika dibiarkan kosong itu disimpan sebagai nol.
Tim Tisdall
77
from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator

size = models.IntegerField(validators=[MinValueValidator(0),
                                       MaxValueValidator(5)])
congocongo
sumber
52

Saya memiliki masalah yang sama; inilah solusi saya:

SCORE_CHOICES = zip( range(1,n), range(1,n) )
score = models.IntegerField(choices=SCORE_CHOICES, blank=True)
chris.haueter
sumber
10
Menggunakan pemahaman daftar:models.IntegerField(choices=[(i, i) for i in range(1, n)], blank=True)
Razzi Abuissa
10

Ada dua cara untuk melakukan ini. Salah satunya adalah menggunakan validasi formulir untuk tidak pernah membiarkan nomor lebih dari 50 dimasukkan oleh pengguna. Formulir dokumen validasi .

Jika tidak ada pengguna yang terlibat dalam proses, atau Anda tidak menggunakan formulir untuk memasukkan data, maka Anda harus mengganti metode model saveuntuk membuang pengecualian atau membatasi data yang masuk ke bidang.

tghw
sumber
2
Anda dapat menggunakan formulir untuk memvalidasi input non-manusia juga. Bekerja sangat bagus untuk mengisi Formulir sebagai teknik validasi menyeluruh.
S.Lott
1
Setelah memikirkan ini, saya cukup yakin saya tidak ingin memasukkan validasi ke dalam formulir. Pertanyaan tentang kisaran angka mana yang dapat diterima adalah bagian dari model seperti halnya pertanyaan jenis nomor mana yang dapat diterima. Saya tidak ingin harus memberi tahu setiap formulir melalui mana model dapat diedit, hanya kisaran angka yang harus diterima. Ini akan melanggar KERING, dan selain itu, itu jelas tidak pantas. Jadi aku akan melihat ke dalam override model ini menyimpan metode, atau mungkin menciptakan jenis bidang model kustom - kecuali saya dapat menemukan bahkan cara yang lebih baik :)
sampablokuper
tghw, Anda bilang saya bisa "mengganti metode save model untuk membuang pengecualian atau membatasi data yang masuk ke lapangan." Bagaimana saya - dari dalam metode overriden save () definisi model - membuatnya sehingga jika angka yang dimasukkan berada di luar rentang yang diberikan, pengguna menerima kesalahan validasi seperti jika ia memasukkan data karakter ke dalam bidang angka? Yaitu adakah cara saya bisa melakukan ini yang akan berfungsi terlepas dari apakah pengguna mengedit melalui admin atau melalui beberapa bentuk lainnya? Saya tidak ingin membatasi data masuk ke lapangan tanpa memberi tahu pengguna apa yang terjadi :) Terima kasih!
sampablokuper
Bahkan jika Anda menggunakan metode "save", ini tidak akan berfungsi ketika memperbarui tabel melalui QuerySet seperti MyModel.object.filter (blabla) .update (blabla) tidak akan memanggil save sehingga tidak ada pemeriksaan yang akan dilakukan
Olivier Pons
5

Berikut adalah solusi terbaik jika Anda ingin fleksibilitas ekstra dan tidak ingin mengubah bidang model Anda. Cukup tambahkan validator khusus ini:

#Imports
from django.core.exceptions import ValidationError      

class validate_range_or_null(object):
    compare = lambda self, a, b, c: a > c or a < b
    clean = lambda self, x: x
    message = ('Ensure this value is between %(limit_min)s and %(limit_max)s (it is %(show_value)s).')
    code = 'limit_value'

    def __init__(self, limit_min, limit_max):
        self.limit_min = limit_min
        self.limit_max = limit_max

    def __call__(self, value):
        cleaned = self.clean(value)
        params = {'limit_min': self.limit_min, 'limit_max': self.limit_max, 'show_value': cleaned}
        if value:  # make it optional, remove it to make required, or make required on the model
            if self.compare(cleaned, self.limit_min, self.limit_max):
                raise ValidationError(self.message, code=self.code, params=params)

Dan itu bisa digunakan seperti itu:

class YourModel(models.Model):

    ....
    no_dependents = models.PositiveSmallIntegerField("How many dependants?", blank=True, null=True, default=0, validators=[validate_range_or_null(1,100)])

Dua parameter adalah maks dan min, dan memungkinkan nol. Anda dapat menyesuaikan validator jika Anda suka dengan menghilangkan pernyataan jika ditandai atau mengubah bidang Anda menjadi kosong = Salah, null = Salah dalam model. Tentu saja itu membutuhkan migrasi.

Catatan: Saya harus menambahkan validator karena Django tidak memvalidasi rentang pada PositiveSmallIntegerField, sebagai gantinya itu menciptakan smallint (dalam postgres) untuk bidang ini dan Anda mendapatkan kesalahan DB jika angka yang ditentukan di luar kisaran.

Semoga ini bisa membantu :) Lebih lanjut tentang Validator di Django .

PS. Saya mendasarkan jawaban saya pada BaseValidator di django.core.validators, tetapi semuanya berbeda kecuali untuk kode.

radtek
sumber