From fbc48026f264859ab2b67d1602559e8aa466b7e5 Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Thu, 8 Nov 2012 16:53:01 -0500 Subject: [PATCH 1/2] Hopefully the course-info changes I had made w/o link destruction --- cms/djangoapps/contentstore/utils.py | 25 ++++++ cms/djangoapps/contentstore/views.py | 78 +++++++++++++++---- .../coffee/src/views/course_info_edit.coffee | 63 +++++++++++++++ cms/templates/course_info.html | 33 ++++++++ cms/templates/widgets/header.html | 1 + cms/urls.py | 7 +- 6 files changed, 189 insertions(+), 18 deletions(-) create mode 100644 cms/static/coffee/src/views/course_info_edit.coffee create mode 100644 cms/templates/course_info.html diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 18afd331d0..508236a1e9 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -33,6 +33,31 @@ def get_course_location_for_item(location): return location +def get_course_for_item(location): + ''' + cdodge: for a given Xmodule, return the course that it belongs to + NOTE: This makes a lot of assumptions about the format of the course location + Also we have to assert that this module maps to only one course item - it'll throw an + assert if not + ''' + item_loc = Location(location) + + # @hack! We need to find the course location however, we don't + # know the 'name' parameter in this context, so we have + # to assume there's only one item in this query even though we are not specifying a name + course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None] + courses = modulestore().get_items(course_search_location) + + # make sure we found exactly one match on this above course search + found_cnt = len(courses) + if found_cnt == 0: + raise BaseException('Could not find course at {0}'.format(course_search_location)) + + if found_cnt > 1: + raise BaseException('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses)) + + return courses[0] + def get_lms_link_for_item(location, preview=False): location = Location(location) diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index c8f8e8152d..c0cc24a4bc 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1,22 +1,15 @@ -import traceback from util.json_request import expect_json -import exceptions import json import logging -import mimetypes import os -import StringIO import sys import time import tarfile import shutil -import tempfile from datetime import datetime from collections import defaultdict from uuid import uuid4 -from lxml import etree from path import path -from shutil import rmtree # to install PIL on MacOSX: 'easy_install http://dist.repoze.org/PIL-1.1.6.tar.gz' from PIL import Image @@ -28,8 +21,6 @@ from django.core.context_processors import csrf from django_future.csrf import ensure_csrf_cookie from django.core.urlresolvers import reverse from django.conf import settings -from django import forms -from django.shortcuts import redirect from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError @@ -43,23 +34,21 @@ from mitxmako.shortcuts import render_to_response, render_to_string from xmodule.modulestore.django import modulestore from xmodule_modifiers import replace_static_urls, wrap_xmodule from xmodule.exceptions import NotFoundError -from xmodule.timeparse import parse_time, stringify_time from functools import partial -from itertools import groupby -from operator import attrgetter from xmodule.contentstore.django import contentstore from xmodule.contentstore.content import StaticContent -from cache_toolbox.core import set_cached_content, get_cached_content, del_cached_content from auth.authz import is_user_in_course_group_role, get_users_in_course_group_by_role from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group from auth.authz import INSTRUCTOR_ROLE_NAME, STAFF_ROLE_NAME, create_all_course_groups -from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState +from .utils import get_course_location_for_item, get_lms_link_for_item, compute_unit_state, get_date_display, UnitState, get_course_for_item -from xmodule.templates import all_templates from xmodule.modulestore.xml_importer import import_from_xml -from xmodule.modulestore.xml import edx_xml_parser +from contentstore.course_info_model import get_course_updates,\ + update_course_updates, delete_course_update +from cache_toolbox.core import del_cached_content +from xmodule.timeparse import stringify_time log = logging.getLogger(__name__) @@ -346,7 +335,7 @@ def edit_unit(request, location): def preview_component(request, location): # TODO (vshnayder): change name from id to location in coffee+html as well. if not has_access(request.user, location): - raise Http404 # TODO (vshnayder): better error + raise HttpResponseForbidden() component = modulestore().get_item(location) @@ -906,6 +895,61 @@ def server_error(request): return render_to_response('error.html', {'error': '500'}) +@login_required +@ensure_csrf_cookie +def course_info(request, org, course, name, provided_id=None): + """ + Send models and views as well as html for editing the course info to the client. + + org, course, name: Attributes of the Location for the item to edit + """ + location = ['i4x', org, course, 'course', name] + + # check that logged in user has permissions to this item + if not has_access(request.user, location): + raise PermissionDenied() + + course_module = modulestore().get_item(location) + + # get current updates + location = ['i4x', org, course, 'course_info', "updates"] + + return render_to_response('course_info.html', { + 'active_tab': 'courseinfo-tab', + 'context_course': course_module, + 'url_base' : "/" + org + "/" + course + "/", + 'course_updates' : json.dumps(get_course_updates(location)) + }) + +@expect_json +@login_required +@ensure_csrf_cookie +def course_info_updates(request, org, course, provided_id=None): + """ + restful CRUD operations on course_info updates. + + org, course: Attributes of the Location for the item to edit + provided_id should be none if it's new (create) and a composite of the update db id + index otherwise. + """ + # ??? No way to check for access permission afaik + # get current updates + location = ['i4x', org, course, 'course_info', "updates"] + # NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! + if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: + real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] + else: + real_method = request.method + + if request.method == 'GET': + return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json") + elif real_method == 'POST': + # new instance (unless django makes PUT a POST): updates are coming as POST. Not sure why. + return HttpResponse(json.dumps(update_course_updates(location, request.POST, provided_id)), mimetype="application/json") + elif real_method == 'PUT': + return HttpResponse(json.dumps(update_course_updates(location, request.POST, provided_id)), mimetype="application/json") + elif real_method == 'DELETE': # coming as POST need to pull from Request Header X-HTTP-Method-Override DELETE + return HttpResponse(json.dumps(delete_course_update(location, request.POST, provided_id)), mimetype="application/json") + @login_required @ensure_csrf_cookie def asset_index(request, org, course, name): diff --git a/cms/static/coffee/src/views/course_info_edit.coffee b/cms/static/coffee/src/views/course_info_edit.coffee new file mode 100644 index 0000000000..49cb90c47d --- /dev/null +++ b/cms/static/coffee/src/views/course_info_edit.coffee @@ -0,0 +1,63 @@ +## Derived from and should inherit from a common ancestor w/ ModuleEdit +class CMS.Views.CourseInfoEdit extends Backbone.View + tagName: 'div' + className: 'component' + + events: + "click .component-editor .cancel-button": 'clickCancelButton' + "click .component-editor .save-button": 'clickSaveButton' + "click .component-actions .edit-button": 'clickEditButton' + "click .component-actions .delete-button": 'onDelete' + + initialize: -> + @render() + + $component_editor: => @$el.find('.component-editor') + + loadDisplay: -> + XModule.loadModule(@$el.find('.xmodule_display')) + + loadEdit: -> + if not @module + @module = XModule.loadModule(@$el.find('.xmodule_edit')) + + # don't show metadata (deprecated for course_info) + render: -> + if @model.id + @$el.load("/preview_component/#{@model.id}", => + @loadDisplay() + @delegateEvents() + ) + + clickSaveButton: (event) => + event.preventDefault() + data = @module.save() + @model.save(data).done( => + # # showToastMessage("Your changes have been saved.", null, 3) + @module = null + @render() + @$el.removeClass('editing') + ).fail( -> + showToastMessage("There was an error saving your changes. Please try again.", null, 3) + ) + + clickCancelButton: (event) -> + event.preventDefault() + @$el.removeClass('editing') + @$component_editor().slideUp(150) + + clickEditButton: (event) -> + event.preventDefault() + @$el.addClass('editing') + @$component_editor().slideDown(150) + @loadEdit() + + onDelete: (event) -> + # clear contents, don't delete + @model.definition.data = "
    " + # TODO change label to 'clear' + + onNew: (event) -> + ele = $(@model.definition.data).find("ol") + if (ele) + ele = $(ele).first().prepend("
  1. " + $.datepicker.formatDate('MM d', new Date()) + "

    /n
  2. "); \ No newline at end of file diff --git a/cms/templates/course_info.html b/cms/templates/course_info.html new file mode 100644 index 0000000000..72facf7c2e --- /dev/null +++ b/cms/templates/course_info.html @@ -0,0 +1,33 @@ +<%inherit file="base.html" /> + +<%block name="title">Course Info + +<%block name="jsextra"> + + + +<%block name="content"> +
    +
    +

    Course Info

    +
    +
    +

    Updates

    + New Update +
    + +
    +
    + +
    +
    + \ No newline at end of file diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 877f03533c..73ce3f0604 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -10,6 +10,7 @@ ${context_course.display_name}