Repository: uccser/cs-unplugged

Issue:
Add Resource: Modulo 10 clock
Artwork here: https://drive.google.com/file/d/0B-SN1PgPBbCYR3o1ZEpLNGQyc0U/view



Discussion:
https://drive.google.com/open?id=0B-SN1PgPBbCYVDVSa2JpVUQtR1U

Resolving patch:
diff --git a/csunplugged/resources/views/modulo_clock.py b/csunplugged/resources/views/modulo_clock.py
new file mode 100644
--- /dev/null
+++ b/csunplugged/resources/views/modulo_clock.py
@@ -0,0 +1,86 @@
+"""Module for generating Binary Cards resource."""
+
+from math import pi, sin, cos
+from PIL import Image, ImageDraw, ImageFont
+from utils.retrieve_query_parameter import retrieve_query_parameter
+
+
+def resource_image(request, resource):
+    """Create a image for Binary Cards resource.
+
+    Args:
+        request: HTTP request object (QueryDict).
+        resource: Object of resource data (Resource).
+
+    Returns:
+        A list of Pillow image objects.
+    """
+    parameter_options = valid_options()
+    modulo_number = int(retrieve_query_parameter(request, "modulo_number", parameter_options["modulo_number"]))
+    if modulo_number == 2:
+        image_path = "static/img/resources/modulo-clock/modulo-clock-2.png"
+    elif modulo_number == 10:
+        image_path = "static/img/resources/modulo-clock/modulo-clock-10.png"
+    image = Image.open(image_path)
+    draw = ImageDraw.Draw(image)
+
+    font_size = 150
+    font_path = "static/fonts/PatrickHand-Regular.ttf"
+    font = ImageFont.truetype(font_path, font_size)
+
+    x_offset = 75
+    y_offset = 225
+    radius = 750
+    x_center = (image.width - x_offset) / 2
+    y_center = (image.height - y_offset) / 2
+    start_angle = (2 * pi) / modulo_number
+
+    for num in range(0, modulo_number):
+
+        text = str(num)
+
+        angle = start_angle * (num - 2.5)
+        x_coord = radius * cos(angle) + x_center
+        y_coord = radius * sin(angle) + y_center
+
+        draw.text(
+            (x_coord, y_coord),
+            text,
+            font=font,
+            fill="#000"
+        )
+
+    return image
+
+
+def subtitle(request, resource):
+    """Return the subtitle string of the resource.
+
+    Used after the resource name in the filename, and
+    also on the resource image.
+
+    Args:
+        request: HTTP request object (QueryDict).
+        resource: Object of resource data (Resource).
+
+    Returns:
+        Text for subtitle (str).
+    """
+    return "{} - {}".format(
+        retrieve_query_parameter(request, "modulo_number"),
+        retrieve_query_parameter(request, "paper_size")
+    )
+
+
+def valid_options():
+    """Provide dictionary of all valid parameters.
+
+    This excludes the header text parameter.
+
+    Returns:
+        All valid options (dict).
+    """
+    return {
+        "modulo_number": ["2", "10"],
+        "paper_size": ["a4", "letter"]
+    }


Test patch:
diff --git a/csunplugged/tests/resources/urls/test_modulo_clock.py b/csunplugged/tests/resources/urls/test_modulo_clock.py
new file mode 100644
--- /dev/null
+++ b/csunplugged/tests/resources/urls/test_modulo_clock.py
@@ -0,0 +1,19 @@
+from tests.BaseTestWithDB import BaseTestWithDB
+from django.urls import reverse
+
+
+class ModuloClockResourceURLTest(BaseTestWithDB):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.language = "en"
+        self.RESOURCE_SLUG = "modulo-clock"
+        self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG}
+
+    def test_valid_modulo_clock_resource_url(self):
+        url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS)
+        self.assertEqual(url, "/en/resources/modulo-clock/")
+
+    def test_valid_modulo_clock_resource_generate_url(self):
+        url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS)
+        self.assertEqual(url, "/en/resources/modulo-clock/generate")
diff --git a/csunplugged/tests/resources/views/test_modulo_clock.py b/csunplugged/tests/resources/views/test_modulo_clock.py
new file mode 100644
--- /dev/null
+++ b/csunplugged/tests/resources/views/test_modulo_clock.py
@@ -0,0 +1,290 @@
+from django.urls import reverse
+
+from tests.BaseTestWithDB import BaseTestWithDB
+from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator
+
+from utils.create_query_string import query_string
+
+
+class ModuloClockResourceViewTest(BaseTestWithDB):
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.test_data = ResourcesTestDataGenerator()
+        self.language = "en"
+
+    def test_modulo_clock_resource_form_view(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:resource", kwargs=kwargs)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+
+    def test_modulo_clock_resource_generation_2_a4_no_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "2",
+            "paper_size": "a4",
+            "header_text": ""
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (2 - a4).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_2_letter_no_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "2",
+            "paper_size": "letter",
+            "header_text": ""
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (2 - letter).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_10_a4_no_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "10",
+            "paper_size": "a4",
+            "header_text": ""
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (10 - a4).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_10_letter_no_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "10",
+            "paper_size": "letter",
+            "header_text": ""
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (10 - letter).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_2_a4_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "2",
+            "paper_size": "a4",
+            "header_text": "Modulo Clock"
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (2 - a4).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_2_letter_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "2",
+            "paper_size": "letter",
+            "header_text": ""
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (2 - letter).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_10_a4_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "10",
+            "paper_size": "a4",
+            "header_text": "Modulo Clock"
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (10 - a4).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_10_letter_header_text(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "10",
+            "paper_size": "letter",
+            "header_text": "Modulo Clock"
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (10 - letter).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )
+
+    def test_modulo_clock_resource_generation_missing_modulo_number_parameter(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "paper_size": "a4",
+            "header_text": "",
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(404, response.status_code)
+
+    def test_modulo_clock_resource_generation_missing_paper_size_parameter(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "2",
+            "header_text": "",
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(404, response.status_code)
+
+    def test_modulo_clock_resource_generation_missing_header_text_parameter(self):
+        resource = self.test_data.create_resource(
+            "modulo-clock",
+            "Modulo Clock",
+            "resources/modulo-clock.html",
+            "modulo_clock.py",
+        )
+        kwargs = {
+            "resource_slug": resource.slug,
+        }
+        url = reverse("resources:generate", kwargs=kwargs)
+        get_parameters = {
+            "modulo_number": "2",
+            "paper_size": "a4",
+        }
+        url += query_string(get_parameters)
+        response = self.client.get(url)
+        self.assertEqual(200, response.status_code)
+        filename = "Resource Modulo Clock (2 - a4).pdf"
+        self.assertEqual(
+            response.get("Content-Disposition"),
+            'attachment; filename="{}"'.format(filename)
+        )