Unverified Commit 7aa1a346 by Calen Pennington Committed by GitHub

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

Cale/schedules admin improvements
parents c9c67e08 10970f30
......@@ -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(
......
......@@ -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
......
......@@ -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')):
......
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):
......
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'))
......@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment