def handle(self, *args, **options): """Automatically called when the makeresources command is given.""" BASE_PATH = "staticfiles/resources/" if not os.path.exists(BASE_PATH): os.makedirs(BASE_PATH) if options["resource_name"]: resources = [Resource.objects.get(name=options["resource_name"])] else: resources = Resource.objects.order_by("name") for resource in resources: print("Creating {}".format(resource.name)) # TODO: Import repeated in next for loop, check alternatives empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options, header_text=False) progress_bar = tqdm(combinations, ascii=True) # Create PDF for all possible combinations for combination in progress_bar: if resource.copies: combination["copies"] = settings.RESOURCE_COPY_AMOUNT requested_options = QueryDict( urlencode(combination, doseq=True)) generator = get_resource_generator(resource.generator_module, requested_options) (pdf_file, filename) = generate_resource_pdf(resource.name, generator) filename = "{}.pdf".format(filename) pdf_file_output = open(os.path.join(BASE_PATH, filename), "wb") pdf_file_output.write(pdf_file) pdf_file_output.close()
def load(self): """Load the content for resources. Raise: MissingRequiredFieldError: when no object can be found with the matching attribute. """ resources_structure = self.load_yaml_file(self.structure_file_path) for (resource_slug, resource_structure) in resources_structure.items(): try: generator_module = resource_structure["generator-module"] resource_thumbnail = resource_structure[ "thumbnail-static-path"] resource_copies = resource_structure["copies"] except KeyError: raise MissingRequiredFieldError( self.structure_file_path, ["generator-module", "thumbnail-static-path", "copies"], "Resource") resource_translations = self.get_blank_translation_dictionary() content_filename = "{}.md".format(resource_slug) content_translations = self.get_markdown_translations( content_filename) for language, content in content_translations.items(): resource_translations[language][ "content"] = content.html_string resource_translations[language]["name"] = content.title # Remove .py extension if given if generator_module.endswith(".py"): generator_module = generator_module[:-3] # Check module can be imported get_resource_generator(generator_module, QueryDict()) # Check thumbnail exists if not finders.find(resource_thumbnail): error_text = "Thumbnail image {} for resource {} could not be found." raise FileNotFoundError( error_text.format(resource_thumbnail, resource_slug)) # Check copies value is boolean if not isinstance(resource_copies, bool): raise InvalidYAMLValueError(self.structure_file_path, "copies", "'true' or 'false'") resource = Resource( slug=resource_slug, generator_module=generator_module, thumbnail_static_path=resource_thumbnail, copies=resource_copies, ) self.populate_translations(resource, resource_translations) self.mark_translation_availability( resource, required_fields=["name", "content"]) resource.save() self.log("Added Resource: {}".format(resource.name)) self.log("All resources loaded!\n")
def handle(self, *args, **options): """Automatically called when makeresourcethumbnails command is given.""" resources = Resource.objects.order_by("name") for resource in resources: base_path = BASE_PATH_TEMPLATE.format(resource=resource.slug) if not os.path.exists(base_path): os.makedirs(base_path) # TODO: Import repeated in next for loop, check alternatives empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options, header_text=False) # Create thumbnail for all possible combinations print("Creating thumbnails for {}".format(resource.name)) progress_bar = tqdm(combinations, ascii=True) for combination in progress_bar: requested_options = QueryDict( urlencode(combination, doseq=True)) generator = get_resource_generator(resource.generator_module, requested_options) thumbnail = generate_resource_thumbnail( resource.name, generator) filename = resource.slug + "-" for (key, value) in sorted(combination.items()): filename += "{}-{}-".format(key, bool_to_yes_no(value)) filename = "{}.png".format(filename[:-1]) thumbnail_file = open(os.path.join(base_path, filename), "wb") thumbnail_file.write(thumbnail) thumbnail_file.close()
def handle(self, *args, **options): """Automatically called when the makeresources command is given.""" base_path = settings.RESOURCE_GENERATION_LOCATION if options["resource_name"]: resources = [Resource.objects.get(name=options["resource_name"])] else: resources = Resource.objects.order_by("name") if options["resource_language"]: generation_languages = [options["resource_language"]] else: generation_languages = [] for language_code, _ in settings.LANGUAGES: if language_code not in settings.INCONTEXT_L10N_PSEUDOLANGUAGES: generation_languages.append(language_code) for resource in resources: print("Creating {}".format(resource.name)) # TODO: Import repeated in next for loop, check alternatives empty_generator = get_resource_generator(resource.generator_module) if not all([ isinstance(option, EnumResourceParameter) for option in empty_generator.get_options().values() ]): raise TypeError( "Only EnumResourceParameters are supported for pre-generation" ) valid_options = { option.name: list(option.valid_values.keys()) for option in empty_generator.get_options().values() } combinations = resource_valid_configurations(valid_options) progress_bar = tqdm(combinations, ascii=True) # Create PDF for all possible combinations for combination in progress_bar: for language_code in generation_languages: print(" - Creating PDF in '{}'".format(language_code)) with translation.override(language_code): if resource.copies: combination[ "copies"] = settings.RESOURCE_COPY_AMOUNT requested_options = QueryDict( urlencode(combination, doseq=True)) generator = get_resource_generator( resource.generator_module, requested_options) (pdf_file, filename) = generator.pdf(resource.name) pdf_directory = os.path.join(base_path, resource.slug, language_code) if not os.path.exists(pdf_directory): os.makedirs(pdf_directory) filename = "{}.pdf".format(filename) pdf_file_output = open( os.path.join(pdf_directory, filename), "wb") pdf_file_output.write(pdf_file) pdf_file_output.close()
def load(self): """Load the content for resources. Raise: MissingRequiredFieldError: when no object can be found with the matching attribute. """ resources_structure = self.load_yaml_file(self.structure_file_path) for (resource_slug, resource_structure) in resources_structure.items(): try: resource_name = resource_structure["name"] resource_template = resource_structure["webpage-template"] generator_module = resource_structure["generator-module"] resource_thumbnail = resource_structure[ "thumbnail-static-path"] resource_copies = resource_structure["copies"] except KeyError: raise MissingRequiredFieldError(self.structure_file_path, [ "name", "webpage-template", "generator-module", "thumbnail-static-path", "copies" ], "Resource") # Check resource template file exists open(os.path.join("templates", resource_template), encoding="UTF-8") # Remove .py extension if given if generator_module.endswith(".py"): generator_module = generator_module[:-3] # Check module can be imported get_resource_generator(generator_module, QueryDict()) # Check thumbnail exists if not finders.find(resource_thumbnail): raise FileNotFoundError # Check copies value is boolean if not isinstance(resource_copies, bool): raise InvalidConfigValueError(self.structure_file_path, "copies", "'true' or 'false'") resource = Resource( slug=resource_slug, name=resource_name, webpage_template=resource_template, generator_module=generator_module, thumbnail_static_path=resource_thumbnail, copies=resource_copies, ) resource.save() self.log("Added Resource: {}".format(resource.name)) self.log("All resources loaded!\n")
def handle(self, *args, **options): """Automatically called when makeresourcethumbnails command is given.""" resources = Resource.objects.order_by("name") if options.get("all_languages"): languages = settings.DEFAULT_LANGUAGES else: languages = [("en", "")] for language_code, _ in languages: with translation.override(language_code): print("Creating thumbnails for language '{}''".format( language_code)) for resource in resources: base_path = BASE_PATH_TEMPLATE.format( resource=resource.slug, language=language_code) if not os.path.exists(base_path): os.makedirs(base_path) # TODO: Import repeated in next for loop, check alternatives empty_generator = get_resource_generator( resource.generator_module) if not all([ isinstance(option, EnumResourceParameter) for option in empty_generator.get_options().values() ]): raise TypeError( "Only EnumResourceParameters are supported for pre-generation" ) valid_options = { option.name: list(option.valid_values.keys()) for option in empty_generator.get_options().values() } combinations = resource_valid_configurations(valid_options) # Create thumbnail for all possible combinations print("Creating thumbnails for {}".format(resource.name)) progress_bar = tqdm(combinations, ascii=True) for combination in progress_bar: requested_options = QueryDict( urlencode(combination, doseq=True)) generator = get_resource_generator( resource.generator_module, requested_options) filename = resource.slug + "-" for (key, value) in sorted(combination.items()): filename += "{}-{}-".format( key, bool_to_yes_no(value)) filename = "{}.png".format(filename[:-1]) thumbnail_file_path = os.path.join(base_path, filename) generator.save_thumbnail(resource.name, thumbnail_file_path)
def test_piano_keys_resource_generation_valid_configurations(self): resource = self.test_data.create_resource( "piano-keys", "Piano Keys", "resources/piano-keys.html", "PianoKeysResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url = base_url + query_string(combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) subtitle = "{} highlight - {}".format( bool_to_yes_no(combination["highlight"]), combination["paper_size"], ) self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Piano Keys ({subtitle}).pdf"'. format(subtitle=subtitle)) print("ok")
def generate_resource(request, resource_slug): """View for generated PDF of a specific resource. Args: request: HttpRequest object. resource_slug: The slug of the requested resource. Returns: HTML response containing PDF of resource, 404 if not found. """ resource = get_object_or_404(Resource, slug=resource_slug) if not request.GET: raise Http404("No parameters given for resource generation.") try: generator = get_resource_generator(resource.generator_module, request.GET) except (QueryParameterMissingError, QueryParameterInvalidError, QueryParameterMultipleValuesError) as e: raise Http404(e) from e # TODO: Weasyprint handling in production # TODO: Add creation of PDF as job to job queue if settings.DJANGO_PRODUCTION: # Return cached static PDF file of resource. # Currently developing system for dynamically rendering # custom PDFs on request (https://github.com/uccser/render). return resource_pdf_cache(resource, generator) else: (pdf_file, filename) = generator.pdf(resource.name) response = HttpResponse(pdf_file, content_type="application/pdf") response["Content-Disposition"] = RESPONSE_CONTENT_DISPOSITION.format( filename=quote(filename)) return response
def resource(request, resource_slug): """View for a specific resource in the resources application. Args: request: HttpRequest object. resource_slug: The slug of the requested resource. Returns: HTML response of webpage, 404 if not found. """ resource = get_object_or_404(Resource, slug=resource_slug) context = dict() generator = get_resource_generator(resource.generator_module) context["options_html"] = get_options_html(generator.get_options(), generator.get_local_options(), request.GET) context["resource"] = resource context["debug"] = settings.DEBUG if settings.DJANGO_PRODUCTION: resource_language = get_language() if resource_language in settings.INCONTEXT_L10N_PSEUDOLANGUAGES: resource_language = "en" else: resource_language = "en" context["resource_thumbnail_base"] = join(settings.STATIC_URL, "img/resources/", resource.slug, "thumbnails", resource_language, "") context["grouped_lessons"] = group_lessons_by_age(resource.lessons.all()) context["copies_amount"] = settings.RESOURCE_COPY_AMOUNT if resource.thumbnail_static_path: context["thumbnail"] = resource.thumbnail_static_path return render(request, "resources/resource.html", context)
def create_resource_pdf(self, resource, combination, language_code, base_path): """Create a given resource PDF. Args: resource (Resource): Resource to create. combination (dict): Specific option attributes for this resource. language_code (str): Code for language. base_path (str): Base path for outputting P """ print(" - Creating PDF in '{}'".format(language_code)) with translation.override(language_code): if resource.copies: combination["copies"] = settings.RESOURCE_COPY_AMOUNT requested_options = QueryDict(urlencode(combination, doseq=True)) generator = get_resource_generator(resource.generator_module, requested_options) (pdf_file, filename) = generator.pdf(resource.name) pdf_directory = os.path.join(base_path, resource.slug, language_code) if not os.path.exists(pdf_directory): os.makedirs(pdf_directory) filename = "{}.pdf".format(filename) pdf_file_output = open(os.path.join(pdf_directory, filename), "wb") pdf_file_output.write(pdf_file) pdf_file_output.close()
def resource(request, resource_slug): """View for a specific resource in the resources application. Args: request: HttpRequest object. resource_slug: The slug of the requested resource. Returns: HTML response of webpage, 404 if not found. """ resource = get_object_or_404(Resource, slug=resource_slug) context = dict() generator = get_resource_generator(resource.generator_module) context["options_html"] = get_options_html(generator.get_options(), generator.get_local_options(), request.GET) context["resource"] = resource context["debug"] = settings.DEBUG context[ "resource_thumbnail_base"] = "{}img/resources/{}/thumbnails/".format( settings.STATIC_URL, resource.slug) context["grouped_lessons"] = group_lessons_by_age(resource.lessons.all()) context["copies_amount"] = settings.RESOURCE_COPY_AMOUNT if resource.thumbnail_static_path: context["thumbnail"] = resource.thumbnail_static_path return render(request, "resources/resource.html", context)
def test_sorting_network_cards_resource_generation_valid_configurations( self): resource = self.test_data.create_resource( "sorting-network-cards", "Sorting Network Cards", "resources/sorting-network-cards.html", "SortingNetworkCardsResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url = base_url + query_string(combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) subtitle = "{} - {}".format( combination["type"].replace("_", " "), combination["paper_size"], ) self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Sorting Network Cards ({subtitle}).pdf"' .format(subtitle=subtitle)) print("ok")
def test_grid_resource_generation_valid_configurations(self): resource = self.test_data.create_resource( "grid", "Grid", "resources/grid.html", "GridResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url = base_url + query_string(combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) subtitle = combination["paper_size"] self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Grid ({subtitle}).pdf"'.format( subtitle=subtitle)) print("ok")
def test_treasure_hunt_resource_generation_valid_configurations(self): resource = self.test_data.create_resource( "treasure-hunt", "Treasure Hunt", "resources/treasure-hunt.html", "TreasureHuntResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options ) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url = base_url + query_string(combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) if combination["prefilled_values"] == "blank": range_text = "blank" else: range_min = 0 if combination["prefilled_values"] == "easy": range_max = 99 elif combination["prefilled_values"] == "medium": range_max = 999 elif combination["prefilled_values"] == "hard": range_max = 9999 SUBTITLE_TEMPLATE = "{} - {} to {}" number_order_text = combination["number_order"].title() range_text = SUBTITLE_TEMPLATE.format(number_order_text, range_min, range_max) if combination["art"] == "colour": art_style_text = "full colour" else: art_style_text = "black and white" if combination["instructions"]: instructions_text = "with instructions" else: instructions_text = "without instructions" subtitle = "{} - {} - {} - {}".format( range_text, art_style_text, instructions_text, combination["paper_size"] ) self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Treasure Hunt ({subtitle}).pdf"'.format(subtitle=subtitle) ) print("ok")
def test_generate_resources_copy_valid_generator(self): resource = self.test_data.create_resource( "grid", "Grid", "resources/grid.html", "GridResourceGenerator", ) query = QueryDict("paper_size=a4") generator = get_resource_generator(resource.generator_module, query) copy = generate_resource_copy(generator) self.assertEqual(len(copy), 1)
def test_resources_cache_valid_resource(self): resource = self.test_data.create_resource( "grid", "Grid", "resources/grid.html", "GridResourceGenerator", ) query = QueryDict("paper_size=a4") generator = get_resource_generator(resource.generator_module, query) response = resource_pdf_cache(resource.name, generator) self.assertEqual(response.status_code, HTTPStatus.FOUND) self.assertEqual(response.url, "/staticfiles/resources/Grid%20(a4).pdf")
def get_thumbnail_static_path_for_resource(resource): """Return static path to thumbnail for resource. Args: resource (Resource): Resource to get thumbnail for. Returns: String of static path to thumbnail. """ generator = get_resource_generator(resource.generator_module) thumbnail = join( get_thumbnail_base(resource.slug), get_thumbnail_filename(resource.slug, generator.get_option_defaults())) return thumbnail
def test_binary_cards_small_resource_generation_valid_configurations(self): resource = self.test_data.create_resource( "binary-cards", "Binary Cards (small)", "resources/binary-cards-small.html", "BinaryCardsSmallResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url_combination = {} for parameter in combination: if combination[parameter] is True: url_combination[parameter] = "yes" elif combination[parameter] is False: url_combination[parameter] = "no" else: url_combination[parameter] = combination[parameter] url = base_url + query_string(url_combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) if combination["dot_counts"]: display_numbers_text = "with dot counts" else: display_numbers_text = "without dot counts" if combination["black_back"]: black_back_text = "with black back" else: black_back_text = "without black back" subtitle = "{} bits - {} - {} - {}".format( combination["number_bits"], display_numbers_text, black_back_text, combination["paper_size"], ) self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Binary Cards (small) ({subtitle}).pdf"' .format(subtitle=subtitle)) print("ok")
def test_searching_cards_resource_generation_valid_configurations(self): resource = self.test_data.create_resource( "searching-cards", "Searching Cards", "resources/searching-cards.html", "SearchingCardsResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url = base_url + query_string(combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) if combination["max_number"] == "blank": range_text = "blank" elif combination["max_number"] == "cards": range_text = "0 to {}".format(combination["number_cards"]) else: range_text = "0 to {}".format(combination["max_number"]) if combination["help_sheet"]: help_text = "with helper sheet" else: help_text = "without helper sheet" subtitle = "{} cards - {} - {} - {}".format( combination["number_cards"], range_text, help_text, combination["paper_size"], ) self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Searching Cards ({subtitle}).pdf"' .format(subtitle=subtitle)) print("ok")
def handle(self, *args, **options): """Automatically called when the makeresources command is given.""" base_path = settings.RESOURCE_GENERATION_LOCATION if options["resource_name"]: resources = [Resource.objects.get(name=options["resource_name"])] else: resources = Resource.objects.order_by("name") if options["resource_language"]: generation_languages = [options["resource_language"]] else: generation_languages = [] for language_code, _ in settings.LANGUAGES: if language_code not in settings.INCONTEXT_L10N_PSEUDOLANGUAGES: generation_languages.append(language_code) for resource in resources: print("Creating {}".format(resource.name)) # TODO: Import repeated in next for loop, check alternatives empty_generator = get_resource_generator(resource.generator_module) if not all([ isinstance(option, EnumResourceParameter) for option in empty_generator.get_options().values() ]): raise TypeError( "Only EnumResourceParameters are supported for pre-generation" ) valid_options = { option.name: list(option.valid_values.keys()) for option in empty_generator.get_options().values() } combinations = resource_valid_configurations(valid_options) # TODO: Create PDFs in parallel # Create PDF for all possible combinations for combination in combinations: for language_code in generation_languages: self.create_resource_pdf(resource, combination, language_code, base_path)
def test_binary_windows_resource_generation_valid_configurations(self): resource = self.test_data.create_resource( "binary-windows", "Binary Windows", "resources/binary-windows.html", "BinaryWindowsResourceGenerator", ) kwargs = { "resource_slug": resource.slug, } base_url = reverse("resources:generate", kwargs=kwargs) empty_generator = get_resource_generator(resource.generator_module) combinations = resource_valid_test_configurations( empty_generator.valid_options) print() for combination in combinations: print(" - Testing combination: {} ... ".format(combination), end="") url = base_url + query_string(combination) response = self.client.get(url) self.assertEqual(HTTPStatus.OK, response.status_code) if combination["dot_counts"]: count_text = "with dot counts" else: count_text = "without dot counts" TEMPLATE = "{} bits - {} - {} - {}" subtitle = TEMPLATE.format( combination["number_bits"], combination["value_type"], count_text, combination["paper_size"], ) self.assertEqual( response.get("Content-Disposition"), 'attachment; filename="Resource Binary Windows ({subtitle}).pdf"' .format(subtitle=subtitle)) print("ok")