Commit 9bdff748 by Miles Steele

cleanup

- rename list copy
- add beta list
- fix forum and admin API's to be more similar
- add auto-enroll checkbox
- fix list reload
- remove extra js logging
- add course errors visibility toggle
- add underscore.js check
parent 83ff497e
......@@ -18,7 +18,16 @@ from django_comment_common.models import (Role,
def list_with_level(course, level):
grpname = get_access_group_name(course, level)
"""
List users who have 'level' access.
level is in ['instructor', 'staff', 'beta']
"""
if level in ['beta']:
grpname = course_beta_test_group_name(course.location)
else:
grpname = get_access_group_name(course, level)
try:
return Group.objects.get(name=grpname).user_set.all()
except Group.DoesNotExist:
......@@ -52,7 +61,7 @@ def _change_access(course, user, level, mode):
"""
if level in ['beta']:
grpname = course_beta_test_group_name(course)
grpname = course_beta_test_group_name(course.location)
else:
grpname = get_access_group_name(course, level)
group, _ = Group.objects.get_or_create(name=grpname)
......
......@@ -31,22 +31,33 @@ import analytics.csvs
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def enroll_unenroll(request, course_id):
def students_update_enrollment_email(request, course_id):
"""
Enroll or unenroll students by email.
Requires staff access.
Query Parameters:
- action in ['enroll', 'unenroll']
- emails is string containing a list of emails separated by anything split_input_list can handle.
- auto_enroll is a boolean (defaults to false)
"""
course = get_course_with_access(request.user, course_id, 'staff', depth=None)
emails_to_enroll = split_input_list(request.GET.get('enroll', ''))
emails_to_unenroll = split_input_list(request.GET.get('unenroll', ''))
action = request.GET.get('action', '')
emails = split_input_list(request.GET.get('emails', ''))
auto_enroll = request.GET.get('auto_enroll', '') in ['true', 'Talse', True]
enrolled_result = enroll_emails(course_id, emails_to_enroll)
unenrolled_result = unenroll_emails(course_id, emails_to_unenroll)
if action == 'enroll':
results = enroll_emails(course_id, emails, auto_enroll=auto_enroll)
elif action == 'unenroll':
results = unenroll_emails(course_id, emails)
else:
raise ValueError("unrecognized action '{}'".format(action))
response_payload = {
'enrolled': enrolled_result,
'unenrolled': unenrolled_result,
'action': action,
'results': results,
'auto_enroll': auto_enroll,
}
response = HttpResponse(json.dumps(response_payload), content_type="application/json")
return response
......
......@@ -40,9 +40,15 @@ def instructor_dashboard_2(request, course_id):
if not staff_access:
raise Http404
access = {
'instructor': instructor_access,
'staff': staff_access,
'forum_admin': forum_admin_access,
}
sections = [
_section_course_info(course_id),
_section_membership(course_id),
_section_membership(course_id, access),
_section_student_admin(course_id),
_section_data_download(course_id),
_section_analytics(course_id),
......@@ -100,13 +106,16 @@ def _section_course_info(course_id):
return section_data
def _section_membership(course_id):
def _section_membership(course_id, access):
""" Provide data for the corresponding dashboard section """
section_data = {
'section_key': 'membership',
'section_display_name': 'Membership',
'enroll_button_url': reverse('enroll_unenroll', kwargs={'course_id': course_id}),
'unenroll_button_url': reverse('enroll_unenroll', kwargs={'course_id': course_id}),
'access': access,
'enroll_button_url': reverse('students_update_enrollment_email', kwargs={'course_id': course_id}),
'unenroll_button_url': reverse('students_update_enrollment_email', kwargs={'course_id': course_id}),
'list_course_role_members_url': reverse('list_course_role_members', kwargs={'course_id': course_id}),
'access_allow_revoke_url': reverse('access_allow_revoke', kwargs={'course_id': course_id}),
'list_forum_members_url': reverse('list_forum_members', kwargs={'course_id': course_id}),
......@@ -121,7 +130,7 @@ def _section_student_admin(course_id):
'section_key': 'student_admin',
'section_display_name': 'Student Admin',
'get_student_progress_url': reverse('get_student_progress_url', kwargs={'course_id': course_id}),
'unenroll_button_url': reverse('enroll_unenroll', kwargs={'course_id': course_id}),
'unenroll_button_url': reverse('students_update_enrollment_email', kwargs={'course_id': course_id}),
'reset_student_attempts_url': reverse('reset_student_attempts', kwargs={'course_id': course_id}),
}
return section_data
......
......@@ -23,7 +23,7 @@ class Analytics
populate_selector: (cb) ->
@get_profile_distributions [], (data) =>
@$distribution_select.find('option').eq(0).text "-- Select distribution"
@$distribution_select.find('option').eq(0).text "-- Select Distribution --"
for feature in data.available_features
opt = $ '<option/>',
......@@ -107,7 +107,8 @@ class Analytics
# exports
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
Analytics: Analytics
if _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
Analytics: Analytics
log = -> console.log.apply console, arguments
plantTimeout = (ms, cb) -> setTimeout cb, ms
class CourseInfo
constructor: (@$section) ->
log "setting up instructor dashboard section - course info"
@$section.data 'wrapper', @
@$course_errors_wrapper = @$section.find '.course-errors-wrapper'
if @$course_errors_wrapper.length
@$course_error_toggle = @$course_errors_wrapper.find('h2').eq(0)
@$course_error_visibility_wrapper = @$course_errors_wrapper.find '.course-errors-visibility-wrapper'
@$course_errors = @$course_errors_wrapper.find('.course-error')
@$course_error_toggle.text @$course_error_toggle.text() + " (#{@$course_errors.length})"
@$course_error_toggle.click (e) =>
e.preventDefault()
@$course_error_visibility_wrapper.toggle()
# exports
if _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
CourseInfo: CourseInfo
......@@ -48,7 +48,8 @@ class DataDownload
# exports
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
DataDownload: DataDownload
if _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
DataDownload: DataDownload
......@@ -26,6 +26,7 @@ setup_instructor_dashboard = (idash_content) =>
# setup section header click handlers
for link in ($ link for link in links)
link.click (e) ->
e.preventDefault()
# deactivate (styling) all sections
idash_content.find(".#{CSS_IDASH_SECTION}").removeClass CSS_ACTIVE_SECTION
idash_content.find(".#{CSS_INSTRUCTOR_NAV}").children().removeClass CSS_ACTIVE_SECTION
......@@ -34,8 +35,6 @@ setup_instructor_dashboard = (idash_content) =>
section_name = $(this).data 'section'
section = idash_content.find "##{section_name}"
section.data('wrapper')?.onClickTitle?()
# activate (styling) active
section.addClass CSS_ACTIVE_SECTION
$(this).addClass CSS_ACTIVE_SECTION
......@@ -43,8 +42,7 @@ setup_instructor_dashboard = (idash_content) =>
# write deep link
location.hash = "#{HASH_LINK_PREFIX}#{section_name}"
log "clicked #{section_name}"
e.preventDefault()
plantTimeout 0, -> section.data('wrapper')?.onClickTitle?()
# recover deep link from url
# click default or go to section specified by hash
......@@ -62,6 +60,7 @@ setup_instructor_dashboard_sections = (idash_content) ->
log "setting up instructor dashboard sections"
# fault isolation
# an error thrown in one section will not block other sections from exectuing
plantTimeout 0, -> new window.InstructorDashboard.sections.CourseInfo idash_content.find ".#{CSS_IDASH_SECTION}#course_info"
plantTimeout 0, -> new window.InstructorDashboard.sections.DataDownload idash_content.find ".#{CSS_IDASH_SECTION}#data_download"
plantTimeout 0, -> new window.InstructorDashboard.sections.Membership idash_content.find ".#{CSS_IDASH_SECTION}#membership"
plantTimeout 0, -> new window.InstructorDashboard.sections.StudentAdmin idash_content.find ".#{CSS_IDASH_SECTION}#student_admin"
......
......@@ -9,6 +9,8 @@ class BatchEnrollment
$emails_input = @$container.find("textarea[name='student-emails']'")
$btn_enroll = @$container.find("input[name='enroll']'")
$btn_unenroll = @$container.find("input[name='unenroll']'")
$checkbox_autoenroll = @$container.find("input[name='auto-enroll']'")
window.autoenroll = $checkbox_autoenroll
$task_response = @$container.find(".task-response")
$emails_input.click -> log 'click $emails_input'
......@@ -16,20 +18,27 @@ class BatchEnrollment
$btn_unenroll.click -> log 'click $btn_unenroll'
$btn_enroll.click ->
$.getJSON $btn_enroll.data('endpoint'), enroll: $emails_input.val() , (data) ->
send_data =
action: 'enroll'
emails: $emails_input.val()
auto_enroll: $checkbox_autoenroll.is(':checked')
$.getJSON $btn_enroll.data('endpoint'), send_data, (data) ->
log 'received response for enroll button', data
display_response(data)
$btn_unenroll.click ->
log 'VAL', $emails_input.val()
$.getJSON $btn_unenroll.data('endpoint'), unenroll: $emails_input.val() , (data) ->
# log 'received response for unenroll button', data
# display_response(data)
send_data =
action: 'unenroll'
emails: $emails_input.val()
auto_enroll: $checkbox_autoenroll.is(':checked')
$.getJSON $btn_unenroll.data('endpoint'), send_data, (data) ->
log 'received response for unenroll button', data
display_response(data)
display_response = (data_from_server) ->
$task_response.empty()
response_code_dict = _.extend {}, data_from_server.enrolled, data_from_server.unenrolled
response_code_dict = _.extend {}, data_from_server.results
# response_code_dict e.g. {'code': ['email1', 'email2'], ...}
message_ordering = [
'msg_error_enroll'
......@@ -77,13 +86,10 @@ class BatchEnrollment
will_attach = false
for code in msg_to_codes[msg_symbol]
log 'logging code', code
emails = response_code_dict[code]
log 'emails', emails
if emails and emails.length
for email in emails
log 'logging email', email
email_list.append $ '<li/>', text: email
will_attach = true
......@@ -114,7 +120,6 @@ class AuthList
reload_auth_list: =>
list_endpoint = @$display_table.data 'endpoint'
$.getJSON list_endpoint, {rolename: @rolename}, (data) =>
log data
@$display_table.empty()
......@@ -139,11 +144,9 @@ class AuthList
]
table_data = data[@rolename]
log 'table_data', table_data
$table_placeholder = $ '<div/>', class: 'slickgrid'
@$display_table.append $table_placeholder
log '@$display_table', $table_placeholder
grid = new Slick.Grid($table_placeholder, table_data, columns, options)
grid.autosizeColumns()
......@@ -152,11 +155,16 @@ class AuthList
if args.cell is 2
@access_change(item.email, @rolename, 'revoke', @reload_auth_list)
# slickgrid collapses when rendered in an invisible div
# use this method to reload the widget
refresh: ->
@$display_table.empty()
@reload_auth_list()
access_change: (email, rolename, mode, cb) ->
access_change_endpoint = @$add_section.data 'endpoint'
$.getJSON access_change_endpoint, {email: email, rolename: @rolename, mode: mode}, (data) ->
log data
cb?()
cb?(data)
class Membership
......@@ -164,36 +172,41 @@ class Membership
log "setting up instructor dashboard section - membership"
@$section.data 'wrapper', @
# isolate sections from each other's errors.
plantTimeout 0, => @batchenrollment = new BatchEnrollment @$section.find '.batch-enrollment'
plantTimeout 0, => @stafflist = new AuthList (@$section.find '.auth-list-container.auth-list-staff'), 'staff'
plantTimeout 0, => @instructorlist = new AuthList (@$section.find '.auth-list-container.auth-list-instructor'), 'instructor'
@$list_selector = @$section.find('select#member-lists-selector')
# TODO names like 'Administrator' should come from server through template.
plantTimeout 0, => @forum_admin_list = new AuthList (@$section.find '.auth-list-container.auth-list-forum-admin'), 'Administrator'
plantTimeout 0, => @forum_mod_list = new AuthList (@$section.find '.auth-list-container.auth-list-forum-moderator'), 'Moderator'
plantTimeout 0, => @forum_comta_list = new AuthList (@$section.find '.auth-list-container.auth-list-forum-community-ta'), 'Community TA'
plantTimeout 0, => @batchenrollment = new BatchEnrollment @$section.find '.batch-enrollment'
onClickTitle: ->
@stafflist.$display_table.empty()
@stafflist.reload_auth_list()
@auth_lists = _.map (@$section.find '.auth-list-container'), (auth_list_container) ->
rolename = $(auth_list_container).data 'rolename'
new AuthList $(auth_list_container), rolename
@instructorlist.$display_table.empty()
@instructorlist.reload_auth_list()
# populate selector
@$list_selector.empty()
for auth_list in @auth_lists
@$list_selector.append $ '<option/>',
text: auth_list.$container.data 'display-name'
data:
auth_list: auth_list
@forum_admin_list.$display_table.empty()
@forum_admin_list.reload_auth_list()
@$list_selector.change =>
$opt = @$list_selector.children('option:selected')
for auth_list in @auth_lists
auth_list.$container.removeClass 'active'
auth_list = $opt.data('auth_list')
auth_list.refresh()
auth_list.$container.addClass 'active'
@forum_mod_list.$display_table.empty()
@forum_mod_list.reload_auth_list()
@$list_selector.change()
@forum_comta_list.$display_table.empty()
@forum_comta_list.reload_auth_list()
onClickTitle: ->
for auth_list in @auth_lists
auth_list.refresh()
# exports
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
Membership: Membership
if _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
Membership: Membership
......@@ -24,8 +24,12 @@ class StudentAdmin
console.warn 'error getting student progress url for ' + email
@$unenroll_btn.click =>
$.getJSON @$unenroll_btn.data('endpoint'), unenroll: @$student_email_field.val(), (data) ->
log 'data'
send_data =
action: 'unenroll'
emails: @$student_email_field.val()
auto_enroll: false
$.getJSON @$unenroll_btn.data('endpoint'), send_data, (data) ->
log data
@$reset_attempts_btn.click =>
email = @$student_email_field.val()
......@@ -77,7 +81,8 @@ class StudentAdmin
# exports
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
StudentAdmin: StudentAdmin
if _?
_.defaults window, InstructorDashboard: {}
_.defaults window.InstructorDashboard, sections: {}
_.defaults window.InstructorDashboard.sections,
StudentAdmin: StudentAdmin
......@@ -63,19 +63,28 @@
.instructor-dashboard-wrapper-2 section.idash-section#course_info {
.error-log {
margin-top: 1em;
.course-errors-wrapper {
margin-top: 2em;
.course-error {
margin-bottom: 1em;
h2 {
color: #D60000;
}
code {
&.course-error-first {
color: #111;
}
.course-errors-visibility-wrapper {
display: none;
&.course-error-second {
color: black;
.course-error {
margin-bottom: 1em;
margin-left: 0.5em;
code {
&.course-error-first {
color: #111;
}
&.course-error-second {
color: #111;
}
}
}
}
......@@ -86,17 +95,26 @@
.instructor-dashboard-wrapper-2 section.idash-section#membership {
.vert-left {
float: left;
width: 45%;
width: 47%;
}
.vert-right {
float: right;
width: 45%;
width: 47%;
}
select {
margin-bottom: 1em;
}
.auth-list-container {
display: none;
margin-bottom: 1.5em;
&.active {
display: block;
}
.auth-list-table {
.slickgrid {
height: 250px;
......@@ -110,6 +128,9 @@
.batch-enrollment {
textarea {
margin-top: 0.2em;
margin-bottom: 1em;
height: 100px;
width: 500px;
}
......@@ -146,6 +167,7 @@
input {
// display: block;
margin-bottom: 1em;
line-height: 1.3em;
}
.data-display {
......
......@@ -37,14 +37,16 @@
${ section_data['offline_grades'] }
</div>
<div class="error-log">
%if len(section_data['course_errors']):
<h2>Course Errors:</h2>
%for error in section_data['course_errors']:
<div class="course-error">
<code class=course-error-first> ${ error[0] } </code><br>
<code class=course-error-second> ${ error[1] } </code>
</div>
%endfor
%endif
</div>
%if len(section_data['course_errors']):
<div class="course-errors-wrapper">
<a href=""><h2 class="title">Course Warnings:</h2></a>
<div class="course-errors-visibility-wrapper">
%for error in section_data['course_errors']:
<div class="course-error">
<code class=course-error-first> ${ error[0] } </code><br>
<code class=course-error-second> ${ error[1] } </code>
</div>
%endfor
</div>
</div>
%endif
<%page args="section_data"/>
<input type="button" name="list-profiles" value="List enrolled students with profile information" data-endpoint="${ section_data['enrolled_students_profiles_url'] }" >
<input type="button" name="list-profiles" value="[CSV]" data-csv="true" data-endpoint="${ section_data['enrolled_students_profiles_url'] }" >
<input type="button" name="list-profiles" value="CSV" data-csv="true" class="csv" data-endpoint="${ section_data['enrolled_students_profiles_url'] }" >
<br>
<input type="button" name="list-grades" value="Student grades">
<br>
<input type="button" name="list-answer-distributions" value="Answer distributions (x students got y points)">
<br>
<input type="button" name="dump-gradeconf" value="Grading Configuration" data-endpoint="${ section_data['grading_config_url'] }">
<div class="data-display">
<div class="data-display-text"></div>
<div class="data-display-table"></div>
......
......@@ -7,52 +7,68 @@
<br>
<input type="button" name="enroll" value="Enroll" data-endpoint="${ section_data['enroll_button_url'] }" >
<input type="button" name="unenroll" value="Unenroll" data-endpoint="${ section_data['unenroll_button_url'] }" >
<input type="checkbox" name="auto-enroll" value="Auto-Enroll">
<label for="auto-enroll">Auto Enroll</label>
<div class="task-response"></div>
</div>
<div class="auth-list-container auth-list-forum-admin">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Forum Admin">
</div>
</div>
<div class="vert-right member-lists-management">
<h2> Member List Management </h2>
<select id="member-lists-selector">
<option> Getting available lists... </option>
</select>
<div class="auth-list-container auth-list-forum-moderator">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<div class="auth-list-container" data-rolename="staff" data-display-name="Staff">
<div class="auth-list-table" data-endpoint="${ section_data['list_course_role_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['access_allow_revoke_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Forum Moderator">
<input type="button" name="allow" value="Grant Staff Access">
</div>
</div>
<div class="auth-list-container auth-list-forum-community-ta">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Community TA">
%if section_data['access']['instructor']:
<div class="auth-list-container" data-rolename="instructor" data-display-name="Instructors">
<div class="auth-list-table" data-endpoint="${ section_data['list_course_role_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['access_allow_revoke_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Instructor Access">
</div>
</div>
</div>
</div>
%endif
<div class="vert-right instructor-staff-management">
<div class="auth-list-container auth-list-staff">
<h2>Staff Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_instructors_staff_url'] }"></div>
<div class="auth-list-container" data-rolename="beta" data-display-name="Beta Testers">
<div class="auth-list-table" data-endpoint="${ section_data['list_course_role_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['access_allow_revoke_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Staff Access">
<input type="button" name="allow" value="Grant Beta Tester Access">
</div>
</div>
<div class="auth-list-container auth-list-instructor">
<h2>Instructor Management</h2>
<div class="auth-list-table" data-endpoint="${ section_data['list_instructors_staff_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['access_allow_revoke_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Instructor Access">
%if section_data['access']['forum_admin']:
<div class="auth-list-container" data-rolename="Administrator" data-display-name="Forum Admins">
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Forum Admin">
</div>
</div>
</div>
<div class="auth-list-container" data-rolename="Moderator" data-display-name="Forum Moderators">
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Forum Moderator">
</div>
</div>
<div class="auth-list-container" data-rolename="Community TA" data-display-name="Forum Community TAs">
<div class="auth-list-table" data-endpoint="${ section_data['list_forum_members_url'] }"></div>
<div class="auth-list-add" data-endpoint="${ section_data['update_forum_role_membership_url'] }">
<input type="text" name="email" placeholder="Enter Email" spellcheck="false">
<input type="button" name="allow" value="Grant Community TA">
</div>
</div>
%endif
</div>
......@@ -255,8 +255,8 @@ if settings.COURSEWARE_ENABLED:
'instructor.views.instructor_dashboard.instructor_dashboard_2', name="instructor_dashboard_2"),
# api endpoints for instructor
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/enroll_unenroll$',
'instructor.views.api.enroll_unenroll', name="enroll_unenroll"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/students_update_enrollment_email$',
'instructor.views.api.students_update_enrollment_email', name="students_update_enrollment_email"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/list_course_role_members$',
'instructor.views.api.list_course_role_members', name="list_course_role_members"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor_dashboard/api/access_allow_revoke$',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment