asdf asdf asdf asdf 22
+{%- endblock %} + + + diff --git a/docs-src/_theme/sphinx_rtd_theme/sphinx_rtd_theme/search.html b/docs-src/_theme/sphinx_rtd_theme/sphinx_rtd_theme/search.html new file mode 100644 index 0000000..e3aa9b5 --- /dev/null +++ b/docs-src/_theme/sphinx_rtd_theme/sphinx_rtd_theme/search.html @@ -0,0 +1,50 @@ +{# + basic/search.html + ~~~~~~~~~~~~~~~~~ + + Template for the search page. + + :copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- extends "layout.html" %} +{% set title = _('Search') %} +{% set script_files = script_files + ['_static/searchtools.js'] %} +{% block footer %} + + {# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + + {{ super() }} +{% endblock %} +{% block body %} + + + {% if search_performed %} +{{ _('Search Results') }}
+ {% if not search_results %} +{{ _('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.') }}
+ {% endif %} + {% endif %} +-
+ {% for href, caption, context in search_results %}
+
-
+ {{ caption }}
+
{{ context|e }}
+
+ {% endfor %}
+
-
+
- {{ _('Versions') }} + {% for slug, url in versions %} +
- {{ slug }} + {% endfor %} +
-
+
- {{ _('Downloads') }} + {% for type, url in downloads %} +
- {{ type }} + {% endfor %} +
-
+
- {{ _('On Read the Docs') }} +
- + {{ _('Project Home') }} + +
- + {{ _('Builds') }} + +
+ {% trans %}Free document hosting provided by Read the Docs.{% endtrans %} + +
All modules for which code is available
+ + +Source code for markdownx.exceptions
+from django.utils.translation import ugettext_lazy as _
+from django.forms import ValidationError
+
+
+[docs]class MarkdownxImageUploadError(ValidationError):
+ """
+
+ """
+
+ @staticmethod
+[docs] def not_uploaded():
+ """
+ No file is available to upload.
+
+ :return:
+ :rtype:
+ """
+ return MarkdownxImageUploadError(_('No files have been uploaded.'))
+
+ @staticmethod
+[docs] def unsupported_format():
+ """
+ The file is of a format not defined in :guilabel:`settings.py`
+ or if default, in :guilabel:`markdownx/settings.py`.
+
+ :return:
+ :rtype:
+ """
+ return MarkdownxImageUploadError(_('File type is not supported.'))
+
+ @staticmethod
+[docs] def invalid_size(current, expected):
+ """
+ The file is larger in size that the maximum allow in :guilabel:`settings.py` (or the default).
+
+ :param current:
+ :type current:
+ :param expected:
+ :type expected:
+ :return:
+ :rtype:
+ """
+ from django.template.defaultfilters import filesizeformat
+
+ return MarkdownxImageUploadError(
+ _('Please keep file size under %(max)s. Current file size: %(current)s.') % {
+ 'max': filesizeformat(expected),
+ 'current': filesizeformat(current)
+ }
+ )
+Source code for markdownx.forms
+# Python internal library.
+from os import path, SEEK_END
+from uuid import uuid4
+from collections import namedtuple
+
+# Django library.
+from django import forms
+from django.utils.six import BytesIO
+from django.core.files.storage import default_storage
+from django.core.files.uploadedfile import InMemoryUploadedFile
+
+# Internal.
+from .utils import scale_and_crop, has_javascript
+from .exceptions import MarkdownxImageUploadError
+
+from .settings import (
+ MARKDOWNX_IMAGE_MAX_SIZE,
+ MARKDOWNX_MEDIA_PATH,
+ MARKDOWNX_UPLOAD_CONTENT_TYPES,
+ MARKDOWNX_UPLOAD_MAX_SIZE,
+ MARKDOWNX_SVG_JAVASCRIPT_PROTECTION
+)
+
+
+[docs]class ImageForm(forms.Form):
+ """
+ Used for the handling of images uploaded using the editor through :guilabel:`AJAX`.
+ """
+
+ image = forms.FileField()
+
+ # Separately defined as it needs to be
+ # processed a text file rather than image.
+ _SVG_TYPE = 'image/svg+xml'
+
+[docs] def save(self, commit=True):
+ """
+ Saves the uploaded image in the designated location.
+
+ If image type is not SVG, a byteIO of image content_type is created and
+ subsequently save; otherwise, the SVG is saved in its existing ``charset``
+ as an ``image/svg+xml``.
+
+ *Note*: The dimension of image files (excluding SVG) are set using ``PIL``.
+
+ :param commit: If ``True``, the file is saved to the disk;
+ otherwise, it is held in the memory.
+ :type commit: bool
+ :return: An instance of saved image if ``commit is True``,
+ else ``namedtuple(path, image)``.
+ :rtype: bool, namedtuple
+ """
+ image = self.files.get('image')
+ content_type = image.content_type
+ file_name = image.name
+ image_extension = content_type.split('/')[-1].upper()
+ image_size = getattr(image, '_size')
+
+ if content_type.lower() != self._SVG_TYPE:
+ # Processing the raster graphic image.
+ # Note that vector graphics in SVG format
+ # do not require additional processing and
+ # may be stored as uploaded.
+ image = self._process_raster(image, image_extension)
+ image_size = image.tell()
+
+ # Processed file (or the actual file in the case of SVG) is now
+ # saved in the memory as a Django object.
+ uploaded_image = InMemoryUploadedFile(
+ file=image,
+ field_name=None,
+ name=file_name,
+ content_type=content_type,
+ size=image_size,
+ charset=None
+ )
+
+ return self._save(uploaded_image, file_name, commit)
+
+ def _save(self, image, file_name, commit):
+ """
+ Final saving process, called internally after processing tasks are complete.
+
+ :param image: Prepared image
+ :type image: django.core.files.uploadedfile.InMemoryUploadedFile
+ :param file_name: Name of the file using which the image is to be saved.
+ :type file_name: str
+ :param commit: If ``True``, the image is saved onto the disk.
+ :type commit: bool
+ :return: URL of the uploaded image ``commit=True``, otherwise a namedtuple of ``(path, image)`` where
+ ``path`` is the absolute path generated for saving the file, and ``image`` is the prepared
+ image.
+ :rtype: str, namedtuple
+ """
+ # Defining a universally unique name for the file
+ # to be saved on the disk.
+ unique_file_name = self.get_unique_file_name(file_name)
+ full_path = path.join(MARKDOWNX_MEDIA_PATH, unique_file_name)
+
+ if commit:
+ default_storage.save(full_path, image)
+ return default_storage.url(full_path)
+
+ # If `commit is False`, return the path and in-memory image.
+ image_data = namedtuple('image_data', ['path', 'image'])
+ return image_data(path=full_path, image=image)
+
+ @staticmethod
+ def _process_raster(image, extension):
+ """
+ Processing of raster graphic image using Python Imaging Library (PIL).
+
+ This is where raster graphics are processed to the specifications
+ as defined in ``settings.py``.
+
+ *Note*: The file needs to be uploaded and saved temporarily in the
+ memory to enable processing tasks using Python Imaging Library (PIL)
+ to take place and subsequently retained until written onto the disk.
+
+ :param image: Non-SVG image as processed by Django.
+ :type image: django.forms.BaseForm.file
+ :param extension: Image extension (e.g.: png, jpg, gif)
+ :type extension: str
+ :return: The image object ready to be written into a file.
+ :rtype: BytesIO
+ """
+ thumb_io = BytesIO()
+ preped_image = scale_and_crop(image, **MARKDOWNX_IMAGE_MAX_SIZE)
+ preped_image.save(thumb_io, extension)
+ thumb_io.seek(0, SEEK_END)
+ return thumb_io
+
+ @staticmethod
+[docs] def get_unique_file_name(file_name):
+ """
+ Generates a universally unique ID using Python ``UUID`` and attaches the extension of file name to it.
+
+ :param file_name: Name of the uploaded file, including the extension.
+ :type file_name: str
+ :return: Universally unique ID, ending with the extension extracted from ``file_name``.
+ :rtype: str
+ """
+ extension = 1
+ extension_dot_index = 1
+
+ file_name = "{unique_name}.{extension}".format(
+ unique_name=uuid4(),
+ extension=path.splitext(file_name)[extension][extension_dot_index:]
+ )
+ return file_name
+
+[docs] def clean(self):
+ """
+ Checks the upload against allowed extensions and maximum size.
+
+ :return: Upload
+ """
+ upload = self.cleaned_data.get('image')
+
+ # -----------------------------------------------
+ # See comments in `self._error_templates` for
+ # additional information on each error.
+ # -----------------------------------------------
+ if not upload:
+ raise MarkdownxImageUploadError.not_uploaded()
+
+ content_type = upload.content_type
+ file_size = getattr(upload, '_size')
+
+ if content_type not in MARKDOWNX_UPLOAD_CONTENT_TYPES:
+
+ raise MarkdownxImageUploadError.unsupported_format()
+
+ elif file_size > MARKDOWNX_UPLOAD_MAX_SIZE:
+
+ raise MarkdownxImageUploadError.invalid_size(
+ current=file_size,
+ expected=MARKDOWNX_UPLOAD_MAX_SIZE
+ )
+
+ elif (content_type.lower() != self._SVG_TYPE
+ and MARKDOWNX_SVG_JAVASCRIPT_PROTECTION
+ and has_javascript(upload.read())):
+
+ raise MarkdownxImageUploadError(
+ 'Failed security monitoring: SVG file contains JavaScript.'
+ )
+
+ return upload
+Source code for markdownx.utils
+from markdown import markdown
+
+from PIL import Image
+
+from .settings import MARKDOWNX_MARKDOWN_EXTENSIONS, MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS
+
+
+[docs]def markdownify(content):
+ """
+
+ :param content:
+ :type content:
+ :return:
+ :rtype:
+ """
+ md = markdown(
+ text=content,
+ extensions=MARKDOWNX_MARKDOWN_EXTENSIONS,
+ extension_configs=MARKDOWNX_MARKDOWN_EXTENSION_CONFIGS
+ )
+ return md
+
+
+[docs]def scale_and_crop(image, size, crop=False, upscale=False, quality=None):
+ """
+
+ :param image:
+ :type image:
+ :param size:
+ :type size:
+ :param crop:
+ :type crop:
+ :param upscale:
+ :type upscale:
+ :param quality:
+ :type quality:
+ :return:
+ :rtype:
+ """
+ # ToDo: Possible IO/Runtime exceptions need to handled, and `finally` the file needs to be closed.
+
+ # Open image and store format/metadata.
+ image.open()
+ im = Image.open(image)
+ im_format, im_info = im.format, im.info
+ if quality:
+ im_info['quality'] = quality
+
+ # Force PIL to load image data.
+ im.load()
+
+ source_x, source_y = map(float, im.size)
+ target_x, target_y = map(float, size)
+
+ if crop or not target_x or not target_y:
+ scale = max(target_x / source_x, target_y / source_y)
+ else:
+ scale = min(target_x / source_x, target_y / source_y)
+
+ # Handle one-dimensional targets.
+ if not target_x:
+ target_x = source_x * scale
+ elif not target_y:
+ target_y = source_y * scale
+
+ if scale < 1.0 or (scale > 1.0 and upscale):
+ im = im.resize(
+ (int(source_x * scale), int(source_y * scale)),
+ resample=Image.ANTIALIAS
+ )
+
+ if crop:
+ # Use integer values now.
+ source_x, source_y = im.size
+ # Difference between new image size and requested size.
+ diff_x = int(source_x - min(source_x, target_x))
+ diff_y = int(source_y - min(source_y, target_y))
+
+ if diff_x or diff_y:
+ # Center cropping (default).
+ halfdiff_x, halfdiff_y = diff_x // 2, diff_y // 2
+ box = [
+ halfdiff_x,
+ halfdiff_y,
+ min(source_x, int(target_x) + halfdiff_x),
+ min(source_y, int(target_y) + halfdiff_y)
+ ]
+
+ # Finally, crop the image!
+ im = im.crop(box)
+
+ # Close image and replace format/metadata, as PIL blows this away.
+ im.format, im.info = im_format, im_info
+ image.close()
+ return im
+
+
+[docs]def has_javascript(data):
+ """
+
+ :param data: Contents to be monitored for JavaScript injection.
+ :type data: str
+ :return: ``True`` if **data** contains JavaScript tag(s), otherwise ``False``.
+ :rtype: bool
+ """
+ from re import search, IGNORECASE, MULTILINE
+
+ # ------------------------------------------------
+ # Handles JavaScript nodes and stringified nodes.
+ # ------------------------------------------------
+ pattern = (
+ r'(<\s*\bscript\b.*>.*)|'
+ r'(.*\bif\b\s*\(.?={2,3}.*\))|'
+ r'(.*\bfor\b\s*\(.*\))'
+ )
+
+ found = search(
+ pattern=pattern,
+ string=data,
+ flags=IGNORECASE | MULTILINE
+ )
+
+ if found is not None:
+ return True
+
+ # ------------------------------------------------
+ # Handles JavaScript injection into attributes
+ # for element creation.
+ # ------------------------------------------------
+ from xml.etree.ElementTree import fromstring
+
+ parsed_xml = (
+ (attribute, value)
+ for elm in fromstring(data).iter()
+ for attribute, value in elm.attrib.items()
+ )
+
+ for key, val in parsed_xml:
+ if '"' in val or "'" in val:
+ return True
+
+ # It is (hopefully) safe.
+ return False
+Source code for markdownx.views
+from django.http import HttpResponse, JsonResponse
+from django.utils.module_loading import import_string
+from django.views.generic.edit import View, FormView, BaseFormView
+
+from .forms import ImageForm
+from .settings import MARKDOWNX_MARKDOWNIFY_FUNCTION
+
+
+[docs]class MarkdownifyView(View):
+ """
+ Conversion of Markdown to HTML.
+ """
+
+[docs] def post(self, request, *args, **kwargs):
+ """
+ Handling of the conversion from Markdown to HTML using the conversion
+ function in settings under ``MARKDOWNX_MARKDOWNIFY_FUNCTION``.
+
+ :param request: HTTP request.
+ :param args: Default Django POST arguments.
+ :param kwargs: Default Django POST keyword arguments.
+ :return: HTTP response
+ :rtype: django.http.HttpResponse
+ """
+ markdownify = import_string(MARKDOWNX_MARKDOWNIFY_FUNCTION)
+ return HttpResponse(markdownify(request.POST['content']))
+
+
+[docs]class ImageUploadView(BaseFormView):
+ """
+ Handling requests for uploading images.
+ """
+
+ # template_name = "dummy.html"
+ form_class = ImageForm
+ success_url = '/'
+
+[docs] def form_invalid(self, form):
+ """
+ Handling of invalid form events.
+
+ :param form: Django form instance.
+ :type form: django.forms.Form
+ :return: JSON response with the HTTP-400 error message for AJAX requests
+ and the default response for HTTP requests.
+ :rtype: django.http.JsonResponse, django.http.HttpResponse
+ """
+ response = super(ImageUploadView, self).form_invalid(form)
+
+ if self.request.is_ajax():
+ return JsonResponse(form.errors, status=400)
+
+ return response
+
+[docs] def form_valid(self, form):
+ """
+ If the form is valid, the contents are saved.
+
+ If the **POST** request is AJAX (image uploads), a JSON response will be
+ produced containing the Markdown encoded image insertion tag with the URL
+ using which the uploaded image may be accessed.
+
+ JSON response would be as follows:
+
+ .. code-block:: json
+
+ { image_code: "" }
+
+ :param form: Django form instance.
+ :type form: django.forms.Form
+ :return: JSON encoded Markdown tag for AJAX requests, and an appropriate
+ response for HTTP requests.
+ :rtype: django.http.JsonResponse, django.http.HttpResponse
+ """
+ response = super(ImageUploadView, self).form_valid(form)
+
+ if self.request.is_ajax():
+ image_path = form.save(commit=True)
+ image_code = ''.format(image_path)
+ return JsonResponse({'image_code': image_code})
+
+ return response
+