Skip to content

Commit 00d32f0

Browse files
Merge pull request #4156 from netbox-community/4153-renaturalize-command
Closes #4153: Add a "renaturalize" management command
2 parents df3fef8 + 139f18b commit 00d32f0

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from django.apps import apps
2+
from django.core.management.base import BaseCommand, CommandError
3+
4+
from utilities.fields import NaturalOrderingField
5+
6+
7+
class Command(BaseCommand):
8+
help = "Recalculate natural ordering values for the specified models"
9+
10+
def add_arguments(self, parser):
11+
parser.add_argument(
12+
'args', metavar='app_label.ModelName', nargs='*',
13+
help='One or more specific models (each prefixed with its app_label) to renaturalize',
14+
)
15+
16+
def _get_models(self, names):
17+
"""
18+
Compile a list of models to be renaturalized. If no names are specified, all models which have one or more
19+
NaturalOrderingFields will be included.
20+
"""
21+
models = []
22+
23+
if names:
24+
# Collect all NaturalOrderingFields present on the specified models
25+
for name in names:
26+
try:
27+
app_label, model_name = name.split('.')
28+
except ValueError:
29+
raise CommandError(
30+
"Invalid format: {}. Models must be specified in the form app_label.ModelName.".format(name)
31+
)
32+
try:
33+
app_config = apps.get_app_config(app_label)
34+
except LookupError as e:
35+
raise CommandError(str(e))
36+
try:
37+
model = app_config.get_model(model_name)
38+
except LookupError:
39+
raise CommandError("Unknown model: {}.{}".format(app_label, model_name))
40+
fields = [
41+
field for field in model._meta.concrete_fields if type(field) is NaturalOrderingField
42+
]
43+
if not fields:
44+
raise CommandError(
45+
"Invalid model: {}.{} does not employ natural ordering".format(app_label, model_name)
46+
)
47+
models.append(
48+
(model, fields)
49+
)
50+
51+
else:
52+
# Find *all* models with NaturalOrderingFields
53+
for app_config in apps.get_app_configs():
54+
for model in app_config.models.values():
55+
fields = [
56+
field for field in model._meta.concrete_fields if type(field) is NaturalOrderingField
57+
]
58+
if fields:
59+
models.append(
60+
(model, fields)
61+
)
62+
63+
return models
64+
65+
def handle(self, *args, **options):
66+
67+
models = self._get_models(args)
68+
69+
if options['verbosity']:
70+
self.stdout.write("Renaturalizing {} models.".format(len(models)))
71+
72+
for model, fields in models:
73+
for field in fields:
74+
target_field = field.target_field
75+
naturalize = field.naturalize_function
76+
count = 0
77+
78+
# Print the model and field name
79+
if options['verbosity']:
80+
self.stdout.write(
81+
"{}.{} ({})... ".format(model._meta.label, field.target_field, field.name),
82+
ending='\n' if options['verbosity'] >= 2 else ''
83+
)
84+
self.stdout.flush()
85+
86+
# Find all unique values for the field
87+
queryset = model.objects.values_list(target_field, flat=True).order_by(target_field).distinct()
88+
for value in queryset:
89+
naturalized_value = naturalize(value)
90+
91+
# Skip any naturalized values that don't differ from their original form
92+
if value == naturalized_value:
93+
if options['verbosity'] >= 3:
94+
self.stdout.write(self.style.WARNING(
95+
" {} == {} (skipped)".format(value, naturalized_value)
96+
))
97+
continue
98+
99+
if options['verbosity'] >= 2:
100+
self.stdout.write(" {} -> {}".format(value, naturalized_value), ending='')
101+
self.stdout.flush()
102+
103+
# Update each unique field value in bulk
104+
changed = model.objects.filter(name=value).update(**{field.name: naturalized_value})
105+
106+
if options['verbosity'] >= 2:
107+
self.stdout.write(" ({})".format(changed))
108+
count += changed
109+
110+
# Print the total count of alterations for the field
111+
if options['verbosity'] >= 2:
112+
self.stdout.write(self.style.SUCCESS("{} {} updated ({} unique values)".format(
113+
count, model._meta.verbose_name_plural, queryset.count()
114+
)))
115+
elif options['verbosity']:
116+
self.stdout.write(self.style.SUCCESS(str(count)))
117+
118+
if options['verbosity']:
119+
self.stdout.write(self.style.SUCCESS("Done."))

0 commit comments

Comments
 (0)