feat: add generate report button in admin (#31429)
* feat: add generate report button in admin
This commit is contained in:
committed by
GitHub
parent
10e06e792d
commit
045ae44184
@@ -665,6 +665,12 @@ urlpatterns += [
|
||||
'u/',
|
||||
include('openedx.features.learner_profile.urls'),
|
||||
),
|
||||
|
||||
# Survey Report
|
||||
re_path(
|
||||
fr'^survey_report/',
|
||||
include('openedx.features.survey_report.urls'),
|
||||
),
|
||||
]
|
||||
|
||||
if settings.FEATURES.get('ENABLE_TEAMS'):
|
||||
|
||||
@@ -11,14 +11,16 @@ class SurveyReportAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Admin to manage survey reports.
|
||||
"""
|
||||
change_list_template = "survey_report/change_list.html"
|
||||
|
||||
readonly_fields = (
|
||||
'courses_offered', 'learners', 'registered_learners',
|
||||
'enrollments', 'generated_certificates', 'extra_data',
|
||||
'created_at'
|
||||
'created_at', 'state',
|
||||
)
|
||||
|
||||
list_display = (
|
||||
'id', 'summary', 'created_at'
|
||||
'id', 'summary', 'created_at', 'state'
|
||||
)
|
||||
|
||||
def summary(self, obj) -> str:
|
||||
|
||||
@@ -12,12 +12,13 @@ from openedx.features.survey_report.queries import (
|
||||
get_registered_learners,
|
||||
get_unique_courses_offered
|
||||
)
|
||||
from .models import SURVEY_REPORT_ERROR, SURVEY_REPORT_GENERATED
|
||||
|
||||
MAX_WEEKS_SINCE_LAST_LOGIN: int = 4
|
||||
|
||||
|
||||
def generate_report() -> None:
|
||||
""" Generate a report with relevant data."""
|
||||
def get_report_data() -> dict:
|
||||
""" Get data from database to generate a new report."""
|
||||
courses_offered = get_unique_courses_offered()
|
||||
learners = get_recently_active_users(weeks=MAX_WEEKS_SINCE_LAST_LOGIN)
|
||||
registered_learners = get_registered_learners()
|
||||
@@ -25,13 +26,30 @@ def generate_report() -> None:
|
||||
enrollments = get_course_enrollments()
|
||||
extra_data = settings.SURVEY_REPORT_EXTRA_DATA
|
||||
|
||||
survey_report = SurveyReport(
|
||||
courses_offered=courses_offered,
|
||||
learners=learners,
|
||||
registered_learners=registered_learners,
|
||||
generated_certificates=certificates,
|
||||
enrollments=enrollments,
|
||||
extra_data=extra_data,
|
||||
)
|
||||
return {
|
||||
"courses_offered": courses_offered,
|
||||
"learners": learners,
|
||||
"registered_learners": registered_learners,
|
||||
"generated_certificates": certificates,
|
||||
"enrollments": enrollments,
|
||||
"extra_data": extra_data,
|
||||
}
|
||||
|
||||
|
||||
def generate_report() -> None:
|
||||
""" Generate a report with relevant data."""
|
||||
data = {}
|
||||
survey_report = SurveyReport(**data)
|
||||
survey_report.save()
|
||||
|
||||
try:
|
||||
data = get_report_data()
|
||||
data["state"] = SURVEY_REPORT_GENERATED
|
||||
update_report(survey_report.id, data)
|
||||
except (Exception, ) as update_report_error:
|
||||
update_report(survey_report.id, {"state": SURVEY_REPORT_ERROR})
|
||||
raise Exception(update_report_error) from update_report_error
|
||||
|
||||
|
||||
def update_report(survey_report_id: int, data: dict) -> None:
|
||||
SurveyReport.objects.filter(id=survey_report_id).update(**data)
|
||||
|
||||
@@ -28,4 +28,6 @@ class Command(BaseCommand):
|
||||
except Exception as error:
|
||||
raise CommandError(f'An error has occurred while survey report was generating. {error}') from error
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('Survey report has been generated successfully.'))
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS('Survey report has been generated successfully.')
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ from io import StringIO
|
||||
from unittest import mock
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test import TestCase
|
||||
|
||||
from openedx.features.survey_report.models import SurveyReport
|
||||
|
||||
@@ -15,30 +15,29 @@ class GenerateReportTest(TestCase):
|
||||
"""
|
||||
Test for generate_report command.
|
||||
"""
|
||||
@override_settings(SURVEY_REPORT_EXTRA_DATA={'extra_data': 'extra_data'})
|
||||
@mock.patch('openedx.features.survey_report.queries.get_course_enrollments')
|
||||
@mock.patch('openedx.features.survey_report.queries.get_generated_certificates')
|
||||
@mock.patch('openedx.features.survey_report.queries.get_registered_learners')
|
||||
@mock.patch('openedx.features.survey_report.queries.get_recently_active_users')
|
||||
@mock.patch('openedx.features.survey_report.queries.get_unique_courses_offered')
|
||||
def test_generate_report(self, mock_get_unique_courses_offered, mock_get_recently_active_users,
|
||||
mock_get_registered_learners, mock_get_generated_certificates,
|
||||
mock_get_course_enrollments):
|
||||
|
||||
@mock.patch('openedx.features.survey_report.api.get_report_data')
|
||||
def test_generate_report(self, mock_get_report_data):
|
||||
"""
|
||||
Test that generate_report command creates a survey report.
|
||||
"""
|
||||
mock_get_unique_courses_offered.return_value = 1
|
||||
mock_get_recently_active_users.return_value = 2
|
||||
mock_get_registered_learners.return_value = 3
|
||||
mock_get_generated_certificates.return_value = 4
|
||||
mock_get_course_enrollments.return_value = 5
|
||||
report_test_data = {
|
||||
'courses_offered': 1,
|
||||
'learners': 2,
|
||||
'registered_learners': 3,
|
||||
'generated_certificates': 4,
|
||||
'enrollments': 5,
|
||||
'extra_data': {'extra': 'data'},
|
||||
}
|
||||
mock_get_report_data.return_value = report_test_data
|
||||
out = StringIO()
|
||||
call_command('generate_report', stdout=out)
|
||||
|
||||
survey_report = SurveyReport.objects.last()
|
||||
assert survey_report.courses_offered == 1
|
||||
assert survey_report.learners == 2
|
||||
assert survey_report.registered_learners == 3
|
||||
assert survey_report.generated_certificates == 4
|
||||
assert survey_report.enrollments == 5
|
||||
assert survey_report.extra_data == {'extra_data': 'extra_data'}
|
||||
|
||||
assert survey_report.courses_offered == report_test_data['courses_offered']
|
||||
assert survey_report.learners == report_test_data['learners']
|
||||
assert survey_report.registered_learners == report_test_data['registered_learners']
|
||||
assert survey_report.generated_certificates == report_test_data['generated_certificates']
|
||||
assert survey_report.enrollments == report_test_data['enrollments']
|
||||
assert survey_report.extra_data == report_test_data['extra_data']
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 3.2.16 on 2022-12-11 15:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('survey_report', '0002_auto_20221130_1533'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='surveyreport',
|
||||
name='state',
|
||||
field=models.CharField(choices=[('processing', 'Processing'), ('generated', 'Generated'), ('error', 'Error')], default='processing', help_text='State of the async generating process.', max_length=24),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyreport',
|
||||
name='courses_offered',
|
||||
field=models.BigIntegerField(default=0, help_text='Total number of active unique courses.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyreport',
|
||||
name='enrollments',
|
||||
field=models.BigIntegerField(default=0, help_text='Total number of active enrollments in the platform.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyreport',
|
||||
name='generated_certificates',
|
||||
field=models.BigIntegerField(default=0, help_text='Total number of generated certificates.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyreport',
|
||||
name='learners',
|
||||
field=models.BigIntegerField(default=0, help_text='Total number of recently active users with login in some weeks.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='surveyreport',
|
||||
name='registered_learners',
|
||||
field=models.BigIntegerField(default=0, help_text='Total number of users ever registered in the platform.'),
|
||||
),
|
||||
]
|
||||
@@ -5,6 +5,16 @@ Survey Report models.
|
||||
from django.db import models
|
||||
from jsonfield import JSONField
|
||||
|
||||
SURVEY_REPORT_PROCESSING = 'processing'
|
||||
SURVEY_REPORT_GENERATED = 'generated'
|
||||
SURVEY_REPORT_ERROR = 'error'
|
||||
|
||||
SURVEY_REPORT_STATES = [
|
||||
(SURVEY_REPORT_PROCESSING, 'Processing'),
|
||||
(SURVEY_REPORT_GENERATED, 'Generated'),
|
||||
(SURVEY_REPORT_ERROR, 'Error'),
|
||||
]
|
||||
|
||||
|
||||
class SurveyReport(models.Model):
|
||||
"""
|
||||
@@ -19,18 +29,31 @@ class SurveyReport(models.Model):
|
||||
- enrollments: Total number of active enrollments in the platform.
|
||||
- generated_certificates: Total number of generated certificates.
|
||||
- extra_data: Extra information that will be saved in the report, E.g: site_name, openedx-release.
|
||||
- state: State of the async generating process.
|
||||
"""
|
||||
courses_offered = models.BigIntegerField(help_text="Total number of active unique courses.")
|
||||
learners = models.BigIntegerField(help_text="Total number of recently active users with login in some weeks.")
|
||||
registered_learners = models.BigIntegerField(help_text="Total number of users ever registered in the platform.")
|
||||
enrollments = models.BigIntegerField(help_text="Total number of active enrollments in the platform.")
|
||||
generated_certificates = models.BigIntegerField(help_text="Total number of generated certificates.")
|
||||
courses_offered = models.BigIntegerField(default=0, help_text="Total number of active unique courses.")
|
||||
learners = models.BigIntegerField(
|
||||
default=0,
|
||||
help_text="Total number of recently active users with login in some weeks."
|
||||
)
|
||||
registered_learners = models.BigIntegerField(
|
||||
default=0,
|
||||
help_text="Total number of users ever registered in the platform."
|
||||
)
|
||||
enrollments = models.BigIntegerField(default=0, help_text="Total number of active enrollments in the platform.")
|
||||
generated_certificates = models.BigIntegerField(default=0, help_text="Total number of generated certificates.")
|
||||
extra_data = JSONField(
|
||||
blank=True,
|
||||
default=dict,
|
||||
help_text="Extra information that will be saved in the report, E.g: site_name, openedx-release.",
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now=True)
|
||||
state = models.CharField(
|
||||
max_length=24,
|
||||
choices=SURVEY_REPORT_STATES,
|
||||
default=SURVEY_REPORT_PROCESSING,
|
||||
help_text="State of the async generating process."
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
|
||||
27
openedx/features/survey_report/tasks.py
Normal file
27
openedx/features/survey_report/tasks.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Tasks for Survey Report.
|
||||
"""
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
from celery import shared_task
|
||||
from .api import generate_report
|
||||
|
||||
log = logging.getLogger('edx.celery.task')
|
||||
|
||||
|
||||
@shared_task(name='openedx.features.survey_report.tasks.generate_survey_report')
|
||||
def generate_survey_report():
|
||||
"""
|
||||
Tasks to generate a new survey report with non-sensitive data.
|
||||
"""
|
||||
log.info(
|
||||
'Started - generate survey report'
|
||||
)
|
||||
|
||||
try:
|
||||
generate_report()
|
||||
log.info('Done - generate survey report')
|
||||
except (Exception, ): # pylint: disable=broad-except
|
||||
log.error('Error - generate survey report')
|
||||
@@ -0,0 +1,12 @@
|
||||
{% extends 'admin/change_list.html' %}
|
||||
|
||||
{% block object-tools %}
|
||||
<ul class="object-tools">
|
||||
<li>
|
||||
<form method="POST" action="{% url 'openedx.generate_survey_report' %}" class="inline">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Generate Report" class="default" name="_generatereport">
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
14
openedx/features/survey_report/urls.py
Normal file
14
openedx/features/survey_report/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
Defines URLs for Survey Report.
|
||||
"""
|
||||
|
||||
from django.urls import path
|
||||
from .views import SurveyReportView
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
'generate_report',
|
||||
SurveyReportView.as_view(),
|
||||
name='openedx.generate_survey_report',
|
||||
),
|
||||
]
|
||||
27
openedx/features/survey_report/views.py
Normal file
27
openedx/features/survey_report/views.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
Views to manage the Survey Reports.
|
||||
"""
|
||||
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.generic import View
|
||||
from .tasks import generate_survey_report
|
||||
|
||||
|
||||
class SurveyReportView(View):
|
||||
"""
|
||||
View for Survey Reports.
|
||||
"""
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def post(self, _request):
|
||||
"""
|
||||
Generate a new survey report using the generate_report method in api.py
|
||||
Arguments:
|
||||
_request: HTTP request
|
||||
"""
|
||||
generate_survey_report.delay()
|
||||
return redirect("admin:survey_report_surveyreport_changelist")
|
||||
Reference in New Issue
Block a user