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
9e6333f6
Commit
9e6333f6
authored
May 15, 2014
by
Waqas Khalid
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remember the user last thread sort preference
FOR-545
parent
708bb8a5
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
230 additions
and
23 deletions
+230
-23
common/djangoapps/terrain/stubs/comments.py
+18
-4
common/static/coffee/spec/discussion/view/discussion_thread_list_view_spec.coffee
+105
-1
common/static/coffee/src/discussion/discussion.coffee
+8
-1
common/static/coffee/src/discussion/main.coffee
+2
-1
common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
+5
-6
common/test/acceptance/fixtures/discussion.py
+0
-1
common/test/acceptance/pages/lms/discussion.py
+32
-0
common/test/acceptance/tests/test_discussion.py
+52
-2
lms/djangoapps/django_comment_client/forum/tests.py
+1
-0
lms/djangoapps/django_comment_client/forum/views.py
+5
-5
lms/templates/discussion/_thread_list_template.html
+1
-1
lms/templates/discussion/index.html
+1
-1
No files found.
common/djangoapps/terrain/stubs/comments.py
View file @
9e6333f6
...
...
@@ -6,7 +6,6 @@ import re
import
urlparse
from
.http
import
StubHttpRequestHandler
,
StubHttpService
class
StubCommentsServiceHandler
(
StubHttpRequestHandler
):
@property
...
...
@@ -23,26 +22,41 @@ class StubCommentsServiceHandler(StubHttpRequestHandler):
"/api/v1/comments/(?P<comment_id>
\\
w+)$"
:
self
.
do_comment
,
"/api/v1/(?P<commentable_id>
\\
w+)/threads$"
:
self
.
do_commentable
,
}
if
self
.
match_pattern
(
pattern_handlers
):
return
self
.
send_response
(
404
,
content
=
"404 Not Found"
)
def
match_pattern
(
self
,
pattern_handlers
):
path
=
urlparse
.
urlparse
(
self
.
path
)
.
path
for
pattern
in
pattern_handlers
:
match
=
re
.
match
(
pattern
,
path
)
if
match
:
pattern_handlers
[
pattern
](
**
match
.
groupdict
())
return
self
.
send_response
(
404
,
content
=
"404 Not Found"
)
return
True
return
None
def
do_PUT
(
self
):
if
self
.
path
.
startswith
(
'/set_config'
):
return
StubHttpRequestHandler
.
do_PUT
(
self
)
pattern_handlers
=
{
"/api/v1/users/(?P<user_id>
\\
d+)$"
:
self
.
do_put_user
,
}
if
self
.
match_pattern
(
pattern_handlers
):
return
self
.
send_response
(
204
,
""
)
def
do_put_user
(
self
,
user_id
):
self
.
server
.
config
[
'default_sort_key'
]
=
self
.
post_dict
.
get
(
"default_sort_key"
,
"date"
)
self
.
send_json_response
({
'username'
:
self
.
post_dict
.
get
(
"username"
),
'external_id'
:
self
.
post_dict
.
get
(
"external_id"
)})
def
do_DELETE
(
self
):
self
.
send_json_response
({})
def
do_user
(
self
,
user_id
):
response
=
{
"id"
:
user_id
,
"default_sort_key"
:
self
.
server
.
config
.
get
(
"default_sort_key"
,
"date"
),
"upvoted_ids"
:
[],
"downvoted_ids"
:
[],
"subscribed_thread_ids"
:
[],
...
...
common/static/coffee/spec/discussion/view/discussion_thread_list_view_spec.coffee
View file @
9e6333f6
...
...
@@ -3,6 +3,36 @@ describe "DiscussionThreadListView", ->
beforeEach
->
setFixtures
"""
<script type="text/template" id="thread-list-item-template">
<a href="<%- id %>" data-id="<%- id %>">
<span class="title"><%- title %></span>
<span class="comments-count">
<%
var fmt;
var data = {
'span_sr_open': '<span class="sr">',
'span_close': '</span>',
'unread_comments_count': unread_comments_count,
'comments_count': comments_count
};
if (unread_comments_count > 0) {
fmt = '%(comments_count)s %(span_sr_open)scomments (%(unread_comments_count)s unread comments)%(span_close)s';
} else {
fmt = '%(comments_count)s %(span_sr_open)scomments %(span_close)s';
}
print(interpolate(fmt, data, true));
%>
</span>
<span class="votes-count">+<%=
interpolate(
'%(votes_up_count)s%(span_sr_open)s votes %(span_close)s',
{'span_sr_open': '<span class="sr">', 'span_close': '</span>', 'votes_up_count': votes['up_count']},
true
)
%></span>
</a>
</script>
<script type="text/template" id="thread-list-template">
<div class="browse-search">
<div class="home"></div>
...
...
@@ -14,7 +44,14 @@ describe "DiscussionThreadListView", ->
</form>
</div>
</div>
<div class="sort-bar"></div>
<div class="sort-bar">
<span class="sort-label" id="sort-label">Sort by:</span>
<ul role="radiogroup" aria-labelledby="sort-label">
<li><a href="#" role="radio" aria-checked="false" data-sort="date">date</a></li>
<li><a href="#" role="radio" aria-checked="false" data-sort="votes">votes</a></li>
<li><a href="#" role="radio" aria-checked="false" data-sort="comments">comments</a></li>
</ul>
</div>
<div class="search-alerts"></div>
<div class="post-list-wrapper">
<ul class="post-list"></ul>
...
...
@@ -33,6 +70,11 @@ describe "DiscussionThreadListView", ->
</script>
<div class="sidebar"></div>
"""
@
threads
=
[
{
id
:
"1"
,
title
:
"Thread1"
,
body
:
"dummy body"
,
votes
:
{
up_count
:
'20'
},
unread_comments_count
:
0
,
comments_count
:
1
,
created_at
:
'2013-04-03T20:08:39Z'
,},
{
id
:
"2"
,
title
:
"Thread2"
,
body
:
"dummy body"
,
votes
:
{
up_count
:
'42'
},
unread_comments_count
:
0
,
comments_count
:
2
,
created_at
:
'2013-04-03T20:07:39Z'
,},
{
id
:
"3"
,
title
:
"Thread3"
,
body
:
"dummy body"
,
votes
:
{
up_count
:
'12'
},
unread_comments_count
:
0
,
comments_count
:
3
,
created_at
:
'2013-04-03T20:06:39Z'
,},
]
window
.
$
$course_id
=
"TestOrg/TestCourse/TestRun"
window
.
user
=
new
DiscussionUser
({
id
:
"567"
,
upvoted_ids
:
[]})
...
...
@@ -98,3 +140,65 @@ describe "DiscussionThreadListView", ->
spyOn
(
@
view
,
"renderThread"
)
@
view
.
collection
.
trigger
(
"change"
,
new
Thread
({
id
:
1
}))
expect
(
@
view
.
clearSearchAlerts
).
toHaveBeenCalled
()
makeView
=
(
discussion
)
->
return
new
DiscussionThreadListView
(
el
:
$
(
".sidebar"
),
collection
:
discussion
)
checkThreadsOrdering
=
(
view
,
sort_order
,
type
)
->
expect
(
view
.
$el
.
find
(
".post-list .list-item"
).
children
().
length
).
toEqual
(
3
)
expect
(
view
.
$el
.
find
(
".post-list .list-item:nth-child(1) .title"
).
text
()).
toEqual
(
sort_order
[
0
])
expect
(
view
.
$el
.
find
(
".post-list .list-item:nth-child(2) .title"
).
text
()).
toEqual
(
sort_order
[
1
])
expect
(
view
.
$el
.
find
(
".post-list .list-item:nth-child(3) .title"
).
text
()).
toEqual
(
sort_order
[
2
])
expect
(
view
.
$el
.
find
(
".sort-bar a.active"
).
text
()).
toEqual
(
type
)
describe
"thread rendering should be correct"
,
->
checkRender
=
(
threads
,
type
,
sort_order
)
->
discussion
=
new
Discussion
(
threads
,
{
pages
:
1
,
sort
:
type
})
view
=
makeView
(
discussion
)
view
.
render
()
checkThreadsOrdering
(
view
,
sort_order
,
type
)
it
"with sort preference date"
,
->
checkRender
(
@
threads
,
"date"
,
[
"Thread1"
,
"Thread2"
,
"Thread3"
])
it
"with sort preference votes"
,
->
checkRender
(
@
threads
,
"votes"
,
[
"Thread2"
,
"Thread1"
,
"Thread3"
])
it
"with sort preference comments"
,
->
checkRender
(
@
threads
,
"comments"
,
[
"Thread3"
,
"Thread2"
,
"Thread1"
])
describe
"Sort click should be correct"
,
->
changeSorting
=
(
threads
,
selected_type
,
new_type
,
sort_order
)
->
discussion
=
new
Discussion
(
threads
,
{
pages
:
1
,
sort
:
selected_type
})
view
=
makeView
(
discussion
)
view
.
render
()
expect
(
view
.
$el
.
find
(
".sort-bar a.active"
).
text
()).
toEqual
(
selected_type
)
sorted_threads
=
[]
if
new_type
==
'date'
sorted_threads
=
[
threads
[
0
],
threads
[
1
],
threads
[
2
]]
else
if
new_type
==
'comments'
sorted_threads
=
[
threads
[
2
],
threads
[
1
],
threads
[
0
]]
else
if
new_type
==
'votes'
sorted_threads
=
[
threads
[
1
],
threads
[
0
],
threads
[
2
]]
$
.
ajax
.
andCallFake
((
params
)
=>
params
.
success
(
{
"discussion_data"
:
sorted_threads
,
page
:
1
,
num_pages
:
1
}
)
{
always
:
->
}
)
view
.
$el
.
find
(
".sort-bar a[data-sort='"
+
new_type
+
"']"
).
click
()
expect
(
$
.
ajax
).
toHaveBeenCalled
()
expect
(
view
.
sortBy
).
toEqual
(
new_type
)
checkThreadsOrdering
(
view
,
sort_order
,
new_type
)
it
"with sort preference date"
,
->
changeSorting
(
@
threads
,
"comments"
,
"date"
,
[
"Thread1"
,
"Thread2"
,
"Thread3"
])
it
"with sort preference votes"
,
->
changeSorting
(
@
threads
,
"date"
,
"votes"
,
[
"Thread2"
,
"Thread1"
,
"Thread3"
])
it
"with sort preference comments"
,
->
changeSorting
(
@
threads
,
"votes"
,
"comments"
,
[
"Thread3"
,
"Thread2"
,
"Thread1"
])
common/static/coffee/src/discussion/discussion.coffee
View file @
9e6333f6
...
...
@@ -5,9 +5,10 @@ if Backbone?
initialize
:
(
models
,
options
=
{})
->
@
pages
=
options
[
'pages'
]
||
1
@
current_page
=
1
@
sort_preference
=
options
[
'sort'
]
@
bind
"add"
,
(
item
)
=>
item
.
discussion
=
@
@
comparator
=
@
sortByDateRecentFirst
@
setSortComparator
(
@
sort_preference
)
@
on
"thread:remove"
,
(
thread
)
=>
@
remove
(
thread
)
...
...
@@ -17,6 +18,12 @@ if Backbone?
hasMorePages
:
->
@
current_page
<
@
pages
setSortComparator
:
(
sortBy
)
->
switch
sortBy
when
'date'
then
@
comparator
=
@
sortByDateRecentFirst
when
'votes'
then
@
comparator
=
@
sortByVotes
when
'comments'
then
@
comparator
=
@
sortByComments
addThread
:
(
thread
,
options
)
->
# TODO: Check for existing thread with same ID in a faster way
if
not
@
find
(
thread
.
id
)
...
...
common/static/coffee/src/discussion/main.coffee
View file @
9e6333f6
...
...
@@ -6,12 +6,13 @@ if Backbone?
element
=
$
(
elem
)
window
.
$
$course_id
=
element
.
data
(
"course-id"
)
user_info
=
element
.
data
(
"user-info"
)
sort_preference
=
element
.
data
(
"sort-preference"
)
threads
=
element
.
data
(
"threads"
)
thread_pages
=
element
.
data
(
"thread-pages"
)
content_info
=
element
.
data
(
"content-info"
)
window
.
user
=
new
DiscussionUser
(
user_info
)
Content
.
loadContentInfos
(
content_info
)
discussion
=
new
Discussion
(
threads
,
pages
:
thread_pages
)
discussion
=
new
Discussion
(
threads
,
{
pages
:
thread_pages
,
sort
:
sort_preference
}
)
new
DiscussionRouter
({
discussion
:
discussion
})
Backbone
.
history
.
start
({
pushState
:
true
,
root
:
"/courses/
#{
$$course_id
}
/discussion/forum/"
})
DiscussionProfileApp
=
...
...
common/static/coffee/src/discussion/views/discussion_thread_list_view.coffee
View file @
9e6333f6
...
...
@@ -132,6 +132,9 @@ if Backbone?
@
displayedCollection
.
on
"reset"
,
@
renderThreads
@
displayedCollection
.
on
"thread:remove"
,
@
renderThreads
@
renderThreads
()
sort_element
=
@
$
(
'.sort-bar a[data-sort="'
+
this
.
collection
.
sort_preference
+
'"]'
)
sort_element
.
attr
(
'aria-checked'
,
true
)
sort_element
.
addClass
(
'active'
)
@
renderThreads
:
=>
...
...
@@ -413,18 +416,14 @@ if Backbone?
@
loadMorePages
(
event
)
sortThreads
:
(
event
)
->
activeSort
=
@
$
(
".sort-bar a
[class='active']
"
)
activeSort
=
@
$
(
".sort-bar a
.active
"
)
activeSort
.
removeClass
(
"active"
)
activeSort
.
attr
(
"aria-checked"
,
"false"
)
newSort
=
$
(
event
.
target
)
newSort
.
addClass
(
"active"
)
newSort
.
attr
(
"aria-checked"
,
"true"
)
@
sortBy
=
newSort
.
data
(
"sort"
)
@
displayedCollection
.
comparator
=
switch
@
sortBy
when
'date'
then
@
displayedCollection
.
sortByDateRecentFirst
when
'votes'
then
@
displayedCollection
.
sortByVotes
when
'comments'
then
@
displayedCollection
.
sortByComments
@
displayedCollection
.
setSortComparator
(
@
sortBy
)
@
retrieveFirstPage
(
event
)
performSearch
:
(
event
)
->
...
...
common/test/acceptance/fixtures/discussion.py
View file @
9e6333f6
...
...
@@ -125,4 +125,3 @@ class SearchResultFixture(DiscussionContentFixture):
def
get_config_data
(
self
):
return
{
"search_result"
:
json
.
dumps
(
self
.
result
)}
common/test/acceptance/pages/lms/discussion.py
View file @
9e6333f6
...
...
@@ -167,6 +167,38 @@ class DiscussionThreadPage(PageObject, DiscussionPageMixin):
)
.
fulfill
()
class
DiscussionSortPreferencePage
(
CoursePage
):
"""
Page that contain the discussion board with sorting options
"""
def
__init__
(
self
,
browser
,
course_id
):
super
(
DiscussionSortPreferencePage
,
self
)
.
__init__
(
browser
,
course_id
)
self
.
url_path
=
"discussion/forum"
def
is_browser_on_page
(
self
):
"""
Return true if the browser is on the right page else false.
"""
return
self
.
q
(
css
=
"body.discussion .sort-bar"
)
.
present
def
get_selected_sort_preference_text
(
self
):
"""
Return the text of option that is selected for sorting.
"""
return
self
.
q
(
css
=
"body.discussion .sort-bar a.active"
)
.
text
[
0
]
.
lower
()
def
change_sort_preference
(
self
,
sort_by
):
"""
Change the option of sorting by clicking on new option.
"""
self
.
q
(
css
=
"body.discussion .sort-bar a[data-sort='{0}']"
.
format
(
sort_by
))
.
click
()
def
refresh_page
(
self
):
"""
Reload the page.
"""
self
.
browser
.
refresh
()
class
DiscussionTabSingleThreadPage
(
CoursePage
):
def
__init__
(
self
,
browser
,
course_id
,
thread_id
):
super
(
DiscussionTabSingleThreadPage
,
self
)
.
__init__
(
browser
,
course_id
)
...
...
common/test/acceptance/tests/test_discussion.py
View file @
9e6333f6
...
...
@@ -12,7 +12,8 @@ from ..pages.lms.discussion import (
InlineDiscussionPage
,
InlineDiscussionThreadPage
,
DiscussionUserProfilePage
,
DiscussionTabHomePage
DiscussionTabHomePage
,
DiscussionSortPreferencePage
,
)
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
..fixtures.discussion
import
(
...
...
@@ -22,7 +23,7 @@ from ..fixtures.discussion import (
Thread
,
Response
,
Comment
,
SearchResult
SearchResult
,
)
...
...
@@ -458,3 +459,52 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
self
.
setup_corrected_text
(
None
)
self
.
page
.
perform_search
()
self
.
check_search_alert_messages
([])
class
DiscussionSortPreferenceTest
(
UniqueCourseTest
):
"""
Tests for the discussion page displaying a single thread.
"""
def
setUp
(
self
):
super
(
DiscussionSortPreferenceTest
,
self
)
.
setUp
()
# Create a course to register for.
CourseFixture
(
**
self
.
course_info
)
.
install
()
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
self
.
sort_page
=
DiscussionSortPreferencePage
(
self
.
browser
,
self
.
course_id
)
self
.
sort_page
.
visit
()
def
test_default_sort_preference
(
self
):
"""
Test to check the default sorting preference of user. (Default = date )
"""
selected_sort
=
self
.
sort_page
.
get_selected_sort_preference_text
()
self
.
assertEqual
(
selected_sort
,
"date"
)
def
test_change_sort_preference
(
self
):
"""
Test that if user sorting preference is changing properly.
"""
selected_sort
=
""
for
sort_type
in
[
"votes"
,
"comments"
,
"date"
]:
self
.
assertNotEqual
(
selected_sort
,
sort_type
)
self
.
sort_page
.
change_sort_preference
(
sort_type
)
selected_sort
=
self
.
sort_page
.
get_selected_sort_preference_text
()
self
.
assertEqual
(
selected_sort
,
sort_type
)
def
test_last_preference_saved
(
self
):
"""
Test that user last preference is saved.
"""
selected_sort
=
""
for
sort_type
in
[
"votes"
,
"comments"
,
"date"
]:
self
.
assertNotEqual
(
selected_sort
,
sort_type
)
self
.
sort_page
.
change_sort_preference
(
sort_type
)
selected_sort
=
self
.
sort_page
.
get_selected_sort_preference_text
()
self
.
assertEqual
(
selected_sort
,
sort_type
)
self
.
sort_page
.
refresh_page
()
selected_sort
=
self
.
sort_page
.
get_selected_sort_preference_text
()
self
.
assertEqual
(
selected_sort
,
sort_type
)
lms/djangoapps/django_comment_client/forum/tests.py
View file @
9e6333f6
...
...
@@ -118,6 +118,7 @@ def make_mock_request_impl(text, thread_id="dummy_thread_id"):
data
=
make_mock_thread_data
(
text
,
thread_id
,
True
)
elif
"/users/"
in
url
:
data
=
{
"default_sort_key"
:
"date"
,
"upvoted_ids"
:
[],
"downvoted_ids"
:
[],
"subscribed_thread_ids"
:
[],
...
...
lms/djangoapps/django_comment_client/forum/views.py
View file @
9e6333f6
...
...
@@ -3,9 +3,9 @@ import logging
import
xml.sax.saxutils
as
saxutils
from
django.contrib.auth.decorators
import
login_required
from
django.http
import
Http404
from
django.core.context_processors
import
csrf
from
django.contrib.auth.models
import
User
from
django.http
import
Http404
from
django.utils.translation
import
ugettext
as
_
from
django.views.decorators.http
import
require_GET
import
newrelic.agent
...
...
@@ -225,7 +225,8 @@ def forum_form_discussion(request, course_id):
'cohorts'
:
cohorts
,
'user_cohort'
:
user_cohort_id
,
'cohorted_commentables'
:
cohorted_commentables
,
'is_course_cohorted'
:
is_course_cohorted
(
course_id
)
'is_course_cohorted'
:
is_course_cohorted
(
course_id
),
'sort_preference'
:
user
.
default_sort_key
,
}
# print "start rendering.."
return
render_to_response
(
'discussion/index.html'
,
context
)
...
...
@@ -296,7 +297,6 @@ def single_thread(request, course_id, discussion_id, thread_id):
cohorts
=
get_course_cohorts
(
course_id
)
cohorted_commentables
=
get_cohorted_commentables
(
course_id
)
user_cohort
=
get_cohort_id
(
request
.
user
,
course_id
)
context
=
{
'discussion_id'
:
discussion_id
,
'csrf'
:
csrf
(
request
)[
'csrf_token'
],
...
...
@@ -316,9 +316,9 @@ def single_thread(request, course_id, discussion_id, thread_id):
'flag_moderator'
:
cached_has_permission
(
request
.
user
,
'openclose_thread'
,
course
.
id
)
or
has_access
(
request
.
user
,
'staff'
,
course
),
'cohorts'
:
cohorts
,
'user_cohort'
:
get_cohort_id
(
request
.
user
,
course_id
),
'cohorted_commentables'
:
cohorted_commentables
'cohorted_commentables'
:
cohorted_commentables
,
'sort_preference'
:
cc_user
.
default_sort_key
,
}
return
render_to_response
(
'discussion/index.html'
,
context
)
@require_GET
...
...
lms/templates/discussion/_thread_list_template.html
View file @
9e6333f6
...
...
@@ -29,7 +29,7 @@
<
div
class
=
"sort-bar"
>
<
span
class
=
"sort-label"
id
=
"sort-label"
>
$
{
_
(
"Sort by:"
)}
<
/span
>
<
ul
role
=
"radiogroup"
aria
-
labelledby
=
"sort-label"
>
<
li
><
a
href
=
"#"
role
=
"radio"
aria
-
checked
=
"
true"
class
=
"activ
e"
data
-
sort
=
"date"
>
$
{
_
(
"date"
)}
<
/a></
li
>
<
li
><
a
href
=
"#"
role
=
"radio"
aria
-
checked
=
"
fals
e"
data
-
sort
=
"date"
>
$
{
_
(
"date"
)}
<
/a></
li
>
<
li
><
a
href
=
"#"
role
=
"radio"
aria
-
checked
=
"false"
data
-
sort
=
"votes"
>
$
{
_
(
"votes"
)}
<
/a></
li
>
<
li
><
a
href
=
"#"
role
=
"radio"
aria
-
checked
=
"false"
data
-
sort
=
"comments"
>
$
{
_
(
"comments"
)}
<
/a></
li
>
<
/ul
>
...
...
lms/templates/discussion/index.html
View file @
9e6333f6
...
...
@@ -25,7 +25,7 @@
<
%
include
file=
"_new_post.html"
/>
<section
class=
"discussion container"
id=
"discussion-container"
data-roles=
"${roles}"
data-course-id=
"${course_id}"
data-user-info=
"${user_info}"
data-threads=
"${threads}"
data-thread-pages=
"${thread_pages}"
data-content-info=
"${annotated_content_info}"
data-flag-moderator=
"${flag_moderator}"
>
<section
class=
"discussion container"
id=
"discussion-container"
data-roles=
"${roles}"
data-course-id=
"${course_id}"
data-user-info=
"${user_info}"
data-threads=
"${threads}"
data-thread-pages=
"${thread_pages}"
data-content-info=
"${annotated_content_info}"
data-
sort-preference=
"${sort_preference}"
data-
flag-moderator=
"${flag_moderator}"
>
<div
class=
"discussion-body"
>
<div
class=
"sidebar"
></div>
<div
class=
"discussion-column"
>
...
...
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