简体   繁体   中英

Django IntegrityError when editing a value referenced by a foreign key

I have an app with two (relevant) models, in what is essentially a CRM:

class StudentTutorRelationshipProfile(models.Model):
    pupil_name = models.CharField(max_length=100, unique=True)
    parent_name = models.CharField(max_length=100)
    ...

class TimeSheet(models.Model):
    user = models.ForeignKey(
        User, on_delete=models.PROTECT, limit_choices_to={"is_tutor": True}
    )
    student = models.ForeignKey(
        StudentTutorRelationshipProfile, on_delete=models.CASCADE, to_field="pupil_name"
    )
   ...

Now someone has misspelled a pupil's name and would like to change it pupil_name in StudentTutorRelationshipProfile but because there are already TimeSheet records with the Students misspelled name, Django raises an error (from psychog):

ForeignKeyViolation: update or delete on table "invoicing_studenttutorrelationshipprofile" violates foreign key constraint "invoicing_timesheet_student_id_07889dc0_fk_invoicing" on table "invoicing_timesheet"
DETAIL:  Key (pupil_name)=(Student Name) is still referenced from table "invoicing_timesheet".

  File "django/db/backends/base/base.py", line 243, in _commit
    return self.connection.commit()

IntegrityError: update or delete on table "invoicing_studenttutorrelationshipprofile" violates foreign key constraint "invoicing_timesheet_student_id_07889dc0_fk_invoicing" on table "invoicing_timesheet"
DETAIL:  Key (pupil_name)=(Student Name) is still referenced from table "invoicing_timesheet".

What is a nice way of changing this data? I don't mind changing the historic timesheets too or leaving them as is (but can't delete them), what ever is easier / less likley to lead to problems. (And yes the fact that I rely on unique name and surname combinations is not ideal, but won't fix that for now unless the change for the IntegrityError also requires some migrations.

I'm running python 3.6, Django 3.0 if that helps.

This is a good example of why you never want to use a column that might be updated as a foreign key. Other bad foreign key candidates are, for example, data that should not be made public, personal or private data like name or social security number, etc. Also, names are not unique, which automatically disqualifies them from being a unique identifier like a primary key.

It is much simpler and safer to use a SERIAL primary key, or sometimes an UUID or other kind of generic, auto-generated identifier that has no other meaning than to identify the row. It especially should not contain non-ASCII characters, (which also disqualifies names) because you never know if you might have to pipe it through a system that won't handle it, like a human who doesn't speak the language in question ("hello, my login is 指鹿為馬").

In your case, you could have declared your foreign key with ON UPDATE CASCADE, so that updates on the referenced table cascade to the referencing tables. This only takes away some of the annoyance, as a simple update of one row may then cause lots of updates in referencing tables.

You could also update all referencing tables manually, but there will be a time window during which the references will be inconsistent. Setting foreign key constraints to DEFERRED and doing all the updates in a single transaction will probably work.

However this will not update the identifiers in stuff that is outside the database. For example if the system stores a photo of the pupils with a filename based on the primary key, or writes logs, prints paper, or stores anything anywhere that uses the primary key as a reference... Or if the primary key is used as an identifier in a url, and other websites link to yours using this identifier in the link... then none of that will be updated, or even can be updated.

So yeah, updating or reusing primary keys is a huge can of worms, for absolutely no benefit.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM