Skip to content

Commit

Permalink
1676 design new catalog page (#1818)
Browse files Browse the repository at this point in the history
* hold, without course items

* CSS cleanup

* Course runs pulled in

* Hold, computer acting funky

* 3 per row, program type

* Reduce reused code, add function comments

* Improved comments and variable naming

* Update program serializer and tests

* Handle CourseRun date on cards

* Course cards link to course pages

* Formatting

* sticky dept filter and header

* Pixar level animating

* Remove is_catalog_visible property

* Filtering works, start date displays

* Hold animation for tabs

* Tab animate as expected now, course count is wonky

* Start date correctly displayed

* catalog item number animation

* css

* Filtering by department/topic works

* Formatting department buttons

* formatting

* Fix courses serializers tests

* Format courses serializers

* Remove out of date test

* Change topics model to departments

* format migration

* Hold messed up serializer

* remove item count and filter from mobile view

* holding, mostly good but glitchy mobile tab animation

* hold, mobile view

* Slide in menu works

* Format and fix font color of mobile filter overlay

* Large number of changes

Added departments to Program model.  Added animation for department filtering items.  Tested accessibility.

* course Serializer tests updated

* Fix issue with images not updating quickly

* infinite scroll

* Added comments

* Added more comments

* hold, without course items

* CSS cleanup

* Course runs pulled in

* Hold, computer acting funky

* 3 per row, program type

* Reduce reused code, add function comments

* Improved comments and variable naming

* Update program serializer and tests

* Handle CourseRun date on cards

* Course cards link to course pages

* Formatting

* sticky dept filter and header

* Pixar level animating

* Remove is_catalog_visible property

* Filtering works, start date displays

* Hold animation for tabs

* Tab animate as expected now, course count is wonky

* Start date correctly displayed

* catalog item number animation

* css

* Filtering by department/topic works

* Formatting department buttons

* formatting

* Fix courses serializers tests

* Format courses serializers

* Remove out of date test

* Change topics model to departments

* format migration

* Hold messed up serializer

* remove item count and filter from mobile view

* holding, mostly good but glitchy mobile tab animation

* hold, mobile view

* Slide in menu works

* Format and fix font color of mobile filter overlay

* Large number of changes

Added departments to Program model.  Added animation for department filtering items.  Tested accessibility.

* course Serializer tests updated

* Fix issue with images not updating quickly

* infinite scroll

* Added comments

* Added more comments

* Clean up

* Added javascript tests

* hold posthog

* Fix

* Rename migration

* Fix error when switching between tabs

* Mobile view fixes, test for bouncing between tabs

* Remove unused page

* Format and fix test

* Update admin page for departments

* Now making another call when at bottom of page

* Address code review comments

* Add ordering to the program and course models

* Format

* Only paginate when page param is provided

* Remove out of date tests

* Add a better comment on the pagination logic

* Update catalog item image formatting

* add feature flags

* Fix tests

* Fix tests

* Repair javascript test, improve serializer test fix

* Fix flow issue

* Add feature flag check into view

* Fix test for 404

* Fix 404 handling from catalog view

* Fix mistake
  • Loading branch information
collinpreston authored Aug 25, 2023
1 parent 24dc3d3 commit 366b675
Show file tree
Hide file tree
Showing 26 changed files with 2,389 additions and 204 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ UWSGI_THREADS=5
SENTRY_DSN=
MITX_ONLINE_BASE_URL=http://mitxonline.odl.local:8013
MITX_ONLINE_ADMIN_CLIENT_ID=refine-local-client-id
MITX_ONLINE_ADMIN_BASE_URL=http://mitxonline.odl.local:8016
MITX_ONLINE_ADMIN_BASE_URL=http://mitxonline.odl.local:8016
POSTHOG_API_TOKEN=
POSTHOG_API_HOST=https://app.posthog.com/
2 changes: 1 addition & 1 deletion courses/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
- **Title**: the title of the course, e.g. `Leading Change in Organizations`
- **Readable id**: the id, e.g. `course-v1:xPRO+LASERx3`
- **Live**: Set to live when ready to launch. Note that you should not check this box for courses that will not need a catalog page, e.g. a SPOC or a private course.
- **Topics**: select the applicable topic(s)
- **Departments**: select the applicable department(s)



Expand Down
16 changes: 8 additions & 8 deletions courses/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
CourseRunEnrollmentAudit,
CourseRunGrade,
CourseRunGradeAudit,
CourseTopic,
Department,
LearnerProgramRecordShare,
PaidCourseRun,
PartnerSchool,
Expand All @@ -40,7 +40,7 @@ class ProgramAdmin(admin.ModelAdmin):
form = ProgramAdminForm
search_fields = ["title", "readable_id", "program_type"]
list_display = ("id", "title", "readable_id", "program_type")
list_filter = ["live", "program_type"]
list_filter = ["live", "program_type", "departments"]


class ProgramRunAdmin(admin.ModelAdmin):
Expand All @@ -56,13 +56,13 @@ class CourseAdmin(admin.ModelAdmin):
"""Admin for Course"""

model = Course
search_fields = ["title", "topics__name", "readable_id"]
search_fields = ["title", "departments__name", "readable_id"]
list_display = (
"id",
"title",
"readable_id",
)
list_filter = ["live", "topics"]
list_filter = ["live", "departments"]

formfield_overrides = {
models.CharField: {"widget": TextInput(attrs={"size": "80"})}
Expand Down Expand Up @@ -349,10 +349,10 @@ def has_delete_permission(self, request, obj=None):
return False


class CourseTopicAdmin(admin.ModelAdmin):
"""Admin for CourseTopic"""
class DepartmentAdmin(admin.ModelAdmin):
"""Admin for Department"""

model = CourseTopic
model = Department


class BlockedCountryAdmin(TimestampedModelAdmin):
Expand Down Expand Up @@ -493,7 +493,7 @@ class RelatedProgramAdmin(admin.ModelAdmin):
admin.site.register(CourseRunEnrollmentAudit, CourseRunEnrollmentAuditAdmin)
admin.site.register(CourseRunGrade, CourseRunGradeAdmin)
admin.site.register(CourseRunGradeAudit, CourseRunGradeAuditAdmin)
admin.site.register(CourseTopic, CourseTopicAdmin)
admin.site.register(Department, DepartmentAdmin)
admin.site.register(BlockedCountry, BlockedCountryAdmin)
admin.site.register(PaidCourseRun, PaidCourseRunAdmin)
admin.site.register(CourseRunCertificate, CourseRunCertificateAdmin)
Expand Down
1 change: 1 addition & 0 deletions courses/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ class Meta:
"title",
"readable_id",
"program_type",
"departments",
"live",
"requirements",
]
Expand Down
30 changes: 30 additions & 0 deletions courses/migrations/0041_rename_topics_add_to_programs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 3.2.18 on 2023-08-14 16:56

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("courses", "0040_populate_none_letter_grades_for_DEDP"),
]

operations = [
migrations.RenameModel(
old_name="CourseTopic",
new_name="Department",
),
migrations.RemoveField(
model_name="course",
name="topics",
),
migrations.AddField(
model_name="course",
name="departments",
field=models.ManyToManyField(blank=True, to="courses.Department"),
),
migrations.AddField(
model_name="program",
name="departments",
field=models.ManyToManyField(blank=True, to="courses.Department"),
),
]
22 changes: 22 additions & 0 deletions courses/migrations/0042_add_ordering_to_course_and_program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.20 on 2023-08-22 15:30

from django.db import migrations


class Migration(migrations.Migration):
"""Adds ordering to the Course and Program model in order to support pagination."""

dependencies = [
("courses", "0041_rename_topics_add_to_programs"),
]

operations = [
migrations.AlterModelOptions(
name="course",
options={"ordering": ["id"]},
),
migrations.AlterModelOptions(
name="program",
options={"ordering": ["id"]},
),
]
52 changes: 19 additions & 33 deletions courses/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,23 @@ def get_queryset(self):
)


class Department(TimestampedModel):
"""
Departments.
"""

name = models.CharField(max_length=128, unique=True)

def __str__(self):
return self.name


class Program(TimestampedModel, ValidateOnSaveMixin):
"""Model for a course program"""

class Meta:
ordering = ["id"]

objects = ProgramQuerySet.as_manager()
title = models.CharField(max_length=255)
readable_id = models.CharField(
Expand All @@ -118,6 +132,7 @@ class Program(TimestampedModel, ValidateOnSaveMixin):
blank=True,
null=True,
)
departments = models.ManyToManyField(Department, blank=True)

@cached_property
def page(self):
Expand All @@ -129,12 +144,6 @@ def num_courses(self):
"""Gets the number of courses in this program"""
return len(self.courses)

@property
def is_catalog_visible(self):
"""Returns True if this program should be shown on in the catalog"""
just_courses = [course[0] for course in self.courses]
return any(course.is_catalog_visible for course in just_courses)

@property
def first_unexpired_run(self):
"""Gets the earliest unexpired CourseRun"""
Expand Down Expand Up @@ -416,27 +425,19 @@ def __str__(self):
return f"{self.program.readable_id} | {self.program.title}"


class CourseTopic(TimestampedModel):
"""
Topics for all courses (e.g. "History")
"""

name = models.CharField(max_length=128, unique=True)

def __str__(self):
return self.name


class Course(TimestampedModel, ValidateOnSaveMixin):
"""Model for a course"""

class Meta:
ordering = ["id"]

objects = CourseQuerySet.as_manager()
title = models.CharField(max_length=255)
readable_id = models.CharField(
max_length=255, unique=True, validators=[validate_url_path_field]
)
live = models.BooleanField(default=False)
topics = models.ManyToManyField(CourseTopic, blank=True)
departments = models.ManyToManyField(Department, blank=True)
flexible_prices = GenericRelation(
"flexiblepricing.FlexiblePrice",
object_id_field="courseware_object_id",
Expand Down Expand Up @@ -475,21 +476,6 @@ def active_products(self):
relevant_run.products.filter(is_active=True).all() if relevant_run else None
)

@property
def is_catalog_visible(self):
"""Returns True if this course should be shown on in the catalog"""
now = now_in_utc()
# NOTE: This is implemented with courseruns.all() to allow for prefetch_related optimization.
return any(
course_run
for course_run in self.courseruns.all()
if course_run.live
and (
(course_run.start_date and course_run.start_date > now)
or (course_run.enrollment_end and course_run.enrollment_end > now)
)
)

@property
def first_unexpired_run(self):
"""
Expand Down
47 changes: 0 additions & 47 deletions courses/models_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,30 +61,6 @@ def test_program_num_courses():
assert program.num_courses == 2


def test_program_is_catalog_visible():
"""
is_catalog_visible should return True if a program has any course run that has a start date or enrollment end
date in the future
"""
program = ProgramFactory.create()
runs = CourseRunFactory.create_batch(2, past_start=True, past_enrollment_end=True)
for run in runs:
program.add_requirement(run.course)

assert program.is_catalog_visible is False

now = now_in_utc()
run = runs[0]
run.start_date = now + timedelta(hours=1)
run.save()
assert program.is_catalog_visible is True

run.start_date = now - timedelta(hours=1)
run.enrollment_end = now + timedelta(hours=1)
run.save()
assert program.is_catalog_visible is True


def test_courseware_url(settings):
"""Test that the courseware_url property yields the correct values"""
settings.OPENEDX_BASE_REDIRECT_URL = "http://example.com"
Expand Down Expand Up @@ -307,29 +283,6 @@ def test_program_first_unexpired_run():
assert program.first_unexpired_run == first_run


def test_course_is_catalog_visible():
"""
is_catalog_visible should return True if a course has any course run that has a start date or enrollment end
date in the future
"""
course = CourseFactory.create()
runs = CourseRunFactory.create_batch(
2, course=course, past_start=True, past_enrollment_end=True
)
assert course.is_catalog_visible is False

now = now_in_utc()
run = runs[0]
run.start_date = now + timedelta(hours=1)
run.save()
assert course.is_catalog_visible is True

run.start_date = now - timedelta(hours=1)
run.enrollment_end = now + timedelta(hours=1)
run.save()
assert course.is_catalog_visible is True


def test_course_unexpired_runs():
"""unexpired_runs should return expected value"""
course = CourseFactory.create()
Expand Down
Loading

0 comments on commit 366b675

Please sign in to comment.