Make CharField(blank=True) not be considered nullable (#39)

* Make CharField(blank=True) not be considered nullable

The documentation on [blank](https://docs.djangoproject.com/en/2.1/ref/models/fields/#blank) says that it "will allow the entry of an empty value", which for a string is just a 0-length string. This patch allows `CharField(blank=True,...)` to no longer be considered `Optional`.

closes #38

* fixed tests for `CharField(blank=True)`

* allow blank CharField to be nullable in the constructor, but the underlying type
is str (unless `null=True`)
This commit is contained in:
Richard Eames
2019-03-05 15:37:44 -07:00
committed by Maxim Kurnikov
parent 627daa55f5
commit f7dfbefbd6
3 changed files with 23 additions and 7 deletions

View File

@@ -101,10 +101,6 @@ def get_private_descriptor_type(type_info: TypeInfo, private_field_name: str, is
def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance: def set_descriptor_types_for_field(ctx: FunctionContext) -> Instance:
default_return_type = cast(Instance, ctx.default_return_type) default_return_type = cast(Instance, ctx.default_return_type)
is_nullable = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'null')) is_nullable = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'null'))
if not is_nullable and default_return_type.type.has_base(helpers.CHAR_FIELD_FULLNAME):
# blank=True for CharField can be interpreted as null=True
is_nullable = helpers.parse_bool(helpers.get_argument_by_name(ctx, 'blank'))
set_type = get_private_descriptor_type(default_return_type.type, '_pyi_private_set_type', set_type = get_private_descriptor_type(default_return_type.type, '_pyi_private_set_type',
is_nullable=is_nullable) is_nullable=is_nullable)
get_type = get_private_descriptor_type(default_return_type.type, '_pyi_private_get_type', get_type = get_private_descriptor_type(default_return_type.type, '_pyi_private_get_type',

View File

@@ -183,6 +183,13 @@ def extract_expected_types(ctx: FunctionContext, model: TypeInfo,
False): False):
field_type = helpers.make_optional(field_type) field_type = helpers.make_optional(field_type)
# if CharField(blank=True,...) and not nullable, then field can be None in __init__
elif (
helpers.has_any_of_bases(typ.type, (helpers.CHAR_FIELD_FULLNAME,)) and is_init and
field_metadata.get('blank', False) and not field_metadata.get('null', False)
):
field_type = helpers.make_optional(field_type)
expected_types[name] = field_type expected_types[name] = field_type
return expected_types return expected_types

View File

@@ -110,9 +110,22 @@ class MyModel(ParentModel):
reveal_type(MyModel().id) # E: Revealed type is 'uuid.UUID*' reveal_type(MyModel().id) # E: Revealed type is 'uuid.UUID*'
[/CASE] [/CASE]
[CASE blank_for_charfield_is_the_same_as_null] [CASE blank_and_null_char_field_allows_none]
from django.db import models from django.db import models
class MyModel(models.Model): class MyModel(models.Model):
text = models.CharField(max_length=30, blank=True) nulltext=models.CharField(max_length=1, blank=True, null=True)
MyModel(text=None) MyModel(nulltext="")
MyModel(nulltext=None)
MyModel().nulltext=None
reveal_type(MyModel().nulltext) # E: Revealed type is 'Union[builtins.str, None]'
[/CASE]
[CASE blank_and_not_null_charfield_does_not_allow_none]
from django.db import models
class MyModel(models.Model):
notnulltext=models.CharField(max_length=1, blank=True, null=False)
MyModel(notnulltext=None) # Should allow None in constructor
MyModel(notnulltext="")
MyModel().notnulltext = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]")
reveal_type(MyModel().notnulltext) # E: Revealed type is 'builtins.str*'
[/CASE] [/CASE]