From 45baccae858fa8bd9e9fdd4a555ff6c53455c195 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 10 Aug 2012 11:31:29 -0400 Subject: [PATCH 01/26] Added a django template loader that can return Mako templates. Started pulling in new wiki. --- common/lib/mitxmako/mitxmako/makoloader.py | 63 ++++++++++++++++++++++ common/lib/mitxmako/mitxmako/template.py | 33 +++++++++++- lms/envs/common.py | 24 +++++++-- lms/templates/wiki/article.html | 48 +++++++++++++++++ lms/urls.py | 15 ++++-- requirements.txt | 1 + 6 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 common/lib/mitxmako/mitxmako/makoloader.py create mode 100644 lms/templates/wiki/article.html diff --git a/common/lib/mitxmako/mitxmako/makoloader.py b/common/lib/mitxmako/mitxmako/makoloader.py new file mode 100644 index 0000000000..fc1633c35a --- /dev/null +++ b/common/lib/mitxmako/mitxmako/makoloader.py @@ -0,0 +1,63 @@ + +from django.template.base import TemplateDoesNotExist +from django.template.loader import make_origin, get_template_from_string +from django.template.loaders.filesystem import Loader as FilesystemLoader +from django.template.loaders.app_directories import Loader as AppDirectoriesLoader + +from mitxmako.template import Template + +class MakoLoader(object): + """ + This is a Django loader object which will load the template as a + Mako template if the first line is "## mako". It is based off BaseLoader + in django.template.loader. + """ + + is_usable = False + + def __init__(self, base_loader): + # base_loader is an instance of a BaseLoader subclass + self.base_loader = base_loader + + def __call__(self, template_name, template_dirs=None): + return self.load_template(template_name, template_dirs) + + def load_template(self, template_name, template_dirs=None): + source, display_name = self.base_loader.load_template_source(template_name, template_dirs) + + if source.startswith("## mako\n"): + # This is a mako template + template = Template(text=source, uri=template_name) + return template, None + else: + # This is a regular template + origin = make_origin(display_name, self.load_template_source, template_name, template_dirs) + try: + template = get_template_from_string(source, origin, template_name) + return template, None + except TemplateDoesNotExist: + # If compiling the template we found raises TemplateDoesNotExist, back off to + # returning the source and display name for the template we were asked to load. + # This allows for correct identification (later) of the actual template that does + # not exist. + return source, display_name + + def load_template_source(self): + # Just having this makes the template load as an instance, instead of a class. + raise NotImplementedError + + def reset(self): + self.base_loader.reset() + + +class MakoFilesystemLoader(MakoLoader): + is_usable = True + + def __init__(self): + MakoLoader.__init__(self, FilesystemLoader()) + +class MakoAppDirectoriesLoader(MakoLoader): + is_usable = True + + def __init__(self): + MakoLoader.__init__(self, AppDirectoriesLoader()) diff --git a/common/lib/mitxmako/mitxmako/template.py b/common/lib/mitxmako/mitxmako/template.py index 911f5a5b28..e8fc24a4be 100644 --- a/common/lib/mitxmako/mitxmako/template.py +++ b/common/lib/mitxmako/mitxmako/template.py @@ -12,18 +12,47 @@ # See the License for the specific language governing permissions and # limitations under the License. +from django.conf import settings from mako.template import Template as MakoTemplate -from . import middleware +from mitxmako import middleware -django_variables = ['lookup', 'template_dirs', 'output_encoding', +django_variables = ['lookup', 'output_encoding', 'module_directory', 'encoding_errors'] class Template(MakoTemplate): + """ + This bridges the gap between a Mako template and a djano template. It can + be rendered like it is a django template because the arguments are transformed + in a way that MakoTemplate can understand. + """ + def __init__(self, *args, **kwargs): """Overrides base __init__ to provide django variable overrides""" if not kwargs.get('no_django', False): overrides = dict([(k, getattr(middleware, k, None),) for k in django_variables]) + overrides['lookup'] = overrides['lookup']['main'] kwargs.update(overrides) super(Template, self).__init__(*args, **kwargs) + + + def render(self, context_instance): + """ + This takes a render call with a context (from Django) and translates + it to a render call on the mako template. + """ + # collapse context_instance to a single dictionary for mako + context_dictionary = {} + + # In various testing contexts, there might not be a current request context. + if middleware.requestcontext is not None: + for d in middleware.requestcontext: + context_dictionary.update(d) + for d in context_instance: + context_dictionary.update(d) + context_dictionary['settings'] = settings + context_dictionary['MITX_ROOT_URL'] = settings.MITX_ROOT_URL + + return super(Template, self).render(**context_dictionary) + diff --git a/lms/envs/common.py b/lms/envs/common.py index 487e2efe48..6354d58bdf 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -111,6 +111,12 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'askbot.user_messages.context_processors.user_messages',#must be before auth 'django.contrib.auth.context_processors.auth', #this is required for admin 'django.core.context_processors.csrf', #necessary for csrf protection + + # Added for django-wiki + 'django.core.context_processors.media', + 'django.core.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + 'sekizai.context_processors.sekizai', ) @@ -281,9 +287,13 @@ STATICFILES_FINDERS = ( # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - 'askbot.skins.loaders.filesystem_load_template_source', + 'mitxmako.makoloader.MakoFilesystemLoader', + 'mitxmako.makoloader.MakoAppDirectoriesLoader', + + # 'django.template.loaders.filesystem.Loader', + # 'django.template.loaders.app_directories.Loader', + + #'askbot.skins.loaders.filesystem_load_template_source', # 'django.template.loaders.eggs.Loader', ) @@ -514,6 +524,14 @@ INSTALLED_APPS = ( 'track', 'util', 'certificates', + + #For the wiki + 'wiki', # The new django-wiki from benjaoming + 'django_notify', + 'mptt', + 'sekizai', + 'wiki.plugins.attachments', + 'wiki.plugins.notifications', # For testing 'django_jasmine', diff --git a/lms/templates/wiki/article.html b/lms/templates/wiki/article.html new file mode 100644 index 0000000000..45b3071141 --- /dev/null +++ b/lms/templates/wiki/article.html @@ -0,0 +1,48 @@ +## mako +<%inherit file="../main.html"/> +<%namespace name='static' file='../static_content.html'/> + +<%block name="bodyextra"> + +This is a mako template with inheritance! + + + + + + diff --git a/lms/urls.py b/lms/urls.py index bb3952b73c..44523dd827 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -2,7 +2,6 @@ from django.conf import settings from django.conf.urls import patterns, include, url from django.contrib import admin from django.conf.urls.static import static - import django.contrib.auth.views # Uncomment the next two lines to enable the admin: @@ -144,9 +143,17 @@ if settings.COURSEWARE_ENABLED: # Multicourse wiki if settings.WIKI_ENABLED: - urlpatterns += ( - url(r'^wiki/', include('simplewiki.urls')), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')), + from wiki.urls import get_pattern as wiki_pattern + from django_notify.urls import get_pattern as notify_pattern + + # urlpatterns += ( + # url(r'^wiki/', include('simplewiki.urls')), + # url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')), + # ) + urlpatterns += ( + + url(r'wiki/', include(wiki_pattern())), + url(r'^notify/', include(notify_pattern())), ) if settings.QUICKEDIT: diff --git a/requirements.txt b/requirements.txt index ef16d2c577..5905aa839f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,4 +43,5 @@ django-robots django-ses django-storages django-threaded-multihost +-e git+git://github.com/benjaoming/django-wiki.git#egg=django-wiki -r repo-requirements.txt From 3f4c4d74872b8ba0e43199f9bb63c23101aec885 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 10 Aug 2012 14:46:53 -0400 Subject: [PATCH 02/26] Got most of the wiki article page working. It shows the article menu now. --- common/lib/mitxmako/mitxmako/template.py | 2 +- .../mitxmako/mitxmako/templatetag_helpers.py | 23 +++++ lms/templates/wiki/article.html | 87 ++++++++++--------- lms/templates/wiki/includes/article_menu.html | 51 +++++++++++ lms/templates/wiki/mako_base.html | 41 +++++++++ 5 files changed, 161 insertions(+), 43 deletions(-) create mode 100644 common/lib/mitxmako/mitxmako/templatetag_helpers.py create mode 100644 lms/templates/wiki/includes/article_menu.html create mode 100644 lms/templates/wiki/mako_base.html diff --git a/common/lib/mitxmako/mitxmako/template.py b/common/lib/mitxmako/mitxmako/template.py index e8fc24a4be..fa6b1293f8 100644 --- a/common/lib/mitxmako/mitxmako/template.py +++ b/common/lib/mitxmako/mitxmako/template.py @@ -20,7 +20,7 @@ from mitxmako import middleware django_variables = ['lookup', 'output_encoding', 'module_directory', 'encoding_errors'] - +# TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate) class Template(MakoTemplate): """ This bridges the gap between a Mako template and a djano template. It can diff --git a/common/lib/mitxmako/mitxmako/templatetag_helpers.py b/common/lib/mitxmako/mitxmako/templatetag_helpers.py new file mode 100644 index 0000000000..0c66cea090 --- /dev/null +++ b/common/lib/mitxmako/mitxmako/templatetag_helpers.py @@ -0,0 +1,23 @@ +from django.template.base import Template, Context +from django.template.loader import get_template, select_template + + +def render_inclusion(func, file_name, *args, **kwargs): + _dict = func(*args, **kwargs) + if isinstance(file_name, Template): + t = file_name + elif not isinstance(file_name, basestring) and is_iterable(file_name): + t = select_template(file_name) + else: + t = get_template(file_name) + + nodelist = t.nodelist + + new_context = Context(_dict) + # **{ + # 'autoescape': context.autoescape, + # 'current_app': context.current_app, + # 'use_l10n': context.use_l10n, + # 'use_tz': context.use_tz, + # }) + return nodelist.render(new_context) diff --git a/lms/templates/wiki/article.html b/lms/templates/wiki/article.html index 45b3071141..bedeb368d0 100644 --- a/lms/templates/wiki/article.html +++ b/lms/templates/wiki/article.html @@ -1,48 +1,51 @@ ## mako -<%inherit file="../main.html"/> -<%namespace name='static' file='../static_content.html'/> +<%inherit file="mako_base.html"/> -<%block name="bodyextra"> +<%! + from wiki.templatetags.wiki_tags import wiki_render + from mitxmako.templatetag_helpers import render_inclusion +%> -This is a mako template with inheritance! +<%namespace name="article_menu" file="includes/article_menu.html"/> + + +<%block name="title">${article.current_revision.title} + + +<%doc> +<%block name="wiki_breadcrumbs"> +Render "wiki/includes/breadcrumbs.html" +## {% include "wiki/includes/breadcrumbs.html" %} + + + + + +<%block name="wiki_contents"> + +
+ + +
+ ${ render_inclusion(wiki_render, 'wiki/includes/render.html', article ) } +
+
+ +
+ <%doc> + + +
- - - diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html new file mode 100644 index 0000000000..0e5af642e6 --- /dev/null +++ b/lms/templates/wiki/includes/article_menu.html @@ -0,0 +1,51 @@ +## mako +<%page args="selected, article, plugins" /> +<%! from django.core.urlresolvers import reverse %> + +<% + if urlpath: + tab_reverse = lambda name, kwargs={}: reverse(name, kwargs=dict({'path' : urlpath.path}, **kwargs)) + else: + tab_reverse = lambda name, kwargs={}: reverse(name, kwargs=dict({'article_id' : article.id}, **kwargs)) +%> + + +%for plugin in plugins: + %if hasattr(plugin, "article_tab"): +
  • + + + ${plugin.article_tab[0]} + +
  • + %endif +%endfor + + + +
  • + %if not user.is_anonymous: + + + Settings + + %endif +
  • +
  • + + + Changes + +
  • +
  • + + + Edit + +
  • +
  • + + + View + +
  • diff --git a/lms/templates/wiki/mako_base.html b/lms/templates/wiki/mako_base.html new file mode 100644 index 0000000000..e8e6b3664b --- /dev/null +++ b/lms/templates/wiki/mako_base.html @@ -0,0 +1,41 @@ +## mako +<%inherit file="../main.html"/> +<%namespace name='static' file='../static_content.html'/> + +<%block name="headextra"> + <%static:css group='course'/> + + +<%block name="bodyextra"> + +## %if course: +## <%include file="../course_navigation.html" args="active_page='wiki'" /> +## %endif + +
    + <%doc> + {% if messages %} + {% for message in messages %} +
    + × + {{ message }} +
    + {% endfor %} + {% endif %} + + + <%block name="wiki_breadcrumbs" /> + <%block name="wiki_contents" /> + +
    +
    +
    + +

    Powered by django-wiki, an open source application under the GPLv3 license. Let knowledge be the cure.

    +
    +
    +
    + +
    + + From e462fad3676cebe1283d302f1c7daf28aa2f77e7 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Fri, 10 Aug 2012 18:15:21 -0400 Subject: [PATCH 03/26] Converted wiki edit page and breadcrumbs. --- .../mitxmako/mitxmako/templatetag_helpers.py | 5 ++ lms/templates/wiki/article.html | 46 +++++------ lms/templates/wiki/edit.html | 77 +++++++++++++++++++ lms/templates/wiki/includes/breadcrumbs.html | 47 +++++++++++ lms/urls.py | 2 + 5 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 lms/templates/wiki/edit.html create mode 100644 lms/templates/wiki/includes/breadcrumbs.html diff --git a/common/lib/mitxmako/mitxmako/templatetag_helpers.py b/common/lib/mitxmako/mitxmako/templatetag_helpers.py index 0c66cea090..10c502fbfe 100644 --- a/common/lib/mitxmako/mitxmako/templatetag_helpers.py +++ b/common/lib/mitxmako/mitxmako/templatetag_helpers.py @@ -1,3 +1,4 @@ +from django.template import loader from django.template.base import Template, Context from django.template.loader import get_template, select_template @@ -21,3 +22,7 @@ def render_inclusion(func, file_name, *args, **kwargs): # 'use_tz': context.use_tz, # }) return nodelist.render(new_context) + +def django_template_include(file_name, mako_context): + dictionary = dict( mako_context ) + return loader.render_to_string(file_name, dictionary=dictionary) diff --git a/lms/templates/wiki/article.html b/lms/templates/wiki/article.html index bedeb368d0..3a2c9653fa 100644 --- a/lms/templates/wiki/article.html +++ b/lms/templates/wiki/article.html @@ -7,45 +7,35 @@ %> <%namespace name="article_menu" file="includes/article_menu.html"/> +<%namespace name="breadcrumbs" file="includes/breadcrumbs.html"/> <%block name="title">${article.current_revision.title} -<%doc> <%block name="wiki_breadcrumbs"> -Render "wiki/includes/breadcrumbs.html" -## {% include "wiki/includes/breadcrumbs.html" %} +${breadcrumbs.body(article, urlpath)} - - - <%block name="wiki_contents"> - -
    - +
    + -
    - ${ render_inclusion(wiki_render, 'wiki/includes/render.html', article ) } +
    + ${ render_inclusion(wiki_render, 'wiki/includes/render.html', article ) } +
    -
    - -
    - <%doc> - - -
    +
    + +
    diff --git a/lms/templates/wiki/edit.html b/lms/templates/wiki/edit.html new file mode 100644 index 0000000000..6ee1490148 --- /dev/null +++ b/lms/templates/wiki/edit.html @@ -0,0 +1,77 @@ +## mako +<%inherit file="mako_base.html"/> + +<%! + from django.core.urlresolvers import reverse + from mitxmako.templatetag_helpers import django_template_include +%> + +<%namespace name="article_menu" file="includes/article_menu.html"/> +<%namespace name="breadcrumbs" file="includes/breadcrumbs.html"/> + + +<%block name="title">Edit: ${article.current_revision.title} + +<%block name="wiki_breadcrumbs"> +${breadcrumbs.body(article, urlpath)} + + + +<%block name="wiki_contents"> +
    + + +
    +
    + ${django_template_include("wiki/includes/editor.html", context)} +
    + + + + + + Delete article + + +
    + + +
    + +
    +
    + +
    + +
    + + + diff --git a/lms/templates/wiki/includes/breadcrumbs.html b/lms/templates/wiki/includes/breadcrumbs.html new file mode 100644 index 0000000000..3edd9bf0a8 --- /dev/null +++ b/lms/templates/wiki/includes/breadcrumbs.html @@ -0,0 +1,47 @@ +## mako +<%page args="article, urlpath" /> +<%! from django.core.urlresolvers import reverse %> + +%if urlpath: + +
    +
    + + + + + +
    +
    + +
    +%endif \ No newline at end of file diff --git a/lms/urls.py b/lms/urls.py index 44523dd827..23852d00c1 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -152,6 +152,8 @@ if settings.WIKI_ENABLED: # ) urlpatterns += ( + #url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')), + url(r'wiki/', include(wiki_pattern())), url(r'^notify/', include(notify_pattern())), ) From ff1a0ba2741e5f3be02744ad118db4ed74294808 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 13 Aug 2012 14:25:11 -0400 Subject: [PATCH 04/26] Added wiki create and create_root. Fixed some mitxmako bugs. --- common/lib/mitxmako/mitxmako/makoloader.py | 6 +-- common/lib/mitxmako/mitxmako/template.py | 3 +- .../mitxmako/mitxmako/templatetag_helpers.py | 9 ++++- lms/envs/common.py | 3 ++ lms/envs/devplus.py | 4 +- lms/templates/wiki/article.html | 2 +- lms/templates/wiki/article/create_root.html | 38 +++++++++++++++++++ lms/templates/wiki/create.html | 29 ++++++++++++++ lms/templates/wiki/edit.html | 6 +-- lms/templates/wiki/includes/article_menu.html | 21 ++++------ lms/templates/wiki/includes/breadcrumbs.html | 8 ++-- lms/templates/wiki/mako_base.html | 3 ++ 12 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 lms/templates/wiki/article/create_root.html create mode 100644 lms/templates/wiki/create.html diff --git a/common/lib/mitxmako/mitxmako/makoloader.py b/common/lib/mitxmako/mitxmako/makoloader.py index fc1633c35a..53334d1a1b 100644 --- a/common/lib/mitxmako/mitxmako/makoloader.py +++ b/common/lib/mitxmako/mitxmako/makoloader.py @@ -23,7 +23,7 @@ class MakoLoader(object): return self.load_template(template_name, template_dirs) def load_template(self, template_name, template_dirs=None): - source, display_name = self.base_loader.load_template_source(template_name, template_dirs) + source, display_name = self.load_template_source(template_name, template_dirs) if source.startswith("## mako\n"): # This is a mako template @@ -42,9 +42,9 @@ class MakoLoader(object): # not exist. return source, display_name - def load_template_source(self): + def load_template_source(self, template_name, template_dirs=None): # Just having this makes the template load as an instance, instead of a class. - raise NotImplementedError + return self.base_loader.load_template_source(template_name, template_dirs) def reset(self): self.base_loader.reset() diff --git a/common/lib/mitxmako/mitxmako/template.py b/common/lib/mitxmako/mitxmako/template.py index fa6b1293f8..65328ae830 100644 --- a/common/lib/mitxmako/mitxmako/template.py +++ b/common/lib/mitxmako/mitxmako/template.py @@ -53,6 +53,7 @@ class Template(MakoTemplate): context_dictionary.update(d) context_dictionary['settings'] = settings context_dictionary['MITX_ROOT_URL'] = settings.MITX_ROOT_URL - + context_dictionary['django_context'] = context_instance + return super(Template, self).render(**context_dictionary) diff --git a/common/lib/mitxmako/mitxmako/templatetag_helpers.py b/common/lib/mitxmako/mitxmako/templatetag_helpers.py index 10c502fbfe..6742f3ee1c 100644 --- a/common/lib/mitxmako/mitxmako/templatetag_helpers.py +++ b/common/lib/mitxmako/mitxmako/templatetag_helpers.py @@ -3,7 +3,10 @@ from django.template.base import Template, Context from django.template.loader import get_template, select_template -def render_inclusion(func, file_name, *args, **kwargs): +def render_inclusion(func, file_name, takes_context, django_context, *args, **kwargs): + if takes_context: + args = [django_context] + list(args) + _dict = func(*args, **kwargs) if isinstance(file_name, Template): t = file_name @@ -15,6 +18,10 @@ def render_inclusion(func, file_name, *args, **kwargs): nodelist = t.nodelist new_context = Context(_dict) + csrf_token = django_context.get('csrf_token', None) + if csrf_token is not None: + new_context['csrf_token'] = csrf_token + # **{ # 'autoescape': context.autoescape, # 'current_app': context.current_app, diff --git a/lms/envs/common.py b/lms/envs/common.py index 6354d58bdf..af5e3184a3 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -274,6 +274,9 @@ djcelery.setup_loader() SIMPLE_WIKI_REQUIRE_LOGIN_EDIT = True SIMPLE_WIKI_REQUIRE_LOGIN_VIEW = False +################################# WIKI ################################### +WIKI_ACCOUNT_HANDLING = False + ################################# Jasmine ################################### JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' diff --git a/lms/envs/devplus.py b/lms/envs/devplus.py index b15322c2c7..bb4524a1ab 100644 --- a/lms/envs/devplus.py +++ b/lms/envs/devplus.py @@ -65,5 +65,7 @@ DEBUG_TOOLBAR_PANELS = ( # 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', + 'debug_toolbar.panels.profiling.ProfilingDebugPanel', ) + +#PIPELINE = True diff --git a/lms/templates/wiki/article.html b/lms/templates/wiki/article.html index 3a2c9653fa..0dfb9cb4bd 100644 --- a/lms/templates/wiki/article.html +++ b/lms/templates/wiki/article.html @@ -27,7 +27,7 @@ ${breadcrumbs.body(article, urlpath)}
    - ${ render_inclusion(wiki_render, 'wiki/includes/render.html', article ) } + ${ render_inclusion(wiki_render, 'wiki/includes/render.html', False, django_context, article ) }
    diff --git a/lms/templates/wiki/article/create_root.html b/lms/templates/wiki/article/create_root.html new file mode 100644 index 0000000000..0032a30b71 --- /dev/null +++ b/lms/templates/wiki/article/create_root.html @@ -0,0 +1,38 @@ +## mako +<%inherit file="../mako_base.html"/> +<%namespace name='static' file='../../static_content.html'/> + +<%! + from wiki.templatetags.wiki_tags import wiki_form + from mitxmako.templatetag_helpers import django_template_include, render_inclusion +%> + +<%block name="title">Create root article + +<%block name="wiki_headextra"> + %for js in editor.Media.js: + + %endfor + + %for media, srcs in editor.Media.css.items(): + %for src in srcs: + + %endfor + %endfor + + +<%block name="wiki_contents"> +

    Congratulations!

    +

    You have django-wiki installed... but there are no articles. So it's time to create the first one, the root article. In the beginning, it will only be editable by administrators, but you can define permissions after. +

    + + + +
    + ${ render_inclusion(wiki_form, 'wiki/includes/form.html', True, django_context, create_form ) } +
    + +
    +
    + + diff --git a/lms/templates/wiki/create.html b/lms/templates/wiki/create.html new file mode 100644 index 0000000000..af1579d2de --- /dev/null +++ b/lms/templates/wiki/create.html @@ -0,0 +1,29 @@ +## mako +<%inherit file="mako_base.html"/> + +<%! + from django.core.urlresolvers import reverse + from mitxmako.templatetag_helpers import django_template_include, render_inclusion + from wiki.templatetags.wiki_tags import wiki_form +%> + +<%block name="title">Add new article + +<%block name="wiki_contents"> +${django_template_include("wiki/includes/editormedia.html", context)} +

    Add new article

    + +
    + ${ render_inclusion(wiki_form, 'wiki/includes/form.html', True, django_context, create_form ) } +
    + + + Go back + + +
    +
    + diff --git a/lms/templates/wiki/edit.html b/lms/templates/wiki/edit.html index 6ee1490148..a801243970 100644 --- a/lms/templates/wiki/edit.html +++ b/lms/templates/wiki/edit.html @@ -30,11 +30,11 @@ ${breadcrumbs.body(article, urlpath)}
    ${django_template_include("wiki/includes/editor.html", context)}
    - - @@ -54,7 +54,7 @@ ${breadcrumbs.body(article, urlpath)} Back to editor - diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html index 0e5af642e6..30d96613b2 100644 --- a/lms/templates/wiki/includes/article_menu.html +++ b/lms/templates/wiki/includes/article_menu.html @@ -2,18 +2,11 @@ <%page args="selected, article, plugins" /> <%! from django.core.urlresolvers import reverse %> -<% - if urlpath: - tab_reverse = lambda name, kwargs={}: reverse(name, kwargs=dict({'path' : urlpath.path}, **kwargs)) - else: - tab_reverse = lambda name, kwargs={}: reverse(name, kwargs=dict({'article_id' : article.id}, **kwargs)) -%> - -%for plugin in plugins: +%for plugin in article_tabs: %if hasattr(plugin, "article_tab"): -
  • - +
  • + ${plugin.article_tab[0]} @@ -25,26 +18,26 @@
  • %if not user.is_anonymous: - + Settings %endif
  • - + Changes
  • - + Edit
  • - + View diff --git a/lms/templates/wiki/includes/breadcrumbs.html b/lms/templates/wiki/includes/breadcrumbs.html index 3edd9bf0a8..44a85f7573 100644 --- a/lms/templates/wiki/includes/breadcrumbs.html +++ b/lms/templates/wiki/includes/breadcrumbs.html @@ -6,10 +6,10 @@
    @@ -22,7 +22,7 @@ %if len(urlpath.get_children()) > 0: %for child in urlpath.get_children():
  • - + ${child.article.current_revision.title}
  • @@ -38,7 +38,7 @@
    - + Add article diff --git a/lms/templates/wiki/mako_base.html b/lms/templates/wiki/mako_base.html index e8e6b3664b..8bb3fa9fac 100644 --- a/lms/templates/wiki/mako_base.html +++ b/lms/templates/wiki/mako_base.html @@ -4,6 +4,9 @@ <%block name="headextra"> <%static:css group='course'/> + + <%block name="wiki_headextra" /> + <%block name="bodyextra"> From 0a9ad1a5f936092cfa7e7f9f1c57af4a1cf43826 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 13 Aug 2012 16:42:57 -0400 Subject: [PATCH 05/26] Added settings template. --- lms/templates/wiki/includes/article_menu.html | 4 +- lms/templates/wiki/settings.html | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 lms/templates/wiki/settings.html diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html index 30d96613b2..ae2cb64408 100644 --- a/lms/templates/wiki/includes/article_menu.html +++ b/lms/templates/wiki/includes/article_menu.html @@ -16,14 +16,14 @@ +%if not user.is_anonymous():
  • - %if not user.is_anonymous: Settings - %endif
  • +%endif
  • diff --git a/lms/templates/wiki/settings.html b/lms/templates/wiki/settings.html new file mode 100644 index 0000000000..89d525be43 --- /dev/null +++ b/lms/templates/wiki/settings.html @@ -0,0 +1,53 @@ +## mako +<%inherit file="mako_base.html"/> + +<%! + from wiki.templatetags.wiki_tags import wiki_form + from mitxmako.templatetag_helpers import render_inclusion +%> + +<%namespace name="article_menu" file="includes/article_menu.html"/> +<%namespace name="breadcrumbs" file="includes/breadcrumbs.html"/> + + +<%block name="title">Settings: ${article.current_revision.title} + + +<%block name="wiki_breadcrumbs"> +${breadcrumbs.body(article, urlpath)} + + +<%block name="wiki_contents"> +
    + + +
    + %for form in forms: + +

    ${form.settings_form_headline}

    +
    + ${ render_inclusion(wiki_form, 'wiki/includes/form.html', True, django_context, form ) } +
    +
    + +
    + + %endfor +
    +
    + +
    + +
    + From 66d1259d4b436e62ddd2c9c55e3268da0c89061c Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Mon, 13 Aug 2012 19:07:41 -0400 Subject: [PATCH 06/26] Created copy of main.html that is django template. Using this for wiki instead of converting every view. --- lms/templates/footer.html | 1 + lms/templates/main_django.html | 36 +++++++++ lms/templates/navigation.html | 1 + lms/templates/wiki/article.html | 41 ---------- lms/templates/wiki/article/create_root.html | 38 --------- lms/templates/wiki/base.html | 23 ++++++ lms/templates/wiki/create.html | 29 ------- lms/templates/wiki/edit.html | 77 ------------------- lms/templates/wiki/includes/article_menu.html | 44 ----------- lms/templates/wiki/includes/breadcrumbs.html | 47 ----------- lms/templates/wiki/mako_base.html | 44 ----------- lms/templates/wiki/settings.html | 53 ------------- 12 files changed, 61 insertions(+), 373 deletions(-) create mode 100644 lms/templates/main_django.html delete mode 100644 lms/templates/wiki/article.html delete mode 100644 lms/templates/wiki/article/create_root.html create mode 100644 lms/templates/wiki/base.html delete mode 100644 lms/templates/wiki/create.html delete mode 100644 lms/templates/wiki/edit.html delete mode 100644 lms/templates/wiki/includes/article_menu.html delete mode 100644 lms/templates/wiki/includes/breadcrumbs.html delete mode 100644 lms/templates/wiki/mako_base.html delete mode 100644 lms/templates/wiki/settings.html diff --git a/lms/templates/footer.html b/lms/templates/footer.html index c1fc3c3f36..85ed6e1769 100644 --- a/lms/templates/footer.html +++ b/lms/templates/footer.html @@ -1,3 +1,4 @@ +## mako <%! from django.core.urlresolvers import reverse %> <%namespace name='static' file='static_content.html'/> diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html new file mode 100644 index 0000000000..ba53b30fb8 --- /dev/null +++ b/lms/templates/main_django.html @@ -0,0 +1,36 @@ + +{% load compressed %}{% load sekizai_tags i18n %}{% load url from future %} + + + {% block title %}edX{% endblock %} + + + {% compressed_css 'application' %} + {% compressed_js 'main_vendor' %} + + {% block headextra %}{% endblock %} + {% render_block "css" %} + {% render_block "js" %} + + + {% comment %} + TODO: We need to figure out how to put the MITX_ROOT_URL in here + + {% endcomment %} + + + + + {% include "navigation.html" %} + +
    + {% block body %}{% endblock %} + + {% block bodyextra %}{% endblock %} +
    + {% include "footer.html" %} + + {% compressed_js 'application' %} + {% compressed_js 'module-js' %} + + diff --git a/lms/templates/navigation.html b/lms/templates/navigation.html index f628e346f2..d85a146df7 100644 --- a/lms/templates/navigation.html +++ b/lms/templates/navigation.html @@ -1,3 +1,4 @@ +## mako ## TODO: Split this into two files, one for people who are authenticated, and ## one for people who aren't. Assume a Course object is passed to the former, ## instead of using settings.COURSE_TITLE diff --git a/lms/templates/wiki/article.html b/lms/templates/wiki/article.html deleted file mode 100644 index 0dfb9cb4bd..0000000000 --- a/lms/templates/wiki/article.html +++ /dev/null @@ -1,41 +0,0 @@ -## mako -<%inherit file="mako_base.html"/> - -<%! - from wiki.templatetags.wiki_tags import wiki_render - from mitxmako.templatetag_helpers import render_inclusion -%> - -<%namespace name="article_menu" file="includes/article_menu.html"/> -<%namespace name="breadcrumbs" file="includes/breadcrumbs.html"/> - - -<%block name="title">${article.current_revision.title} - - -<%block name="wiki_breadcrumbs"> -${breadcrumbs.body(article, urlpath)} - - -<%block name="wiki_contents"> -
    - - -
    - ${ render_inclusion(wiki_render, 'wiki/includes/render.html', False, django_context, article ) } -
    -
    - -
    - -
    - - diff --git a/lms/templates/wiki/article/create_root.html b/lms/templates/wiki/article/create_root.html deleted file mode 100644 index 0032a30b71..0000000000 --- a/lms/templates/wiki/article/create_root.html +++ /dev/null @@ -1,38 +0,0 @@ -## mako -<%inherit file="../mako_base.html"/> -<%namespace name='static' file='../../static_content.html'/> - -<%! - from wiki.templatetags.wiki_tags import wiki_form - from mitxmako.templatetag_helpers import django_template_include, render_inclusion -%> - -<%block name="title">Create root article - -<%block name="wiki_headextra"> - %for js in editor.Media.js: - - %endfor - - %for media, srcs in editor.Media.css.items(): - %for src in srcs: - - %endfor - %endfor - - -<%block name="wiki_contents"> -

    Congratulations!

    -

    You have django-wiki installed... but there are no articles. So it's time to create the first one, the root article. In the beginning, it will only be editable by administrators, but you can define permissions after. -

    - - - -
    - ${ render_inclusion(wiki_form, 'wiki/includes/form.html', True, django_context, create_form ) } -
    - -
    -
    - - diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html new file mode 100644 index 0000000000..07d800eb03 --- /dev/null +++ b/lms/templates/wiki/base.html @@ -0,0 +1,23 @@ +{% extends "main_django.html" %} +{% load sekizai_tags i18n %}{% load url from future %} + +{% block title %}{% block pagetitle %}{% endblock %} | edX Wiki{% endblock %} + +{% block body %} + {% block wiki_body %} + + {% if messages %} + {% for message in messages %} +
    + × + {{ message }} +
    + {% endfor %} + {% endif %} + + {% block wiki_breadcrumbs %}{% endblock %} + + {% block wiki_contents %}{% endblock %} + + {% endblock %} +{% endblock %} diff --git a/lms/templates/wiki/create.html b/lms/templates/wiki/create.html deleted file mode 100644 index af1579d2de..0000000000 --- a/lms/templates/wiki/create.html +++ /dev/null @@ -1,29 +0,0 @@ -## mako -<%inherit file="mako_base.html"/> - -<%! - from django.core.urlresolvers import reverse - from mitxmako.templatetag_helpers import django_template_include, render_inclusion - from wiki.templatetags.wiki_tags import wiki_form -%> - -<%block name="title">Add new article - -<%block name="wiki_contents"> -${django_template_include("wiki/includes/editormedia.html", context)} -

    Add new article

    - -
    - ${ render_inclusion(wiki_form, 'wiki/includes/form.html', True, django_context, create_form ) } -
    - - - Go back - - -
    -
    - diff --git a/lms/templates/wiki/edit.html b/lms/templates/wiki/edit.html deleted file mode 100644 index a801243970..0000000000 --- a/lms/templates/wiki/edit.html +++ /dev/null @@ -1,77 +0,0 @@ -## mako -<%inherit file="mako_base.html"/> - -<%! - from django.core.urlresolvers import reverse - from mitxmako.templatetag_helpers import django_template_include -%> - -<%namespace name="article_menu" file="includes/article_menu.html"/> -<%namespace name="breadcrumbs" file="includes/breadcrumbs.html"/> - - -<%block name="title">Edit: ${article.current_revision.title} - -<%block name="wiki_breadcrumbs"> -${breadcrumbs.body(article, urlpath)} - - - -<%block name="wiki_contents"> -
    - - -
    -
    - ${django_template_include("wiki/includes/editor.html", context)} -
    - - - - - - Delete article - - -
    - - -
    - -
    -
    - -
    - -
    - - - diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html deleted file mode 100644 index ae2cb64408..0000000000 --- a/lms/templates/wiki/includes/article_menu.html +++ /dev/null @@ -1,44 +0,0 @@ -## mako -<%page args="selected, article, plugins" /> -<%! from django.core.urlresolvers import reverse %> - - -%for plugin in article_tabs: - %if hasattr(plugin, "article_tab"): -
  • - - - ${plugin.article_tab[0]} - -
  • - %endif -%endfor - - - -%if not user.is_anonymous(): -
  • - - - Settings - -
  • -%endif -
  • - - - Changes - -
  • -
  • - - - Edit - -
  • -
  • - - - View - -
  • diff --git a/lms/templates/wiki/includes/breadcrumbs.html b/lms/templates/wiki/includes/breadcrumbs.html deleted file mode 100644 index 44a85f7573..0000000000 --- a/lms/templates/wiki/includes/breadcrumbs.html +++ /dev/null @@ -1,47 +0,0 @@ -## mako -<%page args="article, urlpath" /> -<%! from django.core.urlresolvers import reverse %> - -%if urlpath: - -
    -
    - - - - - -
    -
    - -
    -%endif \ No newline at end of file diff --git a/lms/templates/wiki/mako_base.html b/lms/templates/wiki/mako_base.html deleted file mode 100644 index 8bb3fa9fac..0000000000 --- a/lms/templates/wiki/mako_base.html +++ /dev/null @@ -1,44 +0,0 @@ -## mako -<%inherit file="../main.html"/> -<%namespace name='static' file='../static_content.html'/> - -<%block name="headextra"> - <%static:css group='course'/> - - <%block name="wiki_headextra" /> - - - -<%block name="bodyextra"> - -## %if course: -## <%include file="../course_navigation.html" args="active_page='wiki'" /> -## %endif - -
    - <%doc> - {% if messages %} - {% for message in messages %} -
    - × - {{ message }} -
    - {% endfor %} - {% endif %} - - - <%block name="wiki_breadcrumbs" /> - <%block name="wiki_contents" /> - -
    -
    -
    - -

    Powered by django-wiki, an open source application under the GPLv3 license. Let knowledge be the cure.

    -
    -
    -
    - -
    - - diff --git a/lms/templates/wiki/settings.html b/lms/templates/wiki/settings.html deleted file mode 100644 index 89d525be43..0000000000 --- a/lms/templates/wiki/settings.html +++ /dev/null @@ -1,53 +0,0 @@ -## mako -<%inherit file="mako_base.html"/> - -<%! - from wiki.templatetags.wiki_tags import wiki_form - from mitxmako.templatetag_helpers import render_inclusion -%> - -<%namespace name="article_menu" file="includes/article_menu.html"/> -<%namespace name="breadcrumbs" file="includes/breadcrumbs.html"/> - - -<%block name="title">Settings: ${article.current_revision.title} - - -<%block name="wiki_breadcrumbs"> -${breadcrumbs.body(article, urlpath)} - - -<%block name="wiki_contents"> -
    - - -
    - %for form in forms: -
    -

    ${form.settings_form_headline}

    -
    - ${ render_inclusion(wiki_form, 'wiki/includes/form.html', True, django_context, form ) } -
    -
    - -
    -
    - %endfor -
    -
    - -
    - -
    - From c8fc7bed3ebd4eca341858f55ff7b05ce8d4a023 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 08:39:07 -0400 Subject: [PATCH 07/26] Small template change. --- lms/templates/main_django.html | 3 --- lms/templates/wiki/base.html | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html index ba53b30fb8..22620710c3 100644 --- a/lms/templates/main_django.html +++ b/lms/templates/main_django.html @@ -13,10 +13,7 @@ {% render_block "js" %} - {% comment %} - TODO: We need to figure out how to put the MITX_ROOT_URL in here - {% endcomment %} diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html index 07d800eb03..93558939c4 100644 --- a/lms/templates/wiki/base.html +++ b/lms/templates/wiki/base.html @@ -5,7 +5,7 @@ {% block body %} {% block wiki_body %} - + {% if messages %} {% for message in messages %}
    From a2841af834aa5d45f6ae51675180e23192b1920d Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 08:39:41 -0400 Subject: [PATCH 08/26] Added views for automatic course article creation. --- lms/djangoapps/course_wiki/__init__.py | 0 lms/djangoapps/course_wiki/views.py | 92 ++++++++++++++++++++++++++ lms/envs/common.py | 1 + lms/urls.py | 8 ++- 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 lms/djangoapps/course_wiki/__init__.py create mode 100644 lms/djangoapps/course_wiki/views.py diff --git a/lms/djangoapps/course_wiki/__init__.py b/lms/djangoapps/course_wiki/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py new file mode 100644 index 0000000000..c2e862471f --- /dev/null +++ b/lms/djangoapps/course_wiki/views.py @@ -0,0 +1,92 @@ +from django.shortcuts import redirect + +from wiki.core.exceptions import NoRootURL +from wiki.models import URLPath, Article + +from courseware.courses import check_course + +def root_create(request): + + """ + In the edX wiki, we don't show the root_create view. Instead, we + just create the root automatically if it doesn't exist. + """ + root = get_or_create_root() + return redirect('wiki:get', path=root.path) + + +def course_wiki_redirect(request, course_id): + """ + This redirects to whatever page on the wiki that the course designates + as it's home page. A course's wiki must be an article on the root (for + example, "/6.002x") to keep things simple. + """ + course = check_course(course_id) + + namespace = course.wiki_namespace + #TODO: Make sure this is a legal slug. No "/"'s + + try: + urlpath = URLPath.get_by_path(namespace, select_related=True) + + results = list( Article.objects.filter( id = urlpath.article.id ) ) + if results: + article = results[0] + else: + article = None + + except (NoRootURL, URLPath.DoesNotExist): + # We will create it in the next block + urlpath = None + article = None + + if not article: + # create it + root = get_or_create_root() + + if urlpath: + # Somehow we got a urlpath without an article. Just delete it and + # recerate it. + urlpath.delete() + + urlpath = URLPath.create_article( + root, + namespace, + title=course.title, + content="This is the wiki for " + course.title + ".", + user_message="Course page automatically created.", + user=None, + ip_address=None, + article_kwargs={'owner': None, + 'group': None, + 'group_read': True, + 'group_write': True, + 'other_read': True, + 'other_write': True, + }) + + return redirect("wiki:get", path=urlpath.path) + + +def get_or_create_root(): + """ + Returns the root article, or creates it if it doesn't exist. + """ + try: + root = URLPath.root() + if not root.article: + root.delete() + raise NoRootURL + return root + except NoRootURL: + pass + + starting_content = "\n".join(( + "Welcome to the edX Wiki", + "===", + "Visit a course wiki to add an article.")) + + root = URLPath.create_root(title="edX Wiki", + content=starting_content) + return root + diff --git a/lms/envs/common.py b/lms/envs/common.py index af5e3184a3..8369f2ca78 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -530,6 +530,7 @@ INSTALLED_APPS = ( #For the wiki 'wiki', # The new django-wiki from benjaoming + 'course_wiki', # Our customizations 'django_notify', 'mptt', 'sekizai', diff --git a/lms/urls.py b/lms/urls.py index 23852d00c1..efb55635be 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -151,8 +151,12 @@ if settings.WIKI_ENABLED: # url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')), # ) urlpatterns += ( - - #url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')), + # First we include views from course_wiki that we use to override the default views. + # They come first in the urlpatterns so they get resolved first + url('^wiki/create-root/$', 'course_wiki.views.root_create', name='root_create'), + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki$', + 'course_wiki.views.course_wiki_redirect', name="course_wiki"), + url(r'wiki/', include(wiki_pattern())), url(r'^notify/', include(notify_pattern())), From dd56fd09166d92f3e2426db3e8f5724ec63b51d6 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 09:13:42 -0400 Subject: [PATCH 09/26] Added some comments for the mitxmako additions. --- .../mitxmako/mitxmako/templatetag_helpers.py | 36 ++++++++++++++----- lms/templates/main_django.html | 12 ++++++- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/common/lib/mitxmako/mitxmako/templatetag_helpers.py b/common/lib/mitxmako/mitxmako/templatetag_helpers.py index 6742f3ee1c..e254625d3d 100644 --- a/common/lib/mitxmako/mitxmako/templatetag_helpers.py +++ b/common/lib/mitxmako/mitxmako/templatetag_helpers.py @@ -2,8 +2,34 @@ from django.template import loader from django.template.base import Template, Context from django.template.loader import get_template, select_template +def django_template_include(file_name, mako_context): + """ + This can be used within a mako template to include a django template + in the way that a django-style {% include %} does. Pass it context + which can be the mako context ('context') or a dictionary. + """ + + dictionary = dict( mako_context ) + return loader.render_to_string(file_name, dictionary=dictionary) + def render_inclusion(func, file_name, takes_context, django_context, *args, **kwargs): + """ + This allows a mako template to call a template tag function (written + for django templates) that is an "inclusion tag". These functions are + decorated with @register.inclusion_tag. + + -func: This is the function that is registered as an inclusion tag. + You must import it directly using a python import statement. + -file_name: This is the filename of the template, passed into the + @register.inclusion_tag statement. + -takes_context: This is a parameter of the @register.inclusion_tag. + -django_context: This is an instance of the django context. If this + is a mako template rendered through the regular django rendering calls, + a copy of the django context is available as 'django_context'. + -*args and **kwargs are the arguments to func. + """ + if takes_context: args = [django_context] + list(args) @@ -22,14 +48,6 @@ def render_inclusion(func, file_name, takes_context, django_context, *args, **kw if csrf_token is not None: new_context['csrf_token'] = csrf_token - # **{ - # 'autoescape': context.autoescape, - # 'current_app': context.current_app, - # 'use_l10n': context.use_l10n, - # 'use_tz': context.use_tz, - # }) return nodelist.render(new_context) -def django_template_include(file_name, mako_context): - dictionary = dict( mako_context ) - return loader.render_to_string(file_name, dictionary=dictionary) + diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html index 22620710c3..74838d7b85 100644 --- a/lms/templates/main_django.html +++ b/lms/templates/main_django.html @@ -12,7 +12,6 @@ {% render_block "css" %} {% render_block "js" %} - @@ -31,3 +30,14 @@ {% compressed_js 'module-js' %} + +{% comment %} + This is a django template version of our main page from which all + other pages inherit. This file should be rewritten to reflect any + changes in main.html! Files used by {% include %} can be written + as mako templates. + + Inheriting from this file allows us to include apps that use the + django templating system without rewriting all of their views in + mako. +{% endcomment %} \ No newline at end of file From eeeb6773545459ad2b48ea137f1197cdac647525 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 11:39:49 -0400 Subject: [PATCH 10/26] Fixed wiki urls bug. Fixed reverse for course wiki. --- lms/templates/course_navigation.html | 2 +- lms/urls.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/lms/templates/course_navigation.html b/lms/templates/course_navigation.html index 84b0c04ca0..91c253b39e 100644 --- a/lms/templates/course_navigation.html +++ b/lms/templates/course_navigation.html @@ -22,7 +22,7 @@ def url_class(url): % endif % endif % if settings.WIKI_ENABLED: -
  • Wiki
  • +
  • Wiki
  • % endif % if user.is_authenticated():
  • Profile
  • diff --git a/lms/urls.py b/lms/urls.py index efb55635be..2ff971c072 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -146,10 +146,6 @@ if settings.WIKI_ENABLED: from wiki.urls import get_pattern as wiki_pattern from django_notify.urls import get_pattern as notify_pattern - # urlpatterns += ( - # url(r'^wiki/', include('simplewiki.urls')), - # url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/', include('simplewiki.urls')), - # ) urlpatterns += ( # First we include views from course_wiki that we use to override the default views. # They come first in the urlpatterns so they get resolved first @@ -157,8 +153,7 @@ if settings.WIKI_ENABLED: url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki$', 'course_wiki.views.course_wiki_redirect', name="course_wiki"), - - url(r'wiki/', include(wiki_pattern())), + url(r'^wiki/', include(wiki_pattern())), url(r'^notify/', include(notify_pattern())), ) From 1f43ae6d3eb6a8eb97991e7558b6c93352ae51e5 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 13:49:53 -0400 Subject: [PATCH 11/26] Renamed namespace to wiki_slug in course_module.py. --- common/lib/xmodule/xmodule/course_module.py | 2 +- lms/djangoapps/course_wiki/views.py | 24 ++++++++++++++++----- requirements.txt | 1 + 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index e7d480f4e9..c613283683 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -169,7 +169,7 @@ class CourseDescriptor(SequenceDescriptor): return self.location.course @property - def wiki_namespace(self): + def wiki_slug(self): return self.location.course @property diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index c2e862471f..83e726e49f 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -1,10 +1,14 @@ -from django.shortcuts import redirect +import logging +import re +from django.shortcuts import redirect from wiki.core.exceptions import NoRootURL from wiki.models import URLPath, Article from courseware.courses import check_course +log = logging.getLogger(__name__) + def root_create(request): """ @@ -21,13 +25,23 @@ def course_wiki_redirect(request, course_id): as it's home page. A course's wiki must be an article on the root (for example, "/6.002x") to keep things simple. """ - course = check_course(course_id) + course = check_course(request.user, course_id) - namespace = course.wiki_namespace + course_slug = course.wiki_slug + valid_slug = True #TODO: Make sure this is a legal slug. No "/"'s + if not course_slug: + log.exception("This course is improperly configured. The slug cannot be empty.") + valid_slug = False + if re.match('^[-\w\.]+$', course_slug) == None: + log.exception("This course is improperly configured. The slug can only contain letters, numbers, periods or hyphens.") + valid_slug = False + + if not valid_slug: + return redirect("wiki:get", path="") try: - urlpath = URLPath.get_by_path(namespace, select_related=True) + urlpath = URLPath.get_by_path(course_slug, select_related=True) results = list( Article.objects.filter( id = urlpath.article.id ) ) if results: @@ -51,7 +65,7 @@ def course_wiki_redirect(request, course_id): urlpath = URLPath.create_article( root, - namespace, + course_slug, title=course.title, content="This is the wiki for " + course.title + ".", user_message="Course page automatically created.", diff --git a/requirements.txt b/requirements.txt index 5905aa839f..ad6a8ab4a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -44,4 +44,5 @@ django-ses django-storages django-threaded-multihost -e git+git://github.com/benjaoming/django-wiki.git#egg=django-wiki +django-sekizai<0.7 -r repo-requirements.txt From 85f1899cb64e8c4cda1a013fff7d664c5ae487c3 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 17:15:35 -0400 Subject: [PATCH 12/26] Wiki pages can now be viewed from the course URL and the course nav is shown. It doesn't follow the user yet. --- lms/djangoapps/course_wiki/course_nav.py | 32 ++++++++++++++++++++++++ lms/djangoapps/course_wiki/views.py | 4 +-- lms/envs/common.py | 1 + lms/templates/course_navigation.html | 7 +++++- lms/templates/wiki/base.html | 10 +++++++- lms/urls.py | 13 +++++++--- 6 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 lms/djangoapps/course_wiki/course_nav.py diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py new file mode 100644 index 0000000000..4604e09ff9 --- /dev/null +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -0,0 +1,32 @@ +import re + +from django.http import Http404 +from django.shortcuts import redirect + +from courseware.courses import check_course + + +def context_processor(request): + """ + This is a context processor which looks at the URL while we are + in the wiki. If the url is in the form + /courses/(course_id)/wiki/... + then we add 'course' to the context. This allows the course nav + bar to be shown. + """ + + match = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/wiki(?P.*|)', request.path) + if match: + course_id = match.group('course_id') + + try: + course = check_course(request.user, course_id) + return {'course' : course} + except Http404: + # We couldn't access the course for whatever reason. It is too late to change + # the URL here, so we just leave the course context. The middleware shouldn't + # let this happen + pass + + return {} + \ No newline at end of file diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index 83e726e49f..0ec69c5c3b 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -28,15 +28,15 @@ def course_wiki_redirect(request, course_id): course = check_course(request.user, course_id) course_slug = course.wiki_slug + valid_slug = True - #TODO: Make sure this is a legal slug. No "/"'s if not course_slug: log.exception("This course is improperly configured. The slug cannot be empty.") valid_slug = False if re.match('^[-\w\.]+$', course_slug) == None: log.exception("This course is improperly configured. The slug can only contain letters, numbers, periods or hyphens.") valid_slug = False - + if not valid_slug: return redirect("wiki:get", path="") diff --git a/lms/envs/common.py b/lms/envs/common.py index a611281f2e..e202f30498 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -132,6 +132,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.tz', 'django.contrib.messages.context_processors.messages', 'sekizai.context_processors.sekizai', + 'course_wiki.course_nav.context_processor', ) diff --git a/lms/templates/course_navigation.html b/lms/templates/course_navigation.html index 11ec0a6690..a9ee199774 100644 --- a/lms/templates/course_navigation.html +++ b/lms/templates/course_navigation.html @@ -1,6 +1,11 @@ -<%page args="active_page" /> +## mako +<%page args="active_page=None" /> <% +if active_page == None and active_page_context is not UNDEFINED: + # If active_page is not passed in as an argument, it may be in the context as active_page_context + active_page = active_page_context + def url_class(url): if url == active_page: return "active" diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html index 93558939c4..3bfc8cd2aa 100644 --- a/lms/templates/wiki/base.html +++ b/lms/templates/wiki/base.html @@ -1,9 +1,17 @@ {% extends "main_django.html" %} -{% load sekizai_tags i18n %}{% load url from future %} +{% load compressed %}{% load sekizai_tags i18n %}{% load url from future %} {% block title %}{% block pagetitle %}{% endblock %} | edX Wiki{% endblock %} +{% block headextra %} + {% compressed_css 'course' %} +{% endblock %} + {% block body %} + {% if course %} + {% include "course_navigation.html" with active_page_context="wiki" %} + {% endif %} + {% block wiki_body %} {% if messages %} diff --git a/lms/urls.py b/lms/urls.py index d6a6bcb10e..175cb9b25f 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -159,15 +159,22 @@ if settings.WIKI_ENABLED: from wiki.urls import get_pattern as wiki_pattern from django_notify.urls import get_pattern as notify_pattern + # Note that some of these urls are repeated in course_wiki.course_nav. Make sure to update + # them together. urlpatterns += ( # First we include views from course_wiki that we use to override the default views. # They come first in the urlpatterns so they get resolved first - url('^wiki/create-root/$', 'course_wiki.views.root_create', name='root_create'), - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki$', - 'course_wiki.views.course_wiki_redirect', name="course_wiki"), + url('^wiki/create-root/$', 'course_wiki.views.root_create', name='root_create'), + url(r'^wiki/', include(wiki_pattern())), url(r'^notify/', include(notify_pattern())), + + # These urls are for viewing the wiki in the context of a course. They should + # never be returned by a reverse() so they come after the other url patterns + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki$', + 'course_wiki.views.course_wiki_redirect', name="course_wiki"), + url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/wiki/', include(wiki_pattern())), ) if settings.QUICKEDIT: From ceaa6a4ff27fbce49748b094e9d5ad287d83b297 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 18:44:23 -0400 Subject: [PATCH 13/26] You can now click around in the wiki without losing your course nav bar. --- lms/djangoapps/course_wiki/course_nav.py | 41 +++++++++++++++++++++++- lms/envs/common.py | 2 ++ lms/urls.py | 4 +-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index 4604e09ff9..776f3f914b 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -1,4 +1,5 @@ import re +from urlparse import urlparse from django.http import Http404 from django.shortcuts import redirect @@ -6,6 +7,44 @@ from django.shortcuts import redirect from courseware.courses import check_course +class Middleware(object): + """ + This middleware is to keep the course nav bar above the wiki while + the student clicks around to other wiki pages. + If it intercepts a request for /wiki/.. that has a referrer in the + form /courses/course_id/... it will redirect the user to the page + /courses/course_id/wiki/... + """ + + def process_request(self, request): + #TODO: We should also redirect people who can't see the class to the regular wiki, so urls don't break + + referer = request.META.get('HTTP_REFERER') + + try: + parsed_referer = urlparse(referer) + referer_path = parsed_referer.path + except: + referer_path ="" + + path_match = re.match(r'^/wiki/(?P.*|)$', request.path) + if path_match: + # We are going to the wiki. Check if we came from a course + course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/.*', referer_path) + if course_match: + course_id = course_match.group('course_id') + + # See if we are able to view the course. If we are, redirect to it + try: + course = check_course(request.user, course_id) + return redirect("/courses/" + course.id + "/view_wiki/" + path_match.group('wiki_path') ) + + except Http404: + # Even though we came from the course, we can't see it. So don't worry about it. + pass + + return None + def context_processor(request): """ This is a context processor which looks at the URL while we are @@ -15,7 +54,7 @@ def context_processor(request): bar to be shown. """ - match = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/wiki(?P.*|)', request.path) + match = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/view_wiki(?P.*|)', request.path) if match: course_id = match.group('course_id') diff --git a/lms/envs/common.py b/lms/envs/common.py index e202f30498..678d592f43 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -328,6 +328,8 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', 'track.middleware.TrackMiddleware', 'mitxmako.middleware.MakoMiddleware', + + 'course_wiki.course_nav.Middleware', 'askbot.middleware.anon_user.ConnectToSessionMessagesMiddleware', 'askbot.middleware.forum_mode.ForumModeMiddleware', diff --git a/lms/urls.py b/lms/urls.py index 175cb9b25f..59503dcf6b 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -172,9 +172,9 @@ if settings.WIKI_ENABLED: # These urls are for viewing the wiki in the context of a course. They should # never be returned by a reverse() so they come after the other url patterns - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki$', + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/?$', 'course_wiki.views.course_wiki_redirect', name="course_wiki"), - url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/wiki/', include(wiki_pattern())), + url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/view_wiki/', include(wiki_pattern())), ) if settings.QUICKEDIT: From 9f770ef9214f1c9c8e5e22d3f5267387e1b448a0 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Tue, 14 Aug 2012 19:16:45 -0400 Subject: [PATCH 14/26] Visiting the wiki from a course you aren't in does a redirect to the regular wiki. --- lms/djangoapps/course_wiki/course_nav.py | 24 ++++++++++++++++++++++-- lms/urls.py | 4 ++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index 776f3f914b..51158cf7bd 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -14,6 +14,10 @@ class Middleware(object): If it intercepts a request for /wiki/.. that has a referrer in the form /courses/course_id/... it will redirect the user to the page /courses/course_id/wiki/... + + It is also possible that someone followed a link leading to a course + that they don't have access to. In this case, we redirect them to the + same page on the regular wiki. """ def process_request(self, request): @@ -37,11 +41,27 @@ class Middleware(object): # See if we are able to view the course. If we are, redirect to it try: course = check_course(request.user, course_id) - return redirect("/courses/" + course.id + "/view_wiki/" + path_match.group('wiki_path') ) + return redirect("/courses/" + course.id + "/wiki/" + path_match.group('wiki_path') ) except Http404: # Even though we came from the course, we can't see it. So don't worry about it. pass + + else: + # It is also possible we are going to a course wiki view, but we + # don't have permission to see the course! + course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/wiki/(?P.*|)$', request.path) + if course_match: + course_id = course_match.group('course_id') + # See if we are able to view the course. If we aren't, redirect to regular wiki + try: + course = check_course(request.user, course_id) + # Good, we can see the course. Carry on + return None + except Http404: + # We can't see the course, so redirect to the regular wiki + return redirect("/wiki/" + course_match.group('wiki_path')) + return None @@ -54,7 +74,7 @@ def context_processor(request): bar to be shown. """ - match = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/view_wiki(?P.*|)', request.path) + match = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/wiki(?P.*|)', request.path) if match: course_id = match.group('course_id') diff --git a/lms/urls.py b/lms/urls.py index 59503dcf6b..14e0fa0658 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -172,9 +172,9 @@ if settings.WIKI_ENABLED: # These urls are for viewing the wiki in the context of a course. They should # never be returned by a reverse() so they come after the other url patterns - url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/wiki/?$', + url(r'^courses/(?P[^/]+/[^/]+/[^/]+)/course_wiki/?$', 'course_wiki.views.course_wiki_redirect', name="course_wiki"), - url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/view_wiki/', include(wiki_pattern())), + url(r'^courses/(?:[^/]+/[^/]+/[^/]+)/wiki/', include(wiki_pattern())), ) if settings.QUICKEDIT: From 102dcc08e0bc0d49bb8184666225af5a6ac75d6f Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 15 Aug 2012 11:14:57 -0400 Subject: [PATCH 15/26] Added a test for the wiki course redirection. --- lms/djangoapps/course_wiki/tests/__init__.py | 1 + lms/djangoapps/course_wiki/tests/tests.py | 72 ++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 lms/djangoapps/course_wiki/tests/__init__.py create mode 100644 lms/djangoapps/course_wiki/tests/tests.py diff --git a/lms/djangoapps/course_wiki/tests/__init__.py b/lms/djangoapps/course_wiki/tests/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/lms/djangoapps/course_wiki/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py new file mode 100644 index 0000000000..2fcc437aa4 --- /dev/null +++ b/lms/djangoapps/course_wiki/tests/tests.py @@ -0,0 +1,72 @@ +from django.core.urlresolvers import reverse +from override_settings import override_settings + +import xmodule.modulestore.django + +from courseware.tests.tests import PageLoader, TEST_DATA_XML_MODULESTORE +from xmodule.modulestore.django import modulestore +from xmodule.modulestore.xml_importer import import_from_xml + + +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class WikiRedirectTestCase(PageLoader): + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + courses = modulestore().get_courses() + + def find_course(name): + """Assumes the course is present""" + return [c for c in courses if c.location.course==name][0] + + self.full = find_course("full") + self.toy = find_course("toy") + + # Create two accounts + self.student = 'view@test.com' + self.instructor = 'view2@test.com' + self.password = 'foo' + self.create_account('u1', self.student, self.password) + self.create_account('u2', self.instructor, self.password) + self.activate_user(self.student) + self.activate_user(self.instructor) + + + + def test_wiki_redirect(self): + """ + Test that an enrolled in student going from /courses/edX/toy/2012_Fall/profile + to /wiki/some/fake/wiki/page/ will redirect to + /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ + + Test that an unenrolled student going to /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ + will be redirected to /wiki/some/fake/wiki/page/ + + """ + self.login(self.student, self.password) + + self.enroll(self.toy) + + referer = reverse("profile", kwargs={ 'course_id' : self.toy.id }) + destination = reverse("wiki:get", kwargs={'path': 'some/fake/wiki/page/'}) + + redirected_to = referer.replace("profile", "wiki/some/fake/wiki/page/") + + resp = self.client.get( destination, HTTP_REFERER=referer) + self.assertEqual(resp.status_code, 302 ) + + self.assertEqual(resp['Location'], 'http://testserver' + redirected_to ) + + + # Now we test that the student will be redirected away from that page if they are unenrolled + # We do this in the same test because we want to make sure the redirected_to is the same + + self.unenroll(self.toy) + + resp = self.client.get( redirected_to, HTTP_REFERER=referer) + print "redirected_to" , redirected_to + self.assertEqual(resp.status_code, 302) + self.assertEqual(resp['Location'], 'http://testserver' + destination ) + + + + From 099f499efafbff296d6da8e841a8136b269422f2 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 15 Aug 2012 13:00:17 -0400 Subject: [PATCH 16/26] Fixed access methods for wiki nav. Added tests. --- lms/djangoapps/course_wiki/course_nav.py | 15 +++--- lms/djangoapps/course_wiki/tests/tests.py | 61 ++++++++++++++++++++--- lms/djangoapps/course_wiki/views.py | 4 +- lms/djangoapps/courseware/tests/tests.py | 2 +- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index 51158cf7bd..e95bb4dee4 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -4,7 +4,7 @@ from urlparse import urlparse from django.http import Http404 from django.shortcuts import redirect -from courseware.courses import check_course +from courseware.courses import get_course_with_access class Middleware(object): @@ -20,9 +20,7 @@ class Middleware(object): same page on the regular wiki. """ - def process_request(self, request): - #TODO: We should also redirect people who can't see the class to the regular wiki, so urls don't break - + def process_request(self, request): referer = request.META.get('HTTP_REFERER') try: @@ -30,7 +28,7 @@ class Middleware(object): referer_path = parsed_referer.path except: referer_path ="" - + path_match = re.match(r'^/wiki/(?P.*|)$', request.path) if path_match: # We are going to the wiki. Check if we came from a course @@ -40,7 +38,7 @@ class Middleware(object): # See if we are able to view the course. If we are, redirect to it try: - course = check_course(request.user, course_id) + course = get_course_with_access(request.user, course_id, 'load') return redirect("/courses/" + course.id + "/wiki/" + path_match.group('wiki_path') ) except Http404: @@ -55,14 +53,13 @@ class Middleware(object): course_id = course_match.group('course_id') # See if we are able to view the course. If we aren't, redirect to regular wiki try: - course = check_course(request.user, course_id) + course = get_course_with_access(request.user, course_id, 'load') # Good, we can see the course. Carry on return None except Http404: # We can't see the course, so redirect to the regular wiki return redirect("/wiki/" + course_match.group('wiki_path')) - return None def context_processor(request): @@ -79,7 +76,7 @@ def context_processor(request): course_id = match.group('course_id') try: - course = check_course(request.user, course_id) + course = get_course_with_access(request.user, course_id, 'load') return {'course' : course} except Http404: # We couldn't access the course for whatever reason. It is too late to change diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py index 2fcc437aa4..e004265379 100644 --- a/lms/djangoapps/course_wiki/tests/tests.py +++ b/lms/djangoapps/course_wiki/tests/tests.py @@ -34,11 +34,13 @@ class WikiRedirectTestCase(PageLoader): def test_wiki_redirect(self): """ - Test that an enrolled in student going from /courses/edX/toy/2012_Fall/profile + Test that requesting wiki URLs redirect properly to or out of classes. + + An enrolled in student going from /courses/edX/toy/2012_Fall/profile to /wiki/some/fake/wiki/page/ will redirect to /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ - Test that an unenrolled student going to /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ + An unenrolled student going to /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ will be redirected to /wiki/some/fake/wiki/page/ """ @@ -57,16 +59,61 @@ class WikiRedirectTestCase(PageLoader): self.assertEqual(resp['Location'], 'http://testserver' + redirected_to ) - # Now we test that the student will be redirected away from that page if they are unenrolled - # We do this in the same test because we want to make sure the redirected_to is the same + # Now we test that the student will be redirected away from that page if the course doesn't exist + # We do this in the same test because we want to make sure the redirected_to is constructed correctly - self.unenroll(self.toy) + # This is a location like /courses/*/wiki/* , but with an invalid course ID + bad_course_wiki_page = redirected_to.replace( self.toy.location.course, "bad_course" ) - resp = self.client.get( redirected_to, HTTP_REFERER=referer) - print "redirected_to" , redirected_to + resp = self.client.get( bad_course_wiki_page, HTTP_REFERER=referer) self.assertEqual(resp.status_code, 302) self.assertEqual(resp['Location'], 'http://testserver' + destination ) + + def create_course_page(self, course): + """ + Test that loading the course wiki page creates the wiki page. + The user must be enrolled in the course to see the page. + """ + + course_wiki_home = reverse('course_wiki', kwargs={'course_id' : course.id}) + referer = reverse("profile", kwargs={ 'course_id' : self.toy.id }) + + resp = self.client.get(course_wiki_home, follow=True, HTTP_REFERER=referer) + + course_wiki_page = referer.replace('profile', 'wiki/' + self.toy.wiki_slug + "/") + + ending_location = resp.redirect_chain[-1][0] + ending_status = resp.redirect_chain[-1][1] + + self.assertEquals(ending_location, 'http://testserver' + course_wiki_page ) + self.assertEquals(resp.status_code, 200) + + self.has_course_navigator(resp) + + def has_course_navigator(self, resp): + """ + Ensure that the response has the course navigator. + """ + self.assertTrue( "course info" in resp.content.lower() ) + self.assertTrue( "courseware" in resp.content.lower() ) + + + def test_course_navigator(self): + """" + Test that going from a course page to a wiki page contains the course navigator. + """ + + self.login(self.student, self.password) + self.enroll(self.toy) + self.create_course_page(self.toy) + course_wiki_page = reverse('wiki:get', kwargs={'path' : self.toy.wiki_slug + '/'}) + referer = reverse("courseware", kwargs={ 'course_id' : self.toy.id }) + resp = self.client.get(course_wiki_page, follow=True, HTTP_REFERER=referer) + + self.has_course_navigator(resp) + + diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index 0ec69c5c3b..7a27625297 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -5,7 +5,7 @@ from django.shortcuts import redirect from wiki.core.exceptions import NoRootURL from wiki.models import URLPath, Article -from courseware.courses import check_course +from courseware.courses import get_course_by_id log = logging.getLogger(__name__) @@ -25,7 +25,7 @@ def course_wiki_redirect(request, course_id): as it's home page. A course's wiki must be an article on the root (for example, "/6.002x") to keep things simple. """ - course = check_course(request.user, course_id) + course = get_course_by_id(course_id) course_slug = course.wiki_slug diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 92ddb2767e..f3b978adac 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -187,7 +187,7 @@ class PageLoader(ActivateLoginTestCase): def unenroll(self, course): """Unenroll the currently logged-in user, and check that it worked.""" resp = self.client.post('/change_enrollment', { - 'enrollment_action': 'enroll', + 'enrollment_action': 'unenroll', 'course_id': course.id, }) data = parse_json(resp) From abf26dced9b3e6421be15abf82ff3a1a2bf937c0 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Wed, 15 Aug 2012 13:10:47 -0400 Subject: [PATCH 17/26] started styles --- common/static/images/search-icon.png | Bin 0 -> 1195 bytes lms/static/sass/course/wiki/_wiki.scss | 294 +++++++++--------- lms/templates/wiki/article.html | 36 +++ lms/templates/wiki/base.html | 37 ++- lms/templates/wiki/includes/article_menu.html | 47 +++ lms/templates/wiki/includes/breadcrumbs.html | 52 ++++ 6 files changed, 307 insertions(+), 159 deletions(-) create mode 100644 common/static/images/search-icon.png create mode 100644 lms/templates/wiki/article.html create mode 100644 lms/templates/wiki/includes/article_menu.html create mode 100644 lms/templates/wiki/includes/breadcrumbs.html diff --git a/common/static/images/search-icon.png b/common/static/images/search-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9ab8b34b7532a9e629701211da4cb8fb253de5c9 GIT binary patch literal 1195 zcmeAS@N?(olHy`uVBq!ia0vp^{2c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvO-#)w?G{Iz=0WwQ z;C71>PQCg-$LNEi7AdM>LcsI`V!{(HkONQpsd>QkUIa|odLHMtF)%R7db&7tKf|4A*593TbK(s3Sxv+(*QI>!_-R_wXY%S6@8|r=bN&|^Y;2rZpVb`R zKEe3f>&L&F=G|X2|1saP2~Quz98fN?{Pssb=dku` +
    +

    {{ article.current_revision.title }}

    +
    + {% block wiki_contents_tab %} + {% wiki_render article %} + {% endblock %} +
    +
    + +
    + +
    +
    + + + +{% endblock %} + +{% block footer_prepend %} +

    {% trans "This article was last modified:" %} {{ article.current_revision.modified }}

    +{% endblock %} diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html index 3bfc8cd2aa..3b02690d67 100644 --- a/lms/templates/wiki/base.html +++ b/lms/templates/wiki/base.html @@ -12,20 +12,25 @@ {% include "course_navigation.html" with active_page_context="wiki" %} {% endif %} - {% block wiki_body %} - - {% if messages %} - {% for message in messages %} -
    - × - {{ message }} -
    - {% endfor %} - {% endif %} - - {% block wiki_breadcrumbs %}{% endblock %} - - {% block wiki_contents %}{% endblock %} - - {% endblock %} +
    + + {% block wiki_body %} + + {% block wiki_breadcrumbs %}{% endblock %} + + {% if messages %} + {% for message in messages %} +
    + × + {{ message }} +
    + {% endfor %} + {% endif %} + + {% block wiki_contents %}{% endblock %} + + {% endblock %} + +
    + {% endblock %} diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html new file mode 100644 index 0000000000..fb01fd109d --- /dev/null +++ b/lms/templates/wiki/includes/article_menu.html @@ -0,0 +1,47 @@ +{% load i18n wiki_tags %}{% load url from future %} + +{% with selected_tab as selected %} + + + + + + + +
  • + + + {% trans "View" %} + +
  • +
  • + + + {% trans "Edit" %} + +
  • +
  • + + + {% trans "Changes" %} + +
  • +{% for plugin in article_tabs %} +
  • + + + {{ plugin.article_tab.0 }} + +
  • +{% endfor %} + +
  • + {% if not user.is_anonymous %} + + + {% trans "Settings" %} + + {% endif %} +
  • + +{% endwith %} diff --git a/lms/templates/wiki/includes/breadcrumbs.html b/lms/templates/wiki/includes/breadcrumbs.html new file mode 100644 index 0000000000..6afe248a29 --- /dev/null +++ b/lms/templates/wiki/includes/breadcrumbs.html @@ -0,0 +1,52 @@ +{% load i18n %}{% load url from future %} +{% if urlpath %} + +
    + + + +
    +
    + + + + + +
    +
    + + + +
    + +{% endif %} From 13c2bb588e4792adbd5ff15232876ed6ceb7d896 Mon Sep 17 00:00:00 2001 From: Tom Giannattasio Date: Wed, 15 Aug 2012 15:59:46 -0400 Subject: [PATCH 18/26] added styles for main article, sidebar and breadcrumb --- common/static/images/wiki-icons.png | Bin 0 -> 2410 bytes lms/static/sass/course/wiki/_wiki.scss | 157 +++++++++++++++++- lms/templates/wiki/article.html | 15 +- lms/templates/wiki/includes/article_menu.html | 10 +- 4 files changed, 165 insertions(+), 17 deletions(-) create mode 100644 common/static/images/wiki-icons.png diff --git a/common/static/images/wiki-icons.png b/common/static/images/wiki-icons.png new file mode 100644 index 0000000000000000000000000000000000000000..52c6dbc66f547743dd6d5171e727ccd2fdd0fff6 GIT binary patch literal 2410 zcmbVOc~BF177iea3d$j;S4`y4A!jFnWC)NG0tUI!%#Jf5X-L43Ob!SNDjHpcpk{&` z4#*`dqoT_-pdd^QsK5wDL_`L39HodmDvE%JrL`RucmJ?vtG27Ve@DOfz3;u>_p17E zcvz6lGPh+g7|ezf%#4IaJoIE*Er#B|lxci92%`z5@gSTd;U|KT zAYYjNT@UC7gIS2g(c6^UxLYU!sRYe8*`U=D8RQLv`30zDd_gj(gvW!4;uI=kvb6&N z7YnI~I3gFzl`%k(ICz&FjM^0zE!dSTAPW%z{%}7v1p<(ONNz~XRl5($kX5J@DQHyn?};W1d~CZTWy3Xw#? z`@lb52q>Cdm_UhSvOmUxR#b#YsgzMLn6$JsbQ*w`$`dg-GMQ|`z~fPn1xk^gqU5Vl zDGKBQ0uxjSBI9yD0jDTF778jEM$MODaA+(>A~EIl z-ddrI1ph9_s@-1)G8sZ_C|l)X=ur4OnNopD0;VWA zOezBUf)3}}u%LL`(R8Yv4OC|946{d(k!+^y2UzvaK75+a} z!$4#(CVBjqTo$$<4VtE(#1Ab#wGWsA^^6>9j&Dn_GccHC5r;{OR*yU>Nv?@ut-3Z* zvb%-4F$A#jzL|555UigX={&b6fZ5$=G-K(UEOoJ7LUyJtl91!i++eqj)l8E_N2*-JNg&rJm$HpLlYI-0|{$Lt?^>5>;yL-JK`e zZB@5|=y^4E#DT3^!l(_EN=2+69UFT(=CiZs6+(*w`lnXE=&~}BjB;bN$g|OjXP)V) zj10w1X|4CIJMiovr8KjU_KJZCR8(}>+hk+uD&fXy*J+qVDlIK=A~21y_vPUh2j0rO zFzkk_&yROsG;Buy5j(mBH9p>FFhkD{InP_2Fwfj!(0FymAuwcE&e77Drv7=3p+37H zJpaLg{zvgY3F!Uv0U6uZY!)%|AG9_ODOkJLQVujzEpC^q+9o-F&ea+S##`C*8+xsp zc;7Ib>AB^D3y;atunJ_MIUp+_T=n5K$UvIm0$idakgP6qc?4mWt(yASl}7kkF4E0+WRa!2>X zl?RnKZ^k=Z2`0+B-L`H$*S%#`)yeQd03YjEFNS%&OhVO37}iOYL!`mQh!YW87lpT3jk#x#-JuvT!pj$XuXrp2 z=qe5Gh}iLjEvN}OmE=&VE!x8z(RfwW=&FN$W^Q8F92q?F^H{S>)eUWsvNw8xeX;R& z!}{7{TAg4v%OGp7owJ;8*zfSebuYP?d(eMhYT)W}O!{kv(QSi8wQD-EYoffFTX4|u zplveJXrmuLq%3gRkb|Wc4n=u{V(JC<9qm#{>rLTyzh(~?JHcGYqY(Xg=*2qS_*Iml zN$0_I^+zh~`=6EM99`Ldx>WbDqG#zbv{AGFS`IMc>UuJ&PUJOTHqj9F#oWOz*QeSi zD`7RxBNk(i%pAEChr~vp-g0Cjt8Hm%kdK+6uits{0A&DqPAdD%cl?GXK88NtK0x@CZSt8QqPrE`qSe*jD^omPjP1qOMi|JHR}$7Z@# ze3hSWFn2iFFO^UFefh$PrH#1pdf(qGLdzXJLSrOXcAVG7Vd1L9k=mQdJHANt8{j+6 zOxAtVPGtAE9^DAUUqwz=bj0`>LKD=9Zq@AEiioN~QH1CE!ECn(Waf=vp!})fwfTLT zM#n>Z?RT~=vU`h|eMYD9TOL_m?8Ppo)E9f&uGl#GOD@nGJ1gZDT$Y6#oN2MGim2Ql zyEel5Y0!HlNOt*$+g7}&;rQRmK$-Ms!cXcl1~58xuO`C}-)D_lUn;V%D7#`T9A3X= zwy|h)2vBSX#{=)i4PCg{sUS|Fwug*lxJ?y>dJ#P1t^lwA;-}BFtE|-PP+LZlRlp2$7 zcjn1-OAU3!DPqZ~JFm`Vww4NV54j+PLaXM@wM9v{H|=V2$X&wl^fU=cs|D^>-DdNC z^N_oLb^XG_+RVYe0sVaZy6?PtMT>R~^ViL_1SUG^b*SFw$F0B%wZD%OJ${XmIxE?m zzCLR2QD}MOY>C|&ByiEi^QEM-s{h^V(Hw&+^B>}}m{R9>?J?chC^K@6rr^srg)dWc z%I4;VZ3SJUgm>MgtD4rd&K;;m7TXCl1F;v*s5A1f8#YBj#h*W{>om89fob=JAm-Rj Q)87w=6~;VI|91bs0W8$nYXATM literal 0 HcmV?d00001 diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index d31ee75721..7813158650 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -3,7 +3,7 @@ section.wiki { header { height: 33px; - margin-bottom: 50px; + margin-bottom: 36px; padding-bottom: 26px; border-bottom: 1px solid $light-gray; } @@ -27,13 +27,22 @@ section.wiki { .breadcrumb { list-style: none; padding-left: 0; - margin: 0; + margin: 0 0 0 flex-gutter(); li { float: left; margin-right: 10px; font-size: 0.9em; - line-height: 31px; + line-height: 31px; + + a { + display: inline-block; + max-width: 200px; + overflow: hidden; + height: 30px; + text-overflow: ellipsis; + white-space: nowrap; + } &:after { content: '›'; @@ -59,6 +68,7 @@ section.wiki { .global-functions { display: block; width: auto; + margin-right: flex-gutter(); } .add-article-btn { @@ -103,6 +113,10 @@ section.wiki { -----------------*/ + .article-wrapper { + + } + h1 { font-weight: bold; letter-spacing: 0; @@ -111,19 +125,79 @@ section.wiki { .main-article { float: left; width: flex-grid(9); + margin-left: flex-gutter(); + color: $base-font-color; h2 { + padding-bottom: 8px; + margin-bottom: 22px; + border-bottom: 1px solid $light-gray; + font-size: 1.33em; font-weight: bold; + color: $base-font-color; text-transform: none; letter-spacing: 0; - font-size: 1.33em; + } + + h3 { + margin-top: 40px; + margin-bottom: 20px; + font-weight: bold; + font-size: 1.1em; + } + + h4 { + + } + + h5 { + + } + + h6 { + + } + + ul { + font-size: inherit; + line-height: inherit; + color: inherit; + } + + li { + margin-bottom: 15px; } } + + + + /*----------------- + + Sidebar + + -----------------*/ + .article-functions { float: left; - width: flex-grid(2); + width: flex-grid(2) + flex-gutter(); margin-left: flex-grid(1); + + .timestamp { + margin: 4px 0 15px; + padding: 0 0 15px 5px; + border-bottom: 1px solid $light-gray; + + .label { + font-size: 0.7em; + color: #aaa; + text-transform: uppercase; + } + + .date { + font-size: 0.9em; + } + } } .nav-tabs { @@ -132,7 +206,78 @@ section.wiki { margin: 0; li { - margin-left: 20px; + &.active { + a { + color: $blue; + + .icon-view { + background-position: -25px 0; + } + + .icon-edit { + background-position: -25px -25px; + } + + .icon-changes { + background-position: -25px -49px; + } + + .icon-attachments { + background-position: -25px -73px; + } + + .icon-settings { + background-position: -25px -99px; + } + + &:hover { + background: none; + } + } + } + } + + a { + display: block; + padding: 2px 4px; + border-radius: 3px; + font-size: 0.9em; + line-height: 25px; + color: #8f8f8f; + + .icon { + float: left; + display: block; + width: 25px; + height: 25px; + margin-right: 3px; + background: url(../images/wiki-icons.png) no-repeat; + } + + .icon-view { + background-position: 0 0; + } + + .icon-edit { + background-position: 0 -25px; + } + + .icon-changes { + background-position: 0 -49px; + } + + .icon-attachments { + background-position: 0 -73px; + } + + .icon-settings { + background-position: 0 -99px; + } + + &:hover { + background-color: #f6f6f6; + text-decoration: none; + } } } diff --git a/lms/templates/wiki/article.html b/lms/templates/wiki/article.html index 5c72f9e177..b377ad284b 100644 --- a/lms/templates/wiki/article.html +++ b/lms/templates/wiki/article.html @@ -10,17 +10,20 @@ {% block wiki_contents %} -
    +
    +
    -

    {{ article.current_revision.title }}

    -
    +

    {{ article.current_revision.title }}

    {% block wiki_contents_tab %} {% wiki_render article %} - {% endblock %} -
    + {% endblock %}
    +
    + {% trans "Last modified:" %}
    + {{ article.current_revision.modified }} +
    @@ -32,5 +35,5 @@ {% endblock %} {% block footer_prepend %} -

    {% trans "This article was last modified:" %} {{ article.current_revision.modified }}

    +

    {% trans "This article was last modified:" %} {{ article.current_revision.modified }}

    {% endblock %} diff --git a/lms/templates/wiki/includes/article_menu.html b/lms/templates/wiki/includes/article_menu.html index fb01fd109d..33d1ba0cf9 100644 --- a/lms/templates/wiki/includes/article_menu.html +++ b/lms/templates/wiki/includes/article_menu.html @@ -10,26 +10,26 @@
  • - + {% trans "View" %}
  • - + {% trans "Edit" %}
  • - + {% trans "Changes" %}
  • {% for plugin in article_tabs %}
  • - + {{ plugin.article_tab.0 }}
  • @@ -38,7 +38,7 @@
  • {% if not user.is_anonymous %} - + {% trans "Settings" %} {% endif %} From 0d28d286e296c75d3e6c638db08c8f5c7ada1d40 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 15 Aug 2012 18:54:01 -0400 Subject: [PATCH 19/26] Added creation of Site() object on wiki load. --- lms/djangoapps/course_wiki/views.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py index 7a27625297..904a674f36 100644 --- a/lms/djangoapps/course_wiki/views.py +++ b/lms/djangoapps/course_wiki/views.py @@ -1,6 +1,9 @@ import logging import re +from django.conf import settings +from django.contrib.sites.models import Site +from django.core.exceptions import ImproperlyConfigured from django.shortcuts import redirect from wiki.core.exceptions import NoRootURL from wiki.models import URLPath, Article @@ -10,7 +13,6 @@ from courseware.courses import get_course_by_id log = logging.getLogger(__name__) def root_create(request): - """ In the edX wiki, we don't show the root_create view. Instead, we just create the root automatically if it doesn't exist. @@ -39,7 +41,19 @@ def course_wiki_redirect(request, course_id): if not valid_slug: return redirect("wiki:get", path="") - + + + # The wiki needs a Site object created. We make sure it exists here + try: + site = Site.objects.get_current() + except Site.DoesNotExist: + new_site = Site() + new_site.domain = settings.SITE_NAME + new_site.name = "edX" + new_site.save() + if str(new_site.id) != str(settings.SITE_ID): + raise ImproperlyConfigured("No site object was created and the SITE_ID doesn't match the newly created one. " + str(new_site.id) + "!=" + str(settings.SITE_ID)) + try: urlpath = URLPath.get_by_path(course_slug, select_related=True) From e76be095e9485147d91429e2a703bf80b956ab86 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 15 Aug 2012 18:54:22 -0400 Subject: [PATCH 20/26] Middleware for changing redirects to stay in course wiki. --- lms/djangoapps/course_wiki/course_nav.py | 78 +++++++++++++++++++----- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index e95bb4dee4..bb3f433b81 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -20,27 +20,27 @@ class Middleware(object): same page on the regular wiki. """ - def process_request(self, request): - referer = request.META.get('HTTP_REFERER') + def get_redirected_url(self, user, referer, destination): + """ + Returns None if the destination shouldn't be changed. + """ + if not referer: + return destination + referer_path = urlparse(referer).path - try: - parsed_referer = urlparse(referer) - referer_path = parsed_referer.path - except: - referer_path ="" - - path_match = re.match(r'^/wiki/(?P.*|)$', request.path) + path_match = re.match(r'^/wiki/(?P.*|)$', destination) + print "path_match" , path_match if path_match: # We are going to the wiki. Check if we came from a course course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/.*', referer_path) + print "course_match" , course_match if course_match: course_id = course_match.group('course_id') # See if we are able to view the course. If we are, redirect to it try: - course = get_course_with_access(request.user, course_id, 'load') - return redirect("/courses/" + course.id + "/wiki/" + path_match.group('wiki_path') ) - + course = get_course_with_access(user, course_id, 'load') + return "/courses/" + course.id + "/wiki/" + path_match.group('wiki_path') except Http404: # Even though we came from the course, we can't see it. So don't worry about it. pass @@ -48,18 +48,64 @@ class Middleware(object): else: # It is also possible we are going to a course wiki view, but we # don't have permission to see the course! - course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/wiki/(?P.*|)$', request.path) + course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/wiki/(?P.*|)$', destination) if course_match: course_id = course_match.group('course_id') # See if we are able to view the course. If we aren't, redirect to regular wiki try: - course = get_course_with_access(request.user, course_id, 'load') + course = get_course_with_access(user, course_id, 'load') # Good, we can see the course. Carry on - return None + return destination except Http404: # We can't see the course, so redirect to the regular wiki - return redirect("/wiki/" + course_match.group('wiki_path')) + return "/wiki/" + course_match.group('wiki_path') + + return destination + + + def process_response(self, request, response): + """ + If this is a redirect response going to /wiki/*, then we might need + to change it to be a redirect going to /courses/*/wiki*. + """ + print "processing response. Request: " , request.path, request.META.get('HTTP_REFERER') + print "response:", response.status_code, response['LOCATION'] if response.status_code == 302 else "" + print "self.redirected" , self.redirected, response.status_code, request.META.get('HTTP_REFERER') + if not self.redirected and response.status_code == 302: #This is a redirect + referer = request.META.get('HTTP_REFERER') + destination_url = response['LOCATION'] + destination = urlparse(destination_url).path + + new_destination = self.get_redirected_url(request.user, referer, destination) + print "old_destination" , destination + print "new_destination" , new_destination + + if new_destination != destination: + print "changinging redirection. Used to be " , destination_url + new_url = destination_url.replace(destination, new_destination) + print "now it is " , new_url + response['LOCATION'] = new_url + + return response + + + + def process_request(self, request): + self.redirected = False + if not request.method == 'GET': + return None + + referer = request.META.get('HTTP_REFERER') + destination = request.path + + new_destination = self.get_redirected_url(request.user, referer, destination) + + if new_destination != destination: + # We mark that we generated this redirection, so we don't modify it again + self.redirected = True + return redirect(new_destination) + return None def context_processor(request): From a163b486ac9b0240f0932d01b79a0a71cc7ec432 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 15 Aug 2012 19:34:42 -0400 Subject: [PATCH 21/26] Fixed up course_nav to swizzle url reversing too. --- lms/djangoapps/course_wiki/course_nav.py | 110 +++++++++++++---------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/lms/djangoapps/course_wiki/course_nav.py b/lms/djangoapps/course_wiki/course_nav.py index bb3f433b81..1d124972c7 100644 --- a/lms/djangoapps/course_wiki/course_nav.py +++ b/lms/djangoapps/course_wiki/course_nav.py @@ -4,9 +4,12 @@ from urlparse import urlparse from django.http import Http404 from django.shortcuts import redirect +from wiki.models import reverse as wiki_reverse from courseware.courses import get_course_with_access +IN_COURSE_WIKI_REGEX = r'/courses/(?P[^/]+/[^/]+/[^/]+)/wiki/(?P.*|)$' + class Middleware(object): """ This middleware is to keep the course nav bar above the wiki while @@ -18,8 +21,64 @@ class Middleware(object): It is also possible that someone followed a link leading to a course that they don't have access to. In this case, we redirect them to the same page on the regular wiki. + + If we return a redirect, this middleware makes sure that the redirect + keeps the student in the course. + + Finally, if the student is in the course viewing a wiki, we change the + reverse() function to resolve wiki urls as a course wiki url by setting + the _transform_url attribute on wiki.models.reverse. + + Forgive me Father, for I have hacked. """ + def __init__(self): + self.redirected = False + + def process_request(self, request): + self.redirected = False + wiki_reverse._transform_url = lambda url: url + + referer = request.META.get('HTTP_REFERER') + destination = request.path + + + if request.method == 'GET': + new_destination = self.get_redirected_url(request.user, referer, destination) + + if new_destination != destination: + # We mark that we generated this redirection, so we don't modify it again + self.redirected = True + return redirect(new_destination) + + course_match = re.match(IN_COURSE_WIKI_REGEX, destination) + if course_match: + course_id = course_match.group('course_id') + prepend_string = '/courses/' + course_match.group('course_id') + wiki_reverse._transform_url = lambda url: prepend_string + url + + return None + + + def process_response(self, request, response): + """ + If this is a redirect response going to /wiki/*, then we might need + to change it to be a redirect going to /courses/*/wiki*. + """ + if not self.redirected and response.status_code == 302: #This is a redirect + referer = request.META.get('HTTP_REFERER') + destination_url = response['LOCATION'] + destination = urlparse(destination_url).path + + new_destination = self.get_redirected_url(request.user, referer, destination) + + if new_destination != destination: + new_url = destination_url.replace(destination, new_destination) + response['LOCATION'] = new_url + + return response + + def get_redirected_url(self, user, referer, destination): """ Returns None if the destination shouldn't be changed. @@ -29,11 +88,9 @@ class Middleware(object): referer_path = urlparse(referer).path path_match = re.match(r'^/wiki/(?P.*|)$', destination) - print "path_match" , path_match if path_match: # We are going to the wiki. Check if we came from a course course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/.*', referer_path) - print "course_match" , course_match if course_match: course_id = course_match.group('course_id') @@ -48,7 +105,7 @@ class Middleware(object): else: # It is also possible we are going to a course wiki view, but we # don't have permission to see the course! - course_match = re.match(r'/courses/(?P[^/]+/[^/]+/[^/]+)/wiki/(?P.*|)$', destination) + course_match = re.match(IN_COURSE_WIKI_REGEX, destination) if course_match: course_id = course_match.group('course_id') # See if we are able to view the course. If we aren't, redirect to regular wiki @@ -62,51 +119,6 @@ class Middleware(object): return destination - - def process_response(self, request, response): - """ - If this is a redirect response going to /wiki/*, then we might need - to change it to be a redirect going to /courses/*/wiki*. - """ - print "processing response. Request: " , request.path, request.META.get('HTTP_REFERER') - print "response:", response.status_code, response['LOCATION'] if response.status_code == 302 else "" - print "self.redirected" , self.redirected, response.status_code, request.META.get('HTTP_REFERER') - if not self.redirected and response.status_code == 302: #This is a redirect - referer = request.META.get('HTTP_REFERER') - destination_url = response['LOCATION'] - destination = urlparse(destination_url).path - - new_destination = self.get_redirected_url(request.user, referer, destination) - - print "old_destination" , destination - print "new_destination" , new_destination - - if new_destination != destination: - print "changinging redirection. Used to be " , destination_url - new_url = destination_url.replace(destination, new_destination) - print "now it is " , new_url - response['LOCATION'] = new_url - - return response - - - - def process_request(self, request): - self.redirected = False - if not request.method == 'GET': - return None - - referer = request.META.get('HTTP_REFERER') - destination = request.path - - new_destination = self.get_redirected_url(request.user, referer, destination) - - if new_destination != destination: - # We mark that we generated this redirection, so we don't modify it again - self.redirected = True - return redirect(new_destination) - - return None def context_processor(request): """ @@ -117,7 +129,7 @@ def context_processor(request): bar to be shown. """ - match = re.match(r'^/courses/(?P[^/]+/[^/]+/[^/]+)/wiki(?P.*|)', request.path) + match = re.match(IN_COURSE_WIKI_REGEX, request.path) if match: course_id = match.group('course_id') From 48e9dc80319c4cfea7a0615c1d1faf42a00ddc18 Mon Sep 17 00:00:00 2001 From: Bridger Maxwell Date: Wed, 15 Aug 2012 20:07:05 -0400 Subject: [PATCH 22/26] Added bootstrap-modal for the wiki preview modals. --- lms/static/js/bootstrap-modal.js | 218 +++++++++++++++++++++++++++++++ lms/templates/main_django.html | 2 +- lms/templates/wiki/base.html | 2 + 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 lms/static/js/bootstrap-modal.js diff --git a/lms/static/js/bootstrap-modal.js b/lms/static/js/bootstrap-modal.js new file mode 100644 index 0000000000..38fd0c8468 --- /dev/null +++ b/lms/static/js/bootstrap-modal.js @@ -0,0 +1,218 @@ +/* ========================================================= + * bootstrap-modal.js v2.0.4 + * http://twitter.github.com/bootstrap/javascript.html#modals + * ========================================================= + * Copyright 2012 Twitter, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= */ + + +!function ($) { + + "use strict"; // jshint ;_; + + + /* MODAL CLASS DEFINITION + * ====================== */ + + var Modal = function (content, options) { + this.options = options + this.$element = $(content) + .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) + } + + Modal.prototype = { + + constructor: Modal + + , toggle: function () { + return this[!this.isShown ? 'show' : 'hide']() + } + + , show: function () { + var that = this + , e = $.Event('show') + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + $('body').addClass('modal-open') + + this.isShown = true + + escape.call(this) + backdrop.call(this, function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(document.body) //don't move modals dom position + } + + that.$element + .show() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element.addClass('in') + + transition ? + that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : + that.$element.trigger('shown') + + }) + } + + , hide: function (e) { + e && e.preventDefault() + + var that = this + + e = $.Event('hide') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + $('body').removeClass('modal-open') + + escape.call(this) + + this.$element.removeClass('in') + + $.support.transition && this.$element.hasClass('fade') ? + hideWithTransition.call(this) : + hideModal.call(this) + } + + } + + + /* MODAL PRIVATE METHODS + * ===================== */ + + function hideWithTransition() { + var that = this + , timeout = setTimeout(function () { + that.$element.off($.support.transition.end) + hideModal.call(that) + }, 500) + + this.$element.one($.support.transition.end, function () { + clearTimeout(timeout) + hideModal.call(that) + }) + } + + function hideModal(that) { + this.$element + .hide() + .trigger('hidden') + + backdrop.call(this) + } + + function backdrop(callback) { + var that = this + , animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('