مرحبًا ، لقد واجهت شيئًا مثيرًا للاهتمام اليوم بخصوص NullBooleanField والقيمة الفارغة ('' ، 'None') لتصفية القيم الفارغة في db.
للتسجيل ، أستخدم إطار django rest.
ها هي تجربتي:
أقوم بإنشاء نموذج django:
class MyModel(models.Model):
is_solved = models.NullBooleanField()
وفي إطار django rest ، أعلن وجهة نظري:
class MyModelFilter(filters.FilterSet):
class Meta:
model = MyModel
fields = ('is_solved',)
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = MyModelFilter
مع هذا ، لدي مرشح في الحقل is_solved
مع إطار عمل django rest.
يستخدم الفلتر أداة تحديد مع تسمية / قيم:
choices = (('', _('Unknown')),
('true', _('Yes')),
('false', _('No')))
كما هو موضح هنا: https://github.com/carltongibson/django-filter/blob/develop/django_filters/widgets.py#L103
عندما أستخدم هذا الفلتر ، إذا حددت Unknown
، فسيتم إرجاع كل شيء ... لذلك لا تتم تصفية أي شيء ...
ما أردت هو الحصول على كائنات فقط مع تعيين is_solved
على null
بالديسيبل.
علاوة على ذلك ، أردت تغيير طريقة عرض is_solved
، لذا قمت بإجراء تعديل بسيط على النموذج الخاص بي.
النموذج الجديد:
CHOICES = (
(None, "OFF"),
(True, "YES"),
(False, "NO")
)
class MyModel(models.Model):
is_solved = models.NullBooleanField(choices=CHOICES)
من خلال هذا التعديل ، يمنحني المرشح في إطار عمل django rest الصحيح Select Widget ، ولكنه يستخدم الآن ChoiceField ، مع عنصر واجهة المستخدم الخاص به.
إنه أيضًا أكثر منطقية ، حيث لديّ الآن selectBox عند إنشاء نموذج بدلاً من إدخال نص.
ولكن عندما أحدد OFF
للتصفية ، فإن عنوان url يعين معلمة الاستعلام على النحو ?is_solved=
: /
إنه يعمل بشكل جيد مع الصواب والخطأ على أي حال.
لذلك لجعله يعمل مع إطار عمل django rest ، قمت بتغيير اختياراتي إلى:
CHOICES = (
('None', "OFF"),
(True, "YES"),
(False, "NO")
)
لاحظ 'None'
بدلاً من None
. ولا بأس ، لدي الآن معامل استعلام ?is_solved=None
ولكن الآن ، لم يتم إرجاع أي شيء باستخدام هذا الفلتر ... القائمة فارغة ...
جعلني أضع يدي في كود django_filters :)
المفتاح موجود في هذا الجزء من الكود:
https://github.com/carltongibson/django-filter/blob/develop/django_filters/filters.py#L172
def filter(self, qs, value):
if isinstance(value, Lookup):
lookup = six.text_type(value.lookup_type)
value = value.value
else:
lookup = self.lookup_expr
if value in EMPTY_VALUES:
return qs
if self.distinct:
qs = qs.distinct()
qs = self.get_method(qs)(**{'%s__%s' % (self.name, lookup): value})
return qs
هذه القطعة الصغيرة من الكود تقوم بالترشيح.
وقد وجدت شيئًا مثيرًا للاهتمام للغاية في سلوك dkango QuerySet.
جزء واحد من السطور المهمة هو:
if value in EMPTY_VALUES:
return qs
يتم إرجاع الاستعلام بدون تصفية إذا كانت لدينا قيمة فارغة وهي [], (), u'', {}, None
لكن قيمتنا هي 'None'
لذا لا بأس ، كل الكود السابق لم يحول قيمة 'None'
إلى None
. لكن استخدام NullBooleanField
بدون choices
يحدث شيئًا مختلفًا تمامًا لأن الحقل لا يعتبر حقل ChoiceField:
كود من جوهر django:
class NullBooleanField(BooleanField):
"""
A field whose valid values are None, True and False. Invalid values are
cleaned to None.
"""
widget = NullBooleanSelect
def to_python(self, value):
"""
Explicitly checks for the string 'True' and 'False', which is what a
hidden field will submit for True and False, for 'true' and 'false',
which are likely to be returned by JavaScript serializations of forms,
and for '1' and '0', which is what a RadioField will submit. Unlike
the Booleanfield we need to explicitly check for True, because we are
not using the bool() function
"""
if value in (True, 'True', 'true', '1'):
return True
elif value in (False, 'False', 'false', '0'):
return False
else:
return None
def validate(self, value):
pass
الآن أصبحت الأمور ممتعة حقًا ...
ألق نظرة على هذا الخط:
qs = self.get_method(qs)(**{'%s__%s' % (self.name, lookup): value})
يقوم بالترشيح لتمرير django core db Queryset.
يمكننا ترجمته إلى:
qs = qs.filter(is_solved__exact='None')
ستعيد هذه العملية مجموعة استعلام فارغة ...
سينتج استعلام SQL مع is_solved = None
لكن إذا اختبرنا:
qs = qs.filter(is_solved__exact=None)
تقوم بإرجاع المثيلات بشكل صحيح فقط مع قيم null
بالديسيبل.
سينتج استعلام SQL مع is_solved IS NULL
هذا هو الشيء نفسه الذي يجب اختباره بالضبط:
qs = qs.filter(is_solved__isnull=True)
ومع ذلك ، لا تحتوي قيم 'True'
و 'False'
على هذه المشكلة. النسخ صحيح بواسطة Django.
يمكننا التفكير في تجاوز lookup_epxr لعامل التصفية الخاص بنا بمقدار isnull
لكن الفلتر الخاص بنا لن يعمل مع قيم True
و False
...
في هذه المرحلة ، أنا حقًا لا أعرف كيف أتعامل مع هذا السلوك ، علة django؟ مرشحات django في عداد المفقودين شيء؟
على أي حال ، تمكنت من جعله يعمل عن طريق إعادة كتابة وظيفة filter
لفلاتر django:
def filter(self, qs, value):
from django.db.models import NullBooleanField
if isinstance(value, Lookup):
lookup = six.text_type(value.lookup_type)
value = value.value
else:
lookup = self.lookup_expr
if value in EMPTY_VALUES:
return qs
if self.distinct:
qs = qs.distinct()
# if original field is a NullBooleanField, we need to transform 'None' value as None
if isinstance(self.model._meta.get_field(self.name), NullBooleanField) and value in ('None',):
value = None
qs = self.get_method(qs)(**{'%s__%s' % (self.name, lookup): value})
return qs
هذا يبدو وكأنه اختراق ولا يسعدني في الوقت الحالي ...
إذا كان أحدهم مهتمًا بالتفكير في هذه المسألة ، فسأكون سعيدًا لسماع ذلك! :د
في الوقت الحالي ، أي أفكار حول هذا التعديل؟
في الوقت الحالي ، أعتقد أنني سأستخدم IntegerField
مع خيار خيارات لعدم الاضطرار إلى استخدام نسخة متشعبة من مرشحات django ... لأنني أعتقد أنها ليست مشكلة في البداية لفلاتر django :)
شكرا،
مرحبًا GuillaumeCisco - يبدو أن هناك ثلاث مشكلات هنا:
BooleanWidget
لا يعمل مع / NullBooleanField
s ، لأنه لا يتحقق من صحة القيم الخالية.يجب أن يكون هناك على الأرجح NullBooleanWidget
يستوعب الخيار الإضافي.
من المحتمل أن يتم التعامل مع هذا بشكل أفضل عن طريق التصنيف الفرعي لفئة عنصر واجهة المستخدم وتعيين خاصية choices
يدويًا. لا يقبل BooleanWidget
فعلاً وسيطة اختيارات ، لأنه يتوقع قيمًا معينة ( 'true'
/ 'false'
بدلاً من True
/ False
) . ينطبق الأمر نفسه على NullBooleanWidget
محتمل.
None
، لأنها لا تمر بفحص القيمة الفارغة.ستكون القيمة السحرية مثل 'None'
أو 'null'
ضرورية هنا. سيتعين على طريقة filter()
حساب ذلك.
أعتقد أن الجزء الأكبر من هذا تمت تغطيته بالرقم 519.
GuillaumeCisco يرجى مراجعة ذلك. إذا كانت هناك حالة اختبار معينة تعتقد أنه يجب تغطيتها ، فهل يمكنك فتح بيان عام مضيفًا ذلك ويمكننا مراجعته.
شكرا.
شكرا لك @ carltongibson وشكرا لك أيضا rpkilby
فقط سحبت فرع التنمية.
يعمل NullBooleanField الآن مع إطار عمل django rest مع هذا التكوين:
Model
:
CHOICES = (
(None, "OFF"),
(True, "YES"),
(False, "NO")
)
class MyModel(models.Model):
is_solved = models.NullBooleanField(choices=CHOICES)
View
:
choices = (
(True, "YES"),
(False, "NO")
)
class MyModelFilter(filters.FilterSet):
is_solved = ChoiceFilter(null_label='OFF', choices=choices)
class Meta:
model = MyModel
fields = ('is_solved',)
class MyModelViewSet(ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = MyModelFilter
لاحظ الفرق بين مجموعات الاختيارات.
التعليق الأكثر فائدة
شكرا لك @ carltongibson وشكرا لك أيضا rpkilby
فقط سحبت فرع التنمية.
يعمل NullBooleanField الآن مع إطار عمل django rest مع هذا التكوين:
Model
:View
:لاحظ الفرق بين مجموعات الاختيارات.