Merge branch 'master' into discussion2
This commit is contained in:
@@ -33,6 +33,7 @@ def check_course(course_id, course_must_be_open=True, course_required=True):
|
||||
try:
|
||||
course_loc = CourseDescriptor.id_to_location(course_id)
|
||||
course = modulestore().get_item(course_loc)
|
||||
|
||||
except (KeyError, ItemNotFoundError):
|
||||
raise Http404("Course not found.")
|
||||
|
||||
|
||||
@@ -10,37 +10,17 @@ from lxml import etree
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from xmodule.modulestore.xml import XMLModuleStore
|
||||
|
||||
from xmodule.errortracker import make_error_tracker
|
||||
|
||||
def traverse_tree(course):
|
||||
'''Load every descriptor in course. Return bool success value.'''
|
||||
queue = [course]
|
||||
while len(queue) > 0:
|
||||
node = queue.pop()
|
||||
# print '{0}:'.format(node.location)
|
||||
# if 'data' in node.definition:
|
||||
# print '{0}'.format(node.definition['data'])
|
||||
queue.extend(node.get_children())
|
||||
|
||||
return True
|
||||
|
||||
def make_logging_error_handler():
|
||||
'''Return a tuple (handler, error_list), where
|
||||
the handler appends the message and any exc_info
|
||||
to the error_list on every call.
|
||||
'''
|
||||
errors = []
|
||||
|
||||
def error_handler(msg, exc_info=None):
|
||||
'''Log errors'''
|
||||
if exc_info is None:
|
||||
if sys.exc_info() != (None, None, None):
|
||||
exc_info = sys.exc_info()
|
||||
|
||||
errors.append((msg, exc_info))
|
||||
|
||||
return (error_handler, errors)
|
||||
|
||||
|
||||
def export(course, export_dir):
|
||||
"""Export the specified course to course_dir. Creates dir if it doesn't exist.
|
||||
@@ -73,14 +53,14 @@ def import_with_checks(course_dir, verbose=True):
|
||||
data_dir = course_dir.dirname()
|
||||
course_dirs = [course_dir.basename()]
|
||||
|
||||
(error_handler, errors) = make_logging_error_handler()
|
||||
(error_tracker, errors) = make_error_tracker()
|
||||
# No default class--want to complain if it doesn't find plugins for any
|
||||
# module.
|
||||
modulestore = XMLModuleStore(data_dir,
|
||||
default_class=None,
|
||||
eager=True,
|
||||
course_dirs=course_dirs,
|
||||
error_handler=error_handler)
|
||||
error_tracker=error_tracker)
|
||||
|
||||
def str_of_err(tpl):
|
||||
(msg, exc_info) = tpl
|
||||
@@ -143,6 +123,7 @@ def check_roundtrip(course_dir):
|
||||
# dircmp doesn't do recursive diffs.
|
||||
# diff = dircmp(course_dir, export_dir, ignore=[], hide=[])
|
||||
print "======== Roundtrip diff: ========="
|
||||
sys.stdout.flush() # needed to make diff appear in the right place
|
||||
os.system("diff -r {0} {1}".format(course_dir, export_dir))
|
||||
print "======== ideally there is no diff above this ======="
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@ def get_module(user, request, location, student_module_cache, position=None):
|
||||
# a module is coming through get_html and is therefore covered
|
||||
# by the replace_static_urls code below
|
||||
replace_urls=replace_urls,
|
||||
is_staff=user.is_staff,
|
||||
)
|
||||
# pass position specified in URL to module through ModuleSystem
|
||||
system.set('position', position)
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import copy
|
||||
import json
|
||||
from path import path
|
||||
import os
|
||||
|
||||
from pprint import pprint
|
||||
from nose import SkipTest
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from mock import patch, Mock
|
||||
from override_settings import override_settings
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from path import path
|
||||
from mock import patch, Mock
|
||||
from override_settings import override_settings
|
||||
|
||||
from student.models import Registration
|
||||
from django.contrib.auth.models import User
|
||||
from student.models import Registration
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
import xmodule.modulestore.django
|
||||
@@ -189,11 +190,12 @@ class RealCoursesLoadTestCase(PageLoader):
|
||||
xmodule.modulestore.django._MODULESTORES = {}
|
||||
xmodule.modulestore.django.modulestore().collection.drop()
|
||||
|
||||
# TODO: Disabled test for now.. Fix once things are cleaned up.
|
||||
def Xtest_real_courses_loads(self):
|
||||
def test_real_courses_loads(self):
|
||||
'''See if any real courses are available at the REAL_DATA_DIR.
|
||||
If they are, check them.'''
|
||||
|
||||
# TODO: Disabled test for now.. Fix once things are cleaned up.
|
||||
raise SkipTest
|
||||
# TODO: adjust staticfiles_dirs
|
||||
if not os.path.isdir(REAL_DATA_DIR):
|
||||
# No data present. Just pass.
|
||||
|
||||
@@ -31,6 +31,7 @@ from multicourse import multicourse_settings
|
||||
from django_comment_client.utils import get_discussion_title
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
from xmodule.modulestore.exceptions import InvalidLocationError, ItemNotFoundError, NoPathToItem
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
@@ -70,14 +71,20 @@ def user_groups(user):
|
||||
|
||||
|
||||
def format_url_params(params):
|
||||
return [urllib.quote(string.replace(' ', '_')) for string in params]
|
||||
return [urllib.quote(string.replace(' ', '_'))
|
||||
if string is not None else None
|
||||
for string in params]
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous
|
||||
def courses(request):
|
||||
# TODO: Clean up how 'error' is done.
|
||||
courses = sorted(modulestore().get_courses(), key=lambda course: course.number)
|
||||
|
||||
# filter out any courses that errored.
|
||||
courses = [c for c in modulestore().get_courses()
|
||||
if isinstance(c, CourseDescriptor)]
|
||||
courses = sorted(courses, key=lambda course: course.number)
|
||||
universities = defaultdict(list)
|
||||
for course in courses:
|
||||
universities[course.org].append(course)
|
||||
@@ -199,34 +206,57 @@ def index(request, course_id, chapter=None, section=None,
|
||||
chapter = clean(chapter)
|
||||
section = clean(section)
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'accordion': render_accordion(request, course, chapter, section),
|
||||
'COURSE_TITLE': course.title,
|
||||
'course': course,
|
||||
'init': '',
|
||||
'content': ''
|
||||
}
|
||||
try:
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'accordion': render_accordion(request, course, chapter, section),
|
||||
'COURSE_TITLE': course.title,
|
||||
'course': course,
|
||||
'init': '',
|
||||
'content': ''
|
||||
}
|
||||
|
||||
look_for_module = chapter is not None and section is not None
|
||||
if look_for_module:
|
||||
# TODO (cpennington): Pass the right course in here
|
||||
look_for_module = chapter is not None and section is not None
|
||||
if look_for_module:
|
||||
# TODO (cpennington): Pass the right course in here
|
||||
|
||||
section_descriptor = get_section(course, chapter, section)
|
||||
if section_descriptor is not None:
|
||||
student_module_cache = StudentModuleCache(request.user,
|
||||
section_descriptor)
|
||||
module, _, _, _ = get_module(request.user, request,
|
||||
section_descriptor.location,
|
||||
student_module_cache)
|
||||
context['content'] = module.get_html()
|
||||
section_descriptor = get_section(course, chapter, section)
|
||||
if section_descriptor is not None:
|
||||
student_module_cache = StudentModuleCache(request.user,
|
||||
section_descriptor)
|
||||
module, _, _, _ = get_module(request.user, request,
|
||||
section_descriptor.location,
|
||||
student_module_cache)
|
||||
context['content'] = module.get_html()
|
||||
else:
|
||||
log.warning("Couldn't find a section descriptor for course_id '{0}',"
|
||||
"chapter '{1}', section '{2}'".format(
|
||||
course_id, chapter, section))
|
||||
else:
|
||||
log.warning("Couldn't find a section descriptor for course_id '{0}',"
|
||||
"chapter '{1}', section '{2}'".format(
|
||||
course_id, chapter, section))
|
||||
if request.user.is_staff:
|
||||
# Add a list of all the errors...
|
||||
context['course_errors'] = modulestore().get_item_errors(course.location)
|
||||
|
||||
result = render_to_response('courseware.html', context)
|
||||
except:
|
||||
# In production, don't want to let a 500 out for any reason
|
||||
if settings.DEBUG:
|
||||
raise
|
||||
else:
|
||||
log.exception("Error in index view: user={user}, course={course},"
|
||||
" chapter={chapter} section={section}"
|
||||
"position={position}".format(
|
||||
user=request.user,
|
||||
course=course,
|
||||
chapter=chapter,
|
||||
section=section,
|
||||
position=position
|
||||
))
|
||||
try:
|
||||
result = render_to_response('courseware-error.html', {})
|
||||
except:
|
||||
result = HttpResponse("There was an unrecoverable error")
|
||||
|
||||
result = render_to_response('courseware.html', context)
|
||||
return result
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -249,7 +279,7 @@ def jump_to(request, location):
|
||||
|
||||
# Complain if there's not data for this location
|
||||
try:
|
||||
(course_id, chapter, section, position) = modulestore().path_to_location(location)
|
||||
(course_id, chapter, section, position) = path_to_location(modulestore(), location)
|
||||
except ItemNotFoundError:
|
||||
raise Http404("No data at this location: {0}".format(location))
|
||||
except NoPathToItem:
|
||||
|
||||
@@ -3,18 +3,28 @@ from django.views.decorators.http import require_POST
|
||||
from django.http import HttpResponse
|
||||
from django.utils import simplejson
|
||||
from django.core.context_processors import csrf
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from mitxmako.shortcuts import render_to_response, render_to_string
|
||||
from courseware.courses import check_course
|
||||
|
||||
import comment_client
|
||||
import dateutil
|
||||
from dateutil.tz import tzlocal
|
||||
from datehelper import time_ago_in_words
|
||||
|
||||
from django_comment_client.utils import get_categorized_discussion_info
|
||||
from urllib import urlencode
|
||||
|
||||
import json
|
||||
import comment_client
|
||||
import dateutil
|
||||
|
||||
|
||||
THREADS_PER_PAGE = 20
|
||||
PAGES_NEARBY_DELTA = 2
|
||||
|
||||
class HtmlResponse(HttpResponse):
|
||||
def __init__(self, html=''):
|
||||
super(HtmlResponse, self).__init__(html, content_type='text/plain')
|
||||
|
||||
def render_accordion(request, course, discussion_id):
|
||||
|
||||
@@ -29,29 +39,58 @@ def render_accordion(request, course, discussion_id):
|
||||
|
||||
return render_to_string('discussion/_accordion.html', context)
|
||||
|
||||
def render_discussion(request, course_id, threads, discussion_id=None, with_search_bar=True, search_text='', template='discussion/_inline.html'):
|
||||
def render_discussion(request, course_id, threads, discussion_id=None, with_search_bar=True,
|
||||
search_text='', discussion_type='inline', page=1, num_pages=None,
|
||||
per_page=THREADS_PER_PAGE):
|
||||
template = {
|
||||
'inline': 'discussion/_inline.html',
|
||||
'forum': 'discussion/_forum.html',
|
||||
}[discussion_type]
|
||||
|
||||
def _url_for_inline_page(page, per_page):
|
||||
raw_url = reverse('django_comment_client.forum.views.inline_discussion', args=[course_id, discussion_id])
|
||||
return raw_url + '?' + urlencode({'page': page, 'per_page': per_page})
|
||||
|
||||
def _url_for_forum_page(page, per_page):
|
||||
raw_url = reverse('django_comment_client.forum.views.forum_form_discussion', args=[course_id, discussion_id])
|
||||
return raw_url + '?' + urlencode({'page': page, 'per_page': per_page})
|
||||
|
||||
url_for_page = {
|
||||
'inline': _url_for_inline_page,
|
||||
'forum': _url_for_forum_page,
|
||||
}[discussion_type]
|
||||
|
||||
|
||||
context = {
|
||||
'threads': threads,
|
||||
'discussion_id': discussion_id,
|
||||
'search_bar': '' if not with_search_bar \
|
||||
else render_search_bar(request, course_id, discussion_id, text=search_text),
|
||||
'user_info': comment_client.get_user_info(request.user.id, raw=True),
|
||||
'tags': comment_client.get_threads_tags(raw=True),
|
||||
'course_id': course_id,
|
||||
'request': request,
|
||||
'page': page,
|
||||
'per_page': per_page,
|
||||
'num_pages': num_pages,
|
||||
'pages_nearby_delta': PAGES_NEARBY_DELTA,
|
||||
'discussion_type': discussion_type,
|
||||
'url_for_page': url_for_page,
|
||||
}
|
||||
return render_to_string(template, context)
|
||||
|
||||
def render_inline_discussion(*args, **kwargs):
|
||||
return render_discussion(template='discussion/_inline.html', *args, **kwargs)
|
||||
|
||||
return render_discussion(discussion_type='inline', *args, **kwargs)
|
||||
|
||||
def render_forum_discussion(*args, **kwargs):
|
||||
return render_discussion(template='discussion/_forum.html', *args, **kwargs)
|
||||
return render_discussion(discussion_type='forum', *args, **kwargs)
|
||||
|
||||
# discussion per page is fixed for now
|
||||
def inline_discussion(request, course_id, discussion_id):
|
||||
threads = comment_client.get_threads(discussion_id, recursive=False)
|
||||
html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id)
|
||||
return HttpResponse(html, content_type="text/plain")
|
||||
page = request.GET.get('page', 1)
|
||||
threads, page, per_page, num_pages = comment_client.get_threads(discussion_id, recursive=False, page=page, per_page=THREADS_PER_PAGE)
|
||||
html = render_inline_discussion(request, course_id, threads, discussion_id=discussion_id, num_pages=num_pages, page=page, per_page=per_page)
|
||||
return HtmlResponse(html)
|
||||
|
||||
def render_search_bar(request, course_id, discussion_id=None, text=''):
|
||||
if not discussion_id:
|
||||
@@ -66,18 +105,29 @@ def render_search_bar(request, course_id, discussion_id=None, text=''):
|
||||
def forum_form_discussion(request, course_id, discussion_id):
|
||||
|
||||
course = check_course(course_id)
|
||||
|
||||
search_text = request.GET.get('text', '')
|
||||
page = request.GET.get('page', 1)
|
||||
|
||||
if len(search_text) > 0:
|
||||
threads = comment_client.search_threads({'text': search_text, 'commentable_id': discussion_id})
|
||||
threads, page, per_page, num_pages = comment_client.search_threads({
|
||||
'text': search_text,
|
||||
'commentable_id': discussion_id,
|
||||
'course_id': course_id,
|
||||
'page': page,
|
||||
'per_page': THREADS_PER_PAGE,
|
||||
})
|
||||
else:
|
||||
threads = comment_client.get_threads(discussion_id, recursive=False)
|
||||
threads, page, per_page, num_pages = comment_client.get_threads(discussion_id, recursive=False, page=page, per_page=THREADS_PER_PAGE)
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
'content': render_forum_discussion(request, course_id, threads, discussion_id=discussion_id, search_text=search_text),
|
||||
'content': render_forum_discussion(request, course_id, threads,
|
||||
discussion_id=discussion_id,
|
||||
search_text=search_text,
|
||||
num_pages=num_pages,
|
||||
per_page=per_page,
|
||||
page=page),
|
||||
'accordion': render_accordion(request, course, discussion_id),
|
||||
}
|
||||
|
||||
@@ -102,7 +152,6 @@ def render_single_thread(request, course_id, thread_id):
|
||||
'thread': thread,
|
||||
'user_info': comment_client.get_user_info(request.user.id, raw=True),
|
||||
'annotated_content_info': json.dumps(get_annotated_content_info(thread=thread, user_id=request.user.id)),
|
||||
'tags': comment_client.get_threads_tags(raw=True),
|
||||
'course_id': course_id,
|
||||
'request': request,
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ MITX_FEATURES['DISABLE_START_DATES'] = True
|
||||
|
||||
WIKI_ENABLED = True
|
||||
|
||||
LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||
LOGGING = get_logger_config(ENV_ROOT / "log",
|
||||
logging_env="dev",
|
||||
tracking_filename="tracking.log",
|
||||
debug=True)
|
||||
@@ -30,7 +30,7 @@ DATABASES = {
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# This is the cache used for most things. Askbot will not work without a
|
||||
# functioning cache -- it relies on caching to load its settings in places.
|
||||
# In staging/prod envs, the sessions also live here.
|
||||
'default': {
|
||||
@@ -52,11 +52,14 @@ CACHES = {
|
||||
}
|
||||
}
|
||||
|
||||
# Make the keyedcache startup warnings go away
|
||||
CACHE_TIMEOUT = 0
|
||||
|
||||
# Dummy secret key for dev
|
||||
SECRET_KEY = '85920908f28904ed733fe576320db18cabd7b6cd'
|
||||
|
||||
################################ DEBUG TOOLBAR #################################
|
||||
INSTALLED_APPS += ('debug_toolbar',)
|
||||
INSTALLED_APPS += ('debug_toolbar',)
|
||||
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
|
||||
INTERNAL_IPS = ('127.0.0.1',)
|
||||
|
||||
@@ -71,8 +74,8 @@ DEBUG_TOOLBAR_PANELS = (
|
||||
'debug_toolbar.panels.logger.LoggingPanel',
|
||||
|
||||
# Enabling the profiler has a weird bug as of django-debug-toolbar==0.9.4 and
|
||||
# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
|
||||
# hit twice). So you can uncomment when you need to diagnose performance
|
||||
# Django=1.3.1/1.4 where requests to views get duplicated (your method gets
|
||||
# hit twice). So you can uncomment when you need to diagnose performance
|
||||
# problems, but you shouldn't leave it on.
|
||||
# 'debug_toolbar.panels.profiling.ProfilingDebugPanel',
|
||||
)
|
||||
|
||||
@@ -15,8 +15,9 @@ class CommentClientUnknownError(CommentClientError):
|
||||
def delete_threads(commentable_id, *args, **kwargs):
|
||||
return _perform_request('delete', _url_for_commentable_threads(commentable_id), *args, **kwargs)
|
||||
|
||||
def get_threads(commentable_id, recursive=False, *args, **kwargs):
|
||||
return _perform_request('get', _url_for_threads(commentable_id), {'recursive': recursive}, *args, **kwargs)
|
||||
def get_threads(commentable_id, recursive=False, page=1, per_page=20, *args, **kwargs):
|
||||
response = _perform_request('get', _url_for_threads(commentable_id), {'recursive': recursive, 'page': page, 'per_page': per_page}, *args, **kwargs)
|
||||
return response['collection'], response['page'], response['per_page'], response['num_pages']
|
||||
|
||||
def get_threads_tags(*args, **kwargs):
|
||||
return _perform_request('get', _url_for_threads_tags(), {}, *args, **kwargs)
|
||||
@@ -98,6 +99,8 @@ def unsubscribe_commentable(user_id, commentable_id, *args, **kwargs):
|
||||
return unsubscribe(user_id, {'source_type': 'other', 'source_id': commentable_id})
|
||||
|
||||
def search_threads(attributes, *args, **kwargs):
|
||||
default_attributes = {'page': 1, 'per_page': 20}
|
||||
attributes = dict(default_attributes.items() + attributes.items())
|
||||
return _perform_request('get', _url_for_search_threads(), attributes, *args, **kwargs)
|
||||
|
||||
def _perform_request(method, url, data_or_params=None, *args, **kwargs):
|
||||
|
||||
@@ -276,7 +276,12 @@ $discussion_input_width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discussion-paginator {
|
||||
margin-top: 40px;
|
||||
div {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -52,6 +52,19 @@
|
||||
|
||||
<section class="course-content">
|
||||
${content}
|
||||
|
||||
% if course_errors is not UNDEFINED:
|
||||
<h2>Course errors</h2>
|
||||
<div id="course-errors">
|
||||
<ul>
|
||||
% for (msg, err) in course_errors:
|
||||
<li>${msg}
|
||||
<ul><li><pre>${err}</pre></li></ul>
|
||||
</li>
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
% endif
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
${search_bar}
|
||||
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</div>
|
||||
</div>
|
||||
<%include file="_paginator.html" />
|
||||
% for thread in threads:
|
||||
${renderer.render_thread(course_id, thread, edit_thread=False, show_comments=False)}
|
||||
% endfor
|
||||
<%include file="_paginator.html" />
|
||||
</section>
|
||||
|
||||
<%!
|
||||
@@ -21,5 +23,4 @@
|
||||
<script type="text/javascript">
|
||||
var $$user_info = JSON.parse('${user_info | escape_quotes}');
|
||||
var $$course_id = "${course_id}";
|
||||
var $$tags = JSON.parse("${tags | escape_quotes}");
|
||||
</script>
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
${search_bar}
|
||||
<div class="discussion-new-post control-button" href="javascript:void(0)">New Post</div>
|
||||
</div>
|
||||
<%include file="_paginator.html" />
|
||||
% for thread in threads:
|
||||
${renderer.render_thread(course_id, thread, edit_thread=False, show_comments=False)}
|
||||
% endfor
|
||||
<%include file="_paginator.html" />
|
||||
</section>
|
||||
|
||||
<%!
|
||||
@@ -21,5 +23,4 @@
|
||||
<script type="text/javascript">
|
||||
var $$user_info = JSON.parse('${user_info | escape_quotes}');
|
||||
var $$course_id = "${course_id}";
|
||||
var $$tags = JSON.parse("${tags | escape_quotes}");
|
||||
</script>
|
||||
|
||||
52
lms/templates/discussion/_paginator.html
Normal file
52
lms/templates/discussion/_paginator.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<%def name="link_to_page(_page)">
|
||||
% if _page != page:
|
||||
<div class="page-link">
|
||||
<a href="${url_for_page(_page, per_page)}">${_page}</a>
|
||||
</div>
|
||||
% else:
|
||||
<div class="page-link">${_page}</div>
|
||||
% endif
|
||||
</%def>
|
||||
|
||||
<%def name="list_pages(*args)">
|
||||
% for arg in args:
|
||||
% if arg == 'dots':
|
||||
<div class="page-dots">...</div>
|
||||
% elif isinstance(arg, list):
|
||||
% for _page in arg:
|
||||
${link_to_page(_page)}
|
||||
% endfor
|
||||
% else:
|
||||
${link_to_page(arg)}
|
||||
% endif
|
||||
% endfor
|
||||
</%def>
|
||||
|
||||
<div class="discussion-${discussion_type}-paginator discussion-paginator">
|
||||
<div class="prev-page">
|
||||
% if page > 1:
|
||||
<a href="${url_for_page(page - 1, per_page)}">< Previous page</a>
|
||||
% else:
|
||||
< Previous page
|
||||
% endif
|
||||
</div>
|
||||
|
||||
% if num_pages <= 2 * pages_nearby_delta + 2:
|
||||
${list_pages(range(1, num_pages + 1))}
|
||||
% else:
|
||||
% if page <= 2 * pages_nearby_delta:
|
||||
${list_pages(range(1, 2 * pages_nearby_delta + 2), 'dots', num_pages)}
|
||||
% elif num_pages - page + 1 <= 2 * pages_nearby_delta:
|
||||
${list_pages(1, 'dots', range(num_pages - 2 * pages_nearby_delta, num_pages + 1))}
|
||||
% else:
|
||||
${list_pages(1, 'dots', range(page - pages_nearby_delta, page + pages_nearby_delta + 1), 'dots', num_pages)}
|
||||
% endif
|
||||
% endif
|
||||
<div class="next-page">
|
||||
% if page < num_pages:
|
||||
<a href="${url_for_page(page + 1, per_page)}">Next page ></a>
|
||||
% else:
|
||||
Next page >
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
11
lms/templates/module-error-staff.html
Normal file
11
lms/templates/module-error-staff.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<section class="outside-app">
|
||||
<h1>There has been an error on the <em>MITx</em> servers</h1>
|
||||
<p>We're sorry, this module is temporarily unavailable. Our staff is working to fix it as soon as possible. Please email us at <a href="mailto:technical@mitx.mit.edu">technical@mitx.mit.edu</a> to report any problems or downtime.</p>
|
||||
|
||||
<h1>Staff-only details below:</h1>
|
||||
|
||||
<p>Error: ${error}</p>
|
||||
|
||||
<p>Raw data: ${data}</p>
|
||||
|
||||
</section>
|
||||
Reference in New Issue
Block a user