Commit 8b2b0a2b by Clinton Blackburn Committed by Renzo Lucioni

Fixed bug on program admin page

The previous override of save_model() was largely unnecessary and broke because it attempts to set m2m fields on unsaved models. The override now relies on the super method (which performs the correct behavior) and simply does error handling.

Our use of Select2 has also been cleaned up to make our monkey patching clearer.

ECOM-6238
parent 963a20c2
......@@ -87,38 +87,47 @@ class ProgramAdmin(admin.ModelAdmin):
'authoring_organizations', 'credit_backing_organizations'
)
fields += filter_horizontal
save_error = None
save_error = False
def custom_course_runs_display(self, obj):
return mark_safe('<br>'.join([str(run) for run in obj.course_runs]))
custom_course_runs_display.short_description = _('Included course runs')
def _redirect_course_run_update_page(self, obj):
""" Returns a response redirect to a page where the user can update the
course runs for the program being edited.
Returns:
HttpResponseRedirect
"""
return HttpResponseRedirect(reverse('admin_metadata:update_course_runs', kwargs={'pk': obj.pk}))
def response_add(self, request, obj, post_url_continue=None):
if self.save_error:
return self.response_post_save_add(request, obj)
else:
return HttpResponseRedirect(reverse('admin_metadata:update_course_runs', kwargs={'pk': obj.pk}))
return self._redirect_course_run_update_page(obj)
def response_change(self, request, obj):
if self.save_error:
return self.response_post_save_change(request, obj)
else:
if any(status in request.POST for status in ['_continue', '_save']):
return HttpResponseRedirect(reverse('admin_metadata:update_course_runs', kwargs={'pk': obj.pk}))
return self._redirect_course_run_update_page(obj)
else:
return HttpResponseRedirect(reverse('admin:course_metadata_program_add'))
def save_model(self, request, obj, form, change):
try:
# courses are ordered by django id, but form.cleaned_data is ordered correctly
obj.courses = form.cleaned_data.get('courses')
obj.authoring_organizations = form.cleaned_data.get('authoring_organizations')
obj.credit_backing_organizations = form.cleaned_data.get('credit_backing_organizations')
obj.save()
super().save_model(request, obj, form, change)
self.save_error = False
except ProgramPublisherException as ex:
messages.add_message(request, messages.ERROR, ex.message)
except ProgramPublisherException:
# TODO Redirect the user back to the form so that he/she can try again.
logger.exception('An error occurred while publishing the program [%s] to the marketing site.', obj.uuid)
msg = _('An error occurred while publishing the program to the marketing site. Please try again. '
'If the error persists, please contact the Engineering Team.')
messages.add_message(request, messages.ERROR, msg)
self.save_error = True
class Media:
......
from dal import autocomplete, widgets
from dal import autocomplete
from django import forms
from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList
......@@ -8,31 +8,30 @@ from course_discovery.apps.course_metadata.choices import ProgramStatus
from course_discovery.apps.course_metadata.models import Program, CourseRun
class HackDjangoAutocompleteMixin(object):
# It seems to me there is an issue with the select 2 widget in django autocomplete.
# When the widget loads selected choices it loads them in order of django id, not the order
# they are stored in in the database. This workaround works, but not sure what approach
# would be less hacky. Perhaps opening a PR to the django autocomplete repo if this is
# fact an issue?
def filter_choices_to_render_with_order_preserved(self, selected_choices):
"""
Preserves ordering of selected_choices when creating the choices queryset.
class QuerySetSelectMixin2(widgets.WidgetMixin):
def filter_choices_to_render(self, selected_choices):
# preserve ordering of selected_choices in queryset
# https://codybonney.com/creating-a-queryset-from-a-list-while-preserving-order-using-django/
See https://codybonney.com/creating-a-queryset-from-a-list-while-preserving-order-using-django.
django-autocomplete's definition of this method on QuerySetSelectMixin loads selected choices in
order of primary key instead of the order in which the choices are actually stored.
"""
clauses = ' '.join(['WHEN id={} THEN {}'.format(pk, i) for i, pk in enumerate(selected_choices)])
ordering = 'CASE {} END'.format(clauses)
self.choices.queryset = self.choices.queryset.filter(
pk__in=[c for c in selected_choices if c]
).extra(select={'ordering': ordering}, order_by=('ordering',))
widgets.QuerySetSelectMixin = QuerySetSelectMixin2
class ProgramAdminForm(HackDjangoAutocompleteMixin, forms.ModelForm):
class ProgramAdminForm(forms.ModelForm):
class Meta:
model = Program
fields = '__all__'
# Monkey patch filter_choices_to_render with our own definition which preserves ordering.
autocomplete.ModelSelect2Multiple.filter_choices_to_render = filter_choices_to_render_with_order_preserved
widgets = {
'courses': autocomplete.ModelSelect2Multiple(
url='admin_metadata:course-autocomplete',
......
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect, Http404
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, UpdateView
from course_discovery.apps.course_metadata.forms import CourseRunSelectionForm
......@@ -25,7 +26,10 @@ class CourseRunSelectionAdmin(UpdateView):
def get_context_data(self, **kwargs):
if self.request.user.is_authenticated() and self.request.user.is_staff:
context = super(CourseRunSelectionAdmin, self).get_context_data(**kwargs)
context['program_id'] = self.object.id
context.update({
'program_id': self.object.id,
'title': _('Change program excluded course runs')
})
return context
raise Http404
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-31 11:33-0400\n"
"POT-Creation-Date: 2016-10-31 19:34-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -200,6 +200,12 @@ msgstr ""
msgid "Included course runs"
msgstr ""
#: apps/course_metadata/admin.py
msgid ""
"An error occurred while publishing the program to the marketing site. Please"
" try again. If the error persists, please contact the Engineering Team."
msgstr ""
#: apps/course_metadata/choices.py apps/publisher/models.py
msgid "Published"
msgstr ""
......@@ -395,6 +401,10 @@ msgstr ""
msgid "The description of credit redemption for courses in program"
msgstr ""
#: apps/course_metadata/views.py
msgid "Change program excluded course runs"
msgstr ""
#: apps/edx_haystack_extensions/models.py
msgid "Function Score"
msgstr ""
......@@ -662,10 +672,6 @@ msgid "Sign Out"
msgstr ""
#: templates/metadata/admin/course_run.html
msgid "CourseRun Selection Form"
msgstr ""
#: templates/metadata/admin/course_run.html
msgid "Cancel"
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-31 11:33-0400\n"
"POT-Creation-Date: 2016-10-31 19:34-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-31 11:33-0400\n"
"POT-Creation-Date: 2016-10-31 19:34-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -241,6 +241,20 @@ msgstr "Pärtnérs Ⱡ'σяєм ιρѕυм ∂#"
msgid "Included course runs"
msgstr "Ìnçlüdéd çöürsé rüns Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
#: apps/course_metadata/admin.py
msgid ""
"An error occurred while publishing the program to the marketing site. Please"
" try again. If the error persists, please contact the Engineering Team."
msgstr ""
"Àn érrör öççürréd whïlé püßlïshïng thé prögräm tö thé märkétïng sïté. Pléäsé"
" trý ägäïn. Ìf thé érrör pérsïsts, pléäsé çöntäçt thé Éngïnéérïng Téäm. "
"Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ "
"тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм,"
" qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ "
"¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє "
"¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт "
"ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм #"
#: apps/course_metadata/choices.py apps/publisher/models.py
msgid "Published"
msgstr "Püßlïshéd Ⱡ'σяєм ιρѕυм ∂σł#"
......@@ -488,6 +502,11 @@ msgstr ""
"Thé désçrïptïön öf çrédït rédémptïön för çöürsés ïn prögräm Ⱡ'σяєм ιρѕυм "
"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/course_metadata/views.py
msgid "Change program excluded course runs"
msgstr ""
"Çhängé prögräm éxçlüdéd çöürsé rüns Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєт#"
#: apps/edx_haystack_extensions/models.py
msgid "Function Score"
msgstr "Fünçtïön Sçöré Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
......@@ -783,10 +802,6 @@ msgid "Sign Out"
msgstr "Sïgn Öüt Ⱡ'σяєм ιρѕυм ∂#"
#: templates/metadata/admin/course_run.html
msgid "CourseRun Selection Form"
msgstr "ÇöürséRün Séléçtïön Förm Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#"
#: templates/metadata/admin/course_run.html
msgid "Cancel"
msgstr "Çänçél Ⱡ'σяєм ιρѕυ#"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-31 11:33-0400\n"
"POT-Creation-Date: 2016-10-31 19:34-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
{% extends 'admin/base_site.html' %}
{% load i18n %}
{% block title %}
{% trans "CourseRun Selection Form" %}
{% endblock title %}
{% block content %}
{% block bodyclass %}
change-program-excluded-course-runs-form
{% endblock %}
{% block content %}
<form class="form" method="post" action="">
{% csrf_token %}
<fieldset class="module aligned ">
......@@ -30,5 +30,4 @@
</div>
{% endblock %}
</form>
{% endblock %}
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