From patchwork Thu Aug 30 21:31:11 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michael-Doyle Hudson X-Patchwork-Id: 11098 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 65F9223E00 for ; Thu, 30 Aug 2012 21:31:15 +0000 (UTC) Received: from mail-iy0-f180.google.com (mail-iy0-f180.google.com [209.85.210.180]) by fiordland.canonical.com (Postfix) with ESMTP id 7F88FA18013 for ; Thu, 30 Aug 2012 21:30:38 +0000 (UTC) Received: by iafj25 with SMTP id j25so3710168iaf.11 for ; Thu, 30 Aug 2012 14:31:14 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=x-forwarded-to:x-forwarded-for:delivered-to:received-spf :content-type:mime-version:x-launchpad-project:x-launchpad-branch :x-launchpad-message-rationale:x-launchpad-branch-revision-number :x-launchpad-notification-type:to:from:subject:message-id:date :reply-to:sender:errors-to:precedence:x-generated-by :x-launchpad-hash:x-gm-message-state; bh=PbdLKKy5p3C99EUAPkSmD5HozPYwIAHSwTeMrco5tJU=; b=SJywycNYG+ZuWN5r2107ZZkxWRGUsbHlscVvLUzURtxVxvS+vfcxYyHnRw2YGkI3hB LRhFhWveZp9gi2KUSWGSOV/D3m0QGrSrUK9LieJ8zGSss0R1lrSlgw7BsRLZqSr2Ql+o bB3OXLNfwA2XCC+0kqOcyqVPd9j+sLSpQfVZgEee8wHLwFx24P0zEyRrAscJwqq+4wM6 tMyQ6510sXpRv5mU7gEFeDV3X4I8TcYZvEjSW9F+nb3JEyY3k/QIxzG+CsrfjayJXsum eRXsZR6tXbY3EqbQu4yfr33sjn9837l5idOTH63njPveay5c+mDiwck3MbbQnE4ZK/l1 JSpQ== Received: by 10.42.84.69 with SMTP id k5mr6473346icl.5.1346362274054; Thu, 30 Aug 2012 14:31:14 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.50.184.232 with SMTP id ex8csp32358igc; Thu, 30 Aug 2012 14:31:12 -0700 (PDT) Received: by 10.181.13.208 with SMTP id fa16mr3084367wid.11.1346362272269; Thu, 30 Aug 2012 14:31:12 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id b4si3815551wiz.1.2012.08.30.14.31.11 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 30 Aug 2012 14:31:12 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1T7CKV-00015o-5f for ; Thu, 30 Aug 2012 21:31:11 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id 19F7FE0108 for ; Thu, 30 Aug 2012 21:31:11 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-scheduler X-Launchpad-Branch: ~linaro-validation/lava-scheduler/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 210 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [Branch ~linaro-validation/lava-scheduler/trunk] Rev 210: make a few fkeys ON DELETE SET NULL to avoid surprises Message-Id: <20120830213111.28656.1361.launchpad@ackee.canonical.com> Date: Thu, 30 Aug 2012 21:31:11 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="15877"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: bd6a2f9e08fd082afc3612bc1016acd1745b5bfb X-Gm-Message-State: ALoCoQnbKdBoGKRon9yP5iTt8UC7mmQpDm9JYj0p85lrPSKgWcJHYmW68qv5sh9UhZRdn6JKYSYi Merge authors: Michael Hudson-Doyle (mwhudson) Related merge proposals: https://code.launchpad.net/~mwhudson/lava-scheduler/TestJob.result_bundle-no-cascade-bug-1042110/+merge/121984 proposed by: Michael Hudson-Doyle (mwhudson) ------------------------------------------------------------ revno: 210 [merge] committer: Michael Hudson-Doyle branch nick: trunk timestamp: Fri 2012-08-31 09:30:49 +1200 message: make a few fkeys ON DELETE SET NULL to avoid surprises added: lava_scheduler_app/migrations/0025_auto__chg_field_testjob__results_bundle__chg_field_testjob_submit_toke.py modified: lava_scheduler_app/models.py --- lp:lava-scheduler https://code.launchpad.net/~linaro-validation/lava-scheduler/trunk You are subscribed to branch lp:lava-scheduler. To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-scheduler/trunk/+edit-subscription === added file 'lava_scheduler_app/migrations/0025_auto__chg_field_testjob__results_bundle__chg_field_testjob_submit_toke.py' --- lava_scheduler_app/migrations/0025_auto__chg_field_testjob__results_bundle__chg_field_testjob_submit_toke.py 1970-01-01 00:00:00 +0000 +++ lava_scheduler_app/migrations/0025_auto__chg_field_testjob__results_bundle__chg_field_testjob_submit_toke.py 2012-08-30 04:44:11 +0000 @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Changing field 'TestJob._results_bundle' + db.alter_column('lava_scheduler_app_testjob', 'results_bundle_id', self.gf('django.db.models.fields.related.OneToOneField')(null=True, db_column='results_bundle_id', on_delete=models.SET_NULL, to=orm['dashboard_app.Bundle'], unique=True)) + + # Changing field 'TestJob.submit_token' + db.alter_column('lava_scheduler_app_testjob', 'submit_token_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['linaro_django_xmlrpc.AuthToken'], null=True, on_delete=models.SET_NULL)) + + # Changing field 'DeviceStateTransition.created_by' + db.alter_column('lava_scheduler_app_devicestatetransition', 'created_by_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.SET_NULL)) + + # Changing field 'DeviceStateTransition.job' + db.alter_column('lava_scheduler_app_devicestatetransition', 'job_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['lava_scheduler_app.TestJob'], null=True, on_delete=models.SET_NULL)) + + # Changing field 'Device.current_job' + db.alter_column('lava_scheduler_app_device', 'current_job_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['lava_scheduler_app.TestJob'], unique=True)) + + # Changing field 'Device.last_health_report_job' + db.alter_column('lava_scheduler_app_device', 'last_health_report_job_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['lava_scheduler_app.TestJob'], unique=True)) + + def backwards(self, orm): + + # Changing field 'TestJob._results_bundle' + db.alter_column('lava_scheduler_app_testjob', 'results_bundle_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['dashboard_app.Bundle'], unique=True, null=True, db_column='results_bundle_id')) + + # Changing field 'TestJob.submit_token' + db.alter_column('lava_scheduler_app_testjob', 'submit_token_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['linaro_django_xmlrpc.AuthToken'], null=True)) + + # Changing field 'DeviceStateTransition.created_by' + db.alter_column('lava_scheduler_app_devicestatetransition', 'created_by_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)) + + # Changing field 'DeviceStateTransition.job' + db.alter_column('lava_scheduler_app_devicestatetransition', 'job_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['lava_scheduler_app.TestJob'], null=True)) + + # Changing field 'Device.current_job' + db.alter_column('lava_scheduler_app_device', 'current_job_id', self.gf('django.db.models.fields.related.ForeignKey')(unique=True, null=True, to=orm['lava_scheduler_app.TestJob'])) + + # Changing field 'Device.last_health_report_job' + db.alter_column('lava_scheduler_app_device', 'last_health_report_job_id', self.gf('django.db.models.fields.related.ForeignKey')(unique=True, null=True, to=orm['lava_scheduler_app.TestJob'])) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'dashboard_app.bundle': { + 'Meta': {'ordering': "['-uploaded_on']", 'object_name': 'Bundle'}, + '_gz_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'gz_content'"}), + '_raw_content': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'db_column': "'content'"}), + 'bundle_stream': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'bundles'", 'to': "orm['dashboard_app.BundleStream']"}), + 'content_filename': ('django.db.models.fields.CharField', [], {'max_length': '256'}), + 'content_sha1': ('django.db.models.fields.CharField', [], {'max_length': '40', 'unique': 'True', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_deserialized': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'uploaded_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'uploaded_bundles'", 'null': 'True', 'to': "orm['auth.User']"}), + 'uploaded_on': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.utcnow'}) + }, + 'dashboard_app.bundlestream': { + 'Meta': {'object_name': 'BundleStream'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'pathname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '64', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'lava_scheduler_app.device': { + 'Meta': {'object_name': 'Device'}, + 'current_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['lava_scheduler_app.TestJob']", 'blank': 'True', 'unique': 'True'}), + 'device_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.DeviceType']"}), + 'health_status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'hostname': ('django.db.models.fields.CharField', [], {'max_length': '200', 'primary_key': 'True'}), + 'last_health_report_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['lava_scheduler_app.TestJob']", 'blank': 'True', 'unique': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'lava_scheduler_app.devicestatetransition': { + 'Meta': {'object_name': 'DeviceStateTransition'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'device': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'transitions'", 'to': "orm['lava_scheduler_app.Device']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'job': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['lava_scheduler_app.TestJob']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'message': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'new_state': ('django.db.models.fields.IntegerField', [], {}), + 'old_state': ('django.db.models.fields.IntegerField', [], {}) + }, + 'lava_scheduler_app.devicetype': { + 'Meta': {'object_name': 'DeviceType'}, + 'display': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'health_check_job': ('django.db.models.fields.TextField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'primary_key': 'True'}), + 'use_celery': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'lava_scheduler_app.tag': { + 'Meta': {'object_name': 'Tag'}, + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + 'lava_scheduler_app.testjob': { + 'Meta': {'object_name': 'TestJob'}, + '_results_bundle': ('django.db.models.fields.related.OneToOneField', [], {'null': 'True', 'db_column': "'results_bundle_id'", 'on_delete': 'models.SET_NULL', 'to': "orm['dashboard_app.Bundle']", 'blank': 'True', 'unique': 'True'}), + '_results_link': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '400', 'null': 'True', 'db_column': "'results_link'", 'blank': 'True'}), + 'actual_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}), + 'definition': ('django.db.models.fields.TextField', [], {}), + 'description': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), + 'health_check': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'log_file': ('django.db.models.fields.files.FileField', [], {'default': 'None', 'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'requested_device': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.Device']"}), + 'requested_device_type': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'+'", 'null': 'True', 'blank': 'True', 'to': "orm['lava_scheduler_app.DeviceType']"}), + 'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'status': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'submit_time': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'submit_token': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['linaro_django_xmlrpc.AuthToken']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'submitter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'to': "orm['auth.User']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['lava_scheduler_app.Tag']", 'symmetrical': 'False', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'linaro_django_xmlrpc.authtoken': { + 'Meta': {'object_name': 'AuthToken'}, + 'created_on': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_used_on': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'secret': ('django.db.models.fields.CharField', [], {'default': "'omk0tualeq2crdog45ys0cgo2gfgp626w6kchza9belc08w9p10xlcpiq1i0f86ta1m8us92ksryupbl4ll0c7zhbeluj0s4sw40gxsc0hkwkip3fgjg0gsh2j6lbbhy'", 'unique': 'True', 'max_length': '128'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['lava_scheduler_app'] \ No newline at end of file === modified file 'lava_scheduler_app/models.py' --- lava_scheduler_app/models.py 2012-08-03 16:12:09 +0000 +++ lava_scheduler_app/models.py 2012-08-30 04:44:11 +0000 @@ -107,7 +107,8 @@ DeviceType, verbose_name=_(u"Device type")) current_job = models.ForeignKey( - "TestJob", blank=True, unique=True, null=True, related_name='+') + "TestJob", blank=True, unique=True, null=True, related_name='+', + on_delete=models.SET_NULL) tags = models.ManyToManyField(Tag, blank=True) @@ -124,7 +125,8 @@ ) last_health_report_job = models.ForeignKey( - "TestJob", blank=True, unique=True, null=True, related_name='+') + "TestJob", blank=True, unique=True, null=True, related_name='+', + on_delete=models.SET_NULL) def __unicode__(self): return self.hostname @@ -227,7 +229,8 @@ related_name = '+', ) - submit_token = models.ForeignKey(AuthToken, null=True, blank=True) + submit_token = models.ForeignKey( + AuthToken, null=True, blank=True, on_delete=models.SET_NULL) description = models.CharField( verbose_name = _(u"Description"), @@ -299,7 +302,8 @@ max_length=400, default=None, null=True, blank=True, db_column="results_link") _results_bundle = models.OneToOneField( - Bundle, null=True, blank=True, db_column="results_bundle_id") + Bundle, null=True, blank=True, db_column="results_bundle_id", + on_delete=models.SET_NULL) @property def results_link(self): @@ -451,9 +455,9 @@ class DeviceStateTransition(models.Model): created_on = models.DateTimeField(auto_now_add=True) - created_by = models.ForeignKey(User, null=True, blank=True) + created_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) device = models.ForeignKey(Device, related_name='transitions') - job = models.ForeignKey(TestJob, null=True, blank=True) + job = models.ForeignKey(TestJob, null=True, blank=True, on_delete=models.SET_NULL) old_state = models.IntegerField(choices=Device.STATUS_CHOICES) new_state = models.IntegerField(choices=Device.STATUS_CHOICES) message = models.TextField(null=True, blank=True)