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.
"""
from django.contrib.auth.models import Group
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):
......@@ -40,6 +43,24 @@ class CourseForm(BaseCourseForm):
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):
""" Course Run Form. """
......@@ -49,6 +70,30 @@ class CourseRunForm(BaseCourseForm):
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):
""" Course Seat Form. """
......@@ -76,3 +121,20 @@ class SeatForm(BaseCourseForm):
seat.save()
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
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel
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 sortedm2m.fields import SortedManyToManyField
from stdimage.models import StdImageField
......@@ -84,18 +84,18 @@ class Course(TimeStampedModel, ChangedByMixin):
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'))
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(
Organization, blank=True, related_name='publisher_courses', verbose_name=_('Partner Name')
)
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)
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)
verification_deadline = models.DateTimeField(
null=True,
......@@ -147,6 +147,15 @@ class Course(TimeStampedModel, ChangedByMixin):
for group in user.groups.all():
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):
""" Publisher CourseRun model. It contains fields related to the course run intake form."""
......@@ -185,7 +194,10 @@ class CourseRun(TimeStampedModel, ChangedByMixin):
max_effort = models.PositiveSmallIntegerField(
null=True, blank=True,
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(
LanguageTag, blank=True, related_name='publisher_transcript_course_runs'
)
......
......@@ -7,6 +7,7 @@ from factory.fuzzy import FuzzyText, FuzzyChoice, FuzzyDecimal, FuzzyDateTime, F
from pytz import UTC
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.tests import factories
from course_discovery.apps.ietf_language_tags.models import LanguageTag
......@@ -34,6 +35,8 @@ class CourseFactory(factory.DjangoModelFactory):
secondary_subject = factory.SubFactory(factories.SubjectFactory)
tertiary_subject = factory.SubFactory(factories.SubjectFactory)
team_admin = factory.SubFactory(UserFactory)
class Meta:
model = Course
......
......@@ -7,6 +7,7 @@ from course_discovery.apps.publisher import views
urlpatterns = [
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'^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'),
......
......@@ -2,8 +2,11 @@
Course publisher views.
"""
from django.contrib import messages
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.db import transaction
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.views.generic import View
from django.views.generic.detail import DetailView
......@@ -12,7 +15,9 @@ from django.views.generic.list import ListView
from django_fsm import TransitionNotAllowed
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.models import Course, CourseRun, Seat, State
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
......@@ -59,16 +64,72 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin,
# pylint: disable=attribute-defined-outside-init
class CreateCourseView(mixins.LoginRequiredMixin, mixins.FormValidMixin, CreateView):
class CreateCourseView(mixins.LoginRequiredMixin, CreateView):
""" Create Course View."""
model = Course
form_class = CourseForm
template_name = 'publisher/course_form.html'
success_url = 'publisher:publisher_courses_edit'
assign_user_groups = True
def get_success_url(self):
return reverse(self.success_url, kwargs={'pk': self.object.id})
course_form = CustomCourseForm
run_form = CustomCourseRunForm
seat_form = CustomSeatForm
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)
messages.success(
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):
......@@ -88,6 +149,17 @@ class UpdateCourseView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin, mi
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):
""" Create Course Run View."""
model = CourseRun
......
......@@ -7,8 +7,20 @@ $(".administration-nav .tab-container > button").click(function(event) {
$(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) {
setTimeout(function(){
$('.alert-messages').html('');
}, wait);
}
\ No newline at end of file
}
......@@ -321,10 +321,97 @@ $light-gray: rgba(204, 204, 204, 1);
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 {
margin-bottom: 30px;
@include float(left);
width: 50%;
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 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 {
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