Browse Source
feat(reference): support canonical dates and BookAuthorDate model for book authors
feat(reference): support canonical dates and BookAuthorDate model for book authors
- Convert birth_year_hijri and death_year_hijri to CharField and add age_at_death - Introduce BookAuthorDate model linking BookAuthor with parsed YearValue records - Add sync_author_dates and date formatting methods to BookAuthor - Support century filtering via YearValue min/max ranges and prefetch author dates - Expose live preview fields and canonical date validation in AdminBookAuthorSerializer - Add migration 0056 to convert fields and populate BookAuthorDate recordsmaster
6 changed files with 443 additions and 20 deletions
-
117apps/hadis/migrations/0056_bookauthor_date_fields_and_bookauthordate.py
-
144apps/hadis/models/reference.py
-
84apps/hadis/serializers/reference_v2.py
-
65apps/hadis/serializers/serializers_admin.py
-
46apps/hadis/views/reference_v2.py
-
7apps/hadis/views_admin.py
@ -0,0 +1,117 @@ |
|||||
|
# Generated by Django 4.2.30 on 2026-10-19 17:08 |
||||
|
|
||||
|
from django.db import migrations, models |
||||
|
import django.db.models.deletion |
||||
|
|
||||
|
|
||||
|
def populate_book_author_dates(apps, schema_editor): |
||||
|
BookAuthor = apps.get_model('hadis', 'BookAuthor') |
||||
|
YearValue = apps.get_model('hadis', 'YearValue') |
||||
|
BookAuthorDate = apps.get_model('hadis', 'BookAuthorDate') |
||||
|
|
||||
|
from apps.hadis.utils.date_engine import ( |
||||
|
parse_canonical_date, |
||||
|
build_translations_for_parsed, |
||||
|
) |
||||
|
|
||||
|
yv_cache = {} |
||||
|
for author in BookAuthor.objects.all().iterator(): |
||||
|
for date_type, raw_val in ( |
||||
|
('birth', author.birth_year_hijri), |
||||
|
('death', author.death_year_hijri), |
||||
|
('age', getattr(author, 'age_at_death', None)), |
||||
|
): |
||||
|
if raw_val is None or str(raw_val).strip() == '': |
||||
|
continue |
||||
|
raw_str = str(raw_val).strip() |
||||
|
parsed = parse_canonical_date(raw_str, category=date_type) |
||||
|
canonical_str = parsed.raw_value if parsed.is_valid else raw_str |
||||
|
|
||||
|
cache_key = (date_type, canonical_str) |
||||
|
yv = yv_cache.get(cache_key) |
||||
|
if not yv: |
||||
|
yv = YearValue.objects.filter(category=date_type, raw_value=canonical_str).first() |
||||
|
if not yv: |
||||
|
if parsed.is_valid: |
||||
|
translations = build_translations_for_parsed(parsed, category=date_type) |
||||
|
else: |
||||
|
translations = [ |
||||
|
{"language_code": "ru", "text": canonical_str}, |
||||
|
{"language_code": "en", "text": canonical_str}, |
||||
|
{"language_code": "ar", "text": canonical_str}, |
||||
|
] |
||||
|
yv = YearValue.objects.create( |
||||
|
category=date_type, |
||||
|
raw_value=canonical_str, |
||||
|
parse_type=parsed.parse_type if parsed.is_valid else 'unparsed', |
||||
|
value=parsed.value if parsed.is_valid else None, |
||||
|
value_min=parsed.value_min if parsed.is_valid else None, |
||||
|
value_max=parsed.value_max if parsed.is_valid else None, |
||||
|
values_list=parsed.values_list if parsed.is_valid else [], |
||||
|
translations=translations, |
||||
|
) |
||||
|
yv_cache[cache_key] = yv |
||||
|
|
||||
|
BookAuthorDate.objects.update_or_create( |
||||
|
author_id=author.id, |
||||
|
date_type=date_type, |
||||
|
defaults={ |
||||
|
'year_value_id': yv.id, |
||||
|
'raw_value': yv.raw_value, |
||||
|
} |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class Migration(migrations.Migration): |
||||
|
|
||||
|
dependencies = [ |
||||
|
('hadis', '0055_yearvalue_narratordate_and_transmitters_date_fields'), |
||||
|
] |
||||
|
|
||||
|
operations = [ |
||||
|
migrations.AlterField( |
||||
|
model_name='bookauthor', |
||||
|
name='birth_year_hijri', |
||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Birth Year (Hijri)'), |
||||
|
), |
||||
|
migrations.AlterField( |
||||
|
model_name='bookauthor', |
||||
|
name='death_year_hijri', |
||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Death Year (Hijri)'), |
||||
|
), |
||||
|
migrations.AddField( |
||||
|
model_name='bookauthor', |
||||
|
name='age_at_death', |
||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Age at Death'), |
||||
|
), |
||||
|
migrations.CreateModel( |
||||
|
name='BookAuthorDate', |
||||
|
fields=[ |
||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
|
('date_type', models.CharField(choices=[('birth', 'Birth'), ('death', 'Death'), ('age', 'Age')], db_index=True, max_length=20, verbose_name='Date Type')), |
||||
|
('raw_value', models.CharField(max_length=100, verbose_name='Canonical Raw Value')), |
||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='created at')), |
||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='updated at')), |
||||
|
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_dates', to='hadis.bookauthor', verbose_name='Book Author')), |
||||
|
('year_value', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='author_dates', to='hadis.yearvalue', verbose_name='Year Value')), |
||||
|
], |
||||
|
options={ |
||||
|
'verbose_name': 'Book Author Date', |
||||
|
'verbose_name_plural': 'Book Author Dates', |
||||
|
'ordering': ('author_id', 'date_type'), |
||||
|
'unique_together': {('author', 'date_type')}, |
||||
|
}, |
||||
|
), |
||||
|
migrations.AddIndex( |
||||
|
model_name='bookauthordate', |
||||
|
index=models.Index(fields=['date_type', 'year_value'], name='hadis_booka_date_ty_9a1e3c_idx'), |
||||
|
), |
||||
|
migrations.AddIndex( |
||||
|
model_name='bookauthordate', |
||||
|
index=models.Index(fields=['author', 'date_type'], name='hadis_booka_author__6c2f1b_idx'), |
||||
|
), |
||||
|
migrations.RunPython( |
||||
|
populate_book_author_dates, |
||||
|
reverse_code=migrations.RunPython.noop, |
||||
|
), |
||||
|
] |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue