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
436d7739
Commit
436d7739
authored
Jul 14, 2014
by
Ben McMorran
Committed by
cahrens
Aug 07, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Displays release date of unit in sidebar
parent
9fe41e76
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
176 additions
and
16 deletions
+176
-16
cms/djangoapps/contentstore/tests/test_utils.py
+56
-1
cms/djangoapps/contentstore/utils.py
+22
-0
cms/djangoapps/contentstore/views/item.py
+23
-0
cms/static/js/models/xblock_info.js
+6
-5
cms/static/js/spec/views/pages/container_subviews_spec.js
+39
-0
cms/static/js/views/pages/container_subviews.js
+4
-1
cms/templates/js/publish-xblock.underscore
+24
-8
common/lib/xmodule/xmodule/course_module.py
+2
-1
No files found.
cms/djangoapps/contentstore/tests/test_utils.py
View file @
436d7739
...
...
@@ -9,8 +9,9 @@ from django.test import TestCase
from
django.test.utils
import
override_settings
from
contentstore
import
utils
from
contentstore.tests.utils
import
CourseTestCase
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
Location
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -260,3 +261,57 @@ class XBlockVisibilityTestCase(TestCase):
modulestore
()
.
publish
(
location
,
self
.
dummy_user
)
return
vertical
class
ReleaseDateSourceTest
(
CourseTestCase
):
"""Tests for finding the source of an xblock's release date."""
def
setUp
(
self
):
super
(
ReleaseDateSourceTest
,
self
)
.
setUp
()
self
.
chapter
=
ItemFactory
.
create
(
category
=
'chapter'
,
parent_location
=
self
.
course
.
location
)
self
.
sequential
=
ItemFactory
.
create
(
category
=
'sequential'
,
parent_location
=
self
.
chapter
.
location
)
self
.
vertical
=
ItemFactory
.
create
(
category
=
'vertical'
,
parent_location
=
self
.
sequential
.
location
)
# Read again so that children lists are accurate
self
.
chapter
=
self
.
store
.
get_item
(
self
.
chapter
.
location
)
self
.
sequential
=
self
.
store
.
get_item
(
self
.
sequential
.
location
)
self
.
vertical
=
self
.
store
.
get_item
(
self
.
vertical
.
location
)
self
.
date_one
=
datetime
(
1980
,
1
,
1
,
tzinfo
=
UTC
)
self
.
date_two
=
datetime
(
2020
,
1
,
1
,
tzinfo
=
UTC
)
def
_update_release_dates
(
self
,
chapter_start
,
sequential_start
,
vertical_start
):
"""Sets the release dates of the chapter, sequential, and vertical"""
self
.
chapter
.
start
=
chapter_start
self
.
chapter
=
self
.
store
.
update_item
(
self
.
chapter
,
ModuleStoreEnum
.
UserID
.
test
)
self
.
sequential
.
start
=
sequential_start
self
.
sequential
=
self
.
store
.
update_item
(
self
.
sequential
,
ModuleStoreEnum
.
UserID
.
test
)
self
.
vertical
.
start
=
vertical_start
self
.
vertical
=
self
.
store
.
update_item
(
self
.
vertical
,
ModuleStoreEnum
.
UserID
.
test
)
def
_verify_release_date_source
(
self
,
item
,
expected_source
):
"""Helper to verify that the release date source of a given item matches the expected source"""
source
=
utils
.
find_release_date_source
(
item
)
self
.
assertEqual
(
source
.
location
,
expected_source
.
location
)
self
.
assertEqual
(
source
.
start
,
expected_source
.
start
)
def
test_chapter_source_for_vertical
(
self
):
"""Tests a vertical's release date being set by its chapter"""
self
.
_update_release_dates
(
self
.
date_one
,
self
.
date_one
,
self
.
date_one
)
self
.
_verify_release_date_source
(
self
.
vertical
,
self
.
chapter
)
def
test_sequential_source_for_vertical
(
self
):
"""Tests a vertical's release date being set by its sequential"""
self
.
_update_release_dates
(
self
.
date_one
,
self
.
date_two
,
self
.
date_two
)
self
.
_verify_release_date_source
(
self
.
vertical
,
self
.
sequential
)
def
test_chapter_source_for_sequential
(
self
):
"""Tests a sequential's release date being set by its chapter"""
self
.
_update_release_dates
(
self
.
date_one
,
self
.
date_one
,
self
.
date_one
)
self
.
_verify_release_date_source
(
self
.
sequential
,
self
.
chapter
)
def
test_sequential_source_for_sequential
(
self
):
"""Tests a sequential's release date being set by itself"""
self
.
_update_release_dates
(
self
.
date_one
,
self
.
date_two
,
self
.
date_two
)
self
.
_verify_release_date_source
(
self
.
sequential
,
self
.
sequential
)
cms/djangoapps/contentstore/utils.py
View file @
436d7739
...
...
@@ -187,6 +187,28 @@ def is_xblock_visible_to_students(xblock):
return
True
def
find_release_date_source
(
xblock
):
"""
Finds the ancestor of xblock that set its release date.
"""
# Stop searching at the section level
if
xblock
.
category
==
'chapter'
:
return
xblock
parent_location
=
modulestore
()
.
get_parent_location
(
xblock
.
location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
)
# Orphaned xblocks set their own release date
if
not
parent_location
:
return
xblock
parent
=
modulestore
()
.
get_item
(
parent_location
)
if
parent
.
start
!=
xblock
.
start
:
return
xblock
else
:
return
find_release_date_source
(
parent
)
def
add_extra_panel_tab
(
tab_type
,
course
):
"""
Used to add the panel tab to a course if it does not exist.
...
...
cms/djangoapps/contentstore/views/item.py
View file @
436d7739
...
...
@@ -4,6 +4,8 @@ from __future__ import absolute_import
import
hashlib
import
logging
from
uuid
import
uuid4
from
datetime
import
datetime
from
pytz
import
UTC
from
collections
import
OrderedDict
from
functools
import
partial
...
...
@@ -26,6 +28,9 @@ from xmodule.modulestore.django import modulestore
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InvalidLocationError
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.x_module
import
PREVIEW_VIEWS
,
STUDIO_VIEW
,
STUDENT_VIEW
from
xmodule.course_module
import
DEFAULT_START_DATE
from
contentstore.utils
import
find_release_date_source
from
django.contrib.auth.models
import
User
from
util.date_utils
import
get_default_time_display
...
...
@@ -591,6 +596,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"""
published
=
modulestore
()
.
has_item
(
xblock
.
location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date
=
get_default_time_display
(
xblock
.
start
)
if
xblock
.
start
!=
DEFAULT_START_DATE
else
None
def
safe_get_username
(
user_id
):
"""
Guard against bad user_ids, like the infamous "**replace_user**".
...
...
@@ -619,6 +627,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
"published_on"
:
get_default_time_display
(
xblock
.
published_date
)
if
xblock
.
published_date
else
None
,
"published_by"
:
safe_get_username
(
xblock
.
published_by
),
'studio_url'
:
xblock_studio_url
(
xblock
),
"released_to_students"
:
datetime
.
now
(
UTC
)
>
xblock
.
start
,
"release_date"
:
release_date
,
"release_date_from"
:
_get_release_date_from
(
xblock
)
if
release_date
else
None
,
}
if
data
is
not
None
:
xblock_info
[
"data"
]
=
data
...
...
@@ -677,3 +688,15 @@ def _create_xblock_child_info(xblock, include_children_predicate=NEVER):
)
for
child
in
xblock
.
get_children
()
]
return
child_info
def
_get_release_date_from
(
xblock
):
"""
Returns a string representation of the section or subsection that sets the xblock's release date
"""
source
=
find_release_date_source
(
xblock
)
# Translators: this will be a part of the release date message.
# For example, 'Released: Jul 02, 2014 at 4:00 UTC with Section "Week 1"'
return
_
(
'{section_or_subsection} "{display_name}"'
)
.
format
(
section_or_subsection
=
xblock_type_display_name
(
source
),
display_name
=
source
.
display_name_with_default
)
cms/static/js/models/xblock_info.js
View file @
436d7739
...
...
@@ -31,11 +31,6 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"has_changes"
:
null
,
/**
* True iff a published version of the xblock exists with a release date in the past,
* and the xblock is not locked.
*/
"released_to_students"
:
null
,
/**
* True iff a published version of the xblock exists.
*/
"published"
:
null
,
...
...
@@ -61,12 +56,18 @@ define(["backbone", "underscore", "js/utils/module"], function(Backbone, _, Modu
*/
"published_by"
:
null
,
/**
* True iff the release date of the xblock is in the past.
*/
"released_to_students"
:
null
,
/**
* If the xblock is published, the date on which it will be released to students.
* This can be null if the release date is unscheduled.
*/
"release_date"
:
null
,
/**
* The xblock which is determining the release date. For instance, for a unit,
* this will either be the parent subsection or the grandparent section.
* This can be null if the release date is unscheduled.
*/
"release_date_from"
:
null
},
...
...
cms/static/js/spec/views/pages/container_subviews_spec.js
View file @
436d7739
...
...
@@ -102,6 +102,8 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
publishButtonCss
=
".action-publish"
,
discardChangesButtonCss
=
".action-discard"
,
lastDraftCss
=
".wrapper-last-draft"
,
releaseDateTitleCss
=
".wrapper-release .title"
,
releaseDateContentCss
=
".wrapper-release .copy"
,
lastRequest
,
promptSpies
,
sendDiscardChangesToServer
;
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
];
};
...
...
@@ -276,6 +278,43 @@ define(["jquery", "underscore", "underscore.string", "js/spec_helpers/create_sin
expect
(
containerPage
.
$
(
lastDraftCss
).
text
()).
toContain
(
"Draft saved on Jul 02, 2014 at 14:20 UTC by joe"
);
});
it
(
'renders the release date correctly when unreleased'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"released_to_students"
:
false
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Scheduled:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
});
it
(
'renders the release date correctly when released'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"released_to_students"
:
true
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Released:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
});
it
(
'renders the release date correctly when the release date is not set'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
true
,
"released_to_students"
:
false
,
"release_date"
:
null
,
"release_date_from"
:
null
});
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Release:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
"Unscheduled"
);
});
it
(
'renders the release date correctly when the unit is not published'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
fetch
({
"id"
:
"locator-container"
,
"published"
:
false
,
"released_to_students"
:
true
,
"release_date"
:
"Jul 02, 2014 at 14:20 UTC"
,
"release_date_from"
:
'Section "Week 1"'
});
// Force a render because none of the fetched fields will trigger a render
containerPage
.
xblockPublisher
.
render
();
expect
(
containerPage
.
$
(
releaseDateTitleCss
).
text
()).
toContain
(
"Release:"
);
expect
(
containerPage
.
$
(
releaseDateContentCss
).
text
()).
toContain
(
'Jul 02, 2014 at 14:20 UTC with Section "Week 1"'
);
});
});
describe
(
"PublishHistory"
,
function
()
{
...
...
cms/static/js/views/pages/container_subviews.js
View file @
436d7739
...
...
@@ -84,7 +84,10 @@ define(["jquery", "underscore", "gettext", "js/views/baseview", "js/views/utils/
edited_on
:
this
.
model
.
get
(
'edited_on'
),
edited_by
:
this
.
model
.
get
(
'edited_by'
),
published_on
:
this
.
model
.
get
(
'published_on'
),
published_by
:
this
.
model
.
get
(
'published_by'
)
published_by
:
this
.
model
.
get
(
'published_by'
),
released_to_students
:
this
.
model
.
get
(
'released_to_students'
),
release_date
:
this
.
model
.
get
(
'release_date'
),
release_date_from
:
this
.
model
.
get
(
'release_date_from'
)
}));
return
this
;
...
...
cms/templates/js/publish-xblock.underscore
View file @
436d7739
...
...
@@ -25,14 +25,30 @@
</p>
</div>
<!--To be added in STUD-1829-->
<!--<div class="wrapper-release bar-mod-content">-->
<!--<h5 class="title">Will Release:</h5>-->
<!--<p class="copy">-->
<!--<span class="release-date">July 25, 2014</span> with-->
<!--<span class="release-with">Section "Week 1"</span>-->
<!--</p>-->
<!--</div>-->
<!--TODO this needs strikeout styles once staff lock exists-->
<div class="wrapper-release bar-mod-content">
<h5 class="title">
<% if (published && release_date) {
if (released_to_students) { %>
<%= gettext("Released:") %>
<% } else { %>
<%= gettext("Scheduled:") %>
<% }
} else { %>
<%= gettext("Release:") %>
<% } %>
</h5>
<p class="copy">
<% if (release_date) { %>
<% var message = gettext("%(release_date)s with %(section_or_subsection)s") %>
<%= interpolate(message, {
release_date: '<span class="release-date">' + release_date + '</span>',
section_or_subsection: '<span class="release-with">' + release_date_from + '</span>' }, true) %>
<% } else { %>
<%= gettext("Unscheduled") %>
<% } %>
</p>
</div>
<!--To be added in STUD-1830-->
<!--<div class="wrapper-visibility bar-mod-content">-->
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
436d7739
...
...
@@ -22,6 +22,7 @@ log = logging.getLogger(__name__)
# Make '_' a no-op so we can scrape strings
_
=
lambda
text
:
text
DEFAULT_START_DATE
=
datetime
(
2030
,
1
,
1
,
tzinfo
=
UTC
())
class
StringOrDate
(
Date
):
def
from_json
(
self
,
value
):
...
...
@@ -170,7 +171,7 @@ class CourseFields(object):
enrollment_start
=
Date
(
help
=
"Date that enrollment for this class is opened"
,
scope
=
Scope
.
settings
)
enrollment_end
=
Date
(
help
=
"Date that enrollment for this class is closed"
,
scope
=
Scope
.
settings
)
start
=
Date
(
help
=
"Start time when this module is visible"
,
default
=
datetime
(
2030
,
1
,
1
,
tzinfo
=
UTC
())
,
default
=
DEFAULT_START_DATE
,
scope
=
Scope
.
settings
)
end
=
Date
(
help
=
"Date that this class ends"
,
scope
=
Scope
.
settings
)
advertised_start
=
String
(
...
...
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