Django IntegrityError:表中不存在键(但键的查询有效)

2024-04-26 04:39:32 发布

您现在位置:Python中文网/ 问答频道 /正文

我对Django测试有一个奇怪的问题。只有在运行测试命令python3 manage.py test时,才会出现此问题

背景信息:我有一个带有自定义保存方法的订阅模型,它可以发送信息邮件、创建账单和课程等。使用super().save(using=using, *args, **kwargs)命令将订阅保存到数据库后,调用方法self.create_lessons()get,该方法在创建课程时导致IntegrityError,表示订阅在表中不存在

为了检查订阅表中是否真的不存在订阅,我添加了一个查询s = Subscription.objects.get(id=self.id),并将其打印到控制台print(s)。查询和print命令工作正常,但我仍然得到完整性错误

至少对我来说,真正令人困惑的是,只有在运行python3 manage.py test命令时才会发生错误。当我运行Web服务器并使用管理面板或shell创建订阅时,一切都正常运行

更奇怪的是,当我对代码进行更改时,错误会消失一段时间(2-3次成功执行测试命令),这其实并不重要。例如,注释或数据库查询

我在下面添加了回溯、subcription模型的自定义保存方法和create_-lessons方法。只要告诉我你是否需要任何进一步的代码或信息。我希望你们能帮我解决这个问题,任何提示或提示都非常感谢!我为这个问题挣扎了很长一段时间

编辑:

我已经找到了一个解决方法来避免这个问题。当我将keep database和debug mode选项添加到test命令并运行python3 manage.py test -k --failfast -v 3 --debug-mode时,我的测试总是在工作

回溯:

Traceback (most recent call last):
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py", line 84, in _execute
sut_1  |     return self.cursor.execute(sql, params)
sut_1  | psycopg2.errors.ForeignKeyViolation: insert or update on table "main_lesson" violates foreign key constraint "main_lesson_subscription_id_41a5ca44_fk_main_subscription_id"
sut_1  | DETAIL:  Key (subscription_id)=(5) is not present in table "main_subscription".
sut_1  |
sut_1  |
sut_1  | The above exception was the direct cause of the following exception:
sut_1  |
sut_1  | Traceback (most recent call last):
sut_1  |   File "/code/main/tests_commands.py", line 105, in setUp
sut_1  |     sub1 = Subscription.objects.create(first_lessons_date=date,
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/manager.py", line 82, in manager_method
sut_1  |     return getattr(self.get_queryset(), name)(*args, **kwargs)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 422, in create
sut_1  |     obj.save(force_insert=True, using=self.db)
sut_1  |   File "/code/main/models.py", line 431, in save
sut_1  |     self.create_lessons()
sut_1  |   File "/code/main/models.py", line 211, in create_lessons
sut_1  |     l.save(using='primary')
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/base.py", line 740, in save
sut_1  |     self.save_base(using=using, force_insert=force_insert,
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/base.py", line 777, in save_base
sut_1  |     updated = self._save_table(
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/base.py", line 870, in _save_table
sut_1  |     result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/base.py", line 907, in _do_insert
sut_1  |     return manager._insert([self], fields=fields, return_id=update_pk,
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/manager.py", line 82, in manager_method
sut_1  |     return getattr(self.get_queryset(), name)(*args, **kwargs)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/query.py", line 1186, in _insert
sut_1  |     return query.get_compiler(using=using).execute_sql(return_id)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/models/sql/compiler.py", line 1375, in execute_sql
sut_1  |     cursor.execute(sql, params)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py", line 99, in execute
sut_1  |     return super().execute(sql, params)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py", line 67, in execute
sut_1  |     return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py", line 76, in _execute_with_wrappers
sut_1  |     return executor(sql, params, many, context)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py", line 84, in _execute
sut_1  |     return self.cursor.execute(sql, params)
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/utils.py", line 89, in __exit__
sut_1  |     raise dj_exc_value.with_traceback(traceback) from exc_value
sut_1  |   File "/usr/local/lib/python3.8/dist-packages/django/db/backends/utils.py", line 84, in _execute
sut_1  |     return self.cursor.execute(sql, params)
sut_1  | django.db.utils.IntegrityError: insert or update on table "main_lesson" violates foreign key constraint "main_lesson_subscription_id_41a5ca44_fk_main_subscription_id"
sut_1  | DETAIL:  Key (subscription_id)=(5) is not present in table "main_subscription".

订阅模型的自定义保存方法:

#Custom save function which includes creating the lessons of the 
#subscription
def save(self, using='primary', *args, **kwargs):
    #Marks the bill as paid if the bill already was paid in advance
    if self.billing_mode == '2':
        self.bill_paid = '1'
    #If lessons_outstanding isn't defined and isn't zero, the value 
    #will be equal to the amount of lessons.
    if not self.lessons_outstanding:
        if self.lessons_outstanding != 0:
            self.lessons_outstanding = self.contract.lessons_count

    #Sends info email
    if self.bexio_id == None and self.info_mail != '1':
        #Sends email to student
        subject = 'Neues Abo wurde erstellt'

        #If there is a bill, the text with a bill will be sent.
        #Elsewise there's only the text without the reference
        #to the bill. 
        first_lessons_date = self.first_lessons_date + timedelta(minutes=60)
        if self.billing_mode == '2':
            body_text = """Hallo {} {}

            {} {} hat soeben ein Abo mit Startdatum {} für Sie erstellt.

            Wir wünschen viel Spass im Unterricht und stehen bei Fragen gerne zur Verfügung.

            """.format(self.contract.student.first_name, 
            self.contract.student.last_name,
            self.contract.teacher.first_name,
            self.contract.teacher.last_name,
            first_lessons_date.strftime("%d.%m.%Y"))

            body_html = """<p>Hallo {} {}</p><p></p><p>{} {} hat soeben
            ein Abo mit Startdatum {} für Sie erstellt.
            <p></p>
            <p>Wir wünschen viel Spass im Unterricht und stehen bei 
            Fragen gerne zur Verfügung.</p>

            """.format(self.contract.student.first_name, 
            self.contract.student.last_name,
            self.contract.teacher.first_name,
            self.contract.teacher.last_name,
            first_lessons_date.strftime("%d.%m.%Y"))
        else:
            body_text = """Hallo {} {}

            {} {} hat soeben ein Abo mit Startdatum {} für Sie erstellt. Die Rechnung erhalten Sie in einem separaten Mail. Falls Sie die Zahlungsweise «Papierrechnung» bei uns hinterlegt haben, erhalten Sie die Rechnung per Post.

            Wir wünschen viel Spass im Unterricht und stehen bei Fragen gerne zur Verfügung.

            """.format(self.contract.student.first_name, 
            self.contract.student.last_name,
            self.contract.teacher.first_name,
            self.contract.teacher.last_name,
            first_lessons_date.strftime("%d.%m.%Y"))

            body_html = """<p>Hallo {} {}</p><p></p><p>{} {} hat soeben
            ein Abo mit Startdatum {} für Sie erstellt. Die Rechnung
            erhalten Sie in einem separaten Mail. Falls Sie die
            Zahlungsweise «Papierrechnung» bei uns hinterlegt haben, 
            erhalten Sie die Rechnung per Post.</p>
            <p></p>
            <p>Wir wünschen viel Spass im Unterricht und stehen bei 
            Fragen gerne zur Verfügung.</p>

            """.format(self.contract.student.first_name, 
            self.contract.student.last_name,
            self.contract.teacher.first_name,
            self.contract.teacher.last_name,
            first_lessons_date.strftime("%d.%m.%Y"))

        #If there is no student address an escalation will
        #be created, because the info email can't be send
        if not self.contract.student.address:
            rel = ('Student (' + str(self.contract.student.pipedrive_id) + 
                    '): ' + self.contract.student.first_name + ' ' + 
                    self.contract.student.last_name)
            create_escalation(type='E1', category='S', object=rel)

        else:
            email_to = [self.contract.student.address.email]
            send_mail(subject, body_text, body_html, email_to)

        #send email to teacher
        subject = 'Abo erstellt für {} {}'.format(self.contract.student.last_name,
                                                  self.contract.student.first_name)
        #If there is a bill, the text with a bill will be sent.
        #Elsewise there's only the text without
        if self.billing_mode == '2':
            body_text = """Hallo {}

            Es wurde folgendes Abo erstellt. Die Rechnung wurde bereits mit einem Gutschein beglichen:

            Schüler: {} {}
            Startdatum {}

            Für weitere Informationen logge dich mit deinem Login unter backend.school78.ch ein. Dort kannst du auch die erteilten Lektionen eintragen.

            Bei Fragen wende dich an dein School78 Team.
            """.format(self.contract.teacher.first_name,
            self.contract.student.last_name,
            self.contract.student.first_name, 
            first_lessons_date.strftime("%d.%m.%Y"))

            body_html = """<p>Hallo {}</p>
            <p></p><p>Es wurde folgendes Abo erstellt. Die Rechnung wurde
            bereits mit einem Gutschein beglichen:</p>
            <p></p><p>Schüler: {} {}<br />Startdatum: {}</p>
            <p></p><p>Für weitere Informationen logge dich mit deinem Login
            unter <a href='https://backend.school78.ch'>backend.school78.ch</a>
            ein. Dort kannst du auch die erteilten Lektionen eintragen.</p><p></p><p>Bei Fragen 
            stehen wir dir gerne zur Verfügung.</P>""".format(self.contract.teacher.first_name,
            self.contract.student.last_name,
            self.contract.student.first_name, 
            first_lessons_date.strftime("%d.%m.%Y"))
        else:
            body_text = """Hallo {}

            Die Rechnung für das folgende Abo wurde per Email versendet – bei Papierrechnung erfolgt der Versand in den nächsten drei Arbeitstagen:

            Schüler: {} {}
            Startdatum {}

            Für weitere Informationen logge dich mit deinem Login unter backend.school78.ch ein. Dort kannst du auch die erteilten Lektionen eintragen und sehen, ob das Abo bereits bezahlt ist oder nicht.

            Bei Fragen wende dich an dein School78 Team.
            """.format(self.contract.teacher.first_name,
            self.contract.student.last_name,
            self.contract.student.first_name, 
            first_lessons_date.strftime("%d.%m.%Y"))

            body_html = """<p>Hallo {}</p>
            <p></p><p>Die Rechnung für das folgende Abo wurde per Email
            versendet – bei Papierrechnung erfolgt der Versand in den
            nächsten drei Arbeitstagen:</p>
            <p></p><p>Schüler: {} {}<br />Startdatum: {}</p>
            <p></p><p>Für weitere Informationen logge dich mit deinem Login
            unter <a href='https://backend.school78.ch'>backend.school78.ch</a>
            ein. Dort kannst du auch die erteilten Lektionen eintragen und sehen,
            ob das Abo bereits bezahlt ist oder nicht.</p><p></p><p>Bei Fragen 
            stehen wir dir gerne zur Verfügung.</P>""".format(self.contract.teacher.first_name,
            self.contract.student.last_name,
            self.contract.student.first_name, 
            first_lessons_date.strftime("%d.%m.%Y"))

        #If there is no teacher address an escalation will
        #be created, because the info email can't be send
        if not self.contract.teacher.address:
            rel = ('Teacher (' + str(self.contract.teacher.pipedrive_id) + 
                    '): ' + self.contract.teacher.first_name + ' ' + 
                    self.contract.teacher.last_name)
            create_escalation(type='E1', category='T', object=rel)

        else:
            email_to = [self.contract.teacher.address.email]
            send_mail(subject, body_text, body_html, email_to, teacher=True)
            self.info_mail = '1'

    #Saves the model        
    super().save(using=using, *args, **kwargs)

    s = Subscription.objects.get(id=self.id)
    print(s)

    #Creates lessons if they don't exist
    if self.lessons.count() == 0:
        self.create_lessons()

    #Creates bill if there is none 
    if self.bexio_id == None and self.bill_paid != '1':
        #If bill type is paper, an escalation will be created and the
        #office will send a bill manually
        if self.contract.bill_type == 'P':
            #Creates bill with customcommand
            args = [None, self.pk]
            opts = {}
            call_command('create_bill', *args, **opts)

        #If bill type is email, the bill will be created directly in
        #Bexio and will be sent...
        elif self.contract.bill_type == 'E': 
            #Creates bill with customcommand
            args = [None, self.pk]
            opts = {}
            call_command('create_bill', *args, **opts)

创建_课程()-方法:

#Creates the lessons related to the new subscription
    def create_lessons(self):
        s = Subscription.objects.get(id=self.id)
        print(s)
        for i in range(0, self.contract.lessons_count):
            if i == 0:
                l = Lesson(lessons_date=self.first_lessons_date, status='T', subscription=self)
                l.save(using='primary')
            else:
                l = Lesson(status='N', subscription=self)
                l.save(using='primary')

Tags: thenameinpyselfidlinestudent