Можно ли использовать регулярные выражения в выражениях Django F()?

У меня есть модель:

class MyModel(models.Model):
    long_name = models.CharField(unique=True, max_length=256)
    important_A = models.CharField(unique=True, max_length=256)
    important_B = models.CharField(unique=True, max_length=256)

MyModel.long_name содержит информацию, которую мне нужно поместить в специальные поля (important_A и important_B). Примером строки в long_name будет S1_arctic_mosaic_tile_029r_016d_300_20221108T062432.png

Мне в основном нужно сопоставить одну часть строки в long_name, т.е. все между 4. и 5. подчеркиванием ("029r") и поместить в important_A, а все между 5. и 6. ("016d") в important_B.

Поскольку база данных (PostgreSQL на Django 3.2.15) довольно большая (~2.000.000 строк), циклирование (и использование таких вещей, как Python str.split()) не является вариантом, поскольку это займет слишком много времени.

Таким образом, я ищу способ использовать regex в миграции для заполнения important_A и important_B из long_field. Моя текущая миграция выглядит следующим образом:

from django.db import migrations, models
from django.db.models import F

def populate_fields(apps, schema_editor):
    MyModel = apps.get_model("myapp", "mymodel")
    MyModel.objects.all().update(important_A=
        F('long_name=r"S1_.*_(\d{2,3}(r|l)_\d{2,3}(u|d))_.*\.png"')
    )

class Migration(migrations.Migration):
    dependencies = [
        ('icedata', '0036_something'),
    ]
    operations = [
        migrations.RunPython(populate_fields),
    ]

Когда я пытаюсь запустить эту миграцию, я получаю следующую ошибку:

django.core.exceptions.FieldError: Cannot resolve keyword 'filename=r"S1_.*_(\d{2,3}(r|l)_\d{2,3}(u|d))_.*\.png"' into field. Choices are: long_name, id

Когда я вместо этого использую F('long_name__regex=r"S1_.*_(\d{2,3}(r|l)_\d{2,3}(u|d))_.*\.png"'), я получаю:

Cannot resolve keyword 'regex=r"S1_.*_(\\d{2,3}(r|l)_\\d{2,3}(u|d))_.*\\.png"' into field. Join on 'long_name' not permitted.

Как я могу использовать регулярные выражения вместе с F()-выражениями?
Или, если я не могу, есть ли другой способ использовать базу данных для извлечения части строки и помещения ее в другое поле?

Я удивлен, что обновление 2M строк является "слишком медленным", но вы определенно хотите избежать создания двух миллионов объектов одновременно или выполнения 2M запросов к БД для обновления одного объекта. Вы можете:

  1. Отредактируйте модель, чтобы создать поля important_A и important_B со значениями по умолчанию, которые никогда не могут быть действительными в производстве. (Пустые или нулевые, как правило). Сделайте миграции и мигрируйте.
  2. Запустите код для обновления базы данных на разумное количество объектов за раз.

Что-то вроде:

DEFAULT = ''
BATCH_SIZE = 1000
while True:
    objects = list( MyModel.objects.filter( important_A=DEFAULT)[:BATCH_SIZE] )
    if len(objects) == 0:
         break   # all done
    for o in objects:

         # stuff to get new_A and new_B non-DEFAULT values
         o.important_A = new_A
         o.important_B = new_B
         assert new_A != DEFAULT, 'avoid infinite loop bug'
         n_updated = MyModel.objects.bulk_update( 
             objects, ['important_A','important_B']
         )
         assert n_updated == len(objects), 'WTF?' # return values should be checked
  1. Реализуйте методы на MyModel, чтобы убедиться, что поля important_A и important_B никогда не смогут рассинхронизироваться с long_name (если связь постоянная, а не текущая). Это может быть метод save или свойства с геттерами и сеттерами для перекрестной ссылки на поля.
Вернуться на верх