oncall-engine/engine/common/database.py
Vadim Stepanov 3c00345f54
Fix Grafana teams sync (#1652)
# What this PR does
Sometimes plugin sync fails with the following exception:
```
Cannot delete or update a parent row: a foreign key constraint fails (`schedules_oncallschedule`, CONSTRAINT `alerts_oncallschedul_team_id_4e633f4b_fk_user_mana` FOREIGN KEY (`team_id`) REFERENCES `user_management_team` (`id`))'
```

How to reproduce:
1. Create a new Grafana team
2. Create two schedules with different types (e.g. ICal and Web) and
assign both schedules to the new team
3. Delete the team in Grafana
4. Trigger plugin sync, the sync will fail with the exception above

This happens because the `OnCallSchedule` Django model is a polymorphic
model and there's a [known
bug](https://github.com/django-polymorphic/django-polymorphic/issues/229)
in `django-polymorphic` with deleting related objects when using
`SET_NULL` and `CASCADE`. This PR adds non-polymorphic versions of
`SET_NULL` and `CASCADE` to use in schedule FKs as per this
[comment](https://github.com/django-polymorphic/django-polymorphic/issues/229#issuecomment-398434412).

This also applies to two other schedule FKs: `organization` and
`user_group`, which are not working properly as well.

## Checklist

- [x] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not
required)
2023-03-28 18:26:24 +00:00

38 lines
1.7 KiB
Python

import random
from django.conf import settings
from django.db import models
def get_random_readonly_database_key_if_present_otherwise_default() -> str:
"""
This function returns a string, representing a key in the `DATABASES` django settings.
If `settings.READONLY_DATABASES` is set, and non-empty, it randomly chooses one of the read-only databases,
otherwise it falls back to "default".
This is primarily intended to be used for django's `QuerySet.using()` function
"""
using_db = "default"
if hasattr(settings, "READONLY_DATABASES") and len(settings.READONLY_DATABASES) > 0:
using_db = random.choice(list(settings.READONLY_DATABASES.keys()))
return using_db
def NON_POLYMORPHIC_SET_NULL(collector, field, sub_objs, using):
"""
django-polymorphic has a bug where it doesn't properly handle the `on_delete` argument:
https://github.com/django-polymorphic/django-polymorphic/issues/229#issuecomment-398434412.
This is a workaround that uses the same code as the original `SET_NULL` function, but with the
`non_polymorphic()` function applied to the `sub_objs` queryset.
"""
return models.SET_NULL(collector, field, sub_objs.non_polymorphic(), using)
def NON_POLYMORPHIC_CASCADE(collector, field, sub_objs, using):
"""
django-polymorphic has a bug where it doesn't properly handle the `on_delete` argument:
https://github.com/django-polymorphic/django-polymorphic/issues/229#issuecomment-398434412.
This is a workaround that uses the same code as the original `CASCADE` function, but with the
`non_polymorphic()` function applied to the `sub_objs` queryset.
"""
return models.CASCADE(collector, field, sub_objs.non_polymorphic(), using)