Skip to content

Commit

Permalink
feat: explore viewflow
Browse files Browse the repository at this point in the history
  • Loading branch information
adamstankiewicz committed Oct 2, 2024
1 parent 3e2c805 commit e0a4a4f
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 11 deletions.
3 changes: 3 additions & 0 deletions enterprise_access/apps/api/serializers/workflows/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Base serializers for workflows
"""
Empty file.
9 changes: 9 additions & 0 deletions enterprise_access/apps/api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@
),
]

# Workflows
urlpatterns += [
path(
'workflows/default-enterprise-course-enrollment/',
FlowViewset(flows.DefaultEnterpriseCourseEnrollmentFlow).urls,
name='workflow-default-enterprise-course-enrollment',
),
]

urlpatterns += router.urls
1 change: 1 addition & 0 deletions enterprise_access/apps/api/v1/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
SubsidyAccessPolicyRedeemViewset,
SubsidyAccessPolicyViewSet
)
from .workflows import EnterpriseCourseEnrollmentViewSet
83 changes: 83 additions & 0 deletions enterprise_access/apps/api/v1/views/workflows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""
Views for workflows
"""

from rest_framework.response import Response
from rest_framework import status
from viewflow.workflow.models import Process
from viewflow.workflow.flow.views import CreateProcessView, UpdateProcessView

from enterprise_access.apps.workflows.flows import DefaultEnterpriseCourseEnrollmentFlow


class WorkflowViewSetMixin:
"""
A reusable mixin for handling Viewflow workflows with Django Rest Framework viewsets.
This mixin can be used across multiple workflow viewsets to handle common actions
like starting a process, executing a task, and checking if a process exists.
"""

process_class = None # This should be set in the viewset to the associated workflow Process class

def get_process_class(self):
"""Return the associated workflow Process class."""
if not self.process_class:
raise NotImplementedError('You must define `process_class` in your viewset.')
return self.process_class


def get_active_process(self, user=None):
"""
Retrieve an active process instance if it exists. Optionally filter by user.
This method assumes that the process model has a 'status' field and checks if there is an active process.
"""
process_class = self.get_process_class()

# Customize the query as needed. Here we're checking if the process is not done (still active).
query = process_class.objects.filter(status__in=[Process.STATUS.NEW, Process.STATUS.IN_PROGRESS])

# Optionally filter by user if provided
if user:
query = query.filter(created_by=user) # Assuming 'created_by' is a field in the process model

return query.first() # Return the first active process, if any


def start_workflow(self, request):
"""
Starts a new workflow process.
"""
# Check if any process already exists for the given criteria
process_class = self.get_process_class()
if not process_class.objects.exists():
# Create a new process
process = process_class.objects.create()

# Optionally start the workflow
request.activation.execute() # Starts the workflow

return Response({'status': 'new process started'}, status=status.HTTP_201_CREATED)
else:
return Response({'status': 'process already exists'}, status=status.HTTP_400_BAD_REQUEST)

def execute_task(self, request, process_instance):
"""
Executes the next task in the workflow.
"""
if process_instance:
request.activation.execute() # Proceed to the next task in the workflow
return Response({'status': 'task executed'}, status=status.HTTP_200_OK)
return Response({'status': 'no process found'}, status=status.HTTP_404_NOT_FOUND)

def get_current_task(self, process_instance):
"""
Retrieve the current task in the workflow for a given process.
"""
# Assuming there's a relationship between tasks and processes
task = process_instance.task_set.filter(status=Process.STATUS.IN_PROGRESS).first()
if task:
return task
return None


class EnterpriseCourseEnrollmentViewSet(FlowViewset):
71 changes: 69 additions & 2 deletions enterprise_access/apps/workflows/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
Admin for workflows app.
"""

import json

from django.contrib import admin
from django.utils.safestring import mark_safe
from viewflow.workflow.admin import ProcessAdmin

from enterprise_access.apps.workflows.models import DefaultEnterpriseCourseEnrollmentProcess
Expand All @@ -13,8 +16,72 @@ class DefaultEnterpriseCourseEnrollmentProcessAdmin(ProcessAdmin):
Admin view for DefaultEnterpriseCourseEnrollmentProcess.
Inherits from Viewflow's ProcessAdmin to display process details.
"""
list_display = ['id', 'created', 'modified', 'status']
list_display = ['pk', 'flow_class', 'created', 'status', 'finished']
list_filter = ['status']
search_fields = ['id']

fields = '__all__'
fields = [
'flow_class',
'created',
'finished',
'status',
'data_formatted',
'activated_subscription_licenses_formatted',
'default_enterprise_course_enrollments_formatted',
'redeemable_default_enterprise_course_enrollments_formatted',
'redeemed_default_enterprise_course_enrollments_formatted',
'parent_task',
'seed_content_type',
'seed_object_id',
'artifact_content_type',
'artifact_object_id',
]

readonly_fields = fields

def data_formatted(self, obj):
"""
Return JSON data as a formatted string.
"""
formatted_json = json.dumps(obj.data, indent=4)
return mark_safe(f"<pre>{formatted_json}</pre>")

data_formatted.short_description = 'Data'

def activated_subscription_licenses_formatted(self, obj):
"""
Return JSON activated subscription licenses as a formatted string.
"""
formatted_json = json.dumps(obj.activated_subscription_licenses, indent=4)
return mark_safe(f"<pre>{formatted_json}</pre>")

activated_subscription_licenses_formatted.short_description = 'Activated subscription licenses'

def default_enterprise_course_enrollments_formatted(self, obj):
"""
Return JSON default enterprise course enrollments as a formatted string.
"""
formatted_json = json.dumps(obj.default_enterprise_course_enrollments, indent=4)
return mark_safe(f"<pre>{formatted_json}</pre>")

default_enterprise_course_enrollments_formatted.short_description = 'Default enterprise course enrollments'

def redeemable_default_enterprise_course_enrollments_formatted(self, obj):
"""
Return JSON redeemable default enterprise course enrollments as a formatted string.
"""
formatted_json = json.dumps(obj.redeemable_default_enterprise_course_enrollments, indent=4)
return mark_safe(f"<pre>{formatted_json}</pre>")

redeemable_default_enterprise_course_enrollments_formatted.short_description =\
'Redeemable default enterprise course enrollments'

def redeemed_default_enterprise_course_enrollments_formatted(self, obj):
"""
Return JSON redeemed default enterprise course enrollments as a formatted string.
"""
formatted_json = json.dumps(obj.redeemed_default_enterprise_course_enrollments, indent=4)
return mark_safe(f"<pre>{formatted_json}</pre>")

redeemed_default_enterprise_course_enrollments_formatted.short_description =\
'Redeemed default enterprise course enrollments'
6 changes: 4 additions & 2 deletions enterprise_access/apps/workflows/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,12 @@ def enroll_in_redeemable_courses(self, activation):
"""
Enroll learners in redeemable courses.
"""
services.enroll_courses(
enrolled_courses = services.enroll_courses(
redeemable_enrollments=activation.process.redeemable_default_enterprise_course_enrollments
)
logger.info(
f"Enrolled learners in redeemable courses for process {activation.process.pk}: "
f"{activation.process.redeemable_default_enterprise_course_enrollments}"
f"{enrolled_courses}"
)
activation.process.redeemed_default_enterprise_course_enrollments = enrolled_courses
activation.process.save()
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.2.16 on 2024-10-02 03:39
# Generated by Django 4.2.16 on 2024-10-02 11:01

from django.db import migrations

Expand Down
Empty file.
6 changes: 4 additions & 2 deletions enterprise_access/apps/workflows/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
from viewflow.workflow.models import Process
from viewflow import jsonstore


class DefaultEnterpriseCourseEnrollmentProcess(Process):
"""
Process model to store workflow data for subscription licenses and enrollments.
Process model to store workflow data related to
redeeming/enrolling in default enterprise course
enrollments for activated subscriptions.
"""
activated_subscription_licenses = jsonstore.JSONField(null=True, blank=True)
default_enterprise_course_enrollments = jsonstore.JSONField(null=True, blank=True)
redeemable_default_enterprise_course_enrollments = jsonstore.JSONField(null=True, blank=True)
redeemed_default_enterprise_course_enrollments = jsonstore.JSONField(null=True, blank=True)

class Meta:
proxy = True
13 changes: 9 additions & 4 deletions enterprise_access/apps/workflows/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ def enroll_courses(redeemable_enrollments, *args, **kwargs):

logger.info(f"Enrolling users in redeemable courses: {redeemable_enrollments}")

return {
"status": "success",
"message": "Enrolled users in redeemable courses",
}
return [
{
"uuid": str(uuid.uuid4()),
"status": "enrolled",
"enterprise_course_enrollment_uuid": str(uuid.uuid4()),
"course_run_key": "course-v1:edX+DemoX+Demo_Course",
"content_key": "edX+DemoX",
},
]
1 change: 1 addition & 0 deletions enterprise_access/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def root(*path_fragments):
'enterprise_access.apps.subsidy_access_policy',
'enterprise_access.apps.content_assignments',
'enterprise_access.apps.enterprise_groups',
'enterprise_access.apps.workflows',
)

INSTALLED_APPS += THIRD_PARTY_APPS
Expand Down

0 comments on commit e0a4a4f

Please sign in to comment.