Skip to content

Instantly share code, notes, and snippets.

@vdboor
Created August 19, 2016 09:03
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vdboor/e3754e19551f2fbbcc31b01eec99ee8e to your computer and use it in GitHub Desktop.
Save vdboor/e3754e19551f2fbbcc31b01eec99ee8e to your computer and use it in GitHub Desktop.
Avoid the `verbose_name` and `help_text` in Django migrations for fields.
"""
Patch the creation of database migrations in Django
Import this early from `__init__.py``.
- Don't want verbose_name changes in the migrations file.
- Don't want help_text in the migrations file.
"""
from functools import wraps
from django.db.models import Field
def patch_deconstruct(old_func, condition):
"""
Patch the ``Field.deconstruct`` to remove useless information.
This only happens on internal apps, not third party apps.
"""
@wraps(old_func)
def new_deconstruct(self):
name, path, args, kwargs = old_func(self)
# AutoField has no model on creation, but can be skipped
if hasattr(self, 'model') and condition(self):
kwargs.pop('verbose_name', None)
kwargs.pop('help_text', None)
return name, path, args, kwargs
return new_deconstruct
Field.deconstruct = patch_deconstruct(Field.deconstruct, lambda self: self.model.__module__.startswith('apps.'))
@rixx
Copy link

rixx commented Apr 29, 2019

Just as a note in case others find their way here: Defining a custom manage command can be a bit cleaner in terms of inheritance and monkeypatching. in app/management/commands/makemigrations.py, write:

from django.core.management.commands.makemigrations import Command as Parent
from django.db import models

IGNORED_ATTRS = ['verbose_name', 'help_text', 'choices']

original_deconstruct = models.Field.deconstruct


def new_deconstruct(self):
    name, path, args, kwargs = original_deconstruct(self)
    for attr in IGNORED_ATTRS:
        kwargs.pop(attr, None)
    return name, path, args, kwargs

models.Field.deconstruct = new_deconstruct

class Command(Parent):
	pass

@piertoni
Copy link

piertoni commented May 6, 2022

Just to note that the solution provided by @rixx is better because importing from __init__.py will cause django test to fail due to infinite recursion problem.

@maksam07
Copy link

from django.core.management.commands.makemigrations import Command

Why import Command if it is not used?

@rixx
Copy link

rixx commented Sep 27, 2022

The unused import is a bit of a hack – Django needs a Command subclass to be present in the file to know what to do. If we simply import Command, Django will find it and try to run it (which means that nothing happens, as Command doesn't take any action).

It's a bit hacky to just import Command – you could also do something more explicit, which I do in my code, and I've updated the snippet above to reflect that: Import Command as SomeOtherName, and define your class Command(SomeOtherName): pass, so that it's clear from reading the code that you need to have this class present.

@maksam07
Copy link

Now I understand.
Will this option still work correctly for django 4.1 version? Because I didn't find another solution to exclude some data from the migration. I use the dynamic field verbose_name_plural in the admin panel, after the model name I display the amount of some filtered data

@rixx
Copy link

rixx commented Sep 27, 2022

I have not used the command with Django 4.1 yet. You'll have to look at the changelog to see if there's anything to suggest that the API changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment