Skip to content

Commit

Permalink
[refs #28] Initial attempts at better python3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
carlio committed Jun 18, 2015
1 parent c4556df commit 3ae839a
Show file tree
Hide file tree
Showing 9 changed files with 78 additions and 22 deletions.
52 changes: 30 additions & 22 deletions pylint_django/checkers/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,28 @@
from pylint.checkers.utils import check_messages
from pylint.checkers import BaseChecker
from pylint_django.__pkginfo__ import BASE_ID
from pylint_django.utils import node_is_subclass, PY3
from pylint_django.utils import node_is_subclass, PY3, iter_cls_and_bases


REPR_NAME = '__str__' if PY3 else '__unicode__'

MESSAGES = {
'E%d01' % BASE_ID: ("__unicode__ on a model must be callable (%s)",
'E%d01' % BASE_ID: ("%s on a model must be callable (%%s)" % REPR_NAME,
'model-unicode-not-callable',
"Django models require a callable __unicode__ method"),
'W%d01' % BASE_ID: ("No __unicode__ method on model (%s)",
"Django models require a callable %s method" % REPR_NAME),
'W%d01' % BASE_ID: ("No %s method on model (%%s)" % REPR_NAME,
'model-missing-unicode',
"Django models should implement a __unicode__ method for string representation"),
"Django models should implement a %s "
"method for string representation" % REPR_NAME),
'W%d02' % BASE_ID: ("Found __unicode__ method on model (%s). Python3 uses __str__.",
'model-has-unicode',
"Django models should not implement a __unicode__ "
"method for string representation when using Python3"),
'W%d03' % BASE_ID: ("Model does not explicitly define __unicode__ (%s)",
'W%d03' % BASE_ID: ("Model does not explicitly define %s (%%s)" % REPR_NAME,
'model-no-explicit-unicode',
"Django models should implement a __unicode__ method for string representation. "
"A parent class of this model does, but ideally all models should be explicit.")
"Django models should implement a %s method for string representation. "
"A parent class of this model does, but ideally all models should be "
"explicit." % REPR_NAME)
}


Expand Down Expand Up @@ -54,10 +58,22 @@ class ModelChecker(BaseChecker):
@check_messages('model-missing-unicode')
def visit_class(self, node):
"""Class visitor."""

if not node_is_subclass(node, 'django.db.models.base.Model'):
# we only care about models
return

has_py2_compat_decorator = False
for cur_node in iter_cls_and_bases(node):
if cur_node.qname() == 'django.db.models.base.Model':
break
if cur_node.decorators is not None:
for decorator in cur_node.decorators.nodes:
print decorator
if getattr(decorator, 'name', None) == 'python_2_unicode_compatible':
has_py2_compat_decorator = True
break

for child in node.get_children():
if _is_meta_with_abstract(child):
return
Expand All @@ -69,7 +85,7 @@ def visit_class(self, node):
continue

name = grandchildren[0].name
if name != '__unicode__':
if name != REPR_NAME:
continue

assigned = grandchildren[1].infered()[0]
Expand All @@ -79,28 +95,20 @@ def visit_class(self, node):
self.add_message('E%s01' % BASE_ID, args=node.name, node=node)
return

if isinstance(child, Function) and child.name == '__unicode__':
if PY3:
if isinstance(child, Function) and child.name == REPR_NAME:
if PY3 and not has_py2_compat_decorator:
self.add_message('W%s02' % BASE_ID, args=node.name, node=node)
return

# if we get here, then we have no __unicode__ method directly on the class itself
if PY3:
return

# a different warning is emitted if a parent declares __unicode__
for method in node.methods():
if method.name == '__unicode__':
if method.name == REPR_NAME:
# this happens if a parent declares the unicode method but
# this node does not
self.add_message('W%s03' % BASE_ID, args=node.name, node=node)
return

# if the Django compatibility decorator is used then we don't emit a warning
# see https://github.com/landscapeio/pylint-django/issues/10
if node.decorators is not None:
for decorator in node.decorators.nodes:
if getattr(decorator, 'name', None) == 'python_2_unicode_compatible':
return

self.add_message('W%s01' % BASE_ID, args=node.name, node=node)
if not has_py2_compat_decorator:
self.add_message('W%s01' % BASE_ID, args=node.name, node=node)
14 changes: 14 additions & 0 deletions pylint_django/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,17 @@ def node_is_subclass(cls, subclass_name):
continue

return False


def iter_cls_and_bases(cls):
yield cls
if cls.bases == YES:
return

for base_cls in cls.bases:
try:
for inf in base_cls.infered():
for yielded in iter_cls_and_bases(inf):
yield yielded
except InferenceError:
continue
15 changes: 15 additions & 0 deletions test/input/func_noerror_model_unicode_callable_py33.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Ensures that django models without a __unicode__ method are flagged
"""
# pylint: disable=C0111

from django.db import models


def external_unicode_func(model):
return model.something


class SomeModel(models.Model):
something = models.CharField(max_length=255)
__str__ = external_unicode_func
11 changes: 11 additions & 0 deletions test/input/func_noerror_model_unicode_lambda_py33.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Ensures that django models without a __unicode__ method are flagged
"""
# pylint: disable=C0111

from django.db import models


class SomeModel(models.Model):
something = models.CharField(max_length=255)
__str__ = lambda s: str(s.something)
3 changes: 3 additions & 0 deletions test/input/func_noerror_models_py33.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ def stuff(self):

print(self.get_some_field_display())

def __str__(self):
print 'some model %s' % self.id


class SubclassModel(SomeModel):
class Meta:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ class ModelName(models.Model):

def __str__(self):
return self.name


class ModelSubclass(ModelName):
count = models.IntegerField()
1 change: 1 addition & 0 deletions test/messages/func_unicode_py2_compatible_py_28.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
W: 20:ModelSubclass: Model does not explicitly define __unicode__ (ModelSubclass)

0 comments on commit 3ae839a

Please sign in to comment.