Возможно, плохая реализация сигналов Django
Недавно я развернул проект полного стека с использованием Django, Gunicorn и Nginx. Вкратце, это сайт для моего друга, пространство, в котором он может делиться и публиковать свои последние действия (например, опубликованные альбомы, фотосессии и статьи). Я попытался автоматизировать сбор информации для заполнения экземпляра модели при загрузке файла (для более быстрой, чистой и простой загрузки из панели администратора), используя несколько сигналов Django. Моя идея заключалась в следующем: если я загружу из админ-панели идеально отформатированный файл (.docx), то с помощью сигналов и других функций я смогу заполнить его информацией и все. Но, если я удалю экземпляр (т.е. альбом или статью), я бы хотел, чтобы это повлияло на файлы (я имею в виду удаление файлов из папки media на сервере). Прежде чем выложить свой код, хочу сказать, что в разработке этот подход работает отлично и для разных моделей он одинаков. К сожалению, в продакшене это не так. Точнее, загрузка файлов работает отлично, обработка иногда нет (и я остаюсь с пустыми полями), удаление файлов для одних моделей работает, для других нет вообще.
settings.py
INSTALLED_APPS = [
'musicsite.apps.MusicsiteConfig',
...
]
apps.py
from django.apps import AppConfig
class MusicsiteConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'musicsite'
def ready(self):
from musicsite import signals
models.py
class Bio(models.Model):
class Meta:
verbose_name_plural = "Bio"
italian_bio = models.TextField(*nothing important*)
english_bio = models.TextField(*nothing important*)
last_updated = models.DateField(auto_now=True)
doc_file = models.FileField(upload_to="documents/", *nothing important*)
signals.py
@receiver(pre_save, sender=Bio)
def replace_file(sender, instance, **kwargs):
if not instance.pk:
return
try:
old_instance = Bio.objects.get(pk=instance.pk)
except Bio.DoesNotExist:
return
old_doc_name = os.path.basename(old_instance.doc_file.name) if (old_doc := old_instance.doc_file) else None
new_doc_name = os.path.basename(instance.doc_file.name) if (new_doc := instance.doc_file) else None
if old_doc_name and (not new_doc_name or old_doc_name != new_doc_name):
old_doc_path = Path(settings.MEDIA_ROOT) / old_doc.name
if old_doc_path.is_file():
safe_unlink(old_doc_path, retries=100)
@receiver(pre_delete, sender=Bio)
def delete_file(sender, instance, using, **kwargs):
if instance.doc_file:
doc_file_path = Path(settings.MEDIA_ROOT) / instance.doc_file.name
if os.path.isfile(doc_file_path):
safe_unlink(doc_file_path, retries=100)
@receiver(post_save, sender=Bio)
def process_biography_file(sender, instance, **kwargs):
if hasattr(instance, '_disable_signals') and instance._disable_signals:
return
temp_it, temp_en = "", ""
if instance.doc_file:
full_path = Path(settings.MEDIA_ROOT) / instance.doc_file.name
try:
temp_data = handle_file(full_path)
temp_it, temp_en = generate_biography(temp_data)
except Exception as e:
instance.delete()
raise ValidationError(f"Error! {e}")
with transaction.atomic():
if instance.italian_bio == "":
if temp_it != "":
instance.italian_bio = temp_it
else:
instance.delete()
raise ValidationError("The italian field should not be empty!")
if instance.english_bio == "":
if temp_it != "":
instance.english_bio = temp_en
else:
instance.delete()
raise ValidationError("The english field should not be empty!")
# PREVENTS INFINITE RECURSION
instance._disable_signals = True
instance.save(update_fields=['italian_bio', 'english_bio',])
instance._disable_signals = False
else:
if instance.italian_bio == "" or instance.english_bio == "":
instance.delete()
raise ValidationError("Fields cannot be empty! Upload a file or provide values for the admin fields!")