Commit 7149e6c5 by Awais Committed by Awais Qureshi

Fixing tinymce character count issue.

Change the char field to text field ( short_description).
Adding migration.

ECOM-7693
parent 6b8a10be
......@@ -17,6 +17,7 @@ from course_discovery.apps.publisher.mixins import LanguageModelSelect2Multiple,
from course_discovery.apps.publisher.models import (Course, CourseRun, CourseUserRole, OrganizationExtension,
OrganizationUserRole, PublisherUser, Seat, User)
from course_discovery.apps.publisher.utils import is_internal_user
from course_discovery.apps.publisher.validators import validate_text_count
class UserModelChoiceField(forms.ModelChoiceField):
......@@ -95,13 +96,16 @@ class CustomCourseForm(CourseForm):
title = forms.CharField(label=_('Course Title'), required=True)
number = forms.CharField(label=_('Course Number'), required=True)
short_description = forms.CharField(
label=_('Brief Description'), max_length=255, widget=forms.Textarea, required=False
label=_('Brief Description'),
widget=forms.Textarea, required=False, validators=[validate_text_count(max_length=255)]
)
full_description = forms.CharField(
label=_('Full Description'), max_length=2500, widget=forms.Textarea, required=False
label=_('Full Description'), widget=forms.Textarea, required=False,
validators=[validate_text_count(max_length=2500)]
)
prerequisites = forms.CharField(
label=_('Prerequisites'), max_length=200, widget=forms.Textarea, required=False
label=_('Prerequisites'), widget=forms.Textarea, required=False,
validators=[validate_text_count(max_length=200)]
)
# users will be loaded through AJAX call based on organization
......@@ -133,19 +137,23 @@ class CustomCourseForm(CourseForm):
)
expected_learnings = forms.CharField(
label=_('Expected Learnings'), max_length=2500, widget=forms.Textarea, required=False
label=_('Expected Learnings'), widget=forms.Textarea, required=False,
validators=[validate_text_count(max_length=2500)]
)
learner_testimonial = forms.CharField(
label=_('Learner Testimonial'), max_length=500, widget=forms.Textarea, required=False
label=_('Learner Testimonial'), widget=forms.Textarea, required=False,
validators=[validate_text_count(max_length=500)]
)
faq = forms.CharField(
label=_('FAQ'), max_length=2500, widget=forms.Textarea, required=False
label=_('FAQ'), widget=forms.Textarea, required=False,
validators=[validate_text_count(max_length=2500)]
)
syllabus = forms.CharField(
label=_('Syllabus'), max_length=2500, widget=forms.Textarea, required=False
label=_('Syllabus'), widget=forms.Textarea, required=False,
validators=[validate_text_count(max_length=2500)]
)
class Meta(CourseForm.Meta):
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-04-13 10:10
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('publisher', '0046_auto_20170413_0935'),
]
operations = [
migrations.AlterField(
model_name='course',
name='short_description',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Brief Description'),
),
migrations.AlterField(
model_name='historicalcourse',
name='short_description',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Brief Description'),
),
]
......@@ -39,8 +39,8 @@ class Course(TimeStampedModel, ChangedByMixin):
title = models.CharField(max_length=255, default=None, null=True, blank=True, verbose_name=_('Course title'))
number = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('Course number'))
short_description = models.CharField(
max_length=255, default=None, null=True, blank=True, verbose_name=_('Brief Description')
short_description = models.TextField(
default=None, null=True, blank=True, verbose_name=_('Brief Description')
)
full_description = models.TextField(default=None, null=True, blank=True, verbose_name=_('Full Description'))
organizations = models.ManyToManyField(
......
......@@ -1898,6 +1898,7 @@ class CourseDetailViewTests(TestCase):
)
@ddt.ddt
class CourseEditViewTests(TestCase):
""" Tests for the course edit view. """
......@@ -2164,7 +2165,7 @@ class CourseEditViewTests(TestCase):
response = self.client.get(self.edit_page_url)
self.assertNotContains(response, 'VIDEO LINK')
def test_update_course_with_non_errors(self):
def test_update_with_errors(self):
"""
Verify that page shows error if any required field data is missing.
"""
......@@ -2175,6 +2176,72 @@ class CourseEditViewTests(TestCase):
response = self.client.post(self.edit_page_url, data=post_data)
self.assertContains(response, 'Please fill all required fields.')
def test_text_area_max_length_error(self):
"""
Verify that page shows error if any text area exceeds the max length.
"""
self.user.groups.add(Group.objects.get(name=ADMIN_GROUP_NAME))
post_data = self._post_data(self.organization_extension)
post_data['title'] = 'test'
post_data['short_description'] = '''
<html>
<body>
<h2>An Unordered HTML List</h2>
<ul>
<li>An Unordered HTML List. An Unordered HTML List</li>
<li>An Unordered HTML List. An Unordered HTML List</li>
<li>An Unordered HTML List</li>
</ul>
<ul>
<li>An Unordered HTML List</li>
<li>An Unordered HTML List</li>
<li>An Unordered HTML List</li>
</ul>
<ul>
<li>An Unordered HTML List</li>
<li>An Unordered HTML List</li>
<li>An Unordered HTML List</li>
</ul>
</body>
</html>
'''
response = self.client.post(self.edit_page_url, data=post_data)
self.assertContains(response, 'Ensure this value has at most 255 characters')
@ddt.data(
'short_description', 'full_description', 'prerequisites', 'expected_learnings',
'learner_testimonial', 'faq', 'syllabus'
)
def test_text_area_post_with_html(self, field):
"""
Verify that page saves the text area html in db.
"""
self.user.groups.add(Group.objects.get(name=ADMIN_GROUP_NAME))
post_data = self._post_data(self.organization_extension)
post_data['title'] = 'test'
post_data[field] = '''
<html>
<body>
<h2>An Unordered HTML List</h2>
<ul>
<li>Coffee</li>
<li>Tea</li>
</ul>
</body>
</html>
'''
response = self.client.post(self.edit_page_url, data=post_data)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_course_detail', kwargs={'pk': self.course.id}),
status_code=302,
target_status_code=200
)
course = Course.objects.get(id=self.course.id)
self.assertEqual(post_data[field].strip(), getattr(course, field).strip())
@ddt.ddt
class CourseRunEditViewTests(TestCase):
......
""" Publisher Utils."""
from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.constants import (
ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME, PROJECT_COORDINATOR_GROUP_NAME
)
from course_discovery.apps.publisher.constants import (ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME,
PROJECT_COORDINATOR_GROUP_NAME)
def is_email_notification_enabled(user):
......
from bs4 import BeautifulSoup
from django import forms
from django.utils.translation import ugettext_lazy as _
from stdimage.validators import BaseSizeValidator
......@@ -14,3 +16,21 @@ class ImageSizeValidator(BaseSizeValidator):
'The image you uploaded is of incorrect resolution. '
'Course image files must be %(with)s x %(height)s pixels in size.'
)
def validate_text_count(max_length):
"""
Custom validator to count the text area characters without html tags.
"""
def innerfn(raw_html):
cleantext = BeautifulSoup(raw_html, 'html.parser').text.strip()
if len(cleantext) > max_length:
# pylint: disable=no-member
raise forms.ValidationError(
_('Ensure this value has at most {allowed_char} characters (it has {current_char}).').format(
allowed_char=max_length,
current_char=len(cleantext)
)
)
return innerfn
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-04-13 13:09+0500\n"
"POT-Creation-Date: 2017-04-13 14:28+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -788,6 +788,13 @@ msgid ""
"be %(with)s x %(height)s pixels in size."
msgstr ""
#: apps/publisher/validators.py
#, python-brace-format
msgid ""
"Ensure this value has at most {allowed_char} characters (it has "
"{current_char})."
msgstr ""
#: apps/publisher/views.py
msgid "PARTNER MANAGER"
msgstr ""
......@@ -975,6 +982,7 @@ msgstr ""
#: templates/publisher/course_edit_form.html
#: templates/publisher/course_run/edit_run_form.html
#: templates/publisher/course_run_detail/_edit_warning.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Cancel"
msgstr ""
......@@ -988,33 +996,40 @@ msgid "Save Course Run"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "New Instructor"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Click here to upload your image"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Name"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/add_courserun_form.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "required"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Family Name"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_drupal.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_salesforce.html
msgid "Title"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_studio.html
#: templates/publisher/dashboard/_in_preview.html
#: templates/publisher/dashboard/_published.html
......@@ -1023,34 +1038,42 @@ msgid "Organization"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Bio"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Facebook URL"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "optional"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Twitter URL"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Blog URL"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Major Works"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "optional - one per line"
msgstr ""
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Add Staff Member"
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-04-13 13:09+0500\n"
"POT-Creation-Date: 2017-04-13 14:28+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-04-13 13:09+0500\n"
"POT-Creation-Date: 2017-04-13 14:28+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -934,6 +934,15 @@ msgstr ""
"Thé ïmägé ýöü üplöädéd ïs öf ïnçörréçt résölütïön. Çöürsé ïmägé fïlés müst "
"ßé %(with)s x %(height)s pïxéls ïn sïzé. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
#: apps/publisher/validators.py
#, python-brace-format
msgid ""
"Ensure this value has at most {allowed_char} characters (it has "
"{current_char})."
msgstr ""
"Énsüré thïs välüé häs ät möst {allowed_char} çhäräçtérs (ït häs "
"{current_char}). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: apps/publisher/views.py
msgid "PARTNER MANAGER"
msgstr "PÀRTNÉR MÀNÀGÉR Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
......@@ -1140,6 +1149,7 @@ msgstr "Sävé Ⱡ'σяєм ι#"
#: templates/publisher/course_edit_form.html
#: templates/publisher/course_run/edit_run_form.html
#: templates/publisher/course_run_detail/_edit_warning.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Cancel"
msgstr "Çänçél Ⱡ'σяєм ιρѕυ#"
......@@ -1153,33 +1163,40 @@ msgid "Save Course Run"
msgstr "Sävé Çöürsé Rün Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "New Instructor"
msgstr "Néw Ìnstrüçtör Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Click here to upload your image"
msgstr "Çlïçk héré tö üplöäd ýöür ïmägé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Name"
msgstr "Nämé Ⱡ'σяєм ι#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/add_courserun_form.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "required"
msgstr "réqüïréd Ⱡ'σяєм ιρѕυм ∂#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Family Name"
msgstr "Fämïlý Nämé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_drupal.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_salesforce.html
msgid "Title"
msgstr "Tïtlé Ⱡ'σяєм ιρѕ#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
#: templates/publisher/course_run_detail/_studio.html
#: templates/publisher/dashboard/_in_preview.html
#: templates/publisher/dashboard/_published.html
......@@ -1188,34 +1205,42 @@ msgid "Organization"
msgstr "Örgänïzätïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Bio"
msgstr "Bïö Ⱡ'σяєм#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Facebook URL"
msgstr "Fäçéßöök ÛRL Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "optional"
msgstr "öptïönäl Ⱡ'σяєм ιρѕυм ∂#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Twitter URL"
msgstr "Twïttér ÛRL Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Blog URL"
msgstr "Blög ÛRL Ⱡ'σяєм ιρѕυм ∂#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Major Works"
msgstr "Mäjör Wörks Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "optional - one per line"
msgstr "öptïönäl - öné pér lïné Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σ#"
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/course_run_detail/_instructor_profile.html
msgid "Add Staff Member"
msgstr "Àdd Stäff Mémßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-04-13 13:09+0500\n"
"POT-Creation-Date: 2017-04-13 14:28+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -31,7 +31,15 @@ tinymce.PluginManager.add('charactercount', function (editor) {
_self.getCount = function () {
var tx = editor.getContent({ format: 'raw' });
return tx.length;
var decoded = decodeHtml(tx);
var decodedStripped = decoded.replace(/(<([^>]+)>)/ig, "").trim();
var tc = decodedStripped.length;
return tc;
};
function decodeHtml(html) {
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
});
$(document).ready(function(){
var tinymceConfig = {
plugins: [
'charactercount','link', 'lists'
'link lists charactercount paste'
],
toolbar: 'undo redo | styleselect | bold italic | bullist numlist outdent indent | link anchor',
menubar: false,
statusbar: true,
skin: false,
setup: function (editor) {
keys(editor);
}
paste_remove_spans: true,
paste_remove_styles: true,
paste_as_text: true,
paste_auto_cleanup_on_paste: true,
skin: false
};
function keys(editor) {
editor.on('keydown', function (e) {
var ContentLength = this.plugins["charactercount"].getCount();
var maxLength = editor.getElement().maxLength;
if (maxLength > 0 && ContentLength >= maxLength ) {
if(e.keyCode != 8 && e.keyCode != 46)
{
tinymce.dom.Event.cancel(e);
}
}
});
editor.on('paste', function (e) {
var data = e.clipboardData.getData('Text');
var ContentLength = this.plugins["charactercount"].getCount();
var maxLength = editor.getElement().maxLength;
if (maxLength > 0 && data.length > (maxLength - ContentLength)){
if(e.keyCode != 8 && e.keyCode != 46)
{
tinymce.dom.Event.cancel(e);
}
}
});
}
tinymceConfig["selector"]="textarea";
tinymce.init(tinymceConfig);
});
......@@ -572,7 +572,7 @@
<script src="{% static 'bower_components/tinymce/plugins/lists/plugin.min.js' %}"></script>
<script src="{% static 'bower_components/tinymce/plugins/link/plugin.min.js' %}"></script>
<script src="{% static 'bower_components/tinymce/plugins/anchor/plugin.min.js' %}"></script>
<script src="{% static 'bower_components/tinymce/plugins/paste/plugin.min.js' %}"></script>
{% endblock %}
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