Merge pull request #7548 from edx/mjevtic/SOL-531
(SOL-531)(SOL-532) Course Cards update on the homepage;
This commit is contained in:
@@ -202,9 +202,9 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'index.html')
|
||||
|
||||
# Now the courses will be stored in their announcement dates.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_later.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_earlier.id)
|
||||
# by default the courses will be sorted by their creation dates, earliest first.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_earlier.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_later.id)
|
||||
self.assertEqual(context['courses'][2].id, self.course_with_default_start_date.id)
|
||||
|
||||
# check the /courses view
|
||||
@@ -213,23 +213,23 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'courseware/courses.html')
|
||||
|
||||
# Now the courses will be stored in their announcement dates.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_later.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_earlier.id)
|
||||
# by default the courses will be sorted by their creation dates, earliest first.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_earlier.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_later.id)
|
||||
self.assertEqual(context['courses'][2].id, self.course_with_default_start_date.id)
|
||||
|
||||
@patch('student.views.render_to_response', RENDER_MOCK)
|
||||
@patch('courseware.views.render_to_response', RENDER_MOCK)
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_COURSE_SORTING_BY_START_DATE': True})
|
||||
def test_course_cards_sorted_by_start_date_show_earliest_first(self):
|
||||
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_COURSE_SORTING_BY_START_DATE': False})
|
||||
def test_course_cards_sorted_by_start_date_disabled(self):
|
||||
response = self.client.get('/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'index.html')
|
||||
|
||||
# now the courses will be sorted by their creation dates, earliest first.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_earlier.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_later.id)
|
||||
# now the courses will be sorted by their announcement dates.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_later.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_earlier.id)
|
||||
self.assertEqual(context['courses'][2].id, self.course_with_default_start_date.id)
|
||||
|
||||
# check the /courses view as well
|
||||
@@ -238,7 +238,7 @@ class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
|
||||
((template, context), _) = RENDER_MOCK.call_args # pylint: disable=unpacking-non-sequence
|
||||
self.assertEqual(template, 'courseware/courses.html')
|
||||
|
||||
# now the courses will be sorted by their creation dates, earliest first.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_earlier.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_later.id)
|
||||
# now the courses will be sorted by their announcement dates.
|
||||
self.assertEqual(context['courses'][0].id, self.starting_later.id)
|
||||
self.assertEqual(context['courses'][1].id, self.starting_earlier.id)
|
||||
self.assertEqual(context['courses'][2].id, self.course_with_default_start_date.id)
|
||||
|
||||
@@ -383,7 +383,11 @@ def sort_by_start_date(courses):
|
||||
"""
|
||||
Returns a list of courses sorted by their start date, latest first.
|
||||
"""
|
||||
courses = sorted(courses, key=lambda course: (course.start is None, course.start), reverse=False)
|
||||
courses = sorted(
|
||||
courses,
|
||||
key=lambda course: (course.has_ended(), course.start is None, course.start),
|
||||
reverse=False
|
||||
)
|
||||
|
||||
return courses
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ FEATURES = {
|
||||
# When a user goes to the homepage ('/') the user see the
|
||||
# courses listed in the announcement dates order - this is default Open edX behavior.
|
||||
# Set to True to change the course sorting behavior by their start dates, latest first.
|
||||
'ENABLE_COURSE_SORTING_BY_START_DATE': False,
|
||||
'ENABLE_COURSE_SORTING_BY_START_DATE': True,
|
||||
|
||||
# Flag to enable new user account APIs.
|
||||
'ENABLE_USER_REST_API': False,
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
@import 'views/verification';
|
||||
@import 'views/shoppingcart';
|
||||
@import 'views/login-register';
|
||||
@import 'views/homepage';
|
||||
|
||||
// applications
|
||||
@import "discussion/utilities/variables";
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
@import 'views/verification';
|
||||
@import 'views/decoupled-verification';
|
||||
@import 'views/shoppingcart';
|
||||
@import 'views/homepage';
|
||||
@import 'course/auto-cert';
|
||||
|
||||
// applications
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
$max-width: 1200px;
|
||||
/* Override the default global box-sizing */
|
||||
$border-box-sizing: false;
|
||||
/* Grid width variables */
|
||||
$large-min-width: 769px;
|
||||
|
||||
|
||||
/* Breakpoints */
|
||||
$mobile: new-breakpoint(max-width 320px 4);
|
||||
$tablet: new-breakpoint(min-width 321px max-width 768px, 8);
|
||||
$desktop: new-breakpoint(min-width 769px 12);
|
||||
$xl-desktop: new-breakpoint(min-width 980px 12);
|
||||
$bp-tiny: new-breakpoint(max-width 320px 4);
|
||||
$bp-small: new-breakpoint(min-width 321px max-width 540px, 4);
|
||||
$bp-medium: new-breakpoint(min-width 541px max-width 768px, 8);
|
||||
$bp-large: new-breakpoint(min-width $large-min-width max-width 979px, 12);
|
||||
$bp-huge: new-breakpoint(min-width 980px 12);
|
||||
|
||||
@@ -13,23 +13,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.courses-listing {
|
||||
@include clearfix();
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
.courses-listing-item {
|
||||
width: flex-grid(4);
|
||||
@include margin-right(flex-gutter());
|
||||
@include float(left);
|
||||
|
||||
&:nth-child(3n+3) {
|
||||
@include margin-right(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course {
|
||||
background: $body-bg;
|
||||
border: 1px solid $border-color-1;
|
||||
|
||||
@@ -446,10 +446,10 @@ $edx-footer-bg-color: rgb(252,252,252);
|
||||
@include span-columns(12);
|
||||
}
|
||||
|
||||
@include media( $tablet ) {
|
||||
@include media( $bp-medium ) {
|
||||
}
|
||||
|
||||
@include media( $desktop ) {
|
||||
@include media( $bp-large ) {
|
||||
.footer-about {
|
||||
@include span-columns(6);
|
||||
}
|
||||
|
||||
@@ -765,7 +765,7 @@ header.global-new {
|
||||
}
|
||||
}
|
||||
|
||||
@include media( $desktop ) {
|
||||
@include media( $bp-large ) {
|
||||
.wrapper-header {
|
||||
width: 100%;
|
||||
min-width: 800px;
|
||||
@@ -828,7 +828,7 @@ header.global-new {
|
||||
}
|
||||
}
|
||||
|
||||
@include media( $xl-desktop ) {
|
||||
@include media( $bp-huge ) {
|
||||
.wrapper-header {
|
||||
padding: 17px 0;
|
||||
}
|
||||
|
||||
163
lms/static/sass/views/_homepage.scss
Normal file
163
lms/static/sass/views/_homepage.scss
Normal file
@@ -0,0 +1,163 @@
|
||||
// lms - views - homepage view
|
||||
// ====================
|
||||
|
||||
$course-card-height: ($baseline*18);
|
||||
$course-image-height: ($baseline*8);
|
||||
$course-info-height: ($baseline*10);
|
||||
$course-title-height: ($baseline*3.6);
|
||||
$learn-more-horizontal-position: calc(50% - 100px); // calculate the left position for "LEARN MORE" content
|
||||
|
||||
.courses-container {
|
||||
@include outer-container;
|
||||
padding: ($baseline*0.9) ($baseline/2) 0 ($baseline/2);
|
||||
|
||||
.courses {
|
||||
@include row();
|
||||
|
||||
.courses-listing {
|
||||
@extend %ui-no-list;
|
||||
|
||||
.courses-listing-item {
|
||||
@include fill-parent();
|
||||
max-height: $course-card-height;
|
||||
margin: ($baseline*0.75) 0 ($baseline*1.5) 0;
|
||||
|
||||
@include media($bp-medium) {
|
||||
@include span-columns(4); // 4 of 8
|
||||
@include omega(2n);
|
||||
}
|
||||
|
||||
@include media($bp-large) {
|
||||
@include span-columns(4); // 4 of 12
|
||||
@include omega(3n);
|
||||
}
|
||||
|
||||
@include media($bp-huge) {
|
||||
@include span-columns(3); // 3 of 12
|
||||
@include omega(4n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.course {
|
||||
@include box-sizing(border-box);
|
||||
@include transition(all $tmg-f3 linear 0s);
|
||||
position: relative;
|
||||
border-bottom: 3px solid $action-primary-bg;
|
||||
box-shadow: 0 1px 10px 0 $black-t0, inset 0 0 0 1px $white-t3;
|
||||
background: $body-bg;
|
||||
width: 100%;
|
||||
|
||||
.course-image .cover-image {
|
||||
height: $course-image-height;
|
||||
overflow: hidden;
|
||||
|
||||
// places the shadow on top of the course image while hovering over the course card
|
||||
&:before {
|
||||
@include left(0);
|
||||
@extend %ui-depth1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
background: $black;
|
||||
width: 100%;
|
||||
height: $course-image-height;
|
||||
content: '';
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.learn-more {
|
||||
@include left($learn-more-horizontal-position);
|
||||
@include box-sizing(border-box);
|
||||
@include line-height(28);
|
||||
@extend %ui-depth2;
|
||||
position: absolute;
|
||||
top: ($baseline*2.75);
|
||||
opacity: 0;
|
||||
border: 3px solid $white;
|
||||
border-radius: 3px;
|
||||
padding: 0 $baseline;
|
||||
width: ($baseline*10);
|
||||
height: ($baseline*2.5);
|
||||
text-align: center;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.course-info {
|
||||
height: $course-info-height;
|
||||
|
||||
.course-organization, .course-code, .course-date {
|
||||
@extend %t-icon6;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.course-organization, .course-code, .course-title {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.course-organization {
|
||||
@include line-height(11);
|
||||
padding: ($baseline/2) ($baseline*0.75) ($baseline/10) ($baseline*0.75);
|
||||
}
|
||||
|
||||
.course-code {
|
||||
@include line-height(16);
|
||||
padding: 0 ($baseline*0.75);
|
||||
}
|
||||
|
||||
.course-title {
|
||||
@include line-height(16);
|
||||
@extend %t-icon4;
|
||||
margin: ($baseline*0.25) 0 ($baseline*1.75) 0;
|
||||
padding: 0 ($baseline*0.75);
|
||||
height: $course-title-height;
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
.course-date {
|
||||
@include line-height(14);
|
||||
padding: ($baseline/10) ($baseline*0.75);
|
||||
}
|
||||
}
|
||||
|
||||
// STATE: hover and focus
|
||||
&:hover,
|
||||
&:focus {
|
||||
.cover-image {
|
||||
&:before {
|
||||
@include transition(opacity $tmg-f2 ease-out $tmg-f2);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.learn-more {
|
||||
@include transition(opacity $tmg-f2 ease-out $tmg-f2);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.courses-more {
|
||||
@include margin-right(0);
|
||||
text-align: center;
|
||||
|
||||
@include media($large-min-width) {
|
||||
@include margin-right($baseline*0.5);
|
||||
@include text-align(right);
|
||||
}
|
||||
|
||||
.courses-more-cta {
|
||||
font-weight: $font-semibold;
|
||||
|
||||
&:after {
|
||||
content: " ›";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1431,7 +1431,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include media( $desktop ) {
|
||||
@include media( $bp-large ) {
|
||||
.contribution-options {
|
||||
.field {
|
||||
width: auto;
|
||||
@@ -1454,7 +1454,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include media( $xl-desktop ) {
|
||||
@include media( $bp-huge ) {
|
||||
.register-choice {
|
||||
.list-actions {
|
||||
float: right;
|
||||
|
||||
@@ -5,35 +5,28 @@ from django.core.urlresolvers import reverse
|
||||
from courseware.courses import course_image_url, get_course_about_section
|
||||
%>
|
||||
<%page args="course" />
|
||||
<article id="${course.id | h}" class="course">
|
||||
%if course.is_newish:
|
||||
<span class="status">${_("New")}</span>
|
||||
%endif
|
||||
<article class="course" id="${course.id | h}" role="region" aria-label="${get_course_about_section(course, 'title')}">
|
||||
<a href="${reverse('about_course', args=[course.id.to_deprecated_string()])}">
|
||||
<div class="inner-wrapper">
|
||||
<header class="course-preview">
|
||||
<hgroup>
|
||||
<h2><span class="course-number">${course.display_number_with_default | h}</span> ${get_course_about_section(course, 'title')}</h2>
|
||||
</hgroup>
|
||||
<div class="info-link">➔</div>
|
||||
</header>
|
||||
<section class="info">
|
||||
<div class="cover-image">
|
||||
<img src="${course_image_url(course)}" alt="${course.display_number_with_default | h} ${get_course_about_section(course, 'title')} Cover Image" />
|
||||
</div>
|
||||
<div class="desc">
|
||||
<p>${get_course_about_section(course, 'short_description')}</p>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<span class="university">${get_course_about_section(course, 'university')}</span>
|
||||
% if not course.start_date_is_still_default:
|
||||
<span class="start-date">${course.start_datetime_text()}</span>
|
||||
% endif
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="meta-info">
|
||||
<p class="university">${get_course_about_section(course, 'university')}</p>
|
||||
<header class="course-image">
|
||||
<div class="cover-image">
|
||||
<img src="${course_image_url(course)}" alt="${get_course_about_section(course, 'title')} ${course.display_number_with_default}" />
|
||||
<div class="learn-more" aria-hidden=true>${_("LEARN MORE")}</div>
|
||||
</div>
|
||||
</header>
|
||||
<section class="course-info" aria-hidden=true>
|
||||
<h2 class="course-name">
|
||||
<span class="course-organization">${get_course_about_section(course, 'university')}</span>
|
||||
<span class="course-code">${course.display_number_with_default}</span>
|
||||
<span class="course-title">${get_course_about_section(course, 'title')}</span>
|
||||
</h2>
|
||||
<div class="course-date" aria-hidden="true">${_("Starts")}: ${course.start_datetime_text()}</div>
|
||||
</section>
|
||||
<div class="sr">
|
||||
<ul>
|
||||
<li>${get_course_about_section(course, 'university')}</li>
|
||||
<li>${course.display_number_with_default}</li>
|
||||
<li>${_("Starts")}: <time itemprop="startDate" datetime="${course.start_datetime_text()}">${course.start_datetime_text()}</time></li>
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%!
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from microsite_configuration import microsite
|
||||
%>
|
||||
@@ -44,7 +44,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="container">
|
||||
<section class="courses-container">
|
||||
<section class="courses">
|
||||
<ul class="courses-listing">
|
||||
%for course in courses:
|
||||
|
||||
@@ -48,19 +48,23 @@
|
||||
% endif
|
||||
</div>
|
||||
</header>
|
||||
<section class="container">
|
||||
<section class="courses-container">
|
||||
<section class="highlighted-courses">
|
||||
|
||||
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
|
||||
<section class="courses">
|
||||
<ul class="courses-listing">
|
||||
%for course in courses:
|
||||
## cap for showing 9 or less courses
|
||||
%for course in courses[:9]:
|
||||
<li class="courses-listing-item">
|
||||
<%include file="course.html" args="course=course" />
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
</section>
|
||||
<div class="courses-more">
|
||||
<a class="courses-more-cta" href="${marketing_link('COURSES')}" > ${_("View all Courses")} </a>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user