Задать вопрос
@nurzhannogerbek

Проблемы в юнит тесте (Entry matching query does not exist)?

Здравствуйте! Помогите пожалуйста разобраться. Столкнулся со странной проблемой.

В шаблон вывожу список названий статей. Пользователь на свое усмотрение может перетаскивать их. Пытась написать юнит тест на вьюху, но она выдает ошибку.

P.S. Ошибка исчезает только если убрать метод delete_old_article_image в файле models.py. Но данный метод мне нужен в моем проекте, так как я его использую, чтобы удалить старое изображение статьи при обновлении новым. Что происходит не так?

ERROR:
Traceback (most recent call last):
  File "C:\Users\Nurzhan\PycharmProjects\CA\article\tests.py", line 119, in test_article_sorting
    content_type='image/jpeg'
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\modeltranslation\manager.py", line 381, in create
    return super(MultilingualQuerySet, self).create(**kwargs)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\db\models\query.py", line 399, in create
    obj.save(force_insert=True, using=self.db)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\db\models\base.py", line 796, in save
    force_update=force_update, update_fields=update_fields)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\db\models\base.py", line 820, in save_base
    update_fields=update_fields)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\dispatch\dispatcher.py", line 191, in send
    response = receiver(signal=self, sender=sender, **named)
  File "C:\Users\Nurzhan\PycharmProjects\CA\slider\models.py", line 67, in delete_old_article_image
    article = Article.objects.get(pk=instance.pk)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\Nurzhan\AppData\Local\Programs\Python\Python35\lib\site-packages\django\db\models\query.py", line 385, in get
    self.model._meta.object_name
article.models.DoesNotExist: Article matching query does not exist.


models.py:
from django.db.models.signals import pre_save
from django.dispatch import receiver

class Article(models.Model):
    head = models.CharField(max_length=200, blank=False)

    idx = models.IntegerField(default=0, blank=True)

    image = models.ImageField(upload_to='slider/images/%Y/%m/%d/',blank=False,)

@receiver(pre_save, sender=Article)
def delete_old_article_image(sender, instance, *args, **kwargs):
    if instance.pk:
        article = Article.objects.get(pk=instance.pk)
        if instance.image and article.image != instance.image:
            slide.image.delete(False)


views.py:
class ArticleSortingView(CsrfExemptMixin, JsonRequestResponseMixin, FormView):
    def post(self, request, *args, **kwargs):
        for pk, idx in self.request_json.items():
            Article.objects.filter(pk=pk).update(idx=idx)
        return self.render_json_response({'saved': 'OK'})


tests.py:
class ArticleTestCase(TestCase):
    def setUp(self):
        self.client = Client()
        self.credentials = {'username': 'user', 'password': 'password'}
        self.user = User.objects.create_user(**self.credentials)
        self.logged_in = self.client.login(**self.credentials)
        self.first_test_image = open(os.path.join(BASE_DIR, 'static/images/tests/test_image_1.png'), "rb")
        self.second_test_image = open(os.path.join(BASE_DIR, 'static/images/tests/test_image_2.jpg'), "rb")

    def test_article_sorting(self):
        first_article = Article.objects.create(
            pk=150,
            idx=0,
            head='First',
            image=SimpleUploadedFile(
                name=self.first_test_image.name,
                content=self.first_test_image.read(),
                content_type='image/jpeg'
            )
        )

        second_article = Article.objects.create(
            pk=160,
            idx=1,
            head='Second',
            image=SimpleUploadedFile(
                name=self.first_test_image.name,
                content=self.first_test_image.read(),
                content_type='image/jpeg'
            )
        )

        third_article = Article.objects.create(
            pk=170,
            idx=2,
            head='Third',
            image=SimpleUploadedFile(
                name=self.first_test_image.name,
                content=self.first_test_image.read(),
                content_type='image/jpeg'
            )
        )

        data = {150: 2, 160: 0, 170: 1}

        response = self.client.post(
            reverse("article:article_sorting"),
            data=json.dumps(data),
            content_type='application/json;',
            follow=True
        )

        self.assertEqual(response.content, '{"saved": "OK"}')
        self.assertEqual(response.status_code, 200)

        first_article.refresh_from_db()
        self.assertEquals(first_article.idx, 2)

        second_article.refresh_from_db()
        self.assertEquals(second_article.idx, 0)

        third_article.refresh_from_db()
        self.assertEquals(third_article.idx, 1)
  • Вопрос задан
  • 736 просмотров
Подписаться 1 Оценить 16 комментариев
Решения вопроса 1
@javedimka
Хочу сока
см. update
Ошибка возникает при обработке сигнала pre_save. В тесте test_article_sorting() создается объект статьи, перед сохранением в бд посылается сигнал pre_save и выполняется функция delete_old_article_image, где в строчке
article = Article.objects.get(pk=instance.pk)
.get() метод пытается получить несуществующий объект статьи из бд и после этого рэйсит ошибку DoesNotExist
Одно из нескольких возможных решений - обернуть в блок try/except:
@receiver(pre_save, sender=Article)
def delete_old_article_image(sender, instance, *args, **kwargs):
    if instance.pk:
        try:
            article = Article.objects.get(pk=instance.pk)
            if instance.image and article.image != instance.image:
                slide.image.delete(False)
        except Article.DoesNotExist:
            pass

Ну и естественно все это тоже надо покрыть тестами, тут их будет как минимум 3.

UPDATE:

Сегодня что-то вспомнил этот вопрос, еще раз открыл, понял почему на "продакшене" работало, а в тестах нет:
Как я уже говорил, в тестах, ты создаешь объекты статьи с помощью шортката .create() который внутри себя создает объект obj с указанными тобою параметрами и вызывает save() для его сохранения в базу данных, отправляется сигнал pre_save, в хэндлер передается твой объект с имеющимся pk, выполняется твой код, условие if instance.pk срабатывает и начинается попытка получить несуществующий объект из базы, что оканчивается ошибкой.

Почему работало на живом сайте? Вот почему:

На "живом" сайте, когда ты создаешь новый объект, при его сохранении в хэндлер сигнала pre_save() передается instance с pk == None, так как primary key вешается самой базой данных после всех джанговских штучек, поэтому на этом этапе if instance.pk возвращало False и вложенный код не выполнялся.

Когда же ты редактировал уже существующий объект при сохранении в хэндлер pre_save() передавался "уже имеющийся" в базе объект у которого есть pk и у которого, возможно, теперь слегка измененные данные (раз уж редактируем то что-то да меняем) что приводило к выполнению условия if instance.pk и выполнению вложенного кода который успешно получал уже существующий базе объект.

Поэтому, решить проблему с тестами можно было просто убрав явное вешание pk из тестов, оставив эту работу бд:
first_article = Article.objects.create(
    # pk=150,
    ...   
    )

second_article = Article.objects.create(
    # pk=160,
    ...   
    )

third_article = Article.objects.create(
   # pk=170,
   ...
   )

data = {first_article.pk: 2, second_article.pk: 0, third_article.pk: 1}


И instance.pk проверять следует так:
@receiver(pre_save, sender=Article)
def delete_old_article_image(sender, instance, *args, **kwargs):
    if instance.pk is not None:
            article = Article.objects.get(pk=instance.pk)
            if instance.image and article.image != instance.image:
                slide.image.delete(False)
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы