Coverage for extras/management/commands/renaturalize.py: 0%
62 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 18:35 +0000
1from django.apps import apps
2from django.core.management.base import BaseCommand, CommandError
4from utilities.fields import NaturalOrderingField
5from utilities.querysets import chunked_update
8class Command(BaseCommand):
9 help = "Recalculate natural ordering values for the specified models"
11 def add_arguments(self, parser):
12 parser.add_argument(
13 'args', metavar='app_label.ModelName', nargs='*',
14 help='One or more specific models (each prefixed with its app_label) to renaturalize',
15 )
17 def _get_models(self, names):
18 """
19 Compile a list of models to be renaturalized. If no names are specified, all models which have one or more
20 NaturalOrderingFields will be included.
21 """
22 models = []
24 if names:
25 # Collect all NaturalOrderingFields present on the specified models
26 for name in names:
27 try:
28 app_label, model_name = name.split('.')
29 except ValueError:
30 raise CommandError(
31 f"Invalid format: {name}. Models must be specified in the form app_label.ModelName."
32 )
33 try:
34 app_config = apps.get_app_config(app_label)
35 except LookupError as e:
36 raise CommandError(str(e))
37 try:
38 model = app_config.get_model(model_name)
39 except LookupError:
40 raise CommandError(f"Unknown model: {app_label}.{model_name}")
41 fields = [
42 field for field in model._meta.concrete_fields if type(field) is NaturalOrderingField
43 ]
44 if not fields:
45 raise CommandError(
46 f"Invalid model: {app_label}.{model_name} does not employ natural ordering"
47 )
48 models.append(
49 (model, fields)
50 )
52 else:
53 # Find *all* models with NaturalOrderingFields
54 for app_config in apps.get_app_configs():
55 for model in app_config.models.values():
56 fields = [
57 field for field in model._meta.concrete_fields if type(field) is NaturalOrderingField
58 ]
59 if fields:
60 models.append(
61 (model, fields)
62 )
64 return models
66 def handle(self, *args, **options):
68 models = self._get_models(args)
70 if options['verbosity']:
71 self.stdout.write(f"Renaturalizing {len(models)} models.")
73 for model, fields in models:
74 for field in fields:
75 target_field = field.target_field
76 naturalize = field.naturalize_function
77 count = 0
79 # Print the model and field name
80 if options['verbosity']:
81 self.stdout.write(
82 f"{model._meta.label}.{field.target_field} ({field.name})... ",
83 ending='\n' if options['verbosity'] >= 2 else ''
84 )
85 self.stdout.flush()
87 # Find all unique values for the field
88 queryset = model.objects.values_list(target_field, flat=True).order_by(target_field).distinct()
89 for value in queryset:
90 naturalized_value = naturalize(value, max_length=field.max_length)
92 if options['verbosity'] >= 2:
93 self.stdout.write(f" {value} -> {naturalized_value}", ending='')
94 self.stdout.flush()
96 # Update each unique field value in bulk
97 changed = chunked_update(model.objects.filter(name=value), **{field.name: naturalized_value})
99 if options['verbosity'] >= 2:
100 self.stdout.write(f" ({changed})")
101 count += changed
103 # Print the total count of alterations for the field
104 if options['verbosity'] >= 2:
105 self.stdout.write(self.style.SUCCESS(
106 f"{count} {model._meta.verbose_name_plural} updated ({queryset.count()} unique values)"
107 ))
108 elif options['verbosity']:
109 self.stdout.write(self.style.SUCCESS(str(count)))
111 if options['verbosity']:
112 self.stdout.write(self.style.SUCCESS("Done."))