Форум программистов, компьютерный форум, киберфорум
Python: Django
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.78/9: Рейтинг темы: голосов - 9, средняя оценка - 4.78
15 / 12 / 4
Регистрация: 06.04.2020
Сообщений: 95

Post_delete. Жизнь после смерти

25.06.2020, 00:53. Показов 1929. Ответов 7
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Есть у меня модели Topic и Comment, который с Topic через ForeignKey связан:
Python
1
topic = models.ForeignKey(Topic, verbose_name=_('Topic'), null=True, blank=True, on_delete=models.CASCADE)
У меня имеется обработка сигнала для обновления последнего коммента к Topic.
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@receiver(post_delete, sender=Comment)
def reduce_topic_answer(sender, instance, **kwargs):
    if hasattr(instance, 'topic'):    # На случай отработки CASCADE
        topic = instance.topic
        my_comments = topic.comment_set.order_by('last_updated')
        last_comment = None
        if my_comments:
            last_comment = my_comments.last()
            if topic.last_updated > last_comment.last_updated:
                last_modify = topic.last_updated
            else:
                last_modify = last_comment.last_updated
        else:
            last_modify = topic.last_updated
        topic.last_activity = last_modify
        topic.last_comment = last_comment
        topic.save()
Всё бы ничего, но при удалении Topic, имеющего связанные комменты, получаю в строке
Python
1
if hasattr(instance, 'topic'):    # На случай отработки CASCADE
ошибку:
DoesNotExist at /forum/topic/remove/1/
Topic matching query does not exist.


Я вообще не очень понимаю, как можно обращаться к объекту в post_delete - он же уже удалён. А Topic ещё ведь не удалён? После ошибки в базе и Topic, и его комменты на месте.

View (удалять можно только Topic из корзины - is_delete=True):

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TopicRemoveView(LoginRequiredMixin, DeleteView):
    model = Topic
    template_name = "forum/topic_confirm_remove.html"
    context_object_name = 'topic'
    pk_url_kwarg = 'topic_pk'
 
    def get_success_url(self):
        return reverse('forum:topics-by-category', kwargs={'category': 'trash'})
 
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['prompt'] = _('Do you really want to remove the Topic')
        context['title'] = _('Remove Topic') # Заголовок окна
        return context
 
    def delete(self, request, *args, **kwargs):
        is_moderator(self.request.user,self.get_object(),raise_exception=True)
        if self.get_object().is_deleted:
            return super().delete(request, *args, **kwargs)
        raise PermissionDenied(_('Topic not in RecycleBin'))
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
25.06.2020, 00:53
Ответы с готовыми решениями:

Жизнь после смерти
#include <iostream> using namespace std; int main() { int life = 5; int *pLife; // состояние дефолта или то что было до вашего...

жизнь после смерти - объект Session
кто нибудь встречался с такой вещью - есть две переменные Session('text1') Session('text2') их инициализирую в форме , написанной для...

Docker byebug, если жизнь после смерти?
Прописываю в docker-compose stdin_open: true tty: true Выполняю docker attach rails || id_docker Да в месте где byebug...

7
 Аватар для m0nte-cr1st0
1043 / 578 / 242
Регистрация: 15.01.2019
Сообщений: 2,178
Записей в блоге: 1
25.06.2020, 01:15
Цитата Сообщение от Shandrik Посмотреть сообщение
if hasattr(instance, 'topic'):
в этой строке не может быть ошибки... Покажи весь трейсбек.

Добавлено через 5 минут
И использование сигналов - не есть хорошо. Это своего рода антипаттерн. Лучше переопределить метод delete у модели.
0
15 / 12 / 4
Регистрация: 06.04.2020
Сообщений: 95
25.06.2020, 01:38  [ТС]
Ещё раз обращаю внимание, что удаляю Topic, а сигнал от Comment

Traceback (most recent call last):
File "D:\Work\PyCharmProject\forum_sc\forum\t ests\test_models.py", line 246, in test_demiurg
reverse(app_name + ':topic-remove', kwargs={'topic_pk': topic_pk}), params_for_recycling, follow=True)
File "c:\python37\lib\site-packages\django\test\client.py", line 543, in post
response = super().post(path, data=data, content_type=content_type, secure=secure, **extra)
File "c:\python37\lib\site-packages\django\test\client.py", line 357, in post
secure=secure, **extra)
File "c:\python37\lib\site-packages\django\test\client.py", line 422, in generic
return self.request(**r)
File "c:\python37\lib\site-packages\django\test\client.py", line 503, in request
raise exc_value
File "c:\python37\lib\site-packages\django\core\handlers\exception. py", line 34, in inner
response = get_response(request)
File "c:\python37\lib\site-packages\django\core\handlers\base.py", line 115, in _get_response
response = self.process_exception_by_middleware(e, request)
File "c:\python37\lib\site-packages\django\core\handlers\base.py", line 113, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "c:\python37\lib\site-packages\django\views\generic\base.py", line 71, in view
return self.dispatch(request, *args, **kwargs)
File "c:\python37\lib\site-packages\braces\views\_access.py", line 102, in dispatch
request, *args, **kwargs)
File "c:\python37\lib\site-packages\django\views\generic\base.py", line 97, in dispatch
return handler(request, *args, **kwargs)
File "c:\python37\lib\site-packages\django\views\generic\edit.py", line 218, in post
return self.delete(request, *args, **kwargs)
File "D:\Work\PyCharmProject\forum_sc\forum\v iews.py", line 392, in delete
return super().delete(request, *args, **kwargs)
File "c:\python37\lib\site-packages\django\views\generic\edit.py", line 213, in delete
self.object.delete()
File "c:\python37\lib\site-packages\django\db\models\base.py", line 919, in delete
return collector.delete()
File "c:\python37\lib\site-packages\django\db\models\deletion.py", line 318, in delete
sender=model, instance=obj, using=self.using
File "c:\python37\lib\site-packages\django\dispatch\dispatcher.py", line 175, in send
for receiver in self._live_receivers(sender)
File "c:\python37\lib\site-packages\django\dispatch\dispatcher.py", line 174, in <listcomp>
(receiver, receiver(signal=self, sender=sender, **named))
File "D:\Work\PyCharmProject\forum_sc\forum\s ignals.py", line 41, in reduce_topic_answer
if hasattr(instance, 'topic'): # На случай отработки CASCADE

File "c:\python37\lib\site-packages\django\db\models\fields\related _descriptors.py", line 178, in __get__
rel_obj = self.get_object(instance)
File "c:\python37\lib\site-packages\django\db\models\fields\related _descriptors.py", line 145, in get_object
return qs.get(self.field.get_reverse_related_fi lter(instance))
File "c:\python37\lib\site-packages\django\db\models\query.py", line 408, in get
self.model._meta.object_name
forum.models.Topic.DoesNotExist: Topic matching query does not exist.

Добавлено через 7 минут
> И использование сигналов - не есть хорошо. Это своего рода антипаттерн. Лучше переопределить метод delete у модели.


Это прямо для всех сигналов? Можете пояснить? У меня поле менялось в разных вьюшках, а реагировать на это надо было одинаково. Сигнал post_save - ну то, что доктор прописал. Во всяком случае, мне так казалось. Какие тут подводные камни?
0
 Аватар для m0nte-cr1st0
1043 / 578 / 242
Регистрация: 15.01.2019
Сообщений: 2,178
Записей в блоге: 1
25.06.2020, 01:51
Shandrik, зачем тебе здесь вообще сигнал - не пойму?

Добавлено через 1 минуту
Цитата Сообщение от Shandrik Посмотреть сообщение
Какие тут подводные камни?
https://lincolnloop.com/blog/d... s-signals/
По своему опыту скажу, что лучше в самых крайних случаях их использовать. И у тебя не тот случай...

Добавлено через 31 секунду
Должно быть достаточно переопределить delete у модели.

Добавлено через 10 минут
Я верно понимаю, что тебе нужно?

1. Удаляешь Topic.
2. Срабатывает CASCADE - удаляются все связанные комменты.
3. Пытаешься снова зачем-то вытащить Topic, который уже удалён, по идее.
4. Пытаешься найти все Comment для этого топика.
5. Проводишь манипуляции с этими Comment.
6. Проводишь манипуляции с этим Topic.

Если так, то это очень странное флоу. Ладно, если бы ты удалял Comment - я бы ещё мог понять...
0
15 / 12 / 4
Регистрация: 06.04.2020
Сообщений: 95
25.06.2020, 01:56  [ТС]
Цитата Сообщение от m0nte-cr1st0 Посмотреть сообщение
Shandrik, зачем тебе здесь вообще сигнал - не пойму?
Поймать удаление коммента, чтобы заменить в topic.last_comment на предыдущий.

Добавлено через 3 минуты
Цитата Сообщение от m0nte-cr1st0 Посмотреть сообщение
Я верно понимаю, что тебе нужно?
1. Удаляешь Topic.
2. Срабатывает CASCADE - удаляются все связанные комменты.
3. Пытаешься снова зачем-то вытащить Topic, который уже удалён, по идее.
4. Пытаешься найти все Comment для этого топика.
5. Проводишь манипуляции с этими Comment.
6. Проводишь манипуляции с этим Topic.
Если так, то это очень странное флоу. Ладно, если бы ты удалял Comment - я бы ещё мог понять...
Нет, мне надо обновить topic.last_comment в случае удаления коммента.

А то, что он сюда попадает при удалении topic - это исключительно из-за CASCADE. В этом случае выполнение тела сигнала не требуется.
0
 Аватар для m0nte-cr1st0
1043 / 578 / 242
Регистрация: 15.01.2019
Сообщений: 2,178
Записей в блоге: 1
25.06.2020, 03:24
Лучший ответ Сообщение было отмечено Shandrik как решение

Решение

Shandrik,
Если будешь удалять кверисет, то придётся также переписывать менеджер. Если нет, то достаточно только заоверрайдить метод delete у самой модели.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class CommentQuerySet(models.QuerySet):
    def delete(self, *args, **kwargs):
        for obj in self:
            obj.delete()
        super(CommentQuerySet, self).delete(*args, **kwargs)
 
 
class Comment(models.Model):
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
    objects = CommentQuerySet.as_manager()
 
    def delete(self):
        topic = self.topic
        my_comments = topic.comment_set.order_by('last_updated')
        last_comment = None
        if my_comments:
            last_comment = my_comments.last()
            if topic.last_updated > last_comment.last_updated:
                last_modify = topic.last_updated
            else:
                last_modify = last_comment.last_updated
        else:
            last_modify = topic.last_updated
        topic.last_activity = last_modify
        topic.last_comment = last_comment
        topic.save()
        return super(Comment, self).delete()
Проверил у себя - работает, как и положено - только при удалении коммента. И не нужно никаких магических сигналов.

Добавлено через 53 минуты
Если немного подрефакторить твой код, то можно переписать так (моржовый оператор можно использовать, если версия питона 3.8+):

Python
1
2
3
4
5
6
7
8
9
10
11
    def delete(self, **kwargs):
        topic = self.topic
        topic.last_comment = None
        topic.last_activity = topic.last_updated
        if my_comments := topic.comment_set.order_by('last_updated'):
            topic.last_comment = my_comments.last()
            topic.last_activity = topic.last_comment.last_updated
            if topic.last_updated > topic.last_comment.last_updated:
                topic.last_activity = topic.last_updated
        topic.save()
        return super(Comment, self).delete()
1
15 / 12 / 4
Регистрация: 06.04.2020
Сообщений: 95
25.06.2020, 23:07  [ТС]
Цитата Сообщение от m0nte-cr1st0 Посмотреть сообщение
И использование сигналов - не есть хорошо. Это своего рода антипаттерн. Лучше переопределить метод delete у модели.
Правильно ли я понимаю - всё, что до вызова delete родителя - это будет pre_delete, а то, что после - post_delete?

Добавлено через 1 час 51 минуту
Вобщем, перенёс в delete модели. Странно, но теперь при удалении родительского Топика этот delete не отрабатывается. Пока что это - то, что доктор прописал, но могут появиться задачи, где он нужен будет. Посмотрим...
0
 Аватар для m0nte-cr1st0
1043 / 578 / 242
Регистрация: 15.01.2019
Сообщений: 2,178
Записей в блоге: 1
26.06.2020, 03:28
Цитата Сообщение от Shandrik Посмотреть сообщение
Правильно ли я понимаю - всё, что до вызова delete родителя - это будет pre_delete, а то, что после - post_delete?
https://docs.djangoproject.com... pre-delete
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
26.06.2020, 03:28
Помогаю со студенческими работами здесь

Что такое сознание и есть ли жизнь после смерти?
Наткнулся намедни на одну мозговыносную статью: http://lurkmore.to/ ...

Сигналы post_delete и pre_delete
подскажите пожалуйста почему post_delete не удаляет объект а pre_delete удаляет. Я с помощью сигнала сделал простую функцию, которая...

Жизнь после С++
Доброго времени суток, уважаемые форумчани! Уже несколько дней не могу определиться, что учить после С++? С++ знаю достаточно неплохо,...

Жизнь после Windows
Microsoft активно работает над преемницей Windows, проектом с кодовым названием Midori. Изданиям SD Times и InformationWeek удалось узнать,...

Жизнь БП после перегрева
Месяц назад на БП вышел из строя кулер. Заметил не сразу, но потом компьютер начал сам выключаться под нагрузкой, а утилита от ASUS...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): Обработчик клика мыши в браузере ПК и касания экрана в браузере на мобильном устройстве
8Observer8 02.02.2026
Содержание блога Для начала пошагово создадим рабочий пример для подготовки к экспериментам в браузере ПК и в браузере мобильного устройства. Потом напишем обработчик клика мыши и обработчик. . .
Философия технологии
iceja 01.02.2026
На мой взгляд у человека в технических проектах остается роль генерального директора. Все остальное нейронки делают уже лучше человека. Они не могут нести предпринимательские риски, не могут. . .
SDL3 для Web (WebAssembly): Вывод текста со шрифтом TTF с помощью SDL3_ttf
8Observer8 01.02.2026
Содержание блога В этой пошаговой инструкции создадим с нуля веб-приложение, которое выводит текст в окне браузера. Запустим на Android на локальном сервере. Загрузим Release на бесплатный. . .
SDL3 для Web (WebAssembly): Сборка C/C++ проекта из консоли
8Observer8 30.01.2026
Содержание блога Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples, то вы увидите, что все примеры используют следующие четыре обязательные функции, а. . .
SDL3 для Web (WebAssembly): Установка Emscripten SDK (emsdk) и CMake для сборки C и C++ приложений в Wasm
8Observer8 30.01.2026
Содержание блога Для того чтобы скачать Emscripten SDK (emsdk) необходимо сначало скачать и уставить Git: Install for Windows. Следуйте стандартной процедуре установки Git через установщик. . . .
SDL3 для Android: Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 29.01.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами. Версия v3 была полностью переписана на Си, в. . .
Инструменты COM: Сохранение данный из VARIANT в файл и загрузка из файла в VARIANT
bedvit 28.01.2026
Сохранение базовых типов COM и массивов (одномерных или двухмерных) любой вложенности (деревья) в файл, с возможностью выбора алгоритмов сжатия и шифрования. Часть библиотеки BedvitCOM Использованы. . .
SDL3 для Android: Загрузка PNG с альфа-каналом с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 28.01.2026
Содержание блога SDL3 имеет собственные средства для загрузки и отображения PNG-файлов с альфа-каналом и базовой работы с ними. В этой инструкции используется функция SDL_LoadPNG(), которая. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru