renaturalize.py 4.5 KB

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