Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
0f6de7f9
Commit
0f6de7f9
authored
Jul 09, 2014
by
cahrens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Discard changes rewrite.
STUD-1860
parent
f8f0ef1f
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
97 additions
and
94 deletions
+97
-94
cms/djangoapps/contentstore/views/item.py
+15
-5
cms/djangoapps/contentstore/views/tests/test_item.py
+16
-0
cms/static/js/spec/views/pages/container_subviews_spec.js
+27
-21
cms/static/js/views/pages/container_subviews.js
+18
-40
common/test/acceptance/pages/studio/container.py
+7
-16
common/test/acceptance/tests/test_studio_container.py
+14
-12
No files found.
cms/djangoapps/contentstore/views/item.py
View file @
0f6de7f9
...
...
@@ -74,7 +74,7 @@ def usage_key_with_run(usage_key_string):
# pylint: disable=unused-argument
@require_http_methods
((
"DELETE"
,
"GET"
,
"PUT"
,
"POST"
))
@require_http_methods
((
"DELETE"
,
"GET"
,
"PUT"
,
"POST"
,
"PATCH"
))
@login_required
@expect_json
def
xblock_handler
(
request
,
usage_key_string
):
...
...
@@ -96,7 +96,10 @@ def xblock_handler(request, usage_key_string):
to None! Absent ones will be left alone.
:nullout: which metadata fields to set to None
:graderType: change how this unit is graded
:publish: can be only one value-- 'make_public'
:publish: can be either -- 'make_public' (which publishes the content) or 'discard_changes'
(which reverts to the last published version). If 'discard_changes', the other fields
will not be used; that is, it is not possible to update and discard changes
in a single operation.
The JSON representation on the updated xblock (minus children) is returned.
if usage_key_string is not specified, create a new xblock instance, either by duplicating
...
...
@@ -273,6 +276,13 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
log
.
error
(
"Can't find item by location."
)
return
JsonResponse
({
"error"
:
"Can't find item by location: "
+
unicode
(
usage_key
)},
404
)
# Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI).
if
publish
==
"discard_changes"
:
store
.
revert_to_published
(
usage_key
,
user
.
id
)
# Returning the same sort of result that we do for other save operations. In the future,
# we may want to return the full XBlockInfo.
return
JsonResponse
({
'id'
:
unicode
(
usage_key
)})
old_metadata
=
own_metadata
(
existing_item
)
old_content
=
existing_item
.
get_explicitly_set_fields_by_scope
(
Scope
.
content
)
...
...
@@ -339,9 +349,9 @@ def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout
if
grader_type
is
not
None
:
result
.
update
(
CourseGradingModel
.
update_section_grader_type
(
existing_item
,
grader_type
,
user
))
# Make public after updating the xblock, in case the caller asked
#
for both an update and a publish
.
if
publish
and
publish
==
'make_public'
:
# Make public after updating the xblock, in case the caller asked
for both an update and a publish.
#
Although not supported in the UI, Bok Choy tests use this
.
if
publish
==
'make_public'
:
modulestore
()
.
publish
(
existing_item
.
location
,
user
.
id
)
# Note that children aren't being returned until we have a use case.
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
0f6de7f9
...
...
@@ -553,6 +553,22 @@ class TestEditItem(ItemTest):
def
test_make_draft
(
self
):
""" Test creating a draft version of a public problem. """
self
.
_make_draft_content_different_from_published
()
def
test_revert_to_published
(
self
):
""" Test reverting draft content to published """
self
.
_make_draft_content_different_from_published
()
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
data
=
{
'publish'
:
'discard_changes'
}
)
published
=
self
.
verify_publish_state
(
self
.
problem_usage_key
,
PublishState
.
public
)
self
.
assertIsNone
(
published
.
due
)
def
_make_draft_content_different_from_published
(
self
):
"""
Helper method to create different draft and published versions of a problem.
"""
# Make problem public.
self
.
client
.
ajax_post
(
self
.
problem_update_url
,
...
...
cms/static/js/spec/views/pages/container_subviews_spec.js
View file @
0f6de7f9
...
...
@@ -149,10 +149,9 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect
(
promptSpies
.
constructor
).
toHaveBeenCalled
();
promptSpies
.
constructor
.
mostRecentCall
.
args
[
0
].
actions
.
primary
.
click
(
promptSpies
);
request
=
lastRequest
();
expect
(
request
.
url
).
toEqual
(
"/xblock/locator-container"
);
expect
(
request
.
method
).
toEqual
(
"DELETE"
);
expect
(
request
.
requestBody
).
toEqual
(
null
);
create_sinon
.
expectJsonRequest
(
requests
,
"POST"
,
"/xblock/locator-container"
,
{
"publish"
:
"discard_changes"
}
);
};
beforeEach
(
function
()
{
...
...
@@ -201,19 +200,15 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
containerPage
.
$
(
publishButtonCss
).
click
();
edit_helpers
.
verifyNotificationShowing
(
notificationSpy
,
/Publishing/
);
request
=
lastRequest
();
expect
(
request
.
url
).
toEqual
(
"/xblock/locator-container"
);
expect
(
request
.
method
).
toEqual
(
"POST"
);
expect
(
JSON
.
parse
(
request
.
requestBody
).
publish
).
toEqual
(
"make_public"
);
create_sinon
.
expectJsonRequest
(
requests
,
"POST"
,
"/xblock/locator-container"
,
{
"publish"
:
"make_public"
}
);
// Response to publish call
respondWithJson
({
"id"
:
"locator-container"
,
"
published"
:
true
,
"has_changes"
:
false
});
respondWithJson
({
"id"
:
"locator-container"
,
"
data"
:
null
,
"metadata"
:{}
});
edit_helpers
.
verifyNotificationHidden
(
notificationSpy
);
request
=
lastRequest
();
expect
(
request
.
url
).
toEqual
(
"/xblock/locator-container"
);
expect
(
request
.
method
).
toEqual
(
"GET"
);
expect
(
request
.
requestBody
).
toEqual
(
null
);
create_sinon
.
expectJsonRequest
(
requests
,
"GET"
,
"/xblock/locator-container"
);
// Response to fetch
respondWithJson
({
"id"
:
"locator-container"
,
"published"
:
true
,
"has_changes"
:
false
});
...
...
@@ -243,28 +238,39 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect
(
containerPage
.
model
.
get
(
"publish"
)).
toBeNull
();
});
/* STUD-1860
it
(
'can discard changes'
,
function
()
{
var
notificationSpy
=
edit_helpers
.
createNotificationSpy
(),
renderPageSpy
=
spyOn
(
containerPage
.
xblockPublisher
,
'renderPage'
).
andCallThrough
(),
numRequests
;
sendDiscardChangesToServer
(
this
);
numRequests
=
requests
.
length
;
var numRequests = requests.length;
create_sinon.respondToDelete(requests);
// Respond with success.
respondWithJson
({
"id"
:
"locator-container"
});
edit_helpers
.
verifyNotificationHidden
(
notificationSpy
);
// Verify other requests are sent to the server to update page state.
// Response to fetch, specifying the very next request (as multiple requests will be sent to server)
respondWithJson({"id": "locator-container", "published": true, "has_changes": false}, numRequests
);
expect(containerPage.
$(discardChangesButtonCss)).toHaveClass('is-disabled'
);
expect(
containerPage.$(bitPublishingCss)).toHaveClass(publishedBit
);
expect
(
requests
.
length
>
numRequests
).
toBeTruthy
(
);
expect
(
containerPage
.
model
.
get
(
"publish"
)).
toBeNull
(
);
expect
(
renderPageSpy
).
toHaveBeenCalled
(
);
});
*/
it
(
'does not fetch if discard changes fails'
,
function
()
{
var
renderPageSpy
=
spyOn
(
containerPage
.
xblockPublisher
,
'renderPage'
).
andCallThrough
(),
numRequests
;
sendDiscardChangesToServer
(
this
);
numRequests
=
requests
.
length
;
var
numRequests
=
requests
.
length
;
// Respond with failure
create_sinon
.
respondWithError
(
requests
);
expect
(
requests
.
length
).
toEqual
(
numRequests
);
expect
(
containerPage
.
$
(
discardChangesButtonCss
)).
not
.
toHaveClass
(
'is-disabled'
);
expect
(
containerPage
.
model
.
get
(
"publish"
)).
toBeNull
();
expect
(
renderPageSpy
).
not
.
toHaveBeenCalled
();
});
it
(
'does not discard changes on cancel'
,
function
()
{
...
...
cms/static/js/views/pages/container_subviews.js
View file @
0f6de7f9
/**
* Subviews (usually small side panels) for XBlockContainerPage.
*/
define
([
"jquery"
,
"underscore"
,
"gettext"
,
"js/views/baseview"
,
"js/views/feedback_prompt"
],
function
(
$
,
_
,
gettext
,
BaseView
,
PromptView
)
{
define
([
"jquery"
,
"underscore"
,
"gettext"
,
"js/views/baseview"
],
function
(
$
,
_
,
gettext
,
BaseView
)
{
var
disabledCss
=
"is-disabled"
;
...
...
@@ -123,7 +123,7 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/feedba
}
this
.
runOperationShowingMessage
(
gettext
(
'Publishing…'
),
function
()
{
return
xblockInfo
.
save
({
publish
:
'make_public'
});
return
xblockInfo
.
save
({
publish
:
'make_public'
}
,
{
patch
:
true
}
);
}).
always
(
function
()
{
xblockInfo
.
set
(
"publish"
,
null
);
}).
done
(
function
()
{
...
...
@@ -132,50 +132,28 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/feedba
},
discardChanges
:
function
(
e
)
{
var
xblockInfo
=
this
.
model
,
that
=
this
,
renderPage
=
this
.
renderPage
;
if
(
e
&&
e
.
preventDefault
)
{
e
.
preventDefault
();
}
var
xblockInfo
=
this
.
model
,
view
,
renderPage
=
this
.
renderPage
;
view
=
new
PromptView
.
Warning
({
title
:
gettext
(
"Discard Changes"
),
message
:
gettext
(
"Are you sure you want to discard changes and revert to the last published version?"
),
actions
:
{
primary
:
{
text
:
gettext
(
"Discard Changes"
),
click
:
function
(
view
)
{
view
.
hide
();
$
.
ajax
({
type
:
'DELETE'
,
url
:
xblockInfo
.
url
()
}).
success
(
function
()
{
window
.
alert
(
"Refresh the page to see that changes were discarded. "
+
"Auto-refresh will be implemented in a later story."
);
/* Fetch is never returning on sandbox-- try
doing a PUT instead of a DELETE with publish option
to work around, or contact dev ops.
STUD-1860
window.crazyAjaxHandler = xblockInfo.fetch({
complete: function(a, b, c) {
debugger;
}
});
renderPage();
*/
});
}
},
secondary
:
{
text
:
gettext
(
"Cancel"
),
click
:
function
(
view
)
{
view
.
hide
();
}
}
this
.
confirmThenRunOperation
(
gettext
(
"Discard Changes"
),
gettext
(
"Are you sure you want to discard changes and revert to the last published version?"
),
gettext
(
"Discard Changes"
),
function
()
{
that
.
runOperationShowingMessage
(
gettext
(
'Discarding Changes…'
),
function
()
{
return
xblockInfo
.
save
({
publish
:
'discard_changes'
},
{
patch
:
true
});
}).
always
(
function
()
{
xblockInfo
.
set
(
"publish"
,
null
);
}).
done
(
function
()
{
renderPage
();
});
}
}).
show
(
);
);
}
});
/**
* PublishHistory displays when and by whom the xblock was last published, if it ever was.
*/
...
...
common/test/acceptance/pages/studio/container.py
View file @
0f6de7f9
...
...
@@ -88,22 +88,13 @@ class ContainerPage(PageObject):
"""
return
self
.
q
(
css
=
'.action-publish'
)
.
first
# def discard_changes(self):
# """
# Discards draft changes and reloads the page.
# NOT YET IMPLEMENTED-- part of future story
# """
#
# self.q(css='.action-discard').first.click()
#
# # TODO: work with Jay/Christine on this. I can't find something to wait on
# # that guarantees the button will be clickable.
# time.sleep(2)
#
# self.q(css='a.button.action-primary').first.click()
# self.wait_for_ajax()
#
# return ContainerPage(self.browser, self.locator).visit()
def
discard_changes
(
self
):
"""
Discards draft changes (which will then re-render the page).
"""
click_css
(
self
,
'a.action-discard'
,
0
,
require_notification
=
False
)
self
.
q
(
css
=
'a.button.action-primary'
)
.
first
.
click
()
self
.
wait_for_ajax
()
def
view_published_version
(
self
):
"""
...
...
common/test/acceptance/tests/test_studio_container.py
View file @
0f6de7f9
...
...
@@ -375,6 +375,8 @@ class UnitPublishingTest(ContainerBase):
"""
Tests of the publishing control and related widgets on the Unit page.
"""
PUBLISHED_STATUS
=
"Publishing Status
\n
Published"
DRAFT_STATUS
=
"Publishing Status
\n
Draft (Unpublished changes)"
def
setup_fixtures
(
self
):
"""
...
...
@@ -407,26 +409,26 @@ class UnitPublishingTest(ContainerBase):
Test the state changes when a published unit has draft changes.
"""
unit
=
self
.
go_to_unit_page
()
self
.
assertEqual
(
"Publishing Status
\n
Published"
,
unit
.
publish_title
)
self
.
assertEqual
(
self
.
PUBLISHED_STATUS
,
unit
.
publish_title
)
# Should not be able to click on Publish action -- but I don't know how to test that it is not clickable.
# TODO: continue discussion with Muhammad and Jay about this.
# Add a component to the page so it will have unpublished changes.
add_discussion
(
unit
)
self
.
assertEqual
(
"Publishing Status
\n
Draft (Unpublished changes)"
,
unit
.
publish_title
)
self
.
assertEqual
(
self
.
DRAFT_STATUS
,
unit
.
publish_title
)
unit
.
publish_action
.
click
()
unit
.
wait_for_ajax
()
self
.
assertEqual
(
"Publishing Status
\n
Published"
,
unit
.
publish_title
)
self
.
assertEqual
(
self
.
PUBLISHED_STATUS
,
unit
.
publish_title
)
# TODO: part of future story.
# def test_discard_changes(self):
# """
# Test the state after discard changes.
# """
# unit = self.go_to_unit_page(
)
# add_discussion(unit
)
# unit =
unit.discard_changes()
# self.assertEqual("Publishing Status\nPublished"
, unit.publish_title)
def
test_discard_changes
(
self
):
"""
Test the state after discard changes.
"""
unit
=
self
.
go_to_unit_page
()
add_discussion
(
unit
)
self
.
assertEqual
(
self
.
DRAFT_STATUS
,
unit
.
publish_title
)
unit
.
discard_changes
()
self
.
assertEqual
(
self
.
PUBLISHED_STATUS
,
unit
.
publish_title
)
def
test_view_live_no_changes
(
self
):
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment