Commit 08667dda by Bill DeRusha Committed by Bill DeRusha

Add course form validations

Add char limits to prereqs and descriptions
Add pixel limits to image uploads

ECOM-6636
parent 310ddd7a
...@@ -7,16 +7,16 @@ from PIL import Image ...@@ -7,16 +7,16 @@ from PIL import Image
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
def make_image_stream(): def make_image_stream(width, height):
""" """
Helper to generate values for program banner_image Helper to generate values for program banner_image
""" """
image = Image.new('RGB', (1440, 900), 'green') image = Image.new('RGB', (width, height), 'green')
bio = BytesIO() bio = BytesIO()
image.save(bio, format='JPEG') image.save(bio, format='JPEG')
return bio return bio
def make_image_file(name): def make_image_file(name, width=2120, height=1192):
image_stream = make_image_stream() image_stream = make_image_stream(width, height)
return SimpleUploadedFile(name, image_stream.getvalue(), content_type='image/jpeg') return SimpleUploadedFile(name, image_stream.getvalue(), content_type='image/jpeg')
...@@ -89,7 +89,7 @@ def mock_api_callback(url, data, results_key=True, pagination=False): ...@@ -89,7 +89,7 @@ def mock_api_callback(url, data, results_key=True, pagination=False):
def mock_jpeg_callback(): def mock_jpeg_callback():
def request_callback(request): # pylint: disable=unused-argument def request_callback(request): # pylint: disable=unused-argument
image_stream = make_image_stream() image_stream = make_image_stream(2120, 1192)
return 200, {}, image_stream.getvalue() return 200, {}, image_stream.getvalue()
......
...@@ -59,6 +59,15 @@ class CustomCourseForm(CourseForm): ...@@ -59,6 +59,15 @@ class CustomCourseForm(CourseForm):
) )
title = forms.CharField(label=_('Course Title'), required=True) title = forms.CharField(label=_('Course Title'), required=True)
number = forms.CharField(label=_('Course Number'), required=True) number = forms.CharField(label=_('Course Number'), required=True)
short_description = forms.CharField(
label=_('Brief Description'), max_length=140, widget=forms.Textarea, required=False
)
full_description = forms.CharField(
label=_('Full Description'), max_length=2500, widget=forms.Textarea, required=False
)
prerequisites = forms.CharField(
label=_('Prerequisites'), max_length=200, widget=forms.Textarea, required=False
)
# users will be loaded through AJAX call based on organization # users will be loaded through AJAX call based on organization
team_admin = UserModelChoiceField( team_admin = UserModelChoiceField(
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-01-04 18:30
from __future__ import unicode_literals
import course_discovery.apps.course_metadata.utils
from django.db import migrations, models
import stdimage.models
import stdimage.validators
class Migration(migrations.Migration):
dependencies = [
('publisher', '0024_auto_20170105_1626'),
]
operations = [
migrations.AlterField(
model_name='course',
name='image',
field=stdimage.models.StdImageField(blank=True, null=True, upload_to=course_discovery.apps.course_metadata.utils.UploadToFieldNamePath('number', path='media/publisher/courses/images'), validators=[stdimage.validators.MaxSizeValidator(2120, 1192), stdimage.validators.MinSizeValidator(2120, 1192)]),
),
migrations.AlterField(
model_name='historicalcourse',
name='image',
field=models.TextField(blank=True, max_length=100, null=True, validators=[stdimage.validators.MaxSizeValidator(2120, 1192), stdimage.validators.MinSizeValidator(2120, 1192)]),
),
]
...@@ -11,6 +11,7 @@ from django_fsm import FSMField, transition ...@@ -11,6 +11,7 @@ from django_fsm import FSMField, transition
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from sortedm2m.fields import SortedManyToManyField from sortedm2m.fields import SortedManyToManyField
from stdimage.models import StdImageField from stdimage.models import StdImageField
from stdimage.validators import MaxSizeValidator, MinSizeValidator
from taggit.managers import TaggableManager from taggit.managers import TaggableManager
import waffle import waffle
...@@ -126,10 +127,9 @@ class Course(TimeStampedModel, ChangedByMixin): ...@@ -126,10 +127,9 @@ class Course(TimeStampedModel, ChangedByMixin):
blank=True, blank=True,
null=True, null=True,
variations={ variations={
'large': (2120, 1192),
'medium': (1440, 480),
'thumbnail': (100, 100, True), 'thumbnail': (100, 100, True),
} },
validators=[MaxSizeValidator(2120, 1192), MinSizeValidator(2120, 1192), ]
) )
is_seo_review = models.BooleanField(default=False) is_seo_review = models.BooleanField(default=False)
......
...@@ -35,6 +35,10 @@ from course_discovery.apps.publisher.wrappers import CourseRunWrapper ...@@ -35,6 +35,10 @@ from course_discovery.apps.publisher.wrappers import CourseRunWrapper
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
IMAGE_TOO_SMALL = 'The image you uploaded is too small. The required minimum resolution is: 2120x1192 px.'
IMAGE_TOO_LARGE = 'The image you uploaded is too large. The required maximum resolution is: 2120x1192 px.'
@ddt.ddt @ddt.ddt
class CreateUpdateCourseViewTests(TestCase): class CreateUpdateCourseViewTests(TestCase):
""" Tests for the publisher `CreateCourseView` and `UpdateCourseView`. """ """ Tests for the publisher `CreateCourseView` and `UpdateCourseView`. """
...@@ -109,6 +113,24 @@ class CreateUpdateCourseViewTests(TestCase): ...@@ -109,6 +113,24 @@ class CreateUpdateCourseViewTests(TestCase):
self._assert_test_data(response, course, self.seat.type, self.seat.price) self._assert_test_data(response, course, self.seat.type, self.seat.price)
@ddt.data(
(make_image_file('test_banner00.jpg', width=2120, height=1191), [IMAGE_TOO_SMALL]),
(make_image_file('test_banner01.jpg', width=2120, height=1193), [IMAGE_TOO_LARGE]),
(make_image_file('test_banner02.jpg', width=2119, height=1192), [IMAGE_TOO_SMALL]),
(make_image_file('test_banner03.jpg', width=2121, height=1192), [IMAGE_TOO_LARGE]),
(make_image_file('test_banner04.jpg', width=2121, height=1191), [IMAGE_TOO_LARGE, IMAGE_TOO_SMALL]),
)
@ddt.unpack
def test_create_course_invalid_image(self, image, errors):
""" Verify that a new course with an invalid image shows the proper error.
"""
self.user.groups.add(Group.objects.get(name=ADMIN_GROUP_NAME))
self._assert_records(1)
course_dict = self._post_data({'image': image}, self.course, self.course_run, self.seat)
response = self.client.post(reverse('publisher:publisher_courses_new'), course_dict, files=image)
self.assertEqual(response.context['course_form'].errors['image'], errors)
self._assert_records(1)
def test_create_with_fail_transaction(self): def test_create_with_fail_transaction(self):
""" Verify that in case of any error transactions roll back and no object """ Verify that in case of any error transactions roll back and no object
created in db. created in db.
......
...@@ -226,6 +226,9 @@ class CreateCourseView(mixins.LoginRequiredMixin, CreateView): ...@@ -226,6 +226,9 @@ class CreateCourseView(mixins.LoginRequiredMixin, CreateView):
if not messages.get_messages(request): if not messages.get_messages(request):
messages.error(request, _('Please fill all required fields.')) messages.error(request, _('Please fill all required fields.'))
if course_form.errors.get('image'):
messages.error(request, course_form.errors.get('image'))
ctx.update( ctx.update(
{ {
'course_form': course_form, 'course_form': course_form,
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-01-05 12:31+0500\n" "POT-Creation-Date: 2017-01-06 09:45-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -448,6 +448,20 @@ msgstr "" ...@@ -448,6 +448,20 @@ msgstr ""
msgid "Course Number" msgid "Course Number"
msgstr "" msgstr ""
#: apps/publisher/forms.py apps/publisher/models.py
#: templates/publisher/add_course_form.html
msgid "Brief Description"
msgstr ""
#: apps/publisher/forms.py apps/publisher/models.py
msgid "Full Description"
msgstr ""
#: apps/publisher/forms.py apps/publisher/models.py
#: templates/publisher/course_run_detail/_all.html
msgid "Prerequisites"
msgstr ""
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Organization Course Admin" msgid "Organization Course Admin"
msgstr "" msgstr ""
...@@ -525,14 +539,6 @@ msgstr "" ...@@ -525,14 +539,6 @@ msgstr ""
msgid "Course number" msgid "Course number"
msgstr "" msgstr ""
#: apps/publisher/models.py templates/publisher/add_course_form.html
msgid "Brief Description"
msgstr ""
#: apps/publisher/models.py
msgid "Full Description"
msgstr ""
#: apps/publisher/models.py templates/publisher/course_run_detail/_all.html #: apps/publisher/models.py templates/publisher/course_run_detail/_all.html
msgid "Partner Name" msgid "Partner Name"
msgstr "" msgstr ""
...@@ -545,10 +551,6 @@ msgstr "" ...@@ -545,10 +551,6 @@ msgstr ""
msgid "Expected Learnings" msgid "Expected Learnings"
msgstr "" msgstr ""
#: apps/publisher/models.py templates/publisher/course_run_detail/_all.html
msgid "Prerequisites"
msgstr ""
#: apps/publisher/models.py #: apps/publisher/models.py
msgid "Verification deadline" msgid "Verification deadline"
msgstr "" msgstr ""
...@@ -1251,6 +1253,7 @@ msgid "Dashboard" ...@@ -1251,6 +1253,7 @@ msgid "Dashboard"
msgstr "" msgstr ""
#: templates/publisher/base.html templates/publisher/courses.html #: templates/publisher/base.html templates/publisher/courses.html
#: templates/publisher/view_course_form.html
msgid "Courses" msgid "Courses"
msgstr "" msgstr ""
...@@ -1432,6 +1435,7 @@ msgid "Additional Notes" ...@@ -1432,6 +1435,7 @@ msgid "Additional Notes"
msgstr "" msgstr ""
#: templates/publisher/course_run_detail/_approval_widget.html #: templates/publisher/course_run_detail/_approval_widget.html
#: templates/publisher/view_course_form.html
msgid "EDIT" msgid "EDIT"
msgstr "" msgstr ""
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-01-05 12:31+0500\n" "POT-Creation-Date: 2017-01-06 09:45-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-01-05 12:31+0500\n" "POT-Creation-Date: 2017-01-06 09:45-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -556,6 +556,20 @@ msgstr "Çöürsé Tïtlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" ...@@ -556,6 +556,20 @@ msgstr "Çöürsé Tïtlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
msgid "Course Number" msgid "Course Number"
msgstr "Çöürsé Nümßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#" msgstr "Çöürsé Nümßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: apps/publisher/forms.py apps/publisher/models.py
#: templates/publisher/add_course_form.html
msgid "Brief Description"
msgstr "Brïéf Désçrïptïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмє#"
#: apps/publisher/forms.py apps/publisher/models.py
msgid "Full Description"
msgstr "Füll Désçrïptïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
#: apps/publisher/forms.py apps/publisher/models.py
#: templates/publisher/course_run_detail/_all.html
msgid "Prerequisites"
msgstr "Préréqüïsïtés Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: apps/publisher/forms.py #: apps/publisher/forms.py
msgid "Organization Course Admin" msgid "Organization Course Admin"
msgstr "Örgänïzätïön Çöürsé Àdmïn Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#" msgstr "Örgänïzätïön Çöürsé Àdmïn Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#"
...@@ -635,14 +649,6 @@ msgstr "Çöürsé tïtlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" ...@@ -635,14 +649,6 @@ msgstr "Çöürsé tïtlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
msgid "Course number" msgid "Course number"
msgstr "Çöürsé nümßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#" msgstr "Çöürsé nümßér Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: apps/publisher/models.py templates/publisher/add_course_form.html
msgid "Brief Description"
msgstr "Brïéf Désçrïptïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмє#"
#: apps/publisher/models.py
msgid "Full Description"
msgstr "Füll Désçrïptïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
#: apps/publisher/models.py templates/publisher/course_run_detail/_all.html #: apps/publisher/models.py templates/publisher/course_run_detail/_all.html
msgid "Partner Name" msgid "Partner Name"
msgstr "Pärtnér Nämé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Pärtnér Nämé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -655,10 +661,6 @@ msgstr "Lévél Týpé Ⱡ'σяєм ιρѕυм ∂σłσ#" ...@@ -655,10 +661,6 @@ msgstr "Lévél Týpé Ⱡ'σяєм ιρѕυм ∂σłσ#"
msgid "Expected Learnings" msgid "Expected Learnings"
msgstr "Éxpéçtéd Léärnïngs Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#" msgstr "Éxpéçtéd Léärnïngs Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
#: apps/publisher/models.py templates/publisher/course_run_detail/_all.html
msgid "Prerequisites"
msgstr "Préréqüïsïtés Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: apps/publisher/models.py #: apps/publisher/models.py
msgid "Verification deadline" msgid "Verification deadline"
msgstr "Vérïfïçätïön déädlïné Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #" msgstr "Vérïfïçätïön déädlïné Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
...@@ -1522,6 +1524,7 @@ msgid "Dashboard" ...@@ -1522,6 +1524,7 @@ msgid "Dashboard"
msgstr "Däshßöärd Ⱡ'σяєм ιρѕυм ∂σł#" msgstr "Däshßöärd Ⱡ'σяєм ιρѕυм ∂σł#"
#: templates/publisher/base.html templates/publisher/courses.html #: templates/publisher/base.html templates/publisher/courses.html
#: templates/publisher/view_course_form.html
msgid "Courses" msgid "Courses"
msgstr "Çöürsés Ⱡ'σяєм ιρѕυм #" msgstr "Çöürsés Ⱡ'σяєм ιρѕυм #"
...@@ -1710,6 +1713,7 @@ msgid "Additional Notes" ...@@ -1710,6 +1713,7 @@ msgid "Additional Notes"
msgstr "Àddïtïönäl Nötés Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#" msgstr "Àddïtïönäl Nötés Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
#: templates/publisher/course_run_detail/_approval_widget.html #: templates/publisher/course_run_detail/_approval_widget.html
#: templates/publisher/view_course_form.html
msgid "EDIT" msgid "EDIT"
msgstr "ÉDÌT Ⱡ'σяєм ι#" msgstr "ÉDÌT Ⱡ'σяєм ι#"
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-01-05 12:31+0500\n" "POT-Creation-Date: 2017-01-06 09:45-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
......
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