Commit 0dca3588 by Awais Qureshi Committed by GitHub

Merge pull request #362 from edx/awais786/ECOM-5496

Awais786/ecom 5496
parents 057bca2d 5b361188
""" """
Course publisher forms. Course publisher forms.
""" """
from django.contrib.auth.models import Group
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.publisher.models import Course, CourseRun, Seat from course_discovery.apps.course_metadata.models import Person
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, User
class BaseCourseForm(forms.ModelForm): class BaseCourseForm(forms.ModelForm):
...@@ -40,6 +43,24 @@ class CourseForm(BaseCourseForm): ...@@ -40,6 +43,24 @@ class CourseForm(BaseCourseForm):
exclude = ('changed_by',) exclude = ('changed_by',)
class CustomCourseForm(CourseForm):
""" Course Form. """
institution = forms.ModelChoiceField(queryset=Group.objects.all(), required=True)
title = forms.CharField(label='Course Title', required=True, max_length=255)
number = forms.CharField(label='Course Number', required=True, max_length=255)
team_admin = forms.ModelChoiceField(queryset=User.objects.filter(is_staff=True), required=True)
class Meta(CourseForm.Meta):
model = Course
fields = (
'title', 'number', 'short_description', 'full_description',
'expected_learnings', 'level_type', 'primary_subject', 'secondary_subject',
'tertiary_subject', 'prerequisites', 'level_type', 'image', 'team_admin',
'level_type', 'institution',
)
class CourseRunForm(BaseCourseForm): class CourseRunForm(BaseCourseForm):
""" Course Run Form. """ """ Course Run Form. """
...@@ -49,6 +70,30 @@ class CourseRunForm(BaseCourseForm): ...@@ -49,6 +70,30 @@ class CourseRunForm(BaseCourseForm):
exclude = ('state', 'changed_by',) exclude = ('state', 'changed_by',)
class CustomCourseRunForm(CourseRunForm):
""" Course Run Form. """
contacted_partner_manager = forms.BooleanField(
widget=forms.RadioSelect(choices=((1, _("Yes")), (0, _("No")))), initial=0, required=False
)
start = forms.DateTimeField(required=True)
staff = forms.ModelMultipleChoiceField(
queryset=Person.objects.all(), widget=forms.SelectMultiple, required=False
)
target_content = forms.BooleanField(
widget=forms.RadioSelect(
choices=((1, _("Yes")), (0, _("No")))), initial=0, required=False
)
class Meta(CourseRunForm.Meta):
fields = (
'keywords', 'start', 'end', 'length',
'transcript_languages', 'language', 'min_effort', 'max_effort', 'keywords',
'contacted_partner_manager', 'target_content', 'pacing_type', 'is_seo_review',
'video_language', 'staff',
)
class SeatForm(BaseCourseForm): class SeatForm(BaseCourseForm):
""" Course Seat Form. """ """ Course Seat Form. """
...@@ -76,3 +121,20 @@ class SeatForm(BaseCourseForm): ...@@ -76,3 +121,20 @@ class SeatForm(BaseCourseForm):
seat.save() seat.save()
return seat return seat
def clean(self):
price = self.cleaned_data.get('price')
seat_type = self.cleaned_data.get('type')
if seat_type in [Seat.PROFESSIONAL, Seat.NO_ID_PROFESSIONAL, Seat.VERIFIED, Seat.CREDIT] \
and not price:
self.add_error('price', _('Only honor/audit seats can be without price.'))
return self.cleaned_data
class CustomSeatForm(SeatForm):
""" Course Seat Form. """
class Meta(SeatForm.Meta):
fields = ('price', 'type')
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('publisher', '0009_auto_20160929_1927'),
]
operations = [
migrations.AlterField(
model_name='course',
name='expected_learnings',
field=models.TextField(default=None, null=True, blank=True, verbose_name='Expected Learnings'),
),
migrations.AlterField(
model_name='course',
name='full_description',
field=models.TextField(default=None, null=True, blank=True, verbose_name='Full Description'),
),
migrations.AlterField(
model_name='course',
name='level_type',
field=models.ForeignKey(related_name='publisher_courses', default=None, to='course_metadata.LevelType', blank=True, verbose_name='Level Type', null=True),
),
migrations.AlterField(
model_name='course',
name='prerequisites',
field=models.TextField(default=None, null=True, blank=True, verbose_name='Prerequisites'),
),
migrations.AlterField(
model_name='course',
name='short_description',
field=models.CharField(default=None, max_length=255, null=True, blank=True, verbose_name='Brief Description'),
),
migrations.AlterField(
model_name='courserun',
name='language',
field=models.ForeignKey(related_name='publisher_course_runs', to='ietf_language_tags.LanguageTag', verbose_name='Content Language', blank=True, null=True),
),
migrations.AlterField(
model_name='historicalcourse',
name='expected_learnings',
field=models.TextField(default=None, null=True, blank=True, verbose_name='Expected Learnings'),
),
migrations.AlterField(
model_name='historicalcourse',
name='full_description',
field=models.TextField(default=None, null=True, blank=True, verbose_name='Full Description'),
),
migrations.AlterField(
model_name='historicalcourse',
name='prerequisites',
field=models.TextField(default=None, null=True, blank=True, verbose_name='Prerequisites'),
),
migrations.AlterField(
model_name='historicalcourse',
name='short_description',
field=models.CharField(default=None, max_length=255, null=True, blank=True, verbose_name='Brief Description'),
),
]
...@@ -7,7 +7,7 @@ from django.dispatch import receiver ...@@ -7,7 +7,7 @@ from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from django_fsm import FSMField, transition from django_fsm import FSMField, transition
from guardian.shortcuts import assign_perm from guardian.shortcuts import assign_perm, get_groups_with_perms
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from sortedm2m.fields import SortedManyToManyField from sortedm2m.fields import SortedManyToManyField
from stdimage.models import StdImageField from stdimage.models import StdImageField
...@@ -84,18 +84,18 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -84,18 +84,18 @@ class Course(TimeStampedModel, ChangedByMixin):
title = models.CharField(max_length=255, default=None, null=True, blank=True, verbose_name=_('Course title')) title = models.CharField(max_length=255, default=None, null=True, blank=True, verbose_name=_('Course title'))
number = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('Course number')) number = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('Course number'))
short_description = models.CharField( short_description = models.CharField(
max_length=255, default=None, null=True, blank=True, verbose_name=_('Course subtitle') max_length=255, default=None, null=True, blank=True, verbose_name=_('Brief Description')
) )
full_description = models.TextField(default=None, null=True, blank=True, verbose_name=_('About this course')) full_description = models.TextField(default=None, null=True, blank=True, verbose_name=_('Full Description'))
organizations = models.ManyToManyField( organizations = models.ManyToManyField(
Organization, blank=True, related_name='publisher_courses', verbose_name=_('Partner Name') Organization, blank=True, related_name='publisher_courses', verbose_name=_('Partner Name')
) )
level_type = models.ForeignKey( level_type = models.ForeignKey(
LevelType, default=None, null=True, blank=True, related_name='publisher_courses', verbose_name=_('Course level') LevelType, default=None, null=True, blank=True, related_name='publisher_courses', verbose_name=_('Level Type')
) )
expected_learnings = models.TextField(default=None, null=True, blank=True, verbose_name=_("What you'll learn")) expected_learnings = models.TextField(default=None, null=True, blank=True, verbose_name=_("Expected Learnings"))
syllabus = models.TextField(default=None, null=True, blank=True) syllabus = models.TextField(default=None, null=True, blank=True)
prerequisites = models.TextField(default=None, null=True, blank=True) prerequisites = models.TextField(default=None, null=True, blank=True, verbose_name=_('Prerequisites'))
learner_testimonial = models.CharField(max_length=50, null=True, blank=True) learner_testimonial = models.CharField(max_length=50, null=True, blank=True)
verification_deadline = models.DateTimeField( verification_deadline = models.DateTimeField(
null=True, null=True,
...@@ -147,6 +147,15 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -147,6 +147,15 @@ class Course(TimeStampedModel, ChangedByMixin):
for group in user.groups.all(): for group in user.groups.all():
assign_perm(self.VIEW_PERMISSION, group, self) assign_perm(self.VIEW_PERMISSION, group, self)
def assign_permission_by_group(self, institution):
assign_perm(self.VIEW_PERMISSION, institution, self)
@property
def get_group_institution(self):
""" Returns the Group object with for the given course object. """
available_groups = get_groups_with_perms(self)
return available_groups[0] if available_groups else None
class CourseRun(TimeStampedModel, ChangedByMixin): class CourseRun(TimeStampedModel, ChangedByMixin):
""" Publisher CourseRun model. It contains fields related to the course run intake form.""" """ Publisher CourseRun model. It contains fields related to the course run intake form."""
...@@ -185,7 +194,10 @@ class CourseRun(TimeStampedModel, ChangedByMixin): ...@@ -185,7 +194,10 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
max_effort = models.PositiveSmallIntegerField( max_effort = models.PositiveSmallIntegerField(
null=True, blank=True, null=True, blank=True,
help_text=_('Estimated maximum number of hours per week needed to complete a course run.')) help_text=_('Estimated maximum number of hours per week needed to complete a course run.'))
language = models.ForeignKey(LanguageTag, null=True, blank=True, related_name='publisher_course_runs') language = models.ForeignKey(
LanguageTag, null=True, blank=True,
related_name='publisher_course_runs', verbose_name=_('Content Language')
)
transcript_languages = models.ManyToManyField( transcript_languages = models.ManyToManyField(
LanguageTag, blank=True, related_name='publisher_transcript_course_runs' LanguageTag, blank=True, related_name='publisher_transcript_course_runs'
) )
......
...@@ -7,6 +7,7 @@ from factory.fuzzy import FuzzyText, FuzzyChoice, FuzzyDecimal, FuzzyDateTime, F ...@@ -7,6 +7,7 @@ from factory.fuzzy import FuzzyText, FuzzyChoice, FuzzyDecimal, FuzzyDateTime, F
from pytz import UTC from pytz import UTC
from course_discovery.apps.core.models import Currency from course_discovery.apps.core.models import Currency
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.course_metadata.choices import CourseRunPacing from course_discovery.apps.course_metadata.choices import CourseRunPacing
from course_discovery.apps.course_metadata.tests import factories from course_discovery.apps.course_metadata.tests import factories
from course_discovery.apps.ietf_language_tags.models import LanguageTag from course_discovery.apps.ietf_language_tags.models import LanguageTag
...@@ -34,6 +35,8 @@ class CourseFactory(factory.DjangoModelFactory): ...@@ -34,6 +35,8 @@ class CourseFactory(factory.DjangoModelFactory):
secondary_subject = factory.SubFactory(factories.SubjectFactory) secondary_subject = factory.SubFactory(factories.SubjectFactory)
tertiary_subject = factory.SubFactory(factories.SubjectFactory) tertiary_subject = factory.SubFactory(factories.SubjectFactory)
team_admin = factory.SubFactory(UserFactory)
class Meta: class Meta:
model = Course model = Course
......
...@@ -7,6 +7,7 @@ from course_discovery.apps.publisher import views ...@@ -7,6 +7,7 @@ from course_discovery.apps.publisher import views
urlpatterns = [ urlpatterns = [
url(r'^courses/new$', views.CreateCourseView.as_view(), name='publisher_courses_new'), url(r'^courses/new$', views.CreateCourseView.as_view(), name='publisher_courses_new'),
url(r'^courses/(?P<pk>\d+)/view/$', views.ReadOnlyView.as_view(), name='publisher_courses_readonly'),
url(r'^courses/(?P<pk>\d+)/edit/$', views.UpdateCourseView.as_view(), name='publisher_courses_edit'), url(r'^courses/(?P<pk>\d+)/edit/$', views.UpdateCourseView.as_view(), name='publisher_courses_edit'),
url(r'^course_runs/(?P<pk>\d+)/$', views.CourseRunDetailView.as_view(), name='publisher_course_run_detail'), url(r'^course_runs/(?P<pk>\d+)/$', views.CourseRunDetailView.as_view(), name='publisher_course_run_detail'),
url(r'^course_runs/$', views.CourseRunListView.as_view(), name='publisher_course_runs'), url(r'^course_runs/$', views.CourseRunListView.as_view(), name='publisher_course_runs'),
......
...@@ -2,8 +2,11 @@ ...@@ -2,8 +2,11 @@
Course publisher views. Course publisher views.
""" """
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction
from django.http import HttpResponseRedirect, HttpResponseForbidden from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import View from django.views.generic import View
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
...@@ -12,7 +15,9 @@ from django.views.generic.list import ListView ...@@ -12,7 +15,9 @@ from django.views.generic.list import ListView
from django_fsm import TransitionNotAllowed from django_fsm import TransitionNotAllowed
from guardian.shortcuts import get_objects_for_user from guardian.shortcuts import get_objects_for_user
from course_discovery.apps.publisher.forms import CourseForm, CourseRunForm, SeatForm from course_discovery.apps.publisher.forms import (
CourseForm, CourseRunForm, SeatForm, CustomCourseForm, CustomCourseRunForm, CustomSeatForm
)
from course_discovery.apps.publisher import mixins from course_discovery.apps.publisher import mixins
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State
from course_discovery.apps.publisher.wrappers import CourseRunWrapper from course_discovery.apps.publisher.wrappers import CourseRunWrapper
...@@ -59,16 +64,72 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, ...@@ -59,16 +64,72 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin,
# pylint: disable=attribute-defined-outside-init # pylint: disable=attribute-defined-outside-init
class CreateCourseView(mixins.LoginRequiredMixin, mixins.FormValidMixin, CreateView): class CreateCourseView(mixins.LoginRequiredMixin, CreateView):
""" Create Course View.""" """ Create Course View."""
model = Course model = Course
form_class = CourseForm course_form = CustomCourseForm
template_name = 'publisher/course_form.html' run_form = CustomCourseRunForm
success_url = 'publisher:publisher_courses_edit' seat_form = CustomSeatForm
assign_user_groups = True template_name = 'publisher/add_course_form.html'
success_url = 'publisher:publisher_courses_readonly'
def get_success_url(self, course_id): # pylint: disable=arguments-differ
return reverse(self.success_url, kwargs={'pk': course_id})
def get_context_data(self):
return {
'course_form': self.course_form,
'run_form': self.run_form,
'seat_form': self.seat_form
}
def get(self, request, *args, **kwargs):
return render(request, self.template_name, self.get_context_data())
def post(self, request, *args, **kwargs):
ctx = self.get_context_data()
course_form = self.course_form(request.POST, request.FILES)
run_form = self.run_form(request.POST)
seat_form = self.seat_form(request.POST)
if course_form.is_valid() and run_form.is_valid() and seat_form.is_valid():
try:
with transaction.atomic():
seat = seat_form.save(commit=False)
run_course = run_form.save(commit=False)
course = course_form.save(commit=False)
course.changed_by = self.request.user
course.save()
run_course.course = course
run_course.changed_by = self.request.user
run_course.save()
# commit false does not save m2m object.
run_form.save_m2m()
seat.course_run = run_course
seat.changed_by = self.request.user
seat.save()
institution = get_object_or_404(Group, pk=course_form.data['institution'])
# assign guardian permission.
course.assign_permission_by_group(institution)
def get_success_url(self): messages.success(
return reverse(self.success_url, kwargs={'pk': self.object.id}) request, _('Course created successfully.')
)
return HttpResponseRedirect(self.get_success_url(course.id))
except Exception as e: # pylint: disable=broad-except
messages.error(request, str(e))
messages.error(request, _('Please fill all required field.'))
ctx.update(
{
'course_form': course_form,
'run_form': run_form,
'seat_form': seat_form
}
)
return render(request, self.template_name, ctx, status=400)
class UpdateCourseView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, mixins.FormValidMixin, UpdateView): class UpdateCourseView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, mixins.FormValidMixin, UpdateView):
...@@ -88,6 +149,17 @@ class UpdateCourseView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, mi ...@@ -88,6 +149,17 @@ class UpdateCourseView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, mi
return context return context
class ReadOnlyView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, DetailView):
""" Course Run Detail View."""
model = Course
template_name = 'publisher/view_course_form.html'
def get_context_data(self, **kwargs):
context = super(ReadOnlyView, self).get_context_data(**kwargs)
context['comment_object'] = self
return context
class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.FormValidMixin, CreateView): class CreateCourseRunView(mixins.LoginRequiredMixin, mixins.FormValidMixin, CreateView):
""" Create Course Run View.""" """ Create Course Run View."""
model = CourseRun model = CourseRun
......
...@@ -7,6 +7,18 @@ $(".administration-nav .tab-container > button").click(function(event) { ...@@ -7,6 +7,18 @@ $(".administration-nav .tab-container > button").click(function(event) {
$(tab).fadeIn(); $(tab).fadeIn();
}); });
$(document).ready(function(){
$('ul.tabs .course-tabs').click(function(){
var tab_id = $(this).attr('data-tab');
$('ul.tabs .course-tabs').removeClass('active');
$('.content').removeClass('active');
$(this).addClass('active');
$("#"+tab_id).addClass('active');
})
});
function alertTimeout(wait) { function alertTimeout(wait) {
setTimeout(function(){ setTimeout(function(){
$('.alert-messages').html(''); $('.alert-messages').html('');
......
...@@ -321,10 +321,97 @@ $light-gray: rgba(204, 204, 204, 1); ...@@ -321,10 +321,97 @@ $light-gray: rgba(204, 204, 204, 1);
margin-bottom: 20px; margin-bottom: 20px;
} }
.publisher-layout {
background: rgb(242, 242, 242);
padding: 15px;
margin-bottom: 20px;
.layout-title {
font-size: 24px;
font-weight: bold;
}
.course-form {
margin: 0;
box-shadow: none;
}
}
input,
select {
max-width: 100%;
}
.required {
color: red;
font-size: 12px;
}
.checkbox-inline {
ul {
list-style: none;
margin-left: 0;
margin-bottom: 15px;
li {
display: inline-block;
@include margin-right(10px);
&:last-child {
margin-right: 0;
}
}
}
}
.course-information { .course-information {
margin-bottom: 30px; margin-bottom: 30px;
input, select {
margin-bottom: 15px;
}
.hd-4 {
margin-bottom: 25px;
padding-bottom: 10px;
border-bottom: 1px solid rgb(51, 51, 51);
}
.field-title {
border-bottom: 1px solid rgb(51, 51, 51);
padding-bottom: 10px;
margin-bottom: 20px;
font-weight: bold;
}
.course-tabs{
list-style: none;
@include float(left); @include float(left);
width: 50%; @include margin-right(10px);
padding: 10px;
border-bottom: 2px solid #ccc;
color: #ccc;
text-align: center;
&:hover{
color: #337ab7;
border-bottom: 2px solid #337ab7;
}
}
.course-tabs.active{
border-bottom: 2px solid #337ab7;
color: #337ab7;
}
.content{
display: none;
p{
margin: 0;
}
p:first-of-type{
margin-bottom: 20px;
}
}
.content.active{
display: inline-block;
}
.info-item { .info-item {
margin-bottom: 15px; margin-bottom: 15px;
......
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{% trans "Course Form" %}
{% endblock title %}
{% block content %}
<div class="alert-messages">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}" role="alert" aria-labelledby="alert-title-{{ message.tags }}" tabindex="-1">
<div class="alert-message-with-action">
<p class="alert-copy">
{{ message }}
</p>
</div>
</div>
{% endfor %}
{% endif %}
</div>
<div class="layout-full publisher-layout layout">
<h2 class="layout-title">{% trans "Base information" %}</h2>
<div class="card course-form">
<div class="course-information">
<h4 class="hd-4">{% trans "Course Form" %}</h4>
<fieldset class="form-group grid-container grid-manual">
<div class="field-title">{% trans "INSTITUTION INFORMATION" %}</div>
<div class="row">
<div class="col col-6">
{{ object.get_group_institution }}
</div>
</div>
</fieldset>
</div>
</div>
</div>
<div class="layout-full layout">
<h2 class="layout-title">{% trans "Course information" %}</h2>
<div class="card course-form">
<div class="course-information">
<fieldset class="form-group">
<div class="field-row">
<div class="field-col">
<label class="field-label " for="title">{{ course_form.title.label }}</label>
{{ object.title }}
</div>
</div>
</fieldset>
</div>
</div>
</div>
{% endblock content %}
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