Skip to content

Commit

Permalink
#145 Product Page: Learners Carousel
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahmed Belal committed May 11, 2019
1 parent 6272cf3 commit e7981aa
Show file tree
Hide file tree
Showing 20 changed files with 474 additions and 7 deletions.
16 changes: 16 additions & 0 deletions cms/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,19 @@ class ResourceBlock(blocks.StructBlock):

heading = blocks.CharBlock(max_length=100)
detail = blocks.RichTextBlock()


# Cannot name TestimonialBlock otherwise pytest will try to pick up as a test
class UserTestimonialBlock(blocks.StructBlock):
"""
Custom block to represent a testimonial
"""

name = blocks.CharBlock(max_length=100, help_text="Name of the attestant.")
title = blocks.CharBlock(
max_length=255, help_text="The title to display after the name."
)
image = ImageChooserBlock(
blank=True, null=True, help_text="The image to display on the testimonial"
)
quote = blocks.TextBlock(help_text="The quote that appears on the testimonial.")
30 changes: 29 additions & 1 deletion cms/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
WhoShouldEnrollPage,
CoursesInProgramPage,
ResourcePage,
UserTestimonialsPage,
)
from cms.blocks import LearningTechniqueBlock, ResourceBlock
from cms.blocks import LearningTechniqueBlock, ResourceBlock, UserTestimonialBlock
from courses.factories import ProgramFactory, CourseFactory


Expand Down Expand Up @@ -142,3 +143,30 @@ class ResourcePageFactory(wagtail_factories.PageFactory):

class Meta:
model = ResourcePage


# Cannot name TestimonialBlockFactory otherwise pytest will try to pick up as a test
class UserTestimonialBlockFactory(wagtail_factories.StructBlockFactory):
"""UserTestimonialBlock factory class"""

name = factory.fuzzy.FuzzyText(prefix="name ")
title = factory.fuzzy.FuzzyText(prefix="title ")
image = factory.SubFactory(wagtail_factories.ImageFactory)
quote = factory.fuzzy.FuzzyText(prefix="quote ")

class Meta:
model = UserTestimonialBlock


# Cannot name TestimonialPageFactory otherwise pytest will try to pick up as a test
class UserTestimonialsPageFactory(wagtail_factories.PageFactory):
"""UserTestimonialsPage factory class"""

heading = factory.fuzzy.FuzzyText(prefix="heading ")
subhead = factory.fuzzy.FuzzyText(prefix="subhead ")
items = wagtail_factories.StreamFieldFactory(
{"testimonial": UserTestimonialBlockFactory}
)

class Meta:
model = UserTestimonialsPage
93 changes: 93 additions & 0 deletions cms/migrations/0015_user_testimonials_subpage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Generated by Django 2.1.7 on 2019-05-08 20:08

from django.db import migrations, models
import django.db.models.deletion
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.images.blocks


class Migration(migrations.Migration):

dependencies = [
("wagtailcore", "0041_group_collection_permissions_verbose_name_plural"),
("cms", "0014_resourcepage"),
]

operations = [
migrations.CreateModel(
name="UserTestimonialsPage",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.Page",
),
),
(
"heading",
models.CharField(
help_text="The heading to display on this section.",
max_length=255,
),
),
(
"subhead",
models.CharField(
help_text="Subhead to display below the heading.",
max_length=255,
),
),
(
"items",
wagtail.core.fields.StreamField(
[
(
"testimonial",
wagtail.core.blocks.StructBlock(
[
(
"name",
wagtail.core.blocks.CharBlock(
help_text="Name of the attestant.",
max_length=100,
),
),
(
"title",
wagtail.core.blocks.CharBlock(
help_text="The title to display after the name.",
max_length=255,
),
),
(
"image",
wagtail.images.blocks.ImageChooserBlock(
blank=True,
help_text="The image to display on the testimonial",
null=True,
),
),
(
"quote",
wagtail.core.blocks.TextBlock(
help_text="The quote that appears on the testimonial."
),
),
]
),
)
],
help_text="Add testimonials to display in this section.",
),
),
],
options={"verbose_name": "Testimonials Page"},
bases=("wagtailcore.page",),
)
]
30 changes: 29 additions & 1 deletion cms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from modelcluster.fields import ParentalKey

from mitxpro.views import get_js_settings_context
from .blocks import LearningTechniqueBlock, ResourceBlock
from .blocks import LearningTechniqueBlock, ResourceBlock, UserTestimonialBlock


class CourseProgramChildPage(Page):
Expand Down Expand Up @@ -53,6 +53,33 @@ def save(self, *args, **kwargs):
super().save(*args, **kwargs)


# Cannot name TestimonialPage otherwise pytest will try to pick up as a test
class UserTestimonialsPage(CourseProgramChildPage):
"""
Page that holds testimonials for a product
"""

heading = models.CharField(
max_length=255, help_text="The heading to display on this section."
)
subhead = models.CharField(
max_length=255, help_text="Subhead to display below the heading."
)
items = StreamField(
[("testimonial", UserTestimonialBlock())],
blank=False,
help_text="Add testimonials to display in this section.",
)
content_panels = [
FieldPanel("heading"),
FieldPanel("subhead"),
StreamFieldPanel("items"),
]

class Meta:
verbose_name = "Testimonials Page"


class LearningOutcomesPage(CourseProgramChildPage):
"""
Learning outcomes page for learning benefits.
Expand Down Expand Up @@ -269,6 +296,7 @@ class Meta:
"ForTeamsPage",
"WhoShouldEnrollPage",
"CoursesInProgramPage",
"UserTestimonialsPage",
]

def get_context(self, request, *args, **kwargs):
Expand Down
6 changes: 6 additions & 0 deletions courses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
ForTeamsPage,
WhoShouldEnrollPage,
CoursesInProgramPage,
UserTestimonialsPage,
)
from courses.constants import (
CATALOG_COURSE_IMG_WAGTAIL_FILL,
Expand Down Expand Up @@ -172,6 +173,11 @@ def faqs(self):
faqs_page = self._get_child_page_of_type(FrequentlyAskedQuestionPage)
return FrequentlyAskedQuestion.objects.filter(faqs_page=faqs_page)

@property
def testimonials(self):
"""Gets the testimonials related to product if they exist"""
return self._get_child_page_of_type(UserTestimonialsPage)

@property
def who_should_enroll(self):
"""Gets the WhoShouldEnroll associated child page from the associated Page if it exists"""
Expand Down
58 changes: 58 additions & 0 deletions courses/models_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ForTeamsPageFactory,
WhoShouldEnrollPageFactory,
CoursesInProgramPageFactory,
UserTestimonialsPageFactory,
)

from cms.models import (
Expand All @@ -26,6 +27,7 @@
ForTeamsPage,
WhoShouldEnrollPage,
CoursesInProgramPage,
UserTestimonialsPage,
)
from mitxpro.utils import now_in_utc

Expand Down Expand Up @@ -499,3 +501,59 @@ def test_program_course_lineup():
assert program.course_lineup == courses_page
assert courses_page.heading == "heading"
assert courses_page.body == "<p>body</p>"


def test_course_testimonials():
"""
testimonials property should return expected value if associated with a course
"""
course = CourseFactory.create()
assert course.testimonials is None

course_page = CoursePageFactory.create(course=course)
assert UserTestimonialsPage.can_create_at(course_page)
testimonials_page = UserTestimonialsPageFactory.create(
parent=course_page,
heading="heading",
subhead="subhead",
items__0__testimonial__name="name",
items__0__testimonial__title="title",
items__0__testimonial__image__title="image",
items__0__testimonial__quote="quote",
)
assert course.testimonials == testimonials_page
assert testimonials_page.heading == "heading"
assert testimonials_page.subhead == "subhead"
for testimonial in testimonials_page.items: # pylint: disable=not-an-iterable
assert testimonial.value.get("name") == "name"
assert testimonial.value.get("title") == "title"
assert testimonial.value.get("image").title == "image"
assert testimonial.value.get("quote") == "quote"


def test_program_testimonials():
"""
testimonials property should return expected value if associated with a program
"""
program = ProgramFactory.create()
assert program.testimonials is None

program_page = ProgramPageFactory.create(program=program)
assert UserTestimonialsPage.can_create_at(program_page)
testimonials_page = UserTestimonialsPageFactory.create(
parent=program_page,
heading="heading",
subhead="subhead",
items__0__testimonial__name="name",
items__0__testimonial__title="title",
items__0__testimonial__image__title="image",
items__0__testimonial__quote="quote",
)
assert program.testimonials == testimonials_page
assert testimonials_page.heading == "heading"
assert testimonials_page.subhead == "subhead"
for testimonial in testimonials_page.items: # pylint: disable=not-an-iterable
assert testimonial.value.get("name") == "name"
assert testimonial.value.get("title") == "title"
assert testimonial.value.get("image").title == "image"
assert testimonial.value.get("quote") == "quote"
4 changes: 3 additions & 1 deletion courses/templates/course_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
{% if course.outcomes %} {% include "partials/learning-outcomes.html" %} {% endif %}
{% if course.who_should_enroll %} {% include "partials/target-audience.html" %} {% endif %}
{% if course.techniques %} {% include "partials/learning-techniques.html" %} {% endif %}
{% if course.testimonials %} {% include "partials/testimonial-carousel.html" with testimonials=course.testimonials %} {% endif %}
{% if course.program and course.program.course_lineup %}
{% include "partials/course-carousel.html" with program=course.program %}
{% endif %}
{% if course.faqs %} {% include "partials/faqs.html" %} {% endif %}
{% if course.for_teams %} {% include "partials/for-teams.html" %} {% endif %}
{% if course.faqs %} {% include "partials/faqs.html" %} {% endif %}

</div>
{% endblock %}
2 changes: 1 addition & 1 deletion courses/templates/partials/metadata-tiles.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
{% if course.current_price %}
<li>
<span class="title">PRICE</span>
<span class="text">${{ course.current_price }}</span>
<span class="text">${{ course.current_price|floatformat:"0" }}</span>
</li>
{% endif %}
</ul>
Expand Down
49 changes: 49 additions & 0 deletions courses/templates/partials/testimonial-carousel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% load wagtailimages_tags wagtailcore_tags %}
<div class="learners-block">
<div class="container">
<div class="head">
<h1>{{ testimonials.heading }}</h1>
<h3>{{ testimonials.subhead }}</h3>
</div>
<div class="learners-slider">
{% for testimonial in testimonials.items %}
<div class="slide">
<div class="slide-holder">
{% if testimonial.value.image %}
{% image testimonial.value.image fill-75x75 %}
{% endif %}
<h2>{{ testimonial.value.name }}, {{ testimonial.value.title }}</h2>
<p>{{ testimonial.value.quote|truncatechars:150 }}</p>
<a data-toggle="modal" href="#testimonial-{{ forloop.counter }}" class="read-more">Continue Reading</a>
</div>
</div>
{% endfor %}
</div>
</div>
{% for testimonial in testimonials.items %}
<div class="modal fade" id="testimonial-{{ forloop.counter }}" role="dialog">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-body">
<div class="container no-gutters px-0">
<div class="d-flex flex-row-reverse">
<a class="text-dark" href="testimonial-{{ forloop.counter }}" data-dismiss="modal"><span class="icon-close-outline" aria-hidden="true"></span></a>
</div>
</div>
<div class="container px-4">
<div class="row py-2">
{% image testimonial.value.image fill-100x100 class="border rounded-circle headshot-image" %}
</div>
<div class="row mb-4">
<h2 class="modal-title text-uppercase">{{ testimonial.value.name }}, {{ testimonial.value.title }}</h2>
</div>
<div class="row quote-container">
<p>{{ testimonial.value.quote }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
Binary file modified static/fonts/icomoon.eot
Binary file not shown.
3 changes: 2 additions & 1 deletion static/fonts/icomoon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified static/fonts/icomoon.ttf
Binary file not shown.
Binary file modified static/fonts/icomoon.woff
Binary file not shown.
Binary file added static/images/testimonial-carousel-bg.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e7981aa

Please sign in to comment.