N
N
nurzhannogerbek2017-09-17 21:56:54
Django
nurzhannogerbek, 2017-09-17 21:56:54

Problems in unit test (Entry matching query does not exist)?

Hello! Please help me figure it out. Faced a strange problem.
In the template I display a list of article titles. The user can drag and drop them at his discretion. Trying to write a unit test for a view, but it throws an error.
PS The error disappears only if you remove the delete_old_article_image method in the models.py file. But I need this method in my project, as I use it to remove the old article image when updating with a new one. What's going wrong?
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)

Answer the question

In order to leave comments, you need to log in

1 answer(s)
J
javedimka, 2017-09-18
@nurzhannogerbek

see update
The error occurs while processing the pre_save signal. In the test_article_sorting() test, an article object is created, a pre_save signal is sent to the database before saving, and the delete_old_article_image function is executed, where in the line the
.get() method tries to get a non-existent article object from the database and after that it raises the DoesNotExist error
One of several possible solutions is to wrap it in try/except block:

@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

Well, of course, all this also needs to be covered with tests, there will be at least 3 of them.
Today I remembered this question, opened it again, understood why it worked in production, but not in tests:
As I said, in tests, you create article objects using the .create() shortcut, which internally creates an obj object with the parameters you specified and calls save() to save it to the database, the pre_save signal is sent, your object with the existing pk is passed to the handler, your code, the if instance.pk condition is triggered and an attempt to get a non-existent object from the database begins, which ends with an error.
Why did it work on a live site? That's why:
On a "live" site, when you create a new object, when you save it, an instance with pk == None is passed to the pre_save () signal handler, since the primary key is hung up by the database itself after all the jang stuff, so at this stage if instance.pk returned False and the nested code was not executed.
When you edited an already existing object, when saving, an “already existing” object in the database was passed to the pre_save () handler, which has pk and which, perhaps, now has slightly changed data (since we are editing, then we change something), which led to fulfilling the if instance.pk condition and executing the nested code that successfully received an object that already exists in the database.
Therefore, it was possible to solve the problem with tests simply by removing the explicit pk hanging from the tests, leaving this work to the database:
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}

And instance.pk should be checked like this:
@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)

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question