Commit 5b361188 by Awais

Adding new publisher course creation page with new design.

Implement new design with css.
Test cases added.
parent 057bca2d
"""
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