Skip to content

Instantly share code, notes, and snippets.

@hakib
Last active July 5, 2025 13:06
Show Gist options
  • Save hakib/e2e50d41d19a6984dc63bd94580c8647 to your computer and use it in GitHub Desktop.
Save hakib/e2e50d41d19a6984dc63bd94580c8647 to your computer and use it in GitHub Desktop.

Revisions

  1. hakib revised this gist Aug 27, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion custom_django_checks.py
    Original file line number Diff line number Diff line change
    @@ -86,7 +86,7 @@ def check_model(model):

    else:
    value = verbose_name.value.args[0].s # type: ignore
    if not all(w.islower() or w.isupper() for w in value.split(' ')):
    if not all(w.islower() or w.isupper() or w.isdigit() for w in value.split(' ')):
    yield django.core.checks.Warning(
    'Words in verbose name must be all upper case or all lower case',
    hint='Change verbose name to "{}".'.format(value.lower()),
  2. hakib created this gist Jun 1, 2018.
    191 changes: 191 additions & 0 deletions custom_django_checks.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,191 @@
    """
    Custom django checks.
    H001: Field has no verbose name.
    H002: Verbose name should use gettext.
    H003: Words in verbose name must be all upper case or all lower case.
    H004: Help text should use gettext.
    H005: Model must define class Meta.
    H006: Model has no verbose name.
    H007: Model has no verbose name plural.
    H008: Must set db_index explicitly on a ForeignKey field.
    """
    import inspect
    import ast

    import django.apps
    import django.core.checks
    from django.db.models import FieldDoesNotExist


    def get_argument(node, arg):
    for kw in node.value.keywords:
    if kw.arg == arg:
    return kw
    return None


    def is_gettext_node(node):
    if not isinstance(node, ast.Call):
    return False

    # We assume gettext is aliased '_'.
    if not node.func.id == '_': # type: ignore
    return False

    return True


    def check_model(model):
    """Check a single model.
    Yields (django.checks.CheckMessage)
    """
    model_source = inspect.getsource(model)
    model_node = ast.parse(model_source)
    assert isinstance(model_node, ast.Module)

    class_meta = None
    for node in model_node.body[0].body: # type: ignore
    if isinstance(node, ast.ClassDef):
    # class Meta
    if node.name == 'Meta':
    class_meta = node

    # Fields
    elif isinstance(node, ast.Assign):
    if len(node.targets) != 1:
    continue

    if not isinstance(node.targets[0], ast.Name):
    continue

    field_name = node.targets[0].id
    try:
    field = model._meta.get_field(field_name)
    except FieldDoesNotExist:
    continue

    verbose_name = get_argument(node, 'verbose_name')
    if verbose_name is None:
    yield django.core.checks.Warning(
    'Field has no verbose name',
    hint='Set verbose name on the field.',
    obj=field,
    id='H001',
    )

    else:
    if not is_gettext_node(verbose_name.value):
    yield django.core.checks.Warning(
    'Verbose name should use gettext',
    hint='Use gettext on the verbose name.',
    obj=field,
    id='H002',
    )

    else:
    value = verbose_name.value.args[0].s # type: ignore
    if not all(w.islower() or w.isupper() for w in value.split(' ')):
    yield django.core.checks.Warning(
    'Words in verbose name must be all upper case or all lower case',
    hint='Change verbose name to "{}".'.format(value.lower()),
    obj=field,
    id='H003',
    )


    help_text = get_argument(node, 'help_text')
    if help_text is not None:
    if not is_gettext_node(help_text.value):
    yield django.core.checks.Warning(
    'Help text should use gettext',
    hint='Use gettext on the help text.',
    obj=field,
    id='H004',
    )


    if field.many_to_one:
    db_index = get_argument(node, 'db_index')
    if db_index is None:
    yield django.core.checks.Warning(
    'Must set db_index explicitly on a ForeignKey field',
    hint='Set db_index on the field.',
    obj=field,
    id='H008',
    )

    if class_meta is None:
    yield django.core.checks.Warning(
    'Model "{}" must define class Meta'.format(model._meta.model_name),
    hint='Add class Meta to model "{}".'.format(model._meta.model_name),
    obj=model,
    id='H005',
    )

    else:
    verbose_name = None
    verbose_name_plural = None

    for node in ast.iter_child_nodes(class_meta):
    if not isinstance(node, ast.Assign):
    continue

    if not isinstance(node.targets[0], ast.Name):
    continue

    attr = node.targets[0].id

    if attr == 'verbose_name':
    verbose_name = node

    if attr == 'verbose_name_plural':
    verbose_name_plural = node

    if verbose_name is None:
    yield django.core.checks.Warning(
    'Model has no verbose name',
    hint='Add verbose_name to class Meta.',
    obj=model,
    id='H006',
    )

    elif not is_gettext_node(verbose_name.value):
    yield django.core.checks.Warning(
    'Verbose name in class Meta should use gettext',
    hint='Use gettext on the verbose_name of class Meta "{}".'.format(model._meta.model_name),
    obj=model,
    id='H002',
    )

    if verbose_name_plural is None:
    yield django.core.checks.Warning(
    'Model has no verbose name plural',
    hint='Add verbose_name_plural to class Meta.',
    obj=model,
    id='H007',
    )

    elif not is_gettext_node(verbose_name_plural.value):
    yield django.core.checks.Warning(
    'Verbose name plural in class Meta should use gettext',
    hint='Use gettext on the verbose_name_plural of class Meta "{}".'.format(model._meta.model_name),
    obj=model,
    id='H002',
    )


    @django.core.checks.register(django.core.checks.Tags.models)
    def check_models(app_configs, **kwargs):
    errors = []
    for app in django.apps.apps.get_app_configs():
    # Skip third party apps.
    if app.path.find('site-packages') > -1:
    continue

    for model in app.get_models():
    for check_message in check_model(model):
    errors.append(check_message)

    return errors