Repository: netbox-community/netbox

Issue:
AutoSyncRecord inconsistent after file change
### Deployment Type

Self-hosted

### NetBox Version

v3.7.1

### Python Version

3.11

### Steps to Reproduce

1. Create data source `TestData`:

    | Field | Value |
    |--------|--------|
    | **Name** | TestData |
    | **Type** | git |
    | **URL** | https://github.com/netbox-community/netbox-plugin-tutorial.git |
    | **enabled** | `yes` |

1. Sync data source `TestData`.
1. Create export template `TestExport`:

    | Field | Value |
    |--------|--------|
    | **Name** | TestExport |
    | **Content Types** | `Circuits > Circuit` (but any will do) |
    | **Data Source** | TestData |
    | **Data File** | README.md |
    | **Auto Sync Enabled** | `yes` |

1. Edit export template `TestExport`.
1. Select **Data file** `tutorial/step01-initial-setup.md` and save. (**will fail**).
1. Go back to edit form for export template `TestExport`.
1. Set **Data source**, **Data file** and **Auto Sync enabled** to null and save.

### Expected Behavior

Step 5: Edit succeeds.
Step 7: `AutoSyncRecord` is removed from database.

### Observed Behavior

Step 5: An `IntegrityError` error is raised, as `SyncedDataMixin`'s [`save()` method](https://github.com/netbox-community/netbox/blob/b9cac97b73c1c08213db6272de11c03711491486/netbox/netbox/models/features.py#L424) won't change existing `AutoSyncRecord` objects.

Step 7: Edit succeeds, but the related `AutoSyncRecord` stays in the database. This is due `SyncedDataMixin`'s [`save()` method](https://github.com/netbox-community/netbox/blob/b9cac97b73c1c08213db6272de11c03711491486/netbox/netbox/models/features.py#L497) will remove related `AutoSyncRecord` objects, but the `QuerySet` filter uses fields of the object previously set to `None` in the edit form.


Discussion:
Please revise your post above to include explicit instructions. It's not clear exactly which actions are needed to reproduce the bug. Providing a concrete example would be very helpful.
@jeremystretch I've updated the instructions to reproduce the issue.
I believe this could be solved easily by altering the delete mechanism in `SyncDataMixin` to delete all records that should not be present in the database at the end of `save()` and `delete()`. I could check this locally and provide a PR if wanted …
> I believe this could be solved easily by altering the delete mechanism in SyncDataMixin to delete all records that should not be present in the database at the end of save() and delete(). I could check this locally and provide a PR if wanted …

Thanks @alehaa. I think you're on the right track here. We can probably just change `get_or_create()` to `update_or_create()` to handle both conditions. I'll submit the change for review in a minute.

Resolving patch:
diff --git a/netbox/netbox/models/features.py b/netbox/netbox/models/features.py
--- a/netbox/netbox/models/features.py
+++ b/netbox/netbox/models/features.py
@@ -489,10 +489,10 @@ def save(self, *args, **kwargs):
         # Create/delete AutoSyncRecord as needed
         content_type = ContentType.objects.get_for_model(self)
         if self.auto_sync_enabled:
-            AutoSyncRecord.objects.get_or_create(
-                datafile=self.data_file,
+            AutoSyncRecord.objects.update_or_create(
                 object_type=content_type,
-                object_id=self.pk
+                object_id=self.pk,
+                defaults={'datafile': self.data_file}
             )
         else:
             AutoSyncRecord.objects.filter(


Test patch: