diff --git a/.travis.yml b/.travis.yml index 4ad70fa7..ea495b37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ env: - DJANGO="2.0,<3.0" install: - - pip install coverage coveralls djangorestframework + - pip install coverage coveralls djangorestframework psycopg2 - pip install "Django>=$DJANGO" - pip install git+https://github.com/landscapeio/pylint-plugin-utils.git@master script: diff --git a/pylint_django/transforms/fields.py b/pylint_django/transforms/fields.py index 5a22dfda..1e3a5a8f 100644 --- a/pylint_django/transforms/fields.py +++ b/pylint_django/transforms/fields.py @@ -9,10 +9,13 @@ _INT_FIELDS = ('IntegerField', 'SmallIntegerField', 'BigIntegerField', 'PositiveIntegerField', 'PositiveSmallIntegerField') _BOOL_FIELDS = ('BooleanField', 'NullBooleanField') +_RANGE_FIELDS = ('RangeField', 'IntegerRangeField', 'BigIntegerRangeField', + 'FloatRangeField', 'DateTimeRangeField', 'DateRangeField') def is_model_field(cls): - return cls.qname().startswith('django.db.models.fields') + return cls.qname().startswith('django.db.models.fields') or \ + cls.qname().startswith('django.contrib.postgres.fields') def is_form_field(cls): @@ -47,10 +50,18 @@ def apply_type_shim(cls, context=None): # noqa base_nodes = MANAGER.ast_from_module_name('datetime').lookup('date') elif cls.name == 'DurationField': base_nodes = MANAGER.ast_from_module_name('datetime').lookup('timedelta') + elif cls.name == 'UUIDField': + base_nodes = MANAGER.ast_from_module_name('uuid').lookup('UUID') elif cls.name == 'ManyToManyField': base_nodes = MANAGER.ast_from_module_name('django.db.models.query').lookup('QuerySet') elif cls.name in ('ImageField', 'FileField'): base_nodes = MANAGER.ast_from_module_name('django.core.files.base').lookup('File') + elif cls.name == 'ArrayField': + base_nodes = scoped_nodes.builtin_lookup('list') + elif cls.name in ('HStoreField', 'JSONField'): + base_nodes = scoped_nodes.builtin_lookup('dict') + elif cls.name in _RANGE_FIELDS: + base_nodes = MANAGER.ast_from_module_name('psycopg2._range').lookup('Range') else: return iter([cls]) diff --git a/pylint_django/transforms/transforms/django_contrib_postgres_fields.py b/pylint_django/transforms/transforms/django_contrib_postgres_fields.py new file mode 100644 index 00000000..64c9a229 --- /dev/null +++ b/pylint_django/transforms/transforms/django_contrib_postgres_fields.py @@ -0,0 +1,47 @@ +from django.contrib.postgres import fields as django_fields +from psycopg2 import extras + + +# -------- +# lists + +class ArrayField(list, django_fields.ArrayField): + pass + + +# -------- +# dicts + +class HStoreField(dict, django_fields.HStoreField): + pass + + +class JSONField(dict, django_fields.JSONField): + pass + + +# -------- +# ranges + +class RangeField(extras.Range, django_fields.RangeField): + pass + + +class IntegerRangeField(extras.NumericRange, django_fields.IntegerRangeField): + pass + + +class BigIntegerRangeField(extras.NumericRange, django_fields.BigIntegerRangeField): + pass + + +class FloatRangeField(extras.NumericRange, django_fields.FloatRangeField): + pass + + +class DateTimeRangeField(extras.DateTimeTZRange, django_fields.DateRangeField): + pass + + +class DateRangeField(extras.DateRange, django_fields.DateRangeField): + pass diff --git a/pylint_django/transforms/transforms/django_db_models_fields.py b/pylint_django/transforms/transforms/django_db_models_fields.py index a0208fc0..f8cf12e4 100644 --- a/pylint_django/transforms/transforms/django_db_models_fields.py +++ b/pylint_django/transforms/transforms/django_db_models_fields.py @@ -1,6 +1,7 @@ from django.db.models import fields as django_fields import datetime from decimal import Decimal +from uuid import UUID # -------- @@ -125,3 +126,7 @@ class GenericIPAddressField(str, django_fields.GenericIPAddressField): class IPAddressField(str, django_fields.IPAddressField): pass + + +class UUIDField(UUID, django_fields.UUIDField): + pass diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/input/external_psycopg2_noerror_postgres_fields.py b/test/input/external_psycopg2_noerror_postgres_fields.py new file mode 100644 index 00000000..e5cb0dde --- /dev/null +++ b/test/input/external_psycopg2_noerror_postgres_fields.py @@ -0,0 +1,52 @@ +""" +Checks that Pylint does not complain Postgres model fields. +""" +# pylint: disable=C0111,W5101 +from __future__ import print_function + +from django.contrib.postgres import fields +from django.db import models + + +class PostgresFieldsModel(models.Model): + arrayfield = fields.ArrayField(models.CharField()) + hstorefield = fields.HStoreField() + jsonfield = fields.JSONField() + rangefield = fields.RangeField() + integerrangefield = fields.IntegerRangeField() + bigintegerrangefield = fields.BigIntegerRangeField() + floatrangefield = fields.FloatRangeField() + datetimerangefield = fields.DateTimeRangeField() + daterangefield = fields.DateRangeField() + + def arrayfield_tests(self): + sorted_array = self.arrayfield.sort() + print(sorted_array) + + def dictfield_tests(self): + print(self.hstorefield.keys()) + print(self.hstorefield.values()) + print(self.hstorefield.update({'foo': 'bar'})) + + print(self.jsonfield.keys()) + print(self.jsonfield.values()) + print(self.jsonfield.update({'foo': 'bar'})) + + def rangefield_tests(self): + print(self.rangefield.lower) + print(self.rangefield.upper) + + print(self.integerrangefield.lower) + print(self.integerrangefield.upper) + + print(self.bigintegerrangefield.lower) + print(self.bigintegerrangefield.upper) + + print(self.floatrangefield.lower) + print(self.floatrangefield.upper) + + print(self.datetimerangefield.lower) + print(self.datetimerangefield.upper) + + print(self.daterangefield.lower) + print(self.daterangefield.upper) diff --git a/test/input/external_psycopg2_noerror_postgres_fields.rc b/test/input/external_psycopg2_noerror_postgres_fields.rc new file mode 100644 index 00000000..79f0c092 --- /dev/null +++ b/test/input/external_psycopg2_noerror_postgres_fields.rc @@ -0,0 +1,2 @@ +[testoptions] +requires = psycopg2 diff --git a/test/input/func_noerror_uuid_field.py b/test/input/func_noerror_uuid_field.py new file mode 100644 index 00000000..1fa5b12c --- /dev/null +++ b/test/input/func_noerror_uuid_field.py @@ -0,0 +1,19 @@ +""" +Checks that Pylint does not complain about UUID fields. +""" +# pylint: disable=C0111,W5101 +from __future__ import print_function +from django.db import models + + +class LotsOfFieldsModel(models.Model): + uuidfield = models.UUIDField() + + def uuidfield_tests(self): + print(self.uuidfield.bytes) + print(self.uuidfield.bytes_le) + print(self.uuidfield.fields[2]) + print(self.uuidfield.hex) + # print(self.uuidfield.int) # Don't know how to properly check this one + print(self.uuidfield.variant) + print(self.uuidfield.version)