# Generated by Django 4.2.30 on 2026-10-20 10:35 from django.db import migrations, models import django.db.models.deletion from django.utils.text import slugify import utils.mixins def populate_cities_from_existing_records(apps, schema_editor): City = apps.get_model('hadis', 'City') Transmitters = apps.get_model('hadis', 'Transmitters') BookEdition = apps.get_model('hadis', 'BookEdition') # Cache cities by normalized text key -> City instance city_by_text = {} for c in City.objects.all(): if isinstance(c.title, list): for entry in c.title: if isinstance(entry, dict): txt = (entry.get('text') or entry.get('title') or '').strip().lower() if txt: city_by_text[txt] = c def get_or_create_city(json_list): if not json_list or not isinstance(json_list, list): return None cleaned = [] lookup_keys = [] for entry in json_list: if isinstance(entry, dict): lang = (entry.get('language_code') or '').strip() txt = (entry.get('text') or entry.get('title') or '').strip() if lang and txt: cleaned.append({'language_code': lang, 'text': txt, 'title': txt}) lookup_keys.append(txt.lower()) if not cleaned: return None for key in lookup_keys: if key in city_by_text: existing = city_by_text[key] # Merge any missing language translations into existing city existing_langs = { item.get('language_code'): item for item in (existing.title or []) if isinstance(item, dict) and item.get('language_code') } updated = False for item in cleaned: if item['language_code'] not in existing_langs: existing_langs[item['language_code']] = item updated = True if updated: existing.title = list(existing_langs.values()) existing.save(update_fields=['title']) for k in lookup_keys: city_by_text[k] = existing return existing first_text = cleaned[0]['text'] slug_val = slugify(first_text, allow_unicode=True)[:240] or 'city' city_obj = City.objects.create(title=cleaned, slug=slug_val) for k in lookup_keys: city_by_text[k] = city_obj return city_obj for tr in Transmitters.objects.all().iterator(): changed_fields = [] for json_attr, fk_attr in ( ('origin', 'origin_city'), ('lived_in', 'lived_in_city'), ('died_in', 'died_in_city'), ): if getattr(tr, f'{fk_attr}_id', None) is None: city_obj = get_or_create_city(getattr(tr, json_attr, None)) if city_obj: setattr(tr, fk_attr, city_obj) changed_fields.append(fk_attr) if changed_fields: tr.save(update_fields=changed_fields) for ed in BookEdition.objects.all().iterator(): if ed.city_of_publication_city_id is None: city_obj = get_or_create_city(ed.city_of_publication) if city_obj: ed.city_of_publication_city = city_obj ed.save(update_fields=['city_of_publication_city']) class Migration(migrations.Migration): dependencies = [ ('hadis', '0056_bookauthor_date_fields_and_bookauthordate'), ] operations = [ migrations.CreateModel( name='City', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('title', models.JSONField(default=list, verbose_name='Title')), ('slug', models.SlugField(allow_unicode=True, blank=True, max_length=255, null=True, verbose_name='slug')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), ], options={ 'verbose_name': 'City', 'verbose_name_plural': 'Cities', 'ordering': ('-created_at', '-id'), }, bases=(utils.mixins.LowercaseSlugMixin, models.Model), ), migrations.AddField( model_name='transmitters', name='origin_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='origin_transmitters', to='hadis.city', verbose_name='Origin City', ), ), migrations.AddField( model_name='transmitters', name='lived_in_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lived_in_transmitters', to='hadis.city', verbose_name='Lived in City', ), ), migrations.AddField( model_name='transmitters', name='died_in_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='died_in_transmitters', to='hadis.city', verbose_name='Died in City', ), ), migrations.AddField( model_name='bookauthor', name='origin', field=models.JSONField(blank=True, default=list, verbose_name='Origin'), ), migrations.AddField( model_name='bookauthor', name='lived_in', field=models.JSONField(blank=True, default=list, verbose_name='Lived in'), ), migrations.AddField( model_name='bookauthor', name='died_in', field=models.JSONField(blank=True, default=list, verbose_name='Died in'), ), migrations.AddField( model_name='bookauthor', name='origin_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='origin_authors', to='hadis.city', verbose_name='Origin City', ), ), migrations.AddField( model_name='bookauthor', name='lived_in_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lived_in_authors', to='hadis.city', verbose_name='Lived in City', ), ), migrations.AddField( model_name='bookauthor', name='died_in_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='died_in_authors', to='hadis.city', verbose_name='Died in City', ), ), migrations.AddField( model_name='bookedition', name='city_of_publication_city', field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publication_editions', to='hadis.city', verbose_name='City of Publication (City)', ), ), migrations.RunPython(populate_cities_from_existing_records, migrations.RunPython.noop), ]