Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Commit

Permalink
author decisions: add decision table and populate it
Browse files Browse the repository at this point in the history
implemented task in airflow that allows for this and added unit tests for all the changes

* ref: cern-sis/issues-inspire/issues/522
  • Loading branch information
DonHaul committed Aug 19, 2024
1 parent be93e90 commit c288e17
Show file tree
Hide file tree
Showing 19 changed files with 316 additions and 59 deletions.
79 changes: 50 additions & 29 deletions backoffice/backoffice/workflows/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django_json_widget.widgets import JSONEditorWidget

from backoffice.management.permissions import IsAdminOrCuratorUser
from backoffice.workflows.models import Workflow
from backoffice.workflows.models import Decision, Workflow


class WorkflowsAdminSite(admin.AdminSite):
Expand All @@ -30,8 +30,41 @@ def has_permission(self, request):
)


class ModelAdmin(admin.ModelAdmin):
def has_view_permission(self, request, obj=None):
"""
Returns True if the user has permission to view the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)

def has_change_permission(self, request, obj=None):
"""
Returns True if the user has permission to change the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)

def has_delete_permission(self, request, obj=None):
"""
Returns True if the user has permission to delete the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)

formfield_overrides = {
JSONField: {"widget": JSONEditorWidget},
}


@admin.register(Workflow)
class WorkflowAdmin(admin.ModelAdmin):
class WorkflowAdmin(ModelAdmin):
"""
Admin class for Workflow model. Define get, update and delete permissions.
"""
Expand All @@ -56,33 +89,21 @@ class WorkflowAdmin(admin.ModelAdmin):
"_updated_at",
]

formfield_overrides = {
JSONField: {"widget": JSONEditorWidget},
}

def has_view_permission(self, request, obj=None):
"""
Returns True if the user has permission to view the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)
@admin.register(Decision)
class DecisionAdmin(admin.ModelAdmin):
"""
Admin class for Decision model. Define get, update and delete permissions.
"""

def has_change_permission(self, request, obj=None):
"""
Returns True if the user has permission to change the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)
ordering = ("-_updated_at",)
search_fields = ["id", "data"]
list_display = ("id", "action_value", "user", "workflow_id")
list_filter = [
"action",
"user",
]

def has_delete_permission(self, request, obj=None):
"""
Returns True if the user has permission to delete the Workflow model.
"""
permission_check = IsAdminOrCuratorUser()
return request.user.is_superuser or permission_check.has_permission(
request, self
)
@admin.display(description="action")
def action_value(self, obj):
return obj.action
8 changes: 7 additions & 1 deletion backoffice/backoffice/workflows/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from backoffice.workflows.constants import ResolutionDags, StatusChoices, WorkflowType
from backoffice.workflows.documents import WorkflowDocument
from backoffice.workflows.models import Workflow, WorkflowTicket
from backoffice.workflows.models import Decision, Workflow, WorkflowTicket


class WorkflowTicketSerializer(serializers.ModelSerializer):
Expand All @@ -31,6 +31,12 @@ class Meta:
fields = "__all__"


class DecisionSerializer(serializers.ModelSerializer):
class Meta:
model = Decision
fields = "__all__"


class WorkflowDocumentSerializer(DocumentSerializer):
class Meta:
document = WorkflowDocument
Expand Down
37 changes: 35 additions & 2 deletions backoffice/backoffice/workflows/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from backoffice.workflows import airflow_utils
from backoffice.workflows.api.serializers import (
AuthorResolutionSerializer,
DecisionSerializer,
WorkflowAuthorSerializer,
WorkflowDocumentSerializer,
WorkflowSerializer,
Expand All @@ -37,7 +38,7 @@
WorkflowType,
)
from backoffice.workflows.documents import WorkflowDocument
from backoffice.workflows.models import Workflow, WorkflowTicket
from backoffice.workflows.models import Decision, Workflow, WorkflowTicket

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -100,6 +101,38 @@ def create(self, request, *args, **kwargs):
)


class DecisionViewSet(viewsets.ModelViewSet):
queryset = Decision.objects.all()
serializer_class = DecisionSerializer

def get_queryset(self):
status = self.request.query_params.get("status")
if status:
return self.queryset.filter(status__status=status)
return self.queryset

def create(self, request, *args, **kwargs):
workflow_id = request.data.get("workflow_id")
action = request.data.get("action")

if not all([workflow_id, action]):
return Response(
{"error": "workflow_id and action are required."},
status=status.HTTP_400_BAD_REQUEST,
)

try:
decision = Decision.objects.create(
workflow_id=workflow_id, user=request.user, action=action
)
serializer = DecisionSerializer(decision)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Exception as e:
return Response(
{"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)


class AuthorWorkflowViewSet(viewsets.ViewSet):
serializer_class = WorkflowAuthorSerializer

Expand Down Expand Up @@ -160,7 +193,7 @@ def resolve(self, request, pk=None):
logger.info("Resolving data: %s", request.data)
serializer = AuthorResolutionSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
extra_data = {"create_ticket": serializer.validated_data["create_ticket"]}
extra_data = serializer.validated_data
logger.info(
"Trigger Airflow DAG: %s for %s",
ResolutionDags[serializer.validated_data["value"]],
Expand Down
4 changes: 4 additions & 0 deletions backoffice/backoffice/workflows/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class WorkflowType(models.TextChoices):
class ResolutionDags(models.TextChoices):
accept = "accept", "author_create_approved_dag"
reject = "reject", "author_create_rejected_dag"
accept_curate = "accept_curate", "author_create_approved_dag"


DECISION_CHOICES = ResolutionDags.choices + []


class AuthorCreateDags(models.TextChoices):
Expand Down
58 changes: 58 additions & 0 deletions backoffice/backoffice/workflows/migrations/0009_decision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.6 on 2024-08-15 12:25

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


class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("workflows", "0008_alter_workflow_status_alter_workflow_workflow_type"),
]

operations = [
migrations.CreateModel(
name="Decision",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"action",
models.CharField(
choices=[
("accept", "author_create_approved_dag"),
("reject", "author_create_rejected_dag"),
("accept_curate", "author_create_approved_dag"),
],
max_length=30,
),
),
("_created_at", models.DateTimeField(auto_now_add=True)),
("_updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
db_column="email",
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
to_field="email",
),
),
(
"workflow",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="workflows.workflow",
),
),
],
),
]
13 changes: 13 additions & 0 deletions backoffice/backoffice/workflows/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

from django.db import models

from backoffice.users.models import User
from backoffice.workflows.constants import (
DECISION_CHOICES,
DEFAULT_STATUS_CHOICE,
DEFAULT_TICKET_TYPE,
DEFAULT_WORKFLOW_TYPE,
Expand Down Expand Up @@ -43,3 +45,14 @@ class WorkflowTicket(models.Model):
ticket_type = models.CharField(
max_length=30, choices=TICKET_TYPES, default=DEFAULT_TICKET_TYPE
)


class Decision(models.Model):
user = models.ForeignKey(
User, to_field="email", db_column="email", on_delete=models.CASCADE
)
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE)
action = models.CharField(max_length=30, choices=DECISION_CHOICES)

_created_at = models.DateTimeField(auto_now_add=True)
_updated_at = models.DateTimeField(auto_now=True)
25 changes: 25 additions & 0 deletions backoffice/backoffice/workflows/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.test import TransactionTestCase
from django.urls import reverse
from django_opensearch_dsl.registries import registry
from rest_framework import status
from rest_framework.test import APIClient

from backoffice.workflows import airflow_utils
Expand All @@ -23,6 +24,7 @@

User = get_user_model()
Workflow = apps.get_model(app_label="workflows", model_name="Workflow")
Decision = apps.get_model(app_label="workflows", model_name="Decision")


class BaseTransactionTestCase(TransactionTestCase):
Expand Down Expand Up @@ -487,3 +489,26 @@ def test_ordering(self):
if previous_date is not None:
assert cur_date < previous_date
previous_date = cur_date


class TestDecisionsViewSet(BaseTransactionTestCase):
endpoint = "/api/decisions"
reset_sequences = True
fixtures = ["backoffice/fixtures/groups.json"]

def setUp(self):
super().setUp()
self.workflow = Workflow.objects.create(
data={}, status="running", core=True, is_update=False
)

def test_create_decision(self):
self.api_client.force_authenticate(user=self.curator)
data = {
"workflow_id": self.workflow.id,
"action": "accept",
}

url = reverse("api:decisions-list")
response = self.api_client.post(url, format="json", data=data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
2 changes: 2 additions & 0 deletions backoffice/config/api_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from backoffice.users.api.views import UserViewSet
from backoffice.workflows.api.views import (
AuthorWorkflowViewSet,
DecisionViewSet,
WorkflowTicketViewSet,
WorkflowViewSet,
)
Expand All @@ -20,5 +21,6 @@
)
router.register("workflows", WorkflowViewSet, basename="workflows")
(router.register("workflow-ticket", WorkflowTicketViewSet, basename="workflow-ticket"),)
router.register("decisions", DecisionViewSet, basename="decisions")
app_name = "api"
urlpatterns = router.urls
3 changes: 2 additions & 1 deletion workflows/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ WORKDIR /opt/airflow
COPY --chown=airflow:root dags ./dags/
COPY --chown=airflow:root plugins ./plugins/
COPY --chown=airflow:root requirements.txt ./requirements.txt
COPY --chown=airflow:root requirements-test.txt ./requirements-test.txt

RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements.txt -r requirements-test.txt
Empty file added workflows/__init__.py
Empty file.
Loading

0 comments on commit c288e17

Please sign in to comment.