Merge pull request #16615 from edx/cale/schedules-admin-improvements

Cale/schedules admin improvements
This commit is contained in:
Calen Pennington
2017-11-21 22:06:37 -05:00
committed by GitHub
6 changed files with 163 additions and 7 deletions

View File

@@ -1760,7 +1760,7 @@ class CourseEnrollment(models.Model):
return None
try:
if not self.schedule:
if not self.schedule or not self.schedule.active:
return None
log.debug(

View File

@@ -12,7 +12,7 @@ from collections import defaultdict
from contextlib import contextmanager
from uuid import uuid4
from factory import Factory, Sequence, lazy_attribute_sequence, lazy_attribute
from factory import Factory, Sequence, lazy_attribute_sequence, lazy_attribute, Faker
from factory.errors import CyclicDefinitionError
from mock import patch
from nose.tools import assert_less_equal, assert_greater_equal

View File

@@ -268,6 +268,22 @@ JWT_AUTH.update({
'JWT_AUDIENCE': 'lms-key',
})
############## Settings for ACE ####################################
ACE_ENABLED_CHANNELS = [
'file_email'
]
ACE_ENABLED_POLICIES = [
'bulk_email_optout'
]
ACE_CHANNEL_SAILTHRU_DEBUG = True
ACE_CHANNEL_SAILTHRU_TEMPLATE_NAME = 'Automated Communication Engine Email'
ACE_CHANNEL_SAILTHRU_API_KEY = None
ACE_CHANNEL_SAILTHRU_API_SECRET = None
ACE_ROUTING_KEY = LOW_PRIORITY_QUEUE
#####################################################################
# See if the developer has any local overrides.
if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')):

View File

@@ -1,5 +1,9 @@
import functools
from django.contrib import admin
from django import forms
from django.db.models import F
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from . import models
@@ -9,22 +13,94 @@ class ScheduleExperienceAdminInline(admin.StackedInline):
model = models.ScheduleExperience
def _set_experience(db_name, human_name, modeladmin, request, queryset):
"""
A django action which will set all selected schedules to the supplied experience.
The intended usage is with functools.partial to generate the action for each experience type
dynamically.
Arguments:
db_name: the database name of the experience being selected
human_name: the human name of the experience being selected
modeladmin: The ModelAdmin subclass, passed by django as part of the standard Action interface
request: The current request, passed by django as part of the standard Action interface
queryset: The queryset selecting schedules, passed by django as part of the standard Action interface
"""
rows_updated = models.ScheduleExperience.objects.filter(
schedule__in=list(queryset)
).update(
experience_type=db_name
)
modeladmin.message_user(request, "{} schedule(s) were changed to use the {} experience".format(rows_updated, human_name))
# Generate a list of all "set_experience_to_X" actions
experience_actions = []
for (db_name, human_name) in models.ScheduleExperience.EXPERIENCES:
partial = functools.partial(_set_experience, db_name, human_name)
partial.short_description = "Convert the selected schedules to the {} experience".format(human_name)
partial.__name__ = "set_experience_to_{}".format(db_name)
experience_actions.append(partial)
class KnownErrorCases(admin.SimpleListFilter):
title = _('KnownErrorCases')
parameter_name = 'error'
def lookups(self, request, model_admin):
return (
('schedule_start', _('Schedule start < course start')),
)
def queryset(self, request, queryset):
if self.value() == 'schedule_start':
return queryset.filter(start__lt=F('enrollment__course__start'))
@admin.register(models.Schedule)
class ScheduleAdmin(admin.ModelAdmin):
list_display = ('username', 'course_id', 'active', 'start', 'upgrade_deadline')
list_display = ('username', 'course_id', 'active', 'start', 'upgrade_deadline', 'experience_display')
list_display_links = ('start', 'upgrade_deadline', 'experience_display')
list_filter = ('experience__experience_type', 'active', KnownErrorCases)
raw_id_fields = ('enrollment',)
readonly_fields = ('modified',)
search_fields = ('enrollment__user__username', 'enrollment__course_id',)
search_fields = ('enrollment__user__username', 'enrollment__course__id',)
inlines = (ScheduleExperienceAdminInline,)
actions = ['deactivate_schedules', 'activate_schedules'] + experience_actions
def deactivate_schedules(self, request, queryset):
rows_updated = queryset.update(active=False)
self.message_user(request, "{} schedule(s) were deactivated".format(rows_updated))
deactivate_schedules.short_description = "Deactivate selected schedules"
def activate_schedules(self, request, queryset):
rows_updated = queryset.update(active=True)
self.message_user(request, "{} schedule(s) were activated".format(rows_updated))
activate_schedules.short_description = "Activate selected schedules"
def experience_display(self, obj):
return obj.experience.get_experience_type_display()
experience_display.short_descriptions = _('Experience')
def username(self, obj):
return obj.enrollment.user.username
return '<a href="{}">{}</a>'.format(
reverse("admin:auth_user_change", args=(obj.enrollment.user.id,)),
obj.enrollment.user.username
)
username.allow_tags = True
username.short_description = _('Username')
def course_id(self, obj):
return obj.enrollment.course_id
return '<a href="{}">{}</a>'.format(
reverse("admin:course_overviews_courseoverview_change", args=(
obj.enrollment.course_id,
)),
obj.enrollment.course_id
)
course_id.allow_tags = True
course_id.short_description = _('Course ID')
def get_queryset(self, request):

View File

@@ -0,0 +1,62 @@
import datetime
import pytz
import factory
from django.core.management.base import BaseCommand
from student.models import CourseEnrollment
from django.contrib.sites.models import Site
from openedx.core.djangoapps.schedules.models import Schedule, ScheduleConfig, ScheduleExperience
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory, ScheduleConfigFactory, ScheduleExperienceFactory
from student.tests.factories import CourseEnrollmentFactory
from xmodule.modulestore.tests.factories import CourseFactory, XMODULE_FACTORY_LOCK
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from xmodule.modulestore.django import modulestore
class ThreeDayNudgeSchedule(ScheduleFactory):
start = factory.Faker('date_time_between', start_date='-3d', end_date='-3d', tzinfo=pytz.UTC)
class TenDayNudgeSchedule(ScheduleFactory):
start = factory.Faker('date_time_between', start_date='-10d', end_date='-10d', tzinfo=pytz.UTC)
class UpgradeReminderSchedule(ScheduleFactory):
start = factory.Faker('past_datetime', tzinfo=pytz.UTC)
upgrade_deadline = factory.Faker('date_time_between', start_date='+2d', end_date='+2d', tzinfo=pytz.UTC)
class ContentHighlightSchedule(ScheduleFactory):
start = factory.Faker('date_time_between', start_date='-7d', end_date='-7d', tzinfo=pytz.UTC)
experience = factory.RelatedFactory(ScheduleExperienceFactory, 'schedule', experience_type=ScheduleExperience.EXPERIENCES.course_updates)
class Command(BaseCommand):
"""
A management command that generates schedule objects for all expected schedule email types, so that it is easy to
generate test emails of all available types.
"""
def handle(self, *args, **options):
courses = modulestore().get_courses()
# Find the largest auto-generated course, and pick the next sequence id to generate the next
# course with.
max_org_sequence_id = max(int(course.org[4:]) for course in courses if course.org.startswith('org.'))
XMODULE_FACTORY_LOCK.enable()
CourseFactory.reset_sequence(max_org_sequence_id + 1, force=True)
course = CourseFactory(
start=datetime.datetime.today() - datetime.timedelta(days=30),
end=datetime.datetime.today() + datetime.timedelta(days=30),
number=factory.Sequence('schedules_test_course_{}'.format),
display_name=factory.Sequence('Schedules Test Course {}'.format),
)
XMODULE_FACTORY_LOCK.disable()
course_overview = CourseOverview.load_from_module_store(course.id)
ThreeDayNudgeSchedule.create(enrollment__course=course_overview)
TenDayNudgeSchedule.create(enrollment__course=course_overview)
UpgradeReminderSchedule.create(enrollment__course=course_overview)
ContentHighlightSchedule.create(enrollment__course=course_overview)
ScheduleConfigFactory.create(site=Site.objects.get(name='example.com'))

View File

@@ -122,10 +122,12 @@ class BinnedSchedulesBaseResolver(PrefixedDebugLoggerMixin, RecipientResolver):
'enrollment__course',
).filter(
Q(enrollment__course__end__isnull=True) | Q(
enrollment__course__end__gte=self.current_datetime),
enrollment__course__end__gte=self.current_datetime
),
self.experience_filter,
enrollment__user__in=users,
enrollment__is_active=True,
active=True,
**schedule_day_equals_target_day_filter
).order_by(order_by)