Commit 94647fd5 by Clinton Blackburn

Merge pull request #214 from edx/clintonb/course-detail-view

Course Detail View
parents 2f57bea5 bc3f4ca0
......@@ -19,6 +19,10 @@
"bootstrapaccessibilityplugin": "~1.0.4",
"datatables": "~1.10.7",
"fontawesome": "~4.3.0",
"edx-ux-pattern-library": "https://github.com/edx/ux-pattern-library.git#82fa1c823bc322ba8b0742f0c546a89b0c69e952"
"edx-ux-pattern-library": "https://github.com/edx/ux-pattern-library.git#82fa1c823bc322ba8b0742f0c546a89b0c69e952",
"text": "~2.0.14",
"backbone.paginator": "~2.0.2",
"moment": "~2.10.3",
"underscore.string": "~3.1.1"
}
}
from django.conf import settings
from django.conf.urls import patterns, url
from ecommerce.courses import views
......@@ -6,4 +7,5 @@ urlpatterns = patterns(
'',
url(r'^$', views.CourseListView.as_view(), name='list'),
url(r'^migrate/$', views.CourseMigrationView.as_view(), name='migrate'),
url(r'^{}/$'.format(settings.COURSE_ID_PATTERN), views.CourseDetailView.as_view(), name='detail'),
)
......@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
from django.core.management import call_command
from django.http import Http404, HttpResponse
from django.utils.decorators import method_decorator
from django.views.generic import View, ListView
from django.views.generic import View, ListView, TemplateView
from ecommerce.courses.models import Course
......@@ -25,6 +25,22 @@ class CourseListView(ListView):
return super(CourseListView, self).dispatch(request, *args, **kwargs)
class CourseDetailView(TemplateView):
template_name = 'courses/course_detail.html'
def get(self, request, *args, **kwargs):
if not Course.objects.filter(id=kwargs['course_id']).exists():
raise Http404
return super(CourseDetailView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(CourseDetailView, self).get_context_data()
context.update({
'course_id': kwargs['course_id']
})
return context
class CourseMigrationView(View):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_superuser:
......
define([
'backbone',
'underscore',
'backbone.paginator'
],
function (Backbone, _) {
'use strict';
return Backbone.PageableCollection.extend({
state: {
pageSize: 20
},
parseRecords: function (resp, options) {
return resp.results;
},
parseState: function (resp, queryParams, state, options) {
return {
totalRecords: resp.count
};
},
parseLinks: function (resp, options) {
return {
first: null,
next: resp.next,
prev: resp.previous
};
}
});
}
);
define([
'collections/drf_pageable_collection',
'models/product_model',
'models/course_seat_model'
],
function (DrfPageableCollection, ProductModel, CourseSeatModel) {
'use strict';
return DrfPageableCollection.extend({
mode: 'client',
model: function (attrs, options) {
var modelClass = ProductModel;
if (attrs.product_class === 'Seat') {
modelClass = CourseSeatModel;
}
return new modelClass(attrs, options);
}
});
});
require([
'jquery',
'backbone',
'bootstrap',
'bootstrap_accessibility'
'bootstrap_accessibility',
'underscore'
], function () {
});
require.config({
baseUrl: '/static/',
paths: {
backbone: 'bower_components/backbone/backbone',
bootstrap: 'bower_components/bootstrap-sass/assets/javascripts/bootstrap',
bootstrap_accessibility: 'bower_components/bootstrapaccessibilityplugin/plugins/js/bootstrap-accessibility',
dataTables: 'bower_components/datatables/media/js/jquery.dataTables',
dataTablesBootstrap: 'vendor/dataTables/dataTables.bootstrap',
jquery: 'bower_components/jquery/dist/jquery',
'backbone': 'bower_components/backbone/backbone',
'backbone.paginator': 'bower_components/backbone.paginator/lib/backbone.paginator',
'bootstrap': 'bower_components/bootstrap-sass/assets/javascripts/bootstrap',
'bootstrap_accessibility': 'bower_components/bootstrapaccessibilityplugin/plugins/js/bootstrap-accessibility',
'collections': 'js/collections',
'dataTables': 'bower_components/datatables/media/js/jquery.dataTables',
'dataTablesBootstrap': 'vendor/dataTables/dataTables.bootstrap',
'jquery': 'bower_components/jquery/dist/jquery',
'jquery-cookie': 'bower_components/jquery-cookie/jquery.cookie',
requirejs: 'bower_components/requirejs/require',
underscore: 'bower_components/underscore/underscore',
views: 'js/views'
'models': 'js/models',
'moment': 'bower_components/moment/moment',
'requirejs': 'bower_components/requirejs/require',
'templates': 'templates',
'text': 'bower_components/text/text',
'underscore': 'bower_components/underscore/underscore',
'underscore.string': 'bower_components/underscore.string/dist/underscore.string',
'views': 'js/views'
},
shim: {
bootstrap: {
......@@ -27,6 +34,6 @@ require.config({
},
'jquery-cookie': {
deps: ['jquery']
},
}
}
});
define([
'backbone',
'underscore',
'collections/product_collection',
'models/course_seat_model'
],
function (Backbone, _, ProductCollection, CourseSeatModel) {
'use strict';
return Backbone.Model.extend({
urlRoot: '/api/v2/courses/',
defaults: {
name: ''
},
getProducts: function () {
if (_.isUndefined(this._products)) {
this._products = new ProductCollection();
this._products.url = this.get('products_url');
return this._products.getFirstPage({fetch: true});
}
return this._products;
},
getSeats: function () {
// Returns the seat products
return this.getProducts().filter(function (product) {
// Filter out parent products since there is no need to display or modify.
return (product instanceof CourseSeatModel) && product.get('structure') !== 'parent';
});
}
});
}
);
define([
'models/product_model'
],
function (ProductModel) {
'use strict';
return ProductModel.extend({
getSeatType: function () {
switch (this.get('certificate_type')) {
case 'verified':
return gettext('Verified');
case 'credit':
return gettext('Credit');
case 'professional':
case 'no-id-professional':
return gettext('Professional');
default:
return gettext('Honor');
}
},
getCertificateDisplayName: function () {
switch (this.get('certificate_type')) {
case 'verified':
case 'credit':
return gettext('Verified Certificate');
case 'professional':
case 'no-id-professional':
return gettext('Professional Certificate');
default:
return gettext('Honor Certificate');
}
}
});
}
);
define([
'backbone'
],
function (Backbone) {
'use strict';
return Backbone.Model.extend({
urlRoot: '/api/v2/products/',
initialize: function () {
// Expose the nested attribute values as top-level attributes on the model
this.get('attribute_values').forEach(function (av) {
this.set(av.name, av.value);
}, this);
}
});
}
);
require([
'views/course_detail_view'
],
function (CourseDetailView) {
'use strict';
new CourseDetailView();
}
);
define([
'jquery',
'backbone',
'underscore',
'underscore.string',
'moment',
'models/course_model',
'text!templates/course_detail.html',
'text!templates/_course_seat.html'
],
function ($,
Backbone,
_,
_s,
moment,
CourseModel,
CourseDetailTemplate,
CourseSeatTemplate) {
'use strict';
return Backbone.View.extend({
el: '.course-detail-view',
initialize: function () {
var self = this,
course_id = self.$el.data('course-id');
this.course = new CourseModel({id: course_id});
this.course.fetch({
success: function (course) {
self.render();
course.getProducts().done(function () {
self.renderSeats();
});
}
});
},
getSeats: function () {
// Returns an array of seats sorted for display
var seats,
sortObj = _.invert(_.object(_.pairs([
'honor', 'verified', 'no-id-professional', 'professional', 'credit'
])));
seats = _.sortBy(this.course.getSeats(), function (seat) {
return sortObj[seat.get('certificate_type')]
});
return seats;
},
render: function () {
var html, templateData;
document.title = this.course.get('name') + ' - ' + gettext('View Course');
templateData = {
course: this.course.attributes,
courseType: _s.capitalize(this.course.get('type'))
};
html = _.template(CourseDetailTemplate)(templateData);
this.$el.html(html)
},
renderSeats: function () {
var html = '',
$seatHolder = $('.course-seats', this.$el);
this.getSeats().forEach(function (seat) {
html += _.template(CourseSeatTemplate)({seat: seat, moment: moment});
});
$seatHolder.append(html);
}
});
}
);
......@@ -28,3 +28,4 @@
// views
// --------------------
@import '../views/credit';
@import '../views/course_detail';
.course-detail-view {
.course-information {
margin-bottom: spacing-vertical(small);
.heading {
font-weight: bold;
}
.course-id {
margin-bottom: spacing-vertical(x-small);
}
}
.course-seats {
.course-seat {
margin-bottom: spacing-vertical(small);
.seat-type {
font-weight: bold;
}
.seat-certificate-type {
}
}
}
}
<div class="row course-seat">
<div class="col-md-4">
<div class="seat-type"><%= seat.getSeatType() %></div>
<% if (seat.get('price')) { %>
<div class="seat-price"><%= gettext('Price:') + ' $' + seat.get('price') %></div>
<% } %>
</div>
<div class="col-md-4 seat-certificate-type">
<i class="fa fa-check-square-o"></i> <%= seat.getCertificateDisplayName() %>
</div>
<div class="col-md-4 seat-additional-info">
<% var expires = seat.get('expires');
if(expires) {
print(gettext('Verification Close:') + ' ' + moment(expires).format('LLL Z'));
}
%>
</div>
</div>
<div class="container">
<div class="page-header">
<h1 class="hd-1 emphasized">
<%= gettext('View Course') %>
<div class="pull-right">
<button class="btn btn-primary btn-small"><%= gettext('Edit Course') %></button>
</div>
</h1>
</div>
<div class="course-information">
<h3 class="hd-3"><%= gettext('Course Information') %></h3>
<div class="heading course-name"><%= course['name'] %></div>
<div class="course-id"><%= course['id'] %></div>
<div class="heading"><%= gettext('Course Type') %></div>
<div class="course-type"><%= courseType %></div>
</div>
<h3 class="hd-3"><%= gettext('Course Seats') %></h3>
<div class="course-seats"></div>
</div>
{% extends 'edx/base.html' %}
{% load staticfiles %}
{% block content %}
<div class="course-detail-view" data-course-id="{{ course_id }}"></div>
{% endblock %}
{% block javascript %}
<script src="{% static 'js/pages/course_detail_page.js' %}"></script>
{% endblock %}
......@@ -26,7 +26,7 @@
<tbody>
{% for course in courses %}
<tr>
<td>{{ course.id }}</td>
<td><a href="{% url 'courses:detail' course.id %}">{{ course.id }}</a></td>
<td>{{ course.name }}</td>
<td data-sort="{{ course.history.latest.history_date|date:'U' }}">
{{ course.history.latest.history_date }}
......
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