Skip to content

Closes #4153: Add a "renaturalize" management command #4156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 12, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions netbox/extras/management/commands/renaturalize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from django.apps import apps
from django.core.management.base import BaseCommand, CommandError

from utilities.fields import NaturalOrderingField


class Command(BaseCommand):
help = "Recalculate natural ordering values for the specified models"

def add_arguments(self, parser):
parser.add_argument(
'args', metavar='app_label.ModelName', nargs='*',
help='One or more specific models (each prefixed with its app_label) to renaturalize',
)

def _get_models(self, names):
"""
Compile a list of models to be renaturalized. If no names are specified, all models which have one or more
NaturalOrderingFields will be included.
"""
models = []

if names:
# Collect all NaturalOrderingFields present on the specified models
for name in names:
try:
app_label, model_name = name.split('.')
except ValueError:
raise CommandError(
"Invalid format: {}. Models must be specified in the form app_label.ModelName.".format(name)
)
try:
app_config = apps.get_app_config(app_label)
except LookupError as e:
raise CommandError(str(e))
try:
model = app_config.get_model(model_name)
except LookupError:
raise CommandError("Unknown model: {}.{}".format(app_label, model_name))
fields = [
field for field in model._meta.concrete_fields if type(field) is NaturalOrderingField
]
if not fields:
raise CommandError(
"Invalid model: {}.{} does not employ natural ordering".format(app_label, model_name)
)
models.append(
(model, fields)
)

else:
# Find *all* models with NaturalOrderingFields
for app_config in apps.get_app_configs():
for model in app_config.models.values():
fields = [
field for field in model._meta.concrete_fields if type(field) is NaturalOrderingField
]
if fields:
models.append(
(model, fields)
)

return models

def handle(self, *args, **options):

models = self._get_models(args)

if options['verbosity']:
self.stdout.write("Renaturalizing {} models.".format(len(models)))

for model, fields in models:
for field in fields:
target_field = field.target_field
naturalize = field.naturalize_function
count = 0

# Print the model and field name
if options['verbosity']:
self.stdout.write(
"{}.{} ({})... ".format(model._meta.label, field.target_field, field.name),
ending='\n' if options['verbosity'] >= 2 else ''
)
self.stdout.flush()

# Find all unique values for the field
queryset = model.objects.values_list(target_field, flat=True).order_by(target_field).distinct()
for value in queryset:
naturalized_value = naturalize(value)

# Skip any naturalized values that don't differ from their original form
if value == naturalized_value:
if options['verbosity'] >= 3:
self.stdout.write(self.style.WARNING(
" {} == {} (skipped)".format(value, naturalized_value)
))
continue

if options['verbosity'] >= 2:
self.stdout.write(" {} -> {}".format(value, naturalized_value), ending='')
self.stdout.flush()

# Update each unique field value in bulk
changed = model.objects.filter(name=value).update(**{field.name: naturalized_value})

if options['verbosity'] >= 2:
self.stdout.write(" ({})".format(changed))
count += changed

# Print the total count of alterations for the field
if options['verbosity'] >= 2:
self.stdout.write(self.style.SUCCESS("{} {} updated ({} unique values)".format(
count, model._meta.verbose_name_plural, queryset.count()
)))
elif options['verbosity']:
self.stdout.write(self.style.SUCCESS(str(count)))

if options['verbosity']:
self.stdout.write(self.style.SUCCESS("Done."))