Files

114 lines
3.9 KiB
Python

"""
Utilities for course updates.
"""
import hashlib
from datetime import datetime
from lms.djangoapps.courseware.courses import get_course_info_section_block
from openedx.core.djangoapps.user_api.course_tag.api import get_course_tag, set_course_tag
STATUS_VISIBLE = 'visible'
STATUS_DELETED = 'deleted'
VIEW_WELCOME_MESSAGE_KEY = 'view-welcome-message'
def _calculate_update_hash(update):
"""
Returns a hash of the content of a course update. Does not need to be secure.
"""
hasher = hashlib.md5()
hasher.update(update['content'].encode('utf-8'))
return hasher.hexdigest()
def _get_dismissed_hashes(user, course_key):
"""
Returns a list of dismissed hashes, or None if all updates have been dismissed.
"""
view_welcome_message = get_course_tag(user, course_key, VIEW_WELCOME_MESSAGE_KEY)
if view_welcome_message == 'False': # legacy value, which dismisses all updates
return None
return view_welcome_message.split(',') if view_welcome_message else []
def _add_dismissed_hash(user, course_key, new_hash):
"""
Add a new hash to the list of previously dismissed updates.
Overwrites a 'False' value with the current hash. Though we likely won't end up in that situation, since
a 'False' value will never show the update to the user to dismiss in the first place.
"""
hashes = _get_dismissed_hashes(user, course_key) or []
hashes.append(new_hash)
set_course_tag(user, course_key, VIEW_WELCOME_MESSAGE_KEY, ','.join(hashes))
def _safe_parse_date(date):
"""
Since this is used solely for ordering purposes, use today's date as a default
"""
try:
return datetime.strptime(date, '%B %d, %Y')
except ValueError: # occurs for ill-formatted date values
return datetime.today()
def get_ordered_updates(request, course):
"""
Returns all public course updates in reverse chronological order, including dismissed ones.
"""
info_block = get_course_info_section_block(request, request.user, course, 'updates')
if not info_block:
return []
info_block = getattr(info_block, '_xmodule', info_block)
ordered_updates = [update for update in info_block.items if update.get('status') == STATUS_VISIBLE]
ordered_updates.sort(
key=lambda item: (_safe_parse_date(item['date']), item['id']),
reverse=True
)
for update in ordered_updates:
update['content'] = info_block.runtime.service(info_block, "replace_urls").replace_urls(update['content'])
return ordered_updates
def get_current_update_for_user(request, course):
"""
Returns the current (most recent) course update HTML.
Some rules about when we show updates:
- If the newest update has not been dismissed yet, it gets returned.
- If the newest update has been dismissed, we will return None.
- Will return a previously-dismissed newest update if it has been edited since being dismissed.
- If a current update is deleted and an already dismissed update is now the newest one, we don't want to show that.
"""
updates = get_ordered_updates(request, course)
if not updates:
return None
dismissed_hashes = _get_dismissed_hashes(request.user, course.id)
if dismissed_hashes is None: # all updates dismissed
return None
update_hash = _calculate_update_hash(updates[0])
if update_hash in dismissed_hashes: # pylint: disable=unsupported-membership-test
return None
return updates[0]['content']
def dismiss_current_update_for_user(request, course):
"""
Marks the current course update for this user as dismissed.
See get_current_update_for_user for what "current course update" means in practice.
"""
updates = get_ordered_updates(request, course)
if not updates:
return None
update_hash = _calculate_update_hash(updates[0])
_add_dismissed_hash(request.user, course.id, update_hash)