Можно ли использовать регулярные выражения в выражениях 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 запросов к БД для обновления одного объекта. Вы можете:
- Отредактируйте модель, чтобы создать поля important_A и important_B со значениями по умолчанию, которые никогда не могут быть действительными в производстве. (Пустые или нулевые, как правило). Сделайте миграции и мигрируйте.
- Запустите код для обновления базы данных на разумное количество объектов за раз.
Что-то вроде:
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
- Реализуйте методы на
MyModel
, чтобы убедиться, что поляimportant_A
иimportant_B
никогда не смогут рассинхронизироваться сlong_name
(если связь постоянная, а не текущая). Это может быть методsave
или свойства с геттерами и сеттерами для перекрестной ссылки на поля.