Django-filter: NullBooleanField рдФрд░ рдХреЛрдИ рдирд╣реАрдВ рдорд╛рди

рдХреЛ рдирд┐рд░реНрдорд┐рдд 14 рдЕрдХреНрддреВре░ 2016  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ  ┬╖  рд╕реНрд░реЛрдд: carltongibson/django-filter

рд╣реИрд▓реЛ, рдореБрдЭреЗ NullBooleanField рдФрд░ рдбреАрдмреА рдореЗрдВ рд╢реВрдиреНрдп рдорд╛рдиреЛрдВ рдХреЛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЦрд╛рд▓реА ('', 'рдХреЛрдИ рдирд╣реАрдВ') рдорд╛рди рдХреЗ рд╕рдВрдмрдВрдз рдореЗрдВ рдЖрдЬ рдХреБрдЫ рдмрд╣реБрдд рд╣реА рд░реЛрдЪрдХ рдмрд╛рдд рдХрд╛ рд╕рд╛рдордирд╛ рдХрд░рдирд╛ рдкрдбрд╝рд╛ред
рд░рд┐рдХреЙрд░реНрдб рдХреЗ рд▓рд┐рдП, рдореИрдВ django рдЖрд░рд╛рдо рдврд╛рдВрдЪреЗ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реВрдВред

рдпрд╣рд╛рдБ рдореЗрд░рд╛ рдЕрдиреБрднрд╡ рд╣реИ:

рдореИрдВ рдПрдХ django рдореЙрдбрд▓ рдмрдирд╛рддрд╛ рд╣реВрдВ:

class MyModel(models.Model):
    is_solved = models.NullBooleanField()

рдФрд░ django рдЖрд░рд╛рдо рдврд╛рдВрдЪреЗ рдореЗрдВ, рдореИрдВ рдЕрдкрдирд╛ рд╡рд┐рдЪрд╛рд░ рдШреЛрд╖рд┐рдд рдХрд░рддрд╛ рд╣реВрдВ:

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

рдЗрд╕рдХреЗ рд╕рд╛рде, рдореЗрд░реЗ рдкрд╛рд╕ django рдЖрд░рд╛рдо рдврд╛рдВрдЪреЗ рдХреЗ рд╕рд╛рде is_solved рдлрд╝реАрд▓реНрдб рдкрд░ рдлрд╝рд┐рд▓реНрдЯрд░ рд╣реИред
рдлрд╝рд┐рд▓реНрдЯрд░ рд▓реЗрдмрд▓/рдорд╛рдиреЛрдВ рдХреЗ рд╕рд╛рде рдПрдХ рдЪреБрдирд┐рдВрджрд╛ рд╡рд┐рдЬреЗрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИ:

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 рдЖрд░рд╛рдо рдврд╛рдВрдЪреЗ рдореЗрдВ рдлрд╝рд┐рд▓реНрдЯрд░ рдореБрдЭреЗ рд╕рд╣реА рд╡рд┐рдЬреЗрдЯ рдХрд╛ рдЪрдпрди рдХрд░рддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЕрдм рдпрд╣ рдЕрдкрдиреЗ рд╕реНрд╡рдпрдВ рдХреЗ рд╡рд┐рдЬреЗрдЯ рдХреЗ рд╕рд╛рде рдЪреЙрдЗрд╕рдлрд┐рд▓реНрдб рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддрд╛ рд╣реИред
рдпрд╣ рдФрд░ рднреА рд╕рдордЭ рдореЗрдВ рдЖрддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдЯреЗрдХреНрд╕реНрдЯ рдЗрдирдкреБрдЯ рдХреЗ рдмрдЬрд╛рдп рдореЙрдбрд▓ рдмрдирд╛рддреЗ рд╕рдордп рдЕрдм тАЛтАЛрдореЗрд░реЗ рдкрд╛рд╕ рдПрдХ рдЪрдпрди рдмреЙрдХреНрд╕ рд╣реИред
рд▓реЗрдХрд┐рди рдЬрдм рдореИрдВ рдлрд╝рд┐рд▓реНрдЯрд░рд┐рдВрдЧ рдХреЗ рд▓рд┐рдП OFF рдХрд╛ рдЪрдпрди рдХрд░рддрд╛ рд╣реВрдВ, рддреЛ url рдПрдХ query_parameter рдХреЛ ?is_solved= рдХреЗ рд░реВрдк рдореЗрдВ рд╕реЗрдЯ рдХрд░рддрд╛ рд╣реИ: /
рдпрд╣ рд╡реИрд╕реЗ рднреА рд╕рд╣реА рдФрд░ рдЧрд▓рдд рдХреЗ рд╕рд╛рде рдЕрдЪреНрдЫреА рддрд░рд╣ рд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред

рддреЛ рдЗрд╕реЗ django рдЖрд░рд╛рдо рдврд╛рдВрдЪреЗ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдЕрдкрдиреА рдкрд╕рдВрдж рдХреЛ рдЗрд╕рдореЗрдВ рдмрджрд▓ рджрд┐рдпрд╛:

CHOICES = (
    ('None', "OFF"),
    (True, "YES"),
    (False, "NO")
)

None рдХреЗ рдмрдЬрд╛рдп None 'None' рдиреЛрдЯ рдХрд░реЗрдВред рдФрд░ рдпрд╣ рдареАрдХ рд╣реИ, рдЕрдм рдореЗрд░реЗ рдкрд╛рд╕ рдПрдХ рдХреНрд╡реЗрд░реА рдкреИрд░рд╛рдореАрдЯрд░ рд╣реИ ?is_solved=None
рд▓реЗрдХрд┐рди рдЕрдм, рдЗрд╕ рдлрд╝рд┐рд▓реНрдЯрд░ рдХреЗ рд╕рд╛рде рдХреБрдЫ рднреА рд╡рд╛рдкрд╕ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ... рд╕реВрдЪреА рдЦрд╛рд▓реА рд╣реИ...

рдореБрдЭреЗ рдЕрдкрдиреЗ рд╣рд╛рде django_filters рдХреЛрдб рдореЗрдВ рдбрд╛рд▓ рджрд┐рдпрд╛ :)

рдХреБрдВрдЬреА рдХреЛрдб рдХреЗ рдЗрд╕ рднрд╛рдЧ рдореЗрдВ рд╣реИ:
https://github.com/carlongibson/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 рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рд╕реЗ рдХреБрдЫ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рдЕрд▓рдЧ рд╣реЛ рдЬрд╛рддрд╛ рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рдлрд╝реАрд▓реНрдб рдХреЛ рдЪреЙрдЗрд╕рдлрд╝рд┐рд▓реНрд▓реНрдб рдХреЗ рд░реВрдк рдореЗрдВ рдирд╣реАрдВ рдорд╛рдирд╛ рдЬрд╛рддрд╛ рд╣реИ:
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')

рдпрд╣ рдСрдкрд░реЗрд╢рди рдПрдХ рдЦрд╛рд▓реА рдХреНрд╡реЗрд░реАрд╕реЗрдЯ рд▓реМрдЯрд╛рдПрдЧрд╛ ...
рдпрд╣ is_solved = None рдХреЗ рд╕рд╛рде рдПрдХ sql рдХреНрд╡реЗрд░реА рддреИрдпрд╛рд░ рдХрд░реЗрдЧрд╛

рд▓реЗрдХрд┐рди рдЕрдЧрд░ рд╣рдо рдкрд░реАрдХреНрд╖рдг рдХрд░рддреЗ рд╣реИрдВ:

qs = qs.filter(is_solved__exact=None)

рдпрд╣ рдбреАрдмреА рдореЗрдВ null рдорд╛рдиреЛрдВ рдХреЗ рд╕рд╛рде рдХреЗрд╡рд▓ рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХреЛ рд╕рд╣реА рдврдВрдЧ рд╕реЗ рд▓реМрдЯрд╛рддрд╛ рд╣реИред
рдпрд╣ is_solved IS NULL рдХреЗ рд╕рд╛рде рдПрдХ sql рдХреНрд╡реЗрд░реА рддреИрдпрд╛рд░ рдХрд░реЗрдЧрд╛
рдкрд░реАрдХреНрд╖рдг рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдпрд╣ рд╡рд╣реА рдмрд╛рдд рд╣реИ:

qs = qs.filter(is_solved__isnull=True)

рд╣рд╛рд▓рд╛рдВрдХрд┐, 'True' рдФрд░ 'False' рдорд╛рдиреЛрдВ рдореЗрдВ рдпрд╣ рд╕рдорд╕реНрдпрд╛ рдирд╣реАрдВ рд╣реИред рдЯреНрд░рд╛рдВрд╕рдХреНрд░рд┐рдкреНрд╢рди Django рджреНрд╡рд╛рд░рд╛ рд╕рд╣реА рд╣реИред

рд╣рдо рдЕрдкрдиреЗ рдлрд╝рд┐рд▓реНрдЯрд░ рдХреЗ lookup_epxr рдХреЛ isnull рд╕реЗ рдУрд╡рд░рд░рд╛рдЗрдб рдХрд░рдиреЗ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рд╕реЛрдЪ рд╕рдХрддреЗ рд╣реИрдВ рд▓реЗрдХрд┐рди рд╣рдорд╛рд░рд╛ рдлрд╝рд┐рд▓реНрдЯрд░ True рдФрд░ False рдорд╛рдиреЛрдВ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдирд╣реАрдВ рдХрд░реЗрдЧрд╛...

рдЗрд╕ рдмрд┐рдВрджреБ рдкрд░, рдореИрдВ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдирд╣реАрдВ рдЬрд╛рдирддрд╛ рдХрд┐ рдЗрд╕ рд╡реНрдпрд╡рд╣рд╛рд░ рдХреЛ рдХреИрд╕реЗ рд╕рдВрднрд╛рд▓рдирд╛ рд╣реИ, django bug? django рдлрд╝рд┐рд▓реНрдЯрд░ рдХреБрдЫ рдпрд╛рдж рдХрд░ рд░рд╣рд╛ рд╣реИ?

рд╡реИрд╕реЗ рднреА, рдореИрдВ django рдлрд╝рд┐рд▓реНрдЯрд░ рдХреЗ filter рдлрд╝рдВрдХреНрд╢рди рдХреЛ рдлрд┐рд░ рд╕реЗ рд▓рд┐рдЦрдХрд░ рдЗрд╕реЗ рдХрд╛рдо рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реВрдВ:

    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

рдпрд╣ рдПрдХ рд╣реИрдХ рдХреА рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ рдФрд░ рдпрд╣ рдореБрдЭреЗ рдЕрднреА рдХреЗ рд▓рд┐рдП рдЦреБрд╢ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ ...
рдЕрдЧрд░ рдХрд┐рд╕реА рдХреЛ рдЗрд╕ рдореБрджреНрджреЗ рдкрд░ рд╕реЛрдЪрдиреЗ рдореЗрдВ рджрд┐рд▓рдЪрд╕реНрдкреА рд╣реИ рддреЛ рдореБрдЭреЗ рдпрд╣ рд╕реБрдирдХрд░ рдЦреБрд╢реА рд╣реЛрдЧреА! :рдбреА

рдЕрднреА рдХреЗ рд▓рд┐рдП, рдЗрд╕ рд╕рдВрд╢реЛрдзрди рдкрд░ рдХреЛрдИ рд╡рд┐рдЪрд╛рд░?
рдЕрднреА, рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдореИрдВ django рдлрд╝рд┐рд▓реНрдЯрд░ рдХреЗ рдлреЛрд░реНрдХрдб рд╕рдВрд╕реНрдХрд░рдг рдХрд╛ рдЙрдкрдпреЛрдЧ рди рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╡рд┐рдХрд▓реНрдк рд╡рд┐рдХрд▓реНрдк рдХреЗ рд╕рд╛рде IntegerField рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реВрдВрдЧрд╛ ... рдХреНрдпреЛрдВрдХрд┐ рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣ рдПрдХ django рдлрд╝рд┐рд▓реНрдЯрд░ рд╢реБрд░реБрдЖрдд рдирд╣реАрдВ рд╣реИ :)

рдзрдиреНрдпрд╡рд╛рдж,

рд╕рдмрд╕реЗ рдЙрдкрдпреЛрдЧреА рдЯрд┐рдкреНрдкрдгреА

рдзрдиреНрдпрд╡рд╛рдж @carltongibson рдФрд░ рдЖрдкрдХреЛ рднреА рдзрдиреНрдпрд╡рд╛рдж @rkilby
рдмрд╕ рд╡рд┐рдХрд╛рд╕ рд╢рд╛рдЦрд╛ рдЦреАрдВрдЪ рд▓реАред

NullBooleanField рдЕрдм рдЗрд╕ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХреЗ рд╕рд╛рде django рд░реЗрд╕реНрдЯ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ:
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

рд╡рд┐рдХрд▓реНрдк рдЯреБрдкрд▓реНрд╕ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдкрд░ рдзреНрдпрд╛рди рджреЗрдВред

рд╕рднреА 3 рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

рд╣рд╛рдп @GuillaumeCisco - рдРрд╕рд╛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдпрд╣рд╛рдВ рддреАрди рдореБрджреНрджреЗ рд╣реИрдВ:

  1. BooleanWidget w/ NullBooleanField s рдХрд╛рдо рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд╢реВрдиреНрдп рдорд╛рдиреЛрдВ рдХреЛ рдорд╛рдиреНрдп рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред

рд╕рдВрднрд╡рддрдГ рдПрдХ NullBooleanWidget рд╣реЛрдирд╛ рдЪрд╛рд╣рд┐рдП рдЬреЛ рдЕрддрд┐рд░рд┐рдХреНрдд рд╡рд┐рдХрд▓реНрдк рдХреЛ рд╕рдорд╛рдпреЛрдЬрд┐рдд рдХрд░рддрд╛ рд╣реЛред

  1. рдЖрдк рд╡рд┐рдХрд▓реНрдкреЛрдВ рдХреЗ рд▓рд┐рдП рдЯреЗрдХреНрд╕реНрдЯ рд▓реЗрдмрд▓ рдХреЛ рдУрд╡рд░рд░рд╛рдЗрдб рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ (рд╣рд╛рдВ/рдирд╣реАрдВ рдХреЗ рдмрдЬрд╛рдп рдЪрд╛рд▓реВ/рдмрдВрдж)ред

рдпрд╣ рд╢рд╛рдпрдж рд╡рд┐рдЬреЗрдЯ рд╡рд░реНрдЧ рдХреЛ рдЙрдкрд╡рд░реНрдЧрд┐рдд рдХрд░рдХреЗ рдФрд░ choices рд╕рдВрдкрддреНрддрд┐ рдХреЛ рдореИрдиреНрдпреБрдЕрд▓ рд░реВрдк рд╕реЗ рд╕реЗрдЯ рдХрд░рдХреЗ рд╕рдмрд╕реЗ рдЕрдЪреНрдЫрд╛ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред BooleanWidget рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдПрдХ рд╡рд┐рдХрд▓реНрдк рддрд░реНрдХ рдХреЛ рд╕реНрд╡реАрдХрд╛рд░ рдирд╣реАрдВ рдХрд░ рд╕рдХрддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдореВрд▓реНрдпреЛрдВ рдХреА рдЕрдкреЗрдХреНрд╖рд╛ рдХрд░рддрд╛ рд╣реИ ( 'true' / 'false' рдХреЗ рдмрдЬрд╛рдп True / False ) . рд╡рд╣реА рд╕рдВрднрд╛рд╡рд┐рдд NullBooleanWidget рдкрд░ рд▓рд╛рдЧреВ рд╣реЛрдЧрд╛ред

  1. None рджреНрд╡рд╛рд░рд╛ рдлрд╝рд┐рд▓реНрдЯрд░ рдХрд░рдиреЗ рдореЗрдВ рд╕рдорд╕реНрдпрд╛рдПрдБ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рд░рд┐рдХреНрдд рдорд╛рди рдЬрд╛рдБрдЪ рдХреЛ рдкрд╛рд╕ рдирд╣реАрдВ рдХрд░рддрд╛ рд╣реИред

рдПрдХ рдЬрд╛рджреБрдИ рдореВрд▓реНрдп рдЬреИрд╕реЗ 'None' рдпрд╛ 'null' рдпрд╣рд╛рдВ рдЖрд╡рд╢реНрдпрдХ рд╣реЛрдиреЗ рдЬрд╛ рд░рд╣рд╛ рд╣реИред рдЗрд╕рдХреЗ рд▓рд┐рдП filter() рдкрджреНрдзрддрд┐ рдХрд╛ рд╣рд┐рд╕рд╛рдм рджреЗрдирд╛ рд╣реЛрдЧрд╛ред

рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдЗрд╕рдХрд╛ рдмрдбрд╝рд╛ рд╣рд┐рд╕реНрд╕рд╛ #519 рджреНрд╡рд╛рд░рд╛ рдХрд╡рд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред

@GuillaumeCisco рдХреГрдкрдпрд╛ рдЗрд╕рдХреА рд╕рдореАрдХреНрд╖рд╛ рдХрд░реЗрдВред рдпрджрд┐ рдХреЛрдИ рд╡рд┐рд╢рд┐рд╖реНрдЯ рдкрд░реАрдХреНрд╖рдг рдорд╛рдорд▓рд╛ рд╣реИ рдЬреЛ рдЖрдкрдХреЛ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рдХрд╡рд░ рдХрд┐рдпрд╛ рдЬрд╛рдирд╛ рдЪрд╛рд╣рд┐рдП, рддреЛ рдХреНрдпрд╛ рдЖрдк рдЗрд╕реЗ рдЬреЛрдбрд╝рдХрд░ рдПрдХ рдкреАрдЖрд░ рдЦреЛрд▓ рд╕рдХрддреЗ рд╣реИрдВ рдФрд░ рд╣рдо рд╕рдореАрдХреНрд╖рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред

рдзрдиреНрдпрд╡рд╛рджред

рдзрдиреНрдпрд╡рд╛рдж @carltongibson рдФрд░ рдЖрдкрдХреЛ рднреА рдзрдиреНрдпрд╡рд╛рдж @rkilby
рдмрд╕ рд╡рд┐рдХрд╛рд╕ рд╢рд╛рдЦрд╛ рдЦреАрдВрдЪ рд▓реАред

NullBooleanField рдЕрдм рдЗрд╕ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХреЗ рд╕рд╛рде django рд░реЗрд╕реНрдЯ рдлреНрд░реЗрдорд╡рд░реНрдХ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░ рд░рд╣рд╛ рд╣реИ:
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

рд╡рд┐рдХрд▓реНрдк рдЯреБрдкрд▓реНрд╕ рдХреЗ рдмреАрдЪ рдЕрдВрддрд░ рдкрд░ рдзреНрдпрд╛рди рджреЗрдВред

рдХреНрдпрд╛ рдпрд╣ рдкреГрд╖реНрда рдЙрдкрдпреЛрдЧреА рдерд╛?
0 / 5 - 0 рд░реЗрдЯрд┐рдВрдЧреНрд╕

рд╕рдВрдмрдВрдзрд┐рдд рдореБрджреНрджреЛрдВ

chromakey picture chromakey  ┬╖  5рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

gotexis picture gotexis  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

madelyneriksen picture madelyneriksen  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

jnegro picture jnegro  ┬╖  3рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ

ses4j picture ses4j  ┬╖  4рдЯрд┐рдкреНрдкрдгрд┐рдпрд╛рдБ