def visit_class(self, node): if is_model(node): self.model_names.append(node.name) self.prev_idx = None self.prev_node = None elif is_model(node.parent.frame()): # Nested class self._visit_django_attribute(node, is_method=False)
def leave_class(self, node): if not is_model(node): return if is_model(node, check_base_classes=False) and self.field_count == 0: self.add_message('W6003', node=node) elif self.field_count > self.config.max_model_fields: self.add_message('W6002', node=node, args=(self.field_count, self.config.max_model_fields)) self.field_count = 0
def leave_class(self, node): if node.name == 'Meta' and is_model(node.parent.parent): # Annotate the model with information from the Meta class try: val = safe_infer(node.locals['abstract'][-1]).value if val is True: node.parent.parent._django_abstract = True except KeyError: pass return if not is_model(node): return
def visit_function(self, node): if not is_model(node.parent.frame()): return if node.name == '__str__': self.add_message('W8011', node=node) try: idx = [ '__unicode__', '__str__', 'save', 'delete', 'get_absolute_url', ].index(node.name) if self.prev_idx == -1: self.add_message('W8012', node=self.prev_node) elif idx < self.prev_idx: self.add_message('W8013', node=node, args=self.prev_node.name) except ValueError: idx = -1 self.prev_idx = idx self.prev_node = node
def visit_class(self, node): if not is_model(node): return self.model_names.append(node.name) self.prev_idx = None self.prev_name = None
def visit_assname(self, node): if not is_model(node.parent.frame()): return if self.prev_idx >= 0: self.add_message('W8013', node=node, args=( '%r assignment' % node.name, self.prev_node.name, ))
def visit_function(self, node): if not is_model(node.parent.frame()): return if node.name == '__str__': self.add_message('W8011', node=node) self._visit_django_attribute(node)
def visit_callfunc(self, node): if not is_model(node.frame()): # We only care about fields attached to models return val = safe_infer(node) if not val or not val.root().name.startswith( 'django.db.models.fields'): # Not a field return assname = '(unknown name)' x = node.parent.get_children().next() if isinstance(x, astng.AssName): assname = x.name self.field_count += 1 # Parse kwargs options = dict([(option, None) for option in ( 'null', 'blank', 'unique', 'default', 'auto_now', 'primary_key', 'auto_now_add', 'verify_exists', 'related_name', 'max_length', 'unique_for_date', 'unique_for_month', 'unique_for_year', )]) for arg in node.args: if not isinstance(arg, astng.Keyword): continue for option in options.keys(): if arg.arg == option: try: options[option] = safe_infer(arg.value).value except AttributeError: # Don't lint this field if we cannot infer everything return if not val.name.lower().startswith('null'): for option in ('null', 'blank'): if options[option] is False: self.add_message('W6015', node=node, args=( assname, option, )) # Field type specific checks if val.name in ('CharField', 'TextField'): if options['null']: self.add_message('W6000', node=node, args=(assname, )) if val.name == 'CharField' and \ options['max_length'] > self.config.max_charfield_length: self.add_message('W6007', node=node, args=( assname, options['max_length'], self.config.max_charfield_length, )) elif val.name == 'BooleanField': if options['default']: self.add_message('W6012', node=node, args=(assname, )) elif val.name == 'ForeignKey': val = safe_infer(node.args[0]) if isinstance(val, astng.Const) and val.value == 'self': self.add_message('W6001', node=node, args=(assname, )) elif not options['related_name']: self.add_message('W6006', node=node, args=(assname, )) if options['primary_key'] and options['unique'] is False: self.add_message('W6014', node=node, args=(assname, )) elif options['primary_key'] or options['unique']: self.add_message('W6013', node=node, args=(assname, )) elif val.name == 'URLField': if options['verify_exists'] is None: self.add_message('W6011', node=node, args=(assname, )) elif val.name in ('PositiveSmallIntegerField', 'SmallIntegerField'): self.add_message('W6010', node=node, args=(assname, val.name)) elif val.name == 'NullBooleanField': self.add_message('W6009', node=node, args=(assname, )) elif val.name == 'ManyToManyField': if options['null']: self.add_message('W6016', node=node, args=(assname, )) # Generic checks if options['null'] and not options['blank']: self.add_message('W6004', node=node, args=(assname, )) if options['auto_now'] or options['auto_now_add']: self.add_message('W6008', node=node, args=(assname, )) for suffix in ('date', 'month', 'year'): if options['unique_for_%s' % suffix]: self.add_message('W6005', node=node, args=(assname, suffix))
def visit_callfunc(self, node): if not is_model(node.frame()): # We only care about fields attached to models return val = safe_infer(node) if not val or not val.root().name.startswith('django.db.models.fields'): # Not a field return assname = '(unknown name)' x = node.parent.get_children().next() if isinstance(x, astng.AssName): assname = x.name self.field_count += 1 # Parse kwargs options = dict([(option, None) for option in ( 'null', 'blank', 'unique', 'default', 'auto_now', 'primary_key', 'auto_now_add', 'verify_exists', 'related_name', 'max_length', 'unique_for_date', 'unique_for_month', 'unique_for_year', )]) for arg in node.args: if not isinstance(arg, astng.Keyword): continue for option in options.keys(): if arg.arg == option: try: options[option] = safe_infer(arg.value).value except AttributeError: # Don't lint this field if we cannot infer everything return if not val.name.lower().startswith('null'): for option in ('null', 'blank'): if options[option] is False: self.add_message('W6015', node=node, args=(assname, option,)) # Field type specific checks if val.name in ('CharField', 'TextField'): if options['null']: self.add_message('W6000', node=node, args=(assname,)) if val.name == 'CharField' and \ options['max_length'] > self.config.max_charfield_length: self.add_message('W6007', node=node, args=( assname, options['max_length'], self.config.max_charfield_length, )) elif val.name == 'BooleanField': if options['default']: self.add_message('W6012', node=node, args=(assname,)) elif val.name == 'ForeignKey': val = safe_infer(node.args[0]) if isinstance(val, astng.Const) and val.value == 'self': self.add_message('W6001', node=node, args=(assname,)) elif not options['related_name']: self.add_message('W6006', node=node, args=(assname,)) if options['primary_key'] and options['unique'] is False: self.add_message('W6014', node=node, args=(assname,)) elif options['primary_key'] or options['unique']: self.add_message('W6013', node=node, args=(assname,)) elif val.name == 'URLField': if options['verify_exists'] is None: self.add_message('W6011', node=node, args=(assname,)) elif val.name in ('PositiveSmallIntegerField', 'SmallIntegerField'): self.add_message('W6010', node=node, args=(assname, val.name)) elif val.name == 'NullBooleanField': self.add_message('W6009', node=node, args=(assname,)) elif val.name == 'ManyToManyField': if options['null']: self.add_message('W6016', node=node, args=(assname,)) # Generic checks if options['null'] and not options['blank']: self.add_message('W6004', node=node, args=(assname,)) if options['auto_now'] or options['auto_now_add']: self.add_message('W6008', node=node, args=(assname,)) for suffix in ('date', 'month', 'year'): if options['unique_for_%s' % suffix]: self.add_message('W6005', node=node, args=(assname, suffix))