<commit_before>#!/usr/bin/env python

from __future__ import unicode_literals
import os, sys
import six

try:
    from django.conf import settings
except ImportError:
    print("Django has not been installed.")
    sys.exit(0)

if not settings.configured:
    settings.configure(
        INSTALLED_APPS=["jstemplate"],
        DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3"}})

def runtests(*test_args):
    if not test_args:
        test_args = ["jstemplate"]

    parent = os.path.dirname(os.path.abspath(__file__))
    sys.path.insert(0, parent)

    try:
        from django.test.simple import DjangoTestSuiteRunner
        def run_tests(test_args, verbosity, interactive):
            runner = DjangoTestSuiteRunner(
                verbosity=verbosity, interactive=interactive, failfast=False)
            return runner.run_tests(test_args)
    except ImportError:
        # for Django versions that don't have DjangoTestSuiteRunner
        from django.test.simple import run_tests
    failures = run_tests(test_args, verbosity=1, interactive=True)
    sys.exit(failures)

if __name__ == '__main__':
    import django
    if six.PY3 and django.VERSION < (1, 5):
        print("Django " + '.'.join([str(i) for i in django.VERSION]) +
              " is not compatible with Python 3. Skipping tests.")
        sys.exit(0)

    runtests()
<commit_msg>Check whether six has been installed before running tests<commit_after>#!/usr/bin/env python

from __future__ import unicode_literals
import os, sys

try:
    import six
    from django.conf import settings
except ImportError:
    print("Django has not been installed.")
    sys.exit(0)

if not settings.configured:
    settings.configure(
        INSTALLED_APPS=["jstemplate"],
        DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3"}})

def runtests(*test_args):
    if not test_args:
        test_args = ["jstemplate"]

    parent = os.path.dirname(os.path.abspath(__file__))
    sys.path.insert(0, parent)

    try:
        from django.test.simple import DjangoTestSuiteRunner
        def run_tests(test_args, verbosity, interactive):
            runner = DjangoTestSuiteRunner(
                verbosity=verbosity, interactive=interactive, failfast=False)
            return runner.run_tests(test_args)
    except ImportError:
        # for Django versions that don't have DjangoTestSuiteRunner
        from django.test.simple import run_tests
    failures = run_tests(test_args, verbosity=1, interactive=True)
    sys.exit(failures)

if __name__ == '__main__':
    import django
    if six.PY3 and django.VERSION < (1, 5):
        print("Django " + '.'.join([str(i) for i in django.VERSION]) +
              " is not compatible with Python 3. Skipping tests.")
        sys.exit(0)

    runtests()
<|endoftext|>


<commit_before>#!/usr/bin/python
# Copyright 2016 Mender Software AS
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        https://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

import requests
import json
from fabric.api import *
import time
import pytest
import os.path

from common import *
from common_docker import *
from MenderAPI import api_version, logger

class Deployments(object):
    # track the last statistic for a deployment id
    last_statistic = {}
    auth = None

    def __init__(self, auth):
        self.auth = auth

    def get_deployments_base_path(self):
        return "https://%s/api/management/%s/deployments/" % (get_mender_gateway(), api_version)

    def upload_image(self, filename, description="abc"):
        image_path_url = self.get_deployments_base_path() + "artifacts"

        r = requests.post(image_path_url, verify=False, headers=self.auth.get_auth_token(), files=(("description", (None, description)),
                          ("size", (None, str(os.path.getsize(filename)))), ("artifact", (filename, open(filename),
                           "multipart/form-data"))))

        logger.info("Received image upload status code: " + str(r.status_code) + " with payload: " + str(r.text))
        assert r.status_code == requests.status_codes.codes.created
        return r.headers["location"]

    def trigger_deployment(self, name, artifact_name, devices):
        deployments_path_url = self.get_deployments_base_path() + "deployments"

        trigger_data = {"name": name,
                        "artifact_name": artifact_name,
                        "devices": devices}

        headers = {'Content-Type': 'application/json'}
        headers.update(self.auth.get_auth_token())

        r = requests.post(deployments_path_url, headers=headers,
                          data=json.dumps(trigger_data), verify=False)

        logger.debug("triggering deployment with: " + json.dumps(trigger_data))
        assert r.status_code == requests.status_codes.codes.created

        deployment_id = str(r.headers['Location'].split("/")[-1])
        logger.info("Deployment id is: " + deployment_id)

        return deployment_id

    def get_logs(self, device, deployment_id, expected_status=200):
        deployments_logs_url = self.get_deployments_base_path() + "deployments/%s/devices/%s/log" % (deployment_id, device)
        r = requests.get(deployments_logs_url, headers=self.auth.get_auth_token(), verify=False)
        assert r.status_code == expected_status

        logger.info("Logs contain " + str(r.text))
        return r.text

    def get_statistics(self, deployment_id):
        deployments_statistics_url = self.get_deployments_base_path() + "deployments/%s/statistics" % (deployment_id)
        r = requests.get(deployments_statistics_url, headers=self.auth.get_auth_token(), verify=False)
        assert r.status_code == requests.status_codes.codes.ok

        try:
            json.loads(r.text)
        except Exception, e:
            assert e is None

        if not self.last_statistic.setdefault(deployment_id, []) or \
            self.last_statistic[deployment_id][-1] != str(r.text):
                self.last_statistic[deployment_id].append(str(r.text))
                logger.info("Statistics contains new entry: " + str(r.text))

        return json.loads(r.text)

    def check_expected_status(self, deployment_id, expected_status, expected_count, max_wait=300, polling_frequency=.2):
        timeout = time.time() + max_wait
        seen = set()

        while time.time() <= timeout:
            time.sleep(polling_frequency)

            data = self.get_statistics(deployment_id)
            seen.add(str(data))

            if data[expected_status] != expected_count:
                continue
            else:
                return

        if time.time() > timeout:
            pytest.fail("Never found: %s:%s, only seen: %s" % (expected_status, expected_count, str(seen)))

    def abort(self, deployment_id):
        deployment_abort_url = self.get_deployments_base_path() + "deployments/%s/status" % (deployment_id)
        r = requests.put(deployment_abort_url, verify=False, headers=self.auth.get_auth_token(), json={"status": "aborted"})
        assert r.status_code == requests.status_codes.codes.no_content
<commit_msg>deployments.abort now checks for status: finished<commit_after>#!/usr/bin/python
# Copyright 2016 Mender Software AS
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        https://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.

import requests
import json
from fabric.api import *
import time
import pytest
import os.path

from common import *
from common_docker import *
from MenderAPI import api_version, logger

class Deployments(object):
    # track the last statistic for a deployment id
    last_statistic = {}
    auth = None

    def __init__(self, auth):
        self.auth = auth

    def get_deployments_base_path(self):
        return "https://%s/api/management/%s/deployments/" % (get_mender_gateway(), api_version)

    def upload_image(self, filename, description="abc"):
        image_path_url = self.get_deployments_base_path() + "artifacts"

        r = requests.post(image_path_url, verify=False, headers=self.auth.get_auth_token(), files=(("description", (None, description)),
                          ("size", (None, str(os.path.getsize(filename)))), ("artifact", (filename, open(filename),
                           "multipart/form-data"))))

        logger.info("Received image upload status code: " + str(r.status_code) + " with payload: " + str(r.text))
        assert r.status_code == requests.status_codes.codes.created
        return r.headers["location"]

    def trigger_deployment(self, name, artifact_name, devices):
        deployments_path_url = self.get_deployments_base_path() + "deployments"

        trigger_data = {"name": name,
                        "artifact_name": artifact_name,
                        "devices": devices}

        headers = {'Content-Type': 'application/json'}
        headers.update(self.auth.get_auth_token())

        r = requests.post(deployments_path_url, headers=headers,
                          data=json.dumps(trigger_data), verify=False)

        logger.debug("triggering deployment with: " + json.dumps(trigger_data))
        assert r.status_code == requests.status_codes.codes.created

        deployment_id = str(r.headers['Location'].split("/")[-1])
        logger.info("Deployment id is: " + deployment_id)

        return deployment_id

    def get_logs(self, device, deployment_id, expected_status=200):
        deployments_logs_url = self.get_deployments_base_path() + "deployments/%s/devices/%s/log" % (deployment_id, device)
        r = requests.get(deployments_logs_url, headers=self.auth.get_auth_token(), verify=False)
        assert r.status_code == expected_status

        logger.info("Logs contain " + str(r.text))
        return r.text

    def get_statistics(self, deployment_id):
        deployments_statistics_url = self.get_deployments_base_path() + "deployments/%s/statistics" % (deployment_id)
        r = requests.get(deployments_statistics_url, headers=self.auth.get_auth_token(), verify=False)
        assert r.status_code == requests.status_codes.codes.ok

        try:
            json.loads(r.text)
        except Exception, e:
            assert e is None

        if not self.last_statistic.setdefault(deployment_id, []) or \
            self.last_statistic[deployment_id][-1] != str(r.text):
                self.last_statistic[deployment_id].append(str(r.text))
                logger.info("Statistics contains new entry: " + str(r.text))

        return json.loads(r.text)

    def check_expected_status(self, deployment_id, expected_status, expected_count, max_wait=300, polling_frequency=.2):
        timeout = time.time() + max_wait
        seen = set()

        while time.time() <= timeout:
            time.sleep(polling_frequency)

            data = self.get_statistics(deployment_id)
            seen.add(str(data))

            if data[expected_status] != expected_count:
                continue
            else:
                return

        if time.time() > timeout:
            pytest.fail("Never found: %s:%s, only seen: %s" % (expected_status, expected_count, str(seen)))

    def abort(self, deployment_id):
        deployment_abort_url = self.get_deployments_base_path() + "deployments/%s/status" % (deployment_id)
        r = requests.put(deployment_abort_url, verify=False, headers=self.auth.get_auth_token(), json={"status": "finished"})
        assert r.status_code == requests.status_codes.codes.no_content
<|endoftext|>


<commit_before>import os
import os.path as op

from flask import url_for

from werkzeug import secure_filename
from werkzeug.datastructures import FileStorage

from wtforms import ValidationError, fields
from wtforms.widgets import HTMLString, html_params

try:
    from wtforms.fields.core import _unset_value as unset_value
except ImportError:
    from wtforms.utils import unset_value

from flask.ext.admin.babel import gettext

from flask.ext.admin._compat import string_types, urljoin

try:
    from PIL import Image, ImageOps
except ImportError:
    Image = None
    ImageOps = None

__all__ = ['FileUploadInput', 'FileUploadField',
           'ImageUploadInput', 'ImageUploadField',
           'namegen_filename', 'thumbgen_filename']

# Widgets
class FileUploadInput(object):
    """
        Renders a file input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' <input %(text)s>'
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        template = self.data_template if field.data else self.empty_template

        return HTMLString(template % {
            'text': html_params(type='text',
                                readonly='readonly',
                                value=field.data),
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        })

class ImageUploadInput(object):
    """
        Renders a image input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' '
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        args = {
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        }

        if field.data and isinstance(field.data, string_types):
            url = self.get_url(field)
            args['image'] = html_params(src=url)

            template = self.data_template
        else:
            template = self.empty_template

        return HTMLString(template % args)

    def get_url(self, field):
        if field.thumbnail_size:
            filename = field.thumbnail_fn(field.data)
        else:
            filename = field.data

        if field.url_relative_path:
            filename = urljoin(field.url_relative_path, filename)

        return url_for(field.endpoint, filename=filename)

# Fields
class FileUploadField(fields.TextField):
    """
        Customizable file-upload field.

        Saves file to configured path, handles updates and deletions. Inherits from `TextField`,
        resulting filename will be stored as string.
    """
    widget = FileUploadInput()

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 permission=0o666,
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
        """
        self.base_path = base_path
        self.relative_path = relative_path

        self.namegen = namegen or namegen_filename
        self.allowed_extensions = allowed_extensions
        self.permission = permission

        self._should_delete = False

        super(FileUploadField, self).__init__(label, validators, **kwargs)

    def is_file_allowed(self, filename):
        """
            Check if file extension is allowed.

            :param filename:
                File name to check
        """
        if not self.allowed_extensions:
            return True

        return ('.' in filename and
                filename.rsplit('.', 1)[1].lower() in
                map(lambda x: x.lower(), self.allowed_extensions))

    def pre_validate(self, form):
        if (self.data and
                isinstance(self.data, FileStorage) and
                not self.is_file_allowed(self.data.filename)):
            raise ValidationError(gettext('Invalid file extension'))

    def process(self, formdata, data=unset_value):
        if formdata:
            marker = '_%s-delete' % self.name
            if marker in formdata:
                self._should_delete = True

        return super(FileUploadField, self).process(formdata, data)

    def populate_obj(self, obj, name):
        field = getattr(obj, name, None)
        if field:
            # If field should be deleted, clean it up
            if self._should_delete:
                self._delete_file(field)
                setattr(obj, name, None)
                return

        if self.data and isinstance(self.data, FileStorage):
            if field:
                self._delete_file(field)

            filename = self.generate_name(obj, self.data)
            filename = self._save_file(self.data, filename)
            # update filename of FileStorage to our validated name
            self.data.filename = filename

            setattr(obj, name, filename)

    def generate_name(self, obj, file_data):
        filename = self.namegen(obj, file_data)

        if not self.relative_path:
            return filename

        return urljoin(self.relative_path, filename)

    def _get_path(self, filename):
        if not self.base_path:
            raise ValueError('FileUploadField field requires base_path to be set.')

        return op.join(self.base_path, filename)

    def _delete_file(self, filename):
        path = self._get_path(filename)

        if op.exists(path):
            os.remove(path)

    def _save_file(self, data, filename):
        path = self._get_path(filename)
        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission | 0o111)

        data.save(path)

        return filename

class ImageUploadField(FileUploadField):
    """
        Image upload field.

        Does image validation, thumbnail generation, updating and deleting images.

        Requires PIL (or Pillow) to be installed.
    """
    widget = ImageUploadInput()

    keep_image_formats = ('PNG',)
    """
        If field detects that uploaded image is not in this list, it will save image
        as PNG.
    """

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 max_size=None,
                 thumbgen=None, thumbnail_size=None,
                 permission=0o666,
                 url_relative_path=None, endpoint='static',
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
            :param max_size:
                Tuple of (width, height, force) or None. If provided, Flask-Admin will
                resize image to the desired size.
            :param thumbgen:
                Thumbnail filename generation function. All thumbnails will be saved as JPEG files,
                so there's no need to keep original file extension.

                For example::

                    import os.path as op

                    def thumb_name(filename):
                        name, _ = op.splitext(filename)
                        return secure_filename('%s-thumb.jpg' % name)

                    class MyForm(BaseForm):
                        upload = ImageUploadField('File', thumbgen=prefix_name)

            :param thumbnail_size:
                Tuple or (width, height, force) values. If not provided, thumbnail won't be created.

                Width and height is in pixels. If `force` is set to `True`, will try to fit image into dimensions and
                keep aspect ratio, otherwise will just resize to target size.
            :param url_relative_path:
                Relative path from the root of the static directory URL. Only gets used when generating
                preview image URLs.

                For example, your model might store just file names (`relative_path` set to `None`), but
                `base_path` is pointing to subdirectory.
            :param endpoint:
                Static endpoint for images. Used by widget to display previews. Defaults to 'static'.
        """
        # Check if PIL is installed
        if Image is None:
            raise ImportError('PIL library was not found')

        self.max_size = max_size
        self.thumbnail_fn = thumbgen or thumbgen_filename
        self.thumbnail_size = thumbnail_size
        self.endpoint = endpoint
        self.image = None
        self.url_relative_path = url_relative_path

        if not allowed_extensions:
            allowed_extensions = ('gif', 'jpg', 'jpeg', 'png', 'tiff')

        super(ImageUploadField, self).__init__(label, validators,
                                               base_path=base_path,
                                               relative_path=relative_path,
                                               namegen=namegen,
                                               allowed_extensions=allowed_extensions,
                                               permission=permission,
                                               **kwargs)

    def pre_validate(self, form):
        super(ImageUploadField, self).pre_validate(form)

        if self.data and isinstance(self.data, FileStorage):
            try:
                self.image = Image.open(self.data)
            except Exception as e:
                raise ValidationError('Invalid image: %s' % e)

    # Deletion
    def _delete_file(self, filename):
        super(ImageUploadField, self)._delete_file(filename)

        self._delete_thumbnail(filename)

    def _delete_thumbnail(self, filename):
        path = self._get_path(self.thumbnail_fn(filename))

        if op.exists(path):
            os.remove(path)

    # Saving
    def _save_file(self, data, filename):
        path = self._get_path(filename)

        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission)

        # Figure out format
        filename, format = self._get_save_format(filename, self.image)

        if self.image and (self.image.format != format or self.max_size):
            if self.max_size:
                image = self._resize(self.image, self.max_size)
            else:
                image = self.image

            self._save_image(image, self._get_path(filename), format)
        else:
            data.seek(0)
            data.save( self._get_path(filename) )

        self._save_thumbnail(data, filename, format)

        return filename

    def _save_thumbnail(self, data, filename, format):
        if self.image and self.thumbnail_size:
            path = self._get_path(self.thumbnail_fn(filename))

            self._save_image(self._resize(self.image, self.thumbnail_size),
                             path,
                             format)

    def _resize(self, image, size):
        (width, height, force) = size

        if image.size[0] > width or image.size[1] > height:
            if force:
                return ImageOps.fit(self.image, (width, height), Image.ANTIALIAS)
            else:
                thumb = self.image.copy()
                thumb.thumbnail((width, height), Image.ANTIALIAS)
                return thumb

        return image

    def _save_image(self, image, path, format='JPEG'):
        if image.mode not in ('RGB', 'RGBA'):
            image = image.convert('RGBA')

        with open(path, 'wb') as fp:
            image.save(fp, format)

    def _get_save_format(self, filename, image):
        if image.format not in self.keep_image_formats:
            name, ext = op.splitext(filename)
            filename = '%s.jpg' % name
            return filename, 'JPEG'

        return filename, image.format

# Helpers
def namegen_filename(obj, file_data):
    """
        Generate secure filename for uploaded file.
    """
    return secure_filename(file_data.filename)

def thumbgen_filename(filename):
    """
        Generate thumbnail name from filename.
    """
    name, ext = op.splitext(filename)
    return '%s_thumb%s' % (name, ext)
<commit_msg>Fixed #569. Properly handle model updates with image field on python 3<commit_after>import os
import os.path as op

from flask import url_for

from werkzeug import secure_filename
from werkzeug.datastructures import FileStorage

from wtforms import ValidationError, fields
from wtforms.widgets import HTMLString, html_params

try:
    from wtforms.fields.core import _unset_value as unset_value
except ImportError:
    from wtforms.utils import unset_value

from flask.ext.admin.babel import gettext

from flask.ext.admin._compat import string_types, urljoin

try:
    from PIL import Image, ImageOps
except ImportError:
    Image = None
    ImageOps = None

__all__ = ['FileUploadInput', 'FileUploadField',
           'ImageUploadInput', 'ImageUploadField',
           'namegen_filename', 'thumbgen_filename']

# Widgets
class FileUploadInput(object):
    """
        Renders a file input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' <input %(text)s>'
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        template = self.data_template if field.data else self.empty_template

        return HTMLString(template % {
            'text': html_params(type='text',
                                readonly='readonly',
                                value=field.data),
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        })

class ImageUploadInput(object):
    """
        Renders a image input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' '
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        args = {
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        }

        if field.data and isinstance(field.data, string_types):
            url = self.get_url(field)
            args['image'] = html_params(src=url)

            template = self.data_template
        else:
            template = self.empty_template

        return HTMLString(template % args)

    def get_url(self, field):
        if field.thumbnail_size:
            filename = field.thumbnail_fn(field.data)
        else:
            filename = field.data

        if field.url_relative_path:
            filename = urljoin(field.url_relative_path, filename)

        return url_for(field.endpoint, filename=filename)

# Fields
class FileUploadField(fields.TextField):
    """
        Customizable file-upload field.

        Saves file to configured path, handles updates and deletions. Inherits from `TextField`,
        resulting filename will be stored as string.
    """
    widget = FileUploadInput()

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 permission=0o666,
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
        """
        self.base_path = base_path
        self.relative_path = relative_path

        self.namegen = namegen or namegen_filename
        self.allowed_extensions = allowed_extensions
        self.permission = permission

        self._should_delete = False

        super(FileUploadField, self).__init__(label, validators, **kwargs)

    def is_file_allowed(self, filename):
        """
            Check if file extension is allowed.

            :param filename:
                File name to check
        """
        if not self.allowed_extensions:
            return True

        return ('.' in filename and
                filename.rsplit('.', 1)[1].lower() in
                map(lambda x: x.lower(), self.allowed_extensions))

    def pre_validate(self, form):
        if (self.data
                and len(self.data.filename)
                and isinstance(self.data, FileStorage)
                and not self.is_file_allowed(self.data.filename)):
            raise ValidationError(gettext('Invalid file extension'))

    def process(self, formdata, data=unset_value):
        if formdata:
            marker = '_%s-delete' % self.name
            if marker in formdata:
                self._should_delete = True

        return super(FileUploadField, self).process(formdata, data)

    def populate_obj(self, obj, name):
        field = getattr(obj, name, None)
        if field:
            # If field should be deleted, clean it up
            if self._should_delete:
                self._delete_file(field)
                setattr(obj, name, None)
                return

        if self.data and isinstance(self.data, FileStorage):
            if field:
                self._delete_file(field)

            filename = self.generate_name(obj, self.data)
            filename = self._save_file(self.data, filename)
            # update filename of FileStorage to our validated name
            self.data.filename = filename

            setattr(obj, name, filename)

    def generate_name(self, obj, file_data):
        filename = self.namegen(obj, file_data)

        if not self.relative_path:
            return filename

        return urljoin(self.relative_path, filename)

    def _get_path(self, filename):
        if not self.base_path:
            raise ValueError('FileUploadField field requires base_path to be set.')

        return op.join(self.base_path, filename)

    def _delete_file(self, filename):
        path = self._get_path(filename)

        if op.exists(path):
            os.remove(path)

    def _save_file(self, data, filename):
        path = self._get_path(filename)
        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission | 0o111)

        data.save(path)

        return filename

class ImageUploadField(FileUploadField):
    """
        Image upload field.

        Does image validation, thumbnail generation, updating and deleting images.

        Requires PIL (or Pillow) to be installed.
    """
    widget = ImageUploadInput()

    keep_image_formats = ('PNG',)
    """
        If field detects that uploaded image is not in this list, it will save image
        as PNG.
    """

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 max_size=None,
                 thumbgen=None, thumbnail_size=None,
                 permission=0o666,
                 url_relative_path=None, endpoint='static',
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
            :param max_size:
                Tuple of (width, height, force) or None. If provided, Flask-Admin will
                resize image to the desired size.
            :param thumbgen:
                Thumbnail filename generation function. All thumbnails will be saved as JPEG files,
                so there's no need to keep original file extension.

                For example::

                    import os.path as op

                    def thumb_name(filename):
                        name, _ = op.splitext(filename)
                        return secure_filename('%s-thumb.jpg' % name)

                    class MyForm(BaseForm):
                        upload = ImageUploadField('File', thumbgen=prefix_name)

            :param thumbnail_size:
                Tuple or (width, height, force) values. If not provided, thumbnail won't be created.

                Width and height is in pixels. If `force` is set to `True`, will try to fit image into dimensions and
                keep aspect ratio, otherwise will just resize to target size.
            :param url_relative_path:
                Relative path from the root of the static directory URL. Only gets used when generating
                preview image URLs.

                For example, your model might store just file names (`relative_path` set to `None`), but
                `base_path` is pointing to subdirectory.
            :param endpoint:
                Static endpoint for images. Used by widget to display previews. Defaults to 'static'.
        """
        # Check if PIL is installed
        if Image is None:
            raise ImportError('PIL library was not found')

        self.max_size = max_size
        self.thumbnail_fn = thumbgen or thumbgen_filename
        self.thumbnail_size = thumbnail_size
        self.endpoint = endpoint
        self.image = None
        self.url_relative_path = url_relative_path

        if not allowed_extensions:
            allowed_extensions = ('gif', 'jpg', 'jpeg', 'png', 'tiff')

        super(ImageUploadField, self).__init__(label, validators,
                                               base_path=base_path,
                                               relative_path=relative_path,
                                               namegen=namegen,
                                               allowed_extensions=allowed_extensions,
                                               permission=permission,
                                               **kwargs)

    def pre_validate(self, form):
        super(ImageUploadField, self).pre_validate(form)

        if self.data and isinstance(self.data, FileStorage):
            try:
                self.image = Image.open(self.data)
            except Exception as e:
                raise ValidationError('Invalid image: %s' % e)

    # Deletion
    def _delete_file(self, filename):
        super(ImageUploadField, self)._delete_file(filename)

        self._delete_thumbnail(filename)

    def _delete_thumbnail(self, filename):
        path = self._get_path(self.thumbnail_fn(filename))

        if op.exists(path):
            os.remove(path)

    # Saving
    def _save_file(self, data, filename):
        path = self._get_path(filename)

        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission)

        # Figure out format
        filename, format = self._get_save_format(filename, self.image)

        if self.image and (self.image.format != format or self.max_size):
            if self.max_size:
                image = self._resize(self.image, self.max_size)
            else:
                image = self.image

            self._save_image(image, self._get_path(filename), format)
        else:
            data.seek(0)
            data.save( self._get_path(filename) )

        self._save_thumbnail(data, filename, format)

        return filename

    def _save_thumbnail(self, data, filename, format):
        if self.image and self.thumbnail_size:
            path = self._get_path(self.thumbnail_fn(filename))

            self._save_image(self._resize(self.image, self.thumbnail_size),
                             path,
                             format)

    def _resize(self, image, size):
        (width, height, force) = size

        if image.size[0] > width or image.size[1] > height:
            if force:
                return ImageOps.fit(self.image, (width, height), Image.ANTIALIAS)
            else:
                thumb = self.image.copy()
                thumb.thumbnail((width, height), Image.ANTIALIAS)
                return thumb

        return image

    def _save_image(self, image, path, format='JPEG'):
        if image.mode not in ('RGB', 'RGBA'):
            image = image.convert('RGBA')

        with open(path, 'wb') as fp:
            image.save(fp, format)

    def _get_save_format(self, filename, image):
        if image.format not in self.keep_image_formats:
            name, ext = op.splitext(filename)
            filename = '%s.jpg' % name
            return filename, 'JPEG'

        return filename, image.format

# Helpers
def namegen_filename(obj, file_data):
    """
        Generate secure filename for uploaded file.
    """
    return secure_filename(file_data.filename)

def thumbgen_filename(filename):
    """
        Generate thumbnail name from filename.
    """
    name, ext = op.splitext(filename)
    return '%s_thumb%s' % (name, ext)
<|endoftext|>


<commit_before>import math
import random

RCONST = 6
SCONST = 0.01
MAXFORCE = 1

def init_force(nodes):
	for node in nodes.values():
		node['fx'] = 0
		node['fy'] = 0

def node_repulsion(nodes):
	for node in nodes.values():
		for o_node in nodes.values():
			if node['id'] == o_node['id']:
				continue
			xdistance = node['x'] - o_node['x']
			ydistance = node['y'] - o_node['y']
			distance = math.sqrt((xdistance)**2+(ydistance)**2)
			angle = math.atan2(ydistance,xdistance)
			xunit = math.cos(angle)
			yunit = math.sin(angle)
			try:
				node['fx'] = node['fx'] + RCONST/(distance**2)*xunit
				node['fy'] = node['fy'] + RCONST/(distance**2)*yunit
			except ZeroDivisionError:
				pass

def edge_attraction(edges):
	for node,o_node in edges:
		xdistance = node['x'] - o_node['x']
		ydistance = node['y'] - o_node['y']
		distance = math.sqrt((xdistance)**2+(ydistance)**2)
		angle = math.atan2(ydistance,xdistance)
		xunit = math.cos(angle)*distance
		yunit = math.sin(angle)*distance
		node['fx'] = node['fx'] - SCONST*xunit
		node['fy'] = node['fy'] - SCONST*yunit
		o_node['fx'] = o_node['fx'] + SCONST*xunit
		o_node['fy'] = o_node['fy'] + SCONST*yunit

def force_limit(nodes):
	for node in nodes.values():
		if abs(node['fx']) > MAXFORCE:
			node['fx'] = MAXFORCE * node['fx']/abs(node['fx'])

		if abs(node['fy']) > MAXFORCE:
			node['fy'] = MAXFORCE * node['fy']/abs(node['fy'])

def propogate(nodes):
	for node in nodes.values():
		node['x'] = node['x'] + node['fx']
		node['y'] = node['y'] + node['fy']

def translate(nodes,x,y):
	for node in nodes.values():
		node['x'] = node['x'] + x
		node['y'] = node['y'] + y

# Take in a list of string tuples
def parse_edge_list(graph):
	nodes = {}
	edges = []
	for edge_nodes in graph:
		for node in edge_nodes:
			if node in nodes:
				continue
			new_node = {}
			new_node['id'] = node
			new_node['label'] = str(node)
			new_node['x'] = random.randrange(-100,100)
			new_node['y'] = random.randrange(-100,100)
			nodes[new_node['id']] = new_node
		edges.append((nodes[edge_nodes[0]],nodes[edge_nodes[1]]))
	return nodes,edges

def get_extremes(nodes):
	xmin = 0
	ymin = 0
	for node in nodes.values():
		if node['x'] < xmin:
			xmin = node['x']
		if node['y'] < ymin:
			ymin = node['y']
	return xmin,ymin

def layout(nodes,edges,iterations):
	for i in range(iterations):
		init_force(nodes)
		node_repulsion(nodes)
		edge_attraction(edges)
		force_limit(nodes)
		propogate(nodes)
<commit_msg>force limiting is OBE<commit_after>import math
import random

RCONST = 6
SCONST = 0.01
MAXFORCE = 1

def init_force(nodes):
	for node in nodes.values():
		node['fx'] = 0
		node['fy'] = 0

def node_repulsion(nodes):
	for node in nodes.values():
		for o_node in nodes.values():
			if node['id'] == o_node['id']:
				continue
			xdistance = node['x'] - o_node['x']
			ydistance = node['y'] - o_node['y']
			distance = math.sqrt((xdistance)**2+(ydistance)**2)
			angle = math.atan2(ydistance,xdistance)
			xunit = math.cos(angle)
			yunit = math.sin(angle)
			try:
				node['fx'] = node['fx'] + RCONST/(distance**2)*xunit
				node['fy'] = node['fy'] + RCONST/(distance**2)*yunit
			except ZeroDivisionError:
				pass

def edge_attraction(edges):
	for node,o_node in edges:
		xdistance = node['x'] - o_node['x']
		ydistance = node['y'] - o_node['y']
		distance = math.sqrt((xdistance)**2+(ydistance)**2)
		angle = math.atan2(ydistance,xdistance)
		xunit = math.cos(angle)*distance
		yunit = math.sin(angle)*distance
		node['fx'] = node['fx'] - SCONST*xunit
		node['fy'] = node['fy'] - SCONST*yunit
		o_node['fx'] = o_node['fx'] + SCONST*xunit
		o_node['fy'] = o_node['fy'] + SCONST*yunit

def force_limit(nodes):
	for node in nodes.values():
		if abs(node['fx']) > MAXFORCE:
			node['fx'] = MAXFORCE * node['fx']/abs(node['fx'])

		if abs(node['fy']) > MAXFORCE:
			node['fy'] = MAXFORCE * node['fy']/abs(node['fy'])

def propogate(nodes):
	for node in nodes.values():
		node['x'] = node['x'] + node['fx']
		node['y'] = node['y'] + node['fy']

def translate(nodes,x,y):
	for node in nodes.values():
		node['x'] = node['x'] + x
		node['y'] = node['y'] + y

# Take in a list of string tuples
def parse_edge_list(graph):
	nodes = {}
	edges = []
	for edge_nodes in graph:
		for node in edge_nodes:
			if node in nodes:
				continue
			new_node = {}
			new_node['id'] = node
			new_node['label'] = str(node)
			new_node['x'] = random.randrange(-100,100)
			new_node['y'] = random.randrange(-100,100)
			nodes[new_node['id']] = new_node
		edges.append((nodes[edge_nodes[0]],nodes[edge_nodes[1]]))
	return nodes,edges

def get_extremes(nodes):
	xmin = 0
	ymin = 0
	for node in nodes.values():
		if node['x'] < xmin:
			xmin = node['x']
		if node['y'] < ymin:
			ymin = node['y']
	return xmin,ymin

def layout(nodes,edges,iterations):
	for i in range(iterations):
		init_force(nodes)
		node_repulsion(nodes)
		edge_attraction(edges)
		propogate(nodes)
<|endoftext|>


<commit_before>"""
Simple script that actually executes py.test runner when passed "--pytest" as
first argument; in this case, all other arguments are forwarded to pytest's
main().
"""

if __name__ == '__main__':
    import sys
    # check manually if we plan to use pytest, so we leave argument handling
    # to pytest's main.
    if len(sys.argv) > 1 and sys.argv[1] == '--pytest':
        import pytest
        import _pytest.capture
        print('DEBUG:' + _pytest.capture.__file__)
        sys.exit(pytest.main(sys.argv[2:]))
    else:
        # now argv can be parsed by your arguments parsing library of choice
        # using argv as usual
        print('Hello')
        sys.exit(0)
<commit_msg>Adding debug code to runtests (2)<commit_after>"""
Simple script that actually executes py.test runner when passed "--pytest" as
first argument; in this case, all other arguments are forwarded to pytest's
main().
"""

if __name__ == '__main__':
    import sys
    # check manually if we plan to use pytest, so we leave argument handling
    # to pytest's main.
    if len(sys.argv) > 1 and sys.argv[1] == '--pytest':
        import _pytest.capture
        print('DEBUG:' + _pytest.capture.__file__)
        import pytest
        sys.exit(pytest.main(sys.argv[2:]))
    else:
        # now argv can be parsed by your arguments parsing library of choice
        # using argv as usual
        print('Hello')
        sys.exit(0)
<|endoftext|>


<commit_before>import os
import os.path as op

from flask import url_for

from werkzeug import secure_filename
from werkzeug.datastructures import FileStorage

from wtforms import ValidationError, fields
from wtforms.widgets import HTMLString, html_params

try:
    from wtforms.fields.core import _unset_value as unset_value
except ImportError:
    from wtforms.utils import unset_value

from flask.ext.admin.babel import gettext

from flask.ext.admin._compat import string_types, urljoin

try:
    from PIL import Image, ImageOps
except ImportError:
    Image = None
    ImageOps = None

__all__ = ['FileUploadInput', 'FileUploadField',
           'ImageUploadInput', 'ImageUploadField',
           'namegen_filename', 'thumbgen_filename']

# Widgets
class FileUploadInput(object):
    """
        Renders a file input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' <input %(text)s>'
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        template = self.data_template if field.data else self.empty_template

        return HTMLString(template % {
            'text': html_params(type='text',
                                readonly='readonly',
                                value=field.data),
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        })

class ImageUploadInput(object):
    """
        Renders a image input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' '
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        args = {
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        }

        if field.data and isinstance(field.data, string_types):
            url = self.get_url(field)
            args['image'] = html_params(src=url)

            template = self.data_template
        else:
            template = self.empty_template

        return HTMLString(template % args)

    def get_url(self, field):
        if field.thumbnail_size:
            filename = field.thumbnail_fn(field.data)
        else:
            filename = field.data

        if field.url_relative_path:
            filename = urljoin(field.url_relative_path, filename)

        return url_for(field.endpoint, filename=filename)

# Fields
class FileUploadField(fields.TextField):
    """
        Customizable file-upload field.

        Saves file to configured path, handles updates and deletions. Inherits from `TextField`,
        resulting filename will be stored as string.
    """
    widget = FileUploadInput()

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 permission=0o666,
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
        """
        self.base_path = base_path
        self.relative_path = relative_path

        self.namegen = namegen or namegen_filename
        self.allowed_extensions = allowed_extensions
        self.permission = permission

        self._should_delete = False

        super(FileUploadField, self).__init__(label, validators, **kwargs)

    def is_file_allowed(self, filename):
        """
            Check if file extension is allowed.

            :param filename:
                File name to check
        """
        if not self.allowed_extensions:
            return True

        return ('.' in filename and
                filename.rsplit('.', 1)[1].lower() in
                map(str.lower, self.allowed_extensions))

    def pre_validate(self, form):
        if (self.data and
                isinstance(self.data, FileStorage) and
                not self.is_file_allowed(self.data.filename)):
            raise ValidationError(gettext('Invalid file extension'))

    def process(self, formdata, data=unset_value):
        if formdata:
            marker = '_%s-delete' % self.name
            if marker in formdata:
                self._should_delete = True

        return super(FileUploadField, self).process(formdata, data)

    def populate_obj(self, obj, name):
        field = getattr(obj, name, None)
        if field:
            # If field should be deleted, clean it up
            if self._should_delete:
                self._delete_file(field)
                setattr(obj, name, None)
                return

        if self.data and isinstance(self.data, FileStorage):
            if field:
                self._delete_file(field)

            filename = self.generate_name(obj, self.data)
            filename = self._save_file(self.data, filename)
            # update filename of FileStorage to our validated name
            self.data.filename = filename

            setattr(obj, name, filename)

    def generate_name(self, obj, file_data):
        filename = self.namegen(obj, file_data)

        if not self.relative_path:
            return filename

        return urljoin(self.relative_path, filename)

    def _get_path(self, filename):
        if not self.base_path:
            raise ValueError('FileUploadField field requires base_path to be set.')

        return op.join(self.base_path, filename)

    def _delete_file(self, filename):
        path = self._get_path(filename)

        if op.exists(path):
            os.remove(path)

    def _save_file(self, data, filename):
        path = self._get_path(filename)
        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission)

        data.save(path)

        return filename

class ImageUploadField(FileUploadField):
    """
        Image upload field.

        Does image validation, thumbnail generation, updating and deleting images.

        Requires PIL (or Pillow) to be installed.
    """
    widget = ImageUploadInput()

    keep_image_formats = ('PNG',)
    """
        If field detects that uploaded image is not in this list, it will save image
        as PNG.
    """

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 max_size=None,
                 thumbgen=None, thumbnail_size=None,
                 permission=0o666,
                 url_relative_path=None, endpoint='static',
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
            :param max_size:
                Tuple of (width, height, force) or None. If provided, Flask-Admin will
                resize image to the desired size.
            :param thumbgen:
                Thumbnail filename generation function. All thumbnails will be saved as JPEG files,
                so there's no need to keep original file extension.

                For example::

                    import os.path as op

                    def thumb_name(filename):
                        name, _ = op.splitext(filename)
                        return secure_filename('%s-thumb.jpg' % name)

                    class MyForm(BaseForm):
                        upload = ImageUploadField('File', thumbgen=prefix_name)

            :param thumbnail_size:
                Tuple or (width, height, force) values. If not provided, thumbnail won't be created.

                Width and height is in pixels. If `force` is set to `True`, will try to fit image into dimensions and
                keep aspect ratio, otherwise will just resize to target size.
            :param url_relative_path:
                Relative path from the root of the static directory URL. Only gets used when generating
                preview image URLs.

                For example, your model might store just file names (`relative_path` set to `None`), but
                `base_path` is pointing to subdirectory.
            :param endpoint:
                Static endpoint for images. Used by widget to display previews. Defaults to 'static'.
        """
        # Check if PIL is installed
        if Image is None:
            raise ImportError('PIL library was not found')

        self.max_size = max_size
        self.thumbnail_fn = thumbgen or thumbgen_filename
        self.thumbnail_size = thumbnail_size
        self.endpoint = endpoint
        self.image = None
        self.url_relative_path = url_relative_path

        if not allowed_extensions:
            allowed_extensions = ('gif', 'jpg', 'jpeg', 'png', 'tiff')

        super(ImageUploadField, self).__init__(label, validators,
                                               base_path=base_path,
                                               relative_path=relative_path,
                                               namegen=namegen,
                                               allowed_extensions=allowed_extensions,
                                               permission=permission,
                                               **kwargs)

    def pre_validate(self, form):
        super(ImageUploadField, self).pre_validate(form)

        if self.data and isinstance(self.data, FileStorage):
            try:
                self.image = Image.open(self.data)
            except Exception as e:
                raise ValidationError('Invalid image: %s' % e)

    # Deletion
    def _delete_file(self, filename):
        super(ImageUploadField, self)._delete_file(filename)

        self._delete_thumbnail(filename)

    def _delete_thumbnail(self, filename):
        path = self._get_path(self.thumbnail_fn(filename))

        if op.exists(path):
            os.remove(path)

    # Saving
    def _save_file(self, data, filename):
        path = self._get_path(filename)

        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission)

        # Figure out format
        filename, format = self._get_save_format(filename, self.image)

        if self.image and (self.image.format != format or self.max_size):
            if self.max_size:
                image = self._resize(self.image, self.max_size)
            else:
                image = self.image

            self._save_image(image, self._get_path(filename), format)
        else:
            data.seek(0)
            data.save(path)

        self._save_thumbnail(data, filename, format)

        return filename

    def _save_thumbnail(self, data, filename, format):
        if self.image and self.thumbnail_size:
            path = self._get_path(self.thumbnail_fn(filename))

            self._save_image(self._resize(self.image, self.thumbnail_size),
                             path,
                             format)

    def _resize(self, image, size):
        (width, height, force) = size

        if image.size[0] > width or image.size[1] > height:
            if force:
                return ImageOps.fit(self.image, (width, height), Image.ANTIALIAS)
            else:
                thumb = self.image.copy()
                thumb.thumbnail((width, height), Image.ANTIALIAS)
                return thumb

        return image

    def _save_image(self, image, path, format='JPEG'):
        if image.mode not in ('RGB', 'RGBA'):
            image = image.convert('RGBA')

        with open(path, 'wb') as fp:
            image.save(fp, format)

    def _get_save_format(self, filename, image):
        if image.format not in self.keep_image_formats:
            name, ext = op.splitext(filename)
            filename = '%s.jpg' % name
            return filename, 'JPEG'

        return filename, image.format

# Helpers
def namegen_filename(obj, file_data):
    """
        Generate secure filename for uploaded file.
    """
    return secure_filename(file_data.filename)

def thumbgen_filename(filename):
    """
        Generate thumbnail name from filename.
    """
    name, ext = op.splitext(filename)
    return '%s_thumb%s' % (name, ext)
<commit_msg>Fix 'TypeError: descriptor 'lower' requires a 'str' object but received a 'unicode''<commit_after>import os
import os.path as op

from flask import url_for

from werkzeug import secure_filename
from werkzeug.datastructures import FileStorage

from wtforms import ValidationError, fields
from wtforms.widgets import HTMLString, html_params

try:
    from wtforms.fields.core import _unset_value as unset_value
except ImportError:
    from wtforms.utils import unset_value

from flask.ext.admin.babel import gettext

from flask.ext.admin._compat import string_types, urljoin

try:
    from PIL import Image, ImageOps
except ImportError:
    Image = None
    ImageOps = None

__all__ = ['FileUploadInput', 'FileUploadField',
           'ImageUploadInput', 'ImageUploadField',
           'namegen_filename', 'thumbgen_filename']

# Widgets
class FileUploadInput(object):
    """
        Renders a file input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' <input %(text)s>'
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        template = self.data_template if field.data else self.empty_template

        return HTMLString(template % {
            'text': html_params(type='text',
                                readonly='readonly',
                                value=field.data),
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        })

class ImageUploadInput(object):
    """
        Renders a image input chooser field.

        You can customize `empty_template` and `data_template` members to customize
        look and feel.
    """
    empty_template = ('<input %(file)s>')

    data_template = ('
'
                     ' '
                     ' <input type="checkbox" name="%(marker)s">Delete</input>'
                     '
'
                     '<input %(file)s>')

    def __call__(self, field, **kwargs):
        kwargs.setdefault('id', field.id)
        kwargs.setdefault('name', field.name)

        args = {
            'file': html_params(type='file',
                                **kwargs),
            'marker': '_%s-delete' % field.name
        }

        if field.data and isinstance(field.data, string_types):
            url = self.get_url(field)
            args['image'] = html_params(src=url)

            template = self.data_template
        else:
            template = self.empty_template

        return HTMLString(template % args)

    def get_url(self, field):
        if field.thumbnail_size:
            filename = field.thumbnail_fn(field.data)
        else:
            filename = field.data

        if field.url_relative_path:
            filename = urljoin(field.url_relative_path, filename)

        return url_for(field.endpoint, filename=filename)

# Fields
class FileUploadField(fields.TextField):
    """
        Customizable file-upload field.

        Saves file to configured path, handles updates and deletions. Inherits from `TextField`,
        resulting filename will be stored as string.
    """
    widget = FileUploadInput()

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 permission=0o666,
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
        """
        self.base_path = base_path
        self.relative_path = relative_path

        self.namegen = namegen or namegen_filename
        self.allowed_extensions = allowed_extensions
        self.permission = permission

        self._should_delete = False

        super(FileUploadField, self).__init__(label, validators, **kwargs)

    def is_file_allowed(self, filename):
        """
            Check if file extension is allowed.

            :param filename:
                File name to check
        """
        if not self.allowed_extensions:
            return True

        return ('.' in filename and
                filename.rsplit('.', 1)[1].lower() in
                map(lambda x: x.lower(), self.allowed_extensions))

    def pre_validate(self, form):
        if (self.data and
                isinstance(self.data, FileStorage) and
                not self.is_file_allowed(self.data.filename)):
            raise ValidationError(gettext('Invalid file extension'))

    def process(self, formdata, data=unset_value):
        if formdata:
            marker = '_%s-delete' % self.name
            if marker in formdata:
                self._should_delete = True

        return super(FileUploadField, self).process(formdata, data)

    def populate_obj(self, obj, name):
        field = getattr(obj, name, None)
        if field:
            # If field should be deleted, clean it up
            if self._should_delete:
                self._delete_file(field)
                setattr(obj, name, None)
                return

        if self.data and isinstance(self.data, FileStorage):
            if field:
                self._delete_file(field)

            filename = self.generate_name(obj, self.data)
            filename = self._save_file(self.data, filename)
            # update filename of FileStorage to our validated name
            self.data.filename = filename

            setattr(obj, name, filename)

    def generate_name(self, obj, file_data):
        filename = self.namegen(obj, file_data)

        if not self.relative_path:
            return filename

        return urljoin(self.relative_path, filename)

    def _get_path(self, filename):
        if not self.base_path:
            raise ValueError('FileUploadField field requires base_path to be set.')

        return op.join(self.base_path, filename)

    def _delete_file(self, filename):
        path = self._get_path(filename)

        if op.exists(path):
            os.remove(path)

    def _save_file(self, data, filename):
        path = self._get_path(filename)
        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission)

        data.save(path)

        return filename

class ImageUploadField(FileUploadField):
    """
        Image upload field.

        Does image validation, thumbnail generation, updating and deleting images.

        Requires PIL (or Pillow) to be installed.
    """
    widget = ImageUploadInput()

    keep_image_formats = ('PNG',)
    """
        If field detects that uploaded image is not in this list, it will save image
        as PNG.
    """

    def __init__(self, label=None, validators=None,
                 base_path=None, relative_path=None,
                 namegen=None, allowed_extensions=None,
                 max_size=None,
                 thumbgen=None, thumbnail_size=None,
                 permission=0o666,
                 url_relative_path=None, endpoint='static',
                 **kwargs):
        """
            Constructor.

            :param label:
                Display label
            :param validators:
                Validators
            :param base_path:
                Absolute path to the directory which will store files
            :param relative_path:
                Relative path from the directory. Will be prepended to the file name for uploaded files.
                Flask-Admin uses `urlparse.urljoin` to generate resulting filename, so make sure you have
                trailing slash.
            :param namegen:
                Function that will generate filename from the model and uploaded file object.
                Please note, that model is "dirty" model object, before it was committed to database.

                For example::

                    import os.path as op

                    def prefix_name(obj, file_data):
                        parts = op.splitext(file_data.filename)
                        return secure_filename('file-%s%s' % parts)

                    class MyForm(BaseForm):
                        upload = FileUploadField('File', namegen=prefix_name)

            :param allowed_extensions:
                List of allowed extensions. If not provided, will allow any file.
            :param max_size:
                Tuple of (width, height, force) or None. If provided, Flask-Admin will
                resize image to the desired size.
            :param thumbgen:
                Thumbnail filename generation function. All thumbnails will be saved as JPEG files,
                so there's no need to keep original file extension.

                For example::

                    import os.path as op

                    def thumb_name(filename):
                        name, _ = op.splitext(filename)
                        return secure_filename('%s-thumb.jpg' % name)

                    class MyForm(BaseForm):
                        upload = ImageUploadField('File', thumbgen=prefix_name)

            :param thumbnail_size:
                Tuple or (width, height, force) values. If not provided, thumbnail won't be created.

                Width and height is in pixels. If `force` is set to `True`, will try to fit image into dimensions and
                keep aspect ratio, otherwise will just resize to target size.
            :param url_relative_path:
                Relative path from the root of the static directory URL. Only gets used when generating
                preview image URLs.

                For example, your model might store just file names (`relative_path` set to `None`), but
                `base_path` is pointing to subdirectory.
            :param endpoint:
                Static endpoint for images. Used by widget to display previews. Defaults to 'static'.
        """
        # Check if PIL is installed
        if Image is None:
            raise ImportError('PIL library was not found')

        self.max_size = max_size
        self.thumbnail_fn = thumbgen or thumbgen_filename
        self.thumbnail_size = thumbnail_size
        self.endpoint = endpoint
        self.image = None
        self.url_relative_path = url_relative_path

        if not allowed_extensions:
            allowed_extensions = ('gif', 'jpg', 'jpeg', 'png', 'tiff')

        super(ImageUploadField, self).__init__(label, validators,
                                               base_path=base_path,
                                               relative_path=relative_path,
                                               namegen=namegen,
                                               allowed_extensions=allowed_extensions,
                                               permission=permission,
                                               **kwargs)

    def pre_validate(self, form):
        super(ImageUploadField, self).pre_validate(form)

        if self.data and isinstance(self.data, FileStorage):
            try:
                self.image = Image.open(self.data)
            except Exception as e:
                raise ValidationError('Invalid image: %s' % e)

    # Deletion
    def _delete_file(self, filename):
        super(ImageUploadField, self)._delete_file(filename)

        self._delete_thumbnail(filename)

    def _delete_thumbnail(self, filename):
        path = self._get_path(self.thumbnail_fn(filename))

        if op.exists(path):
            os.remove(path)

    # Saving
    def _save_file(self, data, filename):
        path = self._get_path(filename)

        if not op.exists(op.dirname(path)):
            os.makedirs(os.path.dirname(path), self.permission)

        # Figure out format
        filename, format = self._get_save_format(filename, self.image)

        if self.image and (self.image.format != format or self.max_size):
            if self.max_size:
                image = self._resize(self.image, self.max_size)
            else:
                image = self.image

            self._save_image(image, self._get_path(filename), format)
        else:
            data.seek(0)
            data.save(path)

        self._save_thumbnail(data, filename, format)

        return filename

    def _save_thumbnail(self, data, filename, format):
        if self.image and self.thumbnail_size:
            path = self._get_path(self.thumbnail_fn(filename))

            self._save_image(self._resize(self.image, self.thumbnail_size),
                             path,
                             format)

    def _resize(self, image, size):
        (width, height, force) = size

        if image.size[0] > width or image.size[1] > height:
            if force:
                return ImageOps.fit(self.image, (width, height), Image.ANTIALIAS)
            else:
                thumb = self.image.copy()
                thumb.thumbnail((width, height), Image.ANTIALIAS)
                return thumb

        return image

    def _save_image(self, image, path, format='JPEG'):
        if image.mode not in ('RGB', 'RGBA'):
            image = image.convert('RGBA')

        with open(path, 'wb') as fp:
            image.save(fp, format)

    def _get_save_format(self, filename, image):
        if image.format not in self.keep_image_formats:
            name, ext = op.splitext(filename)
            filename = '%s.jpg' % name
            return filename, 'JPEG'

        return filename, image.format

# Helpers
def namegen_filename(obj, file_data):
    """
        Generate secure filename for uploaded file.
    """
    return secure_filename(file_data.filename)

def thumbgen_filename(filename):
    """
        Generate thumbnail name from filename.
    """
    name, ext = op.splitext(filename)
    return '%s_thumb%s' % (name, ext)
<|endoftext|>


<commit_before>#!/usr/bin/env python
import sys
from optparse import OptionParser
import logging

import django
from django.conf import settings

logging.disable(logging.CRITICAL)

if not settings.configured:
    datacash_settings = {}
    try:
        from integration import *
    except ImportError:
        datacash_settings.update({
            'DATACASH_HOST': 'testserver.datacash.com',
            'DATACASH_CLIENT': '',
            'DATACASH_PASSWORD': '',
            'DATACASH_CURRENCY': 'GBP',
            'DATACASH_USE_CV2AVS': True,
        })
    else:
        for key, value in locals().items():
            if key.startswith('DATACASH'):
                datacash_settings[key] = value

    from oscar.defaults import *
    for key, value in locals().items():
        if key.startswith('OSCAR'):
            datacash_settings[key] = value
    datacash_settings['OSCAR_EAGER_ALERTS'] = False

    from oscar import get_core_apps

    sandbox_settings = {
        'DATABASES': {
            'default': {
                'ENGINE': 'django.db.backends.sqlite3',
            }
        },
        'INSTALLED_APPS': [
            'django.contrib.auth',
            'django.contrib.admin',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.sites',
            'datacash',
        ] + get_core_apps(),
        'DEBUG': False,
        'SITE_ID': 1,
        'ROOT_URLCONF': 'tests.urls',
        'NOSE_ARGS': ['-s'],
        'HAYSTACK_CONNECTIONS': {
            'default': {
                'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
            },
        },
        'MIDDLEWARE_CLASSES': (
                'django.middleware.common.CommonMiddleware',
                'django.contrib.sessions.middleware.SessionMiddleware',
                'django.middleware.csrf.CsrfViewMiddleware',
                'django.contrib.auth.middleware.AuthenticationMiddleware',
                'django.contrib.messages.middleware.MessageMiddleware',
                'oscar.apps.basket.middleware.BasketMiddleware',
        ),
    }
    if django.VERSION < (1,7):
        sandbox_settings['INSTALLED_APPS'] += ['south']
    sandbox_settings.update(datacash_settings)
    settings.configure(**sandbox_settings)

# Needs to be here to avoid missing SETTINGS env var
from django_nose import NoseTestSuiteRunner

def run_tests(*test_args):
    if 'south' in settings.INSTALLED_APPS:
        from south.management.commands import patch_for_test_db_setup
        patch_for_test_db_setup()

    if not test_args:
        test_args = ['tests']

    # Run tests
    test_runner = NoseTestSuiteRunner(verbosity=1)

    num_failures = test_runner.run_tests(test_args)

    if num_failures > 0:
        sys.exit(num_failures)

def generate_migration():
    from south.management.commands.schemamigration import Command
    com = Command()
    com.handle(app='datacash', initial=True)

if __name__ == '__main__':
    parser = OptionParser()
    (options, args) = parser.parse_args()
    run_tests(*args)
<commit_msg>Python 3: items() now returns an iterator<commit_after>#!/usr/bin/env python
import sys
from optparse import OptionParser
import logging

import django
from django.conf import settings

logging.disable(logging.CRITICAL)

if not settings.configured:
    datacash_settings = {}
    try:
        from integration import *
    except ImportError:
        datacash_settings.update({
            'DATACASH_HOST': 'testserver.datacash.com',
            'DATACASH_CLIENT': '',
            'DATACASH_PASSWORD': '',
            'DATACASH_CURRENCY': 'GBP',
            'DATACASH_USE_CV2AVS': True,
        })
    else:
        for key, value in list(locals().items()):
            if key.startswith('DATACASH'):
                datacash_settings[key] = value

    from oscar.defaults import *
    for key, value in list(locals().items()):
        if key.startswith('OSCAR'):
            datacash_settings[key] = value
    datacash_settings['OSCAR_EAGER_ALERTS'] = False

    from oscar import get_core_apps

    sandbox_settings = {
        'DATABASES': {
            'default': {
                'ENGINE': 'django.db.backends.sqlite3',
            }
        },
        'INSTALLED_APPS': [
            'django.contrib.auth',
            'django.contrib.admin',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.sites',
            'datacash',
        ] + get_core_apps(),
        'DEBUG': False,
        'SITE_ID': 1,
        'ROOT_URLCONF': 'tests.urls',
        'NOSE_ARGS': ['-s'],
        'HAYSTACK_CONNECTIONS': {
            'default': {
                'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
            },
        },
        'MIDDLEWARE_CLASSES': (
                'django.middleware.common.CommonMiddleware',
                'django.contrib.sessions.middleware.SessionMiddleware',
                'django.middleware.csrf.CsrfViewMiddleware',
                'django.contrib.auth.middleware.AuthenticationMiddleware',
                'django.contrib.messages.middleware.MessageMiddleware',
                'oscar.apps.basket.middleware.BasketMiddleware',
        ),
    }
    if django.VERSION < (1,7):
        sandbox_settings['INSTALLED_APPS'] += ['south']
    sandbox_settings.update(datacash_settings)
    settings.configure(**sandbox_settings)

# Needs to be here to avoid missing SETTINGS env var
from django_nose import NoseTestSuiteRunner

def run_tests(*test_args):
    if 'south' in settings.INSTALLED_APPS:
        from south.management.commands import patch_for_test_db_setup
        patch_for_test_db_setup()

    if not test_args:
        test_args = ['tests']

    # Run tests
    test_runner = NoseTestSuiteRunner(verbosity=1)

    num_failures = test_runner.run_tests(test_args)

    if num_failures > 0:
        sys.exit(num_failures)

def generate_migration():
    from south.management.commands.schemamigration import Command
    com = Command()
    com.handle(app='datacash', initial=True)

if __name__ == '__main__':
    parser = OptionParser()
    (options, args) = parser.parse_args()
    run_tests(*args)
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function

import os
import sys
import subprocess

import pytest

def main():
    try:
        sys.argv.remove('--nolint')
    except ValueError:
        run_lint = True
    else:
        run_lint = False

    try:
        sys.argv.remove('--lintonly')
    except ValueError:
        run_tests = True
    else:
        run_tests = False

    if run_tests:
        exit_on_failure(tests_main())

    if run_lint:
        exit_on_failure(run_flake8())
        exit_on_failure(run_2to3())
        exit_on_failure(run_isort())

def tests_main():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    sys.path.insert(0, "tests")

    # pytest was picking up test_utilities as a doctest
    sys.argv.append("--ignore=docs")
    return pytest.main()

def run_flake8():
    print('Running flake8 code linting')
    ret = subprocess.call(['flake8', 'django_mysql', 'tests'])
    print('flake8 failed' if ret else 'flake8 passed')
    return ret

def run_2to3():
    print('Running 2to3 checks')
    output = subprocess.check_output([
        '2to3',
        '-f', 'idioms',
        '-f', 'isinstance',
        '-f', 'set_literal',
        '-f', 'tuple_params',
        'django_mysql', 'tests'
    ]).strip()
    if output > '':
        print('2to3 failed')
        print(output)
        return 1
    else:
        print('2to3 passed')
        return 0

def run_isort():
    print('Running isort check')
    ret = subprocess.call([
        'isort', '--recursive', '--check-only', 'django_mysql', 'tests'
    ])
    return ret

def exit_on_failure(ret, message=None):
    if ret:
        sys.exit(ret)

if __name__ == '__main__':
    main()
<commit_msg>isort lint report diff of what needs changing<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function

import os
import sys
import subprocess

import pytest

def main():
    try:
        sys.argv.remove('--nolint')
    except ValueError:
        run_lint = True
    else:
        run_lint = False

    try:
        sys.argv.remove('--lintonly')
    except ValueError:
        run_tests = True
    else:
        run_tests = False

    if run_tests:
        exit_on_failure(tests_main())

    if run_lint:
        exit_on_failure(run_flake8())
        exit_on_failure(run_2to3())
        exit_on_failure(run_isort())

def tests_main():
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
    sys.path.insert(0, "tests")

    # pytest was picking up test_utilities as a doctest
    sys.argv.append("--ignore=docs")
    return pytest.main()

def run_flake8():
    print('Running flake8 code linting')
    ret = subprocess.call(['flake8', 'django_mysql', 'tests'])
    print('flake8 failed' if ret else 'flake8 passed')
    return ret

def run_2to3():
    print('Running 2to3 checks')
    output = subprocess.check_output([
        '2to3',
        '-f', 'idioms',
        '-f', 'isinstance',
        '-f', 'set_literal',
        '-f', 'tuple_params',
        'django_mysql', 'tests'
    ]).strip()
    if output > '':
        print('2to3 failed')
        print(output)
        return 1
    else:
        print('2to3 passed')
        return 0

def run_isort():
    print('Running isort check')
    ret = subprocess.call([
        'isort', '--recursive', '--check-only', '--diff',
        'django_mysql', 'tests'
    ])
    return ret

def exit_on_failure(ret, message=None):
    if ret:
        sys.exit(ret)

if __name__ == '__main__':
    main()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from flask import request
from flask.views import MethodView
from werkzeug.wrappers import Response

from .utils import unpack

class Resource(MethodView):
    '''
    Represents an abstract RESTful resource.

    Concrete resources should extend from this class
    and expose methods for each supported HTTP method.
    If a resource is invoked with an unsupported HTTP method,
    the API will return a response with status 405 Method Not Allowed.
    Otherwise the appropriate method is called and passed all arguments
    from the url rule used when adding the resource to an Api instance.
    See :meth:`~flask_restplus.Api.add_resource` for details.
    '''

    representations = None
    method_decorators = []

    def __init__(self, api, *args, **kwargs):
        self.api = api

    def dispatch_request(self, *args, **kwargs):
        # Taken from flask
        meth = getattr(self, request.method.lower(), None)
        if meth is None and request.method == 'HEAD':
            meth = getattr(self, 'get', None)
        assert meth is not None, 'Unimplemented method %r' % request.method

        for decorator in self.method_decorators:
            meth = decorator(meth)

        self.validate_payload(meth)

        resp = meth(*args, **kwargs)

        if isinstance(resp, Response):  # There may be a better way to test
            return resp

        representations = self.representations or {}

        mediatype = request.accept_mimetypes.best_match(representations, default=None)
        if mediatype in representations:
            data, code, headers = unpack(resp)
            resp = representations[mediatype](data, code, headers)
            resp.headers['Content-Type'] = mediatype
            return resp

        return resp

    def validate_payload(self, func):
        '''Perform a payload validation on expected model if necessary'''
        if hasattr(func, '__apidoc__'):
            model = func.__apidoc__.get('body')
            validate = func.__apidoc__.get('validate', False)
            if model and validate and hasattr(model, 'validate'):
                # TODO: proper content negotiation
                data = request.get_json()
                model.validate(data, self.api.refresolver)
<commit_msg>Fix resource doc hiding<commit_after># -*- coding: utf-8 -*-
from __future__ import unicode_literals

from flask import request
from flask.views import MethodView
from werkzeug.wrappers import Response

from .utils import unpack

class Resource(MethodView):
    '''
    Represents an abstract RESTful resource.

    Concrete resources should extend from this class
    and expose methods for each supported HTTP method.
    If a resource is invoked with an unsupported HTTP method,
    the API will return a response with status 405 Method Not Allowed.
    Otherwise the appropriate method is called and passed all arguments
    from the url rule used when adding the resource to an Api instance.
    See :meth:`~flask_restplus.Api.add_resource` for details.
    '''

    representations = None
    method_decorators = []

    def __init__(self, api, *args, **kwargs):
        self.api = api

    def dispatch_request(self, *args, **kwargs):
        # Taken from flask
        meth = getattr(self, request.method.lower(), None)
        if meth is None and request.method == 'HEAD':
            meth = getattr(self, 'get', None)
        assert meth is not None, 'Unimplemented method %r' % request.method

        for decorator in self.method_decorators:
            meth = decorator(meth)

        self.validate_payload(meth)

        resp = meth(*args, **kwargs)

        if isinstance(resp, Response):  # There may be a better way to test
            return resp

        representations = self.representations or {}

        mediatype = request.accept_mimetypes.best_match(representations, default=None)
        if mediatype in representations:
            data, code, headers = unpack(resp)
            resp = representations[mediatype](data, code, headers)
            resp.headers['Content-Type'] = mediatype
            return resp

        return resp

    def validate_payload(self, func):
        '''Perform a payload validation on expected model if necessary'''
        if getattr(func, '__apidoc__', False) != False:
            model = func.__apidoc__.get('body')
            validate = func.__apidoc__.get('validate', False)
            if model and validate and hasattr(model, 'validate'):
                # TODO: proper content negotiation
                data = request.get_json()
                model.validate(data, self.api.refresolver)
<|endoftext|>