Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple groups and users #59

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions django_datawatch/admin.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
from django.contrib import admin

from django_datawatch.models import Result, CheckExecution, ResultStatusHistory
from django_datawatch.models import Result, CheckExecution, ResultAssignedGroup, ResultAssignedUser, ResultStatusHistory


class ResultAssignedGroupInline(admin.TabularInline):
model = ResultAssignedGroup
extra = 0

class ResultAssignedUserInline(admin.TabularInline):
model = ResultAssignedUser
extra = 0

@admin.register(Result)
class CheckAdmin(admin.ModelAdmin):
list_display = ('slug', 'identifier', 'status')
readonly_fields = ('created', 'modified')
search_fields = ('slug', 'identifier', 'payload_description')
list_filter = ('status', 'slug', 'assigned_to_group')

list_filter = ('status', 'slug', 'assigned_groups')
inlines = [ResultAssignedGroupInline, ResultAssignedUserInline]

@admin.register(CheckExecution)
class CheckExecutionAdmin(admin.ModelAdmin):
Expand Down
33 changes: 20 additions & 13 deletions django_datawatch/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from contextlib import contextmanager

from django import forms
from django.db import transaction
from django.utils import timezone

from django_datawatch.models import Result, CheckExecution, ResultStatusHistory
Expand All @@ -24,7 +25,6 @@ def track_status_history(slug, identifier, new_status):
class DatawatchCheckSkipException(Exception):
pass


class BaseCheckForm(forms.Form):
def save(self, instance):
instance.config = self.cleaned_data
Expand Down Expand Up @@ -60,8 +60,8 @@ class BaseCheck(object):
Any check should inherits from `BaseCheck` and should implements `.generate(self)`
and `.check(self, payload)` methods.

Optionally, you can implements `.get_assigned_user(self, payload)` (resp. `.get_assigned_group(self, payload)`)
to define to which user (resp. group) the system had to assign the check result.
Optionally, you can implements `.get_assigned_users(self, payload)` (resp. `.get_assigned_groups(self, payload)`)
to define to which user(s) (resp. group(s)) the system had to assign the check result.
"""

config_form = None
Expand Down Expand Up @@ -131,17 +131,24 @@ def get_form_class(self):

def save(self, payload, status, data=None, unacknowledge=False):
# build default data
defaults = dict(status=status, data=data, assigned_to_user=self.get_assigned_user(payload, status),
assigned_to_group=self.get_assigned_group(payload, status),
payload_description=self.get_payload_description(payload))
defaults = dict(status=status, data=data, payload_description=self.get_payload_description(payload))

if unacknowledge:
defaults.update(dict(acknowledged_by=None, acknowledged_at=None, acknowledged_until=None))

with track_status_history(self.slug, self.get_identifier(payload), status):
# save the check
dataset, created = Result.objects.update_or_create(
slug=self.slug, identifier=self.get_identifier(payload),
defaults=defaults)
with transaction.atomic():
with track_status_history(self.slug, self.get_identifier(payload), status):
# save the check
dataset, created = Result.objects.update_or_create(
slug=self.slug, identifier=self.get_identifier(payload),
defaults=defaults)

# set assigned users and groups
if groups := self.get_assigned_groups(payload, status):
dataset.assigned_groups.set(groups)
lociii marked this conversation as resolved.
Show resolved Hide resolved

if users := self.get_assigned_users(payload, status):
dataset.assigned_users.set(users)
lociii marked this conversation as resolved.
Show resolved Hide resolved

return dataset

Expand Down Expand Up @@ -179,10 +186,10 @@ def get_payload_description(self, payload):
def format_result_data(self, result):
return ''

def get_assigned_user(self, payload, result):
def get_assigned_users(self, payload, result):
Kandeel4411 marked this conversation as resolved.
Show resolved Hide resolved
return None

def get_assigned_group(self, payload, result):
def get_assigned_groups(self, payload, result):
Kandeel4411 marked this conversation as resolved.
Show resolved Hide resolved
return None

def get_context_data(self, result):
Expand Down
Binary file modified django_datawatch/locale/de/LC_MESSAGES/django.mo
Binary file not shown.
38 changes: 31 additions & 7 deletions django_datawatch/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 10:02+0000\n"
"POT-Creation-Date: 2024-01-16 01:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -72,6 +72,12 @@ msgstr "Ausgeblendet bis"
msgid "Acknowledge reason"
msgstr "Ausblendungsgrund"

msgid "Assigned users"
msgstr "Zugewiesene Benutzer"

msgid "Assigned groups"
msgstr "Zugewiesene Gruppen"

msgid "Result"
msgstr "Ergebnis"

Expand All @@ -84,6 +90,27 @@ msgstr "Zu Status"
msgid "Result status history"
msgstr "Ergebnis-Status-Historie"

msgid "Group"
msgstr "Gruppe"

msgid "Result assigned group"
msgstr "Zugewiesene Gruppe"

msgid "Result assigned groups"
msgstr "Zugewiesene Gruppen"

msgid "Group must be unique across the result"
msgstr "Gruppen müssen je Ergebnis eindeutig sein"

msgid "Result assigned user"
msgstr "Zugewiesener Benutzer"

msgid "Result assigned users"
msgstr "Zugewiesene Benutzer"

msgid "User must be unique across the result"
msgstr "Benutzer müssen je Ergebnis eindeutig sein"

msgid "Check module slug"
msgstr "Modul-Bezeichner"

Expand Down Expand Up @@ -136,6 +163,9 @@ msgstr "Nicht zugewiesen"
msgid "Config form class"
msgstr "Formular-Klasse für Konfiguration"

msgid "Config"
msgstr "Konfiguration"

msgid "Run every"
msgstr "Periodisches Ausführen"

Expand All @@ -152,12 +182,6 @@ msgstr "Maximale Ausblendezeit"
msgid "%(days)s day(s)"
msgstr "%(days)s Tag(e)"

msgid "Assigned group"
msgstr "Zugewiesene Gruppe"

msgid "Assigned user"
msgstr "Zugewiesener Benutzer"

#, python-format
msgid "Acknowledged until %(date)s by %(user)s"
msgstr "Ausgeblendet bis %(date)s durch %(user)s"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Generated by Django 4.2.9 on 2024-01-16 00:05

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('django_datawatch', '0003_resultstatushistory'),
]

operations = [
migrations.CreateModel(
name='ResultAssignedUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_datawatch.result', verbose_name='Result')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'Result assigned user',
'verbose_name_plural': 'Result assigned users',
},
),
migrations.CreateModel(
name='ResultAssignedGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.group', verbose_name='Group')),
('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_datawatch.result', verbose_name='Result')),
],
options={
'verbose_name': 'Result assigned group',
'verbose_name_plural': 'Result assigned groups',
},
),
migrations.AddField(
model_name='result',
name='assigned_groups',
field=models.ManyToManyField(blank=True, related_name='assigned_groups', through='django_datawatch.ResultAssignedGroup', to='auth.group', verbose_name='Assigned groups'),
),
migrations.AddField(
model_name='result',
name='assigned_users',
field=models.ManyToManyField(blank=True, related_name='assigned_results', through='django_datawatch.ResultAssignedUser', to=settings.AUTH_USER_MODEL, verbose_name='Assigned users'),
),
migrations.AddConstraint(
model_name='resultassigneduser',
constraint=models.UniqueConstraint(fields=('result', 'user'), name='unique_result_assigned_user'),
),
migrations.AddConstraint(
model_name='resultassignedgroup',
constraint=models.UniqueConstraint(fields=('result', 'group'), name='unique_result_assigned_group'),
),
]
47 changes: 47 additions & 0 deletions django_datawatch/migrations/0005_migrate_new_assigned_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 4.2.9 on 2024-01-16 00:06

from django.db import migrations

from django.core.paginator import Paginator

def migrate_new_assigned_fields(apps, schema_editor):
Result = apps.get_model('django_datawatch', 'Result')
ResultAssignedGroup = apps.get_model('django_datawatch', 'ResultAssignedGroup')
ResultAssignedUser = apps.get_model('django_datawatch', 'ResultAssignedUser')

# Paginate the queryset to avoid memory issues
paginator = Paginator(
Result.objects.order_by('pk').only('assigned_to_user', 'assigned_to_group'),
5000,
)

for page_number in paginator.page_range:
page = paginator.page(page_number)
group_instances = []
user_instances = []

for result in page.object_list:
if result.assigned_to_group:
group_instances.append(ResultAssignedGroup(
result_id=result.pk,
group_id=result.assigned_to_group.pk,
))
if result.assigned_to_user:
user_instances.append(ResultAssignedUser(
result_id=result.pk,
user_id=result.assigned_to_user.pk,
))

ResultAssignedGroup.objects.bulk_create(group_instances)
ResultAssignedUser.objects.bulk_create(user_instances)


class Migration(migrations.Migration):

dependencies = [
('django_datawatch', '0004_resultassigneduser_resultassignedgroup_and_more'),
]

operations = [
migrations.RunPython(migrate_new_assigned_fields),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 4.2.9 on 2024-01-16 00:07

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('django_datawatch', '0005_migrate_new_assigned_fields'),
]

operations = [
migrations.RemoveField(
model_name='result',
name='assigned_to_group',
),
migrations.RemoveField(
model_name='result',
name='assigned_to_user',
),
]
43 changes: 40 additions & 3 deletions django_datawatch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.utils import timezone
from django.conf import settings
from django.db import models
from django.core.exceptions import ValidationError
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django_extensions.db.fields.json import JSONField
Expand Down Expand Up @@ -42,9 +43,9 @@ class Result(TimeStampedModel):
acknowledged_until = models.DateTimeField(null=True, blank=True, verbose_name=_('Acknowledged until'))
acknowledged_reason = models.TextField(blank=True, verbose_name=_('Acknowledge reason'))

assigned_to_user = models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, blank=True,
related_name='assigned_to_user', on_delete=models.SET_NULL)
assigned_to_group = models.ForeignKey(to='auth.Group', null=True, blank=True, on_delete=models.SET_NULL)
assigned_users = models.ManyToManyField(to=settings.AUTH_USER_MODEL, through='ResultAssignedUser', related_name='assigned_results', blank=True, verbose_name=_('Assigned users'))
assigned_groups = models.ManyToManyField(to='auth.Group', through='ResultAssignedGroup', related_name='assigned_groups', blank=True,
verbose_name=_('Assigned groups'))

objects = ResultQuerySet.as_manager()

Expand Down Expand Up @@ -116,6 +117,42 @@ class Meta:
verbose_name_plural = _('Result status history')


class ResultAssignedGroup(models.Model):
result = models.ForeignKey(Result, on_delete=models.CASCADE, verbose_name=_('Result'))
group = models.ForeignKey(to='auth.Group', verbose_name=_('Group'), on_delete=models.CASCADE)

class Meta:
verbose_name = _('Result assigned group')
verbose_name_plural = _('Result assigned groups')
constraints = [models.UniqueConstraint(fields=['result', 'group'], name='unique_result_assigned_group')]

def validate_unique(self, exclude=None):
if (
ResultAssignedGroup.objects.filter(result_id=self.result_id, group_id=self.group_id)
.exclude(pk=self.pk)
.exists()
):
raise ValidationError({"group": _("Group must be unique across the result")})
super().validate_unique(exclude)

class ResultAssignedUser(models.Model):
result = models.ForeignKey(Result, on_delete=models.CASCADE, verbose_name=_('Result'))
user = models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name=_('User'), on_delete=models.CASCADE)

class Meta:
verbose_name = _('Result assigned user')
verbose_name_plural = _('Result assigned users')
constraints = [models.UniqueConstraint(fields=['result', 'user'], name='unique_result_assigned_user')]

def validate_unique(self, exclude=None):
if (
ResultAssignedUser.objects.filter(result_id=self.result_id, user_id=self.user_id)
.exclude(pk=self.pk)
.exists()
):
raise ValidationError({"user": _("User must be unique across the result")})
super().validate_unique(exclude)

class CheckExecution(models.Model):
slug = models.TextField(verbose_name=_('Check module slug'), unique=True)
last_run = models.DateTimeField(verbose_name=_('Last run'))
Expand Down
8 changes: 6 additions & 2 deletions django_datawatch/querysets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@

class ResultQuerySet(models.QuerySet):
def for_user(self, user):
return self.filter(Q(assigned_to_group__isnull=True) | Q(assigned_to_group__in=user.groups.all()),
Q(assigned_to_user__isnull=True) | Q(assigned_to_user=user))
user_groups = user.groups.all()
return self.filter(
lociii marked this conversation as resolved.
Show resolved Hide resolved
Q(assigned_users=user)
| Q(assigned_groups__in=user_groups)
| Q(assigned_users__isnull=True, assigned_groups__isnull=True)
).distinct()

def failed(self):
return self.exclude(status__in=(self.model.STATUS.unknown, self.model.STATUS.ok))
Expand Down
Loading