Added SurveyRecordForm for viewing a SurveyRecord.
This is subclassed into GradeSurveyRecord form for the GradingProjectSurveys. This should replace the need for the read_only kwarg in SurveyTakeForm. A TODO to try and extract this has been added.
Fixes Issue 672.
Reviewed by: Lennard de Rijk
#!/usr/bin/env python## Copyright 2007 Google Inc.## 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## http://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.#"""Stub version of the images API."""import loggingimport StringIOtry: import PIL from PIL import _imaging from PIL import Imageexcept ImportError: import _imaging import Imagefrom google.appengine.api import apiproxy_stubfrom google.appengine.api import imagesfrom google.appengine.api.images import images_service_pbfrom google.appengine.runtime import apiproxy_errorsdef _ArgbToRgbaTuple(argb): """Convert from a single ARGB value to a tuple containing RGBA. Args: argb: Signed 32 bit integer containing an ARGB value. Returns: RGBA tuple. """ unsigned_argb = argb % 0x100000000 return ((unsigned_argb >> 16) & 0xFF, (unsigned_argb >> 8) & 0xFF, unsigned_argb & 0xFF, (unsigned_argb >> 24) & 0xFF)class ImagesServiceStub(apiproxy_stub.APIProxyStub): """Stub version of images API to be used with the dev_appserver.""" def __init__(self, service_name='images'): """Preloads PIL to load all modules in the unhardened environment. Args: service_name: Service name expected for all calls. """ super(ImagesServiceStub, self).__init__(service_name) Image.init() def _Dynamic_Composite(self, request, response): """Implementation of ImagesService::Composite. Based off documentation of the PIL library at http://www.pythonware.com/library/pil/handbook/index.htm Args: request: ImagesCompositeRequest, contains image request info. response: ImagesCompositeResponse, contains transformed image. """ width = request.canvas().width() height = request.canvas().height() color = _ArgbToRgbaTuple(request.canvas().color()) canvas = Image.new("RGBA", (width, height), color) sources = [] if (not request.canvas().width() or request.canvas().width() > 4000 or not request.canvas().height() or request.canvas().height() > 4000): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if not request.image_size(): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if not request.options_size(): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if request.options_size() > images.MAX_COMPOSITES_PER_REQUEST: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) for image in request.image_list(): sources.append(self._OpenImage(image.content())) for options in request.options_list(): if (options.anchor() < images.TOP_LEFT or options.anchor() > images.BOTTOM_RIGHT): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if options.source_index() >= len(sources) or options.source_index() < 0: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if options.opacity() < 0 or options.opacity() > 1: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) source = sources[options.source_index()] x_anchor = (options.anchor() % 3) * 0.5 y_anchor = (options.anchor() / 3) * 0.5 x_offset = int(options.x_offset() + x_anchor * (width - source.size[0])) y_offset = int(options.y_offset() + y_anchor * (height - source.size[1])) alpha = options.opacity() * 255 mask = Image.new("L", source.size, alpha) canvas.paste(source, (x_offset, y_offset), mask) response_value = self._EncodeImage(canvas, request.canvas().output()) response.mutable_image().set_content(response_value) def _Dynamic_Histogram(self, request, response): """Trivial implementation of ImagesService::Histogram. Based off documentation of the PIL library at http://www.pythonware.com/library/pil/handbook/index.htm Args: request: ImagesHistogramRequest, contains the image. response: ImagesHistogramResponse, contains histogram of the image. """ image = self._OpenImage(request.image().content()) img_format = image.format if img_format not in ("BMP", "GIF", "ICO", "JPEG", "PNG", "TIFF"): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.NOT_IMAGE) image = image.convert("RGBA") red = [0] * 256 green = [0] * 256 blue = [0] * 256 for pixel in image.getdata(): red[int((pixel[0] * pixel[3]) / 255)] += 1 green[int((pixel[1] * pixel[3]) / 255)] += 1 blue[int((pixel[2] * pixel[3]) / 255)] += 1 histogram = response.mutable_histogram() for value in red: histogram.add_red(value) for value in green: histogram.add_green(value) for value in blue: histogram.add_blue(value) def _Dynamic_Transform(self, request, response): """Trivial implementation of ImagesService::Transform. Based off documentation of the PIL library at http://www.pythonware.com/library/pil/handbook/index.htm Args: request: ImagesTransformRequest, contains image request info. response: ImagesTransformResponse, contains transformed image. """ original_image = self._OpenImage(request.image().content()) new_image = self._ProcessTransforms(original_image, request.transform_list()) response_value = self._EncodeImage(new_image, request.output()) response.mutable_image().set_content(response_value) def _EncodeImage(self, image, output_encoding): """Encode the given image and return it in string form. Args: image: PIL Image object, image to encode. output_encoding: ImagesTransformRequest.OutputSettings object. Returns: str with encoded image information in given encoding format. """ image_string = StringIO.StringIO() image_encoding = "PNG" if (output_encoding.mime_type() == images_service_pb.OutputSettings.JPEG): image_encoding = "JPEG" image = image.convert("RGB") image.save(image_string, image_encoding) return image_string.getvalue() def _OpenImage(self, image): """Opens an image provided as a string. Args: image: image data to be opened Raises: apiproxy_errors.ApplicationError if the image cannot be opened or if it is an unsupported format. Returns: Image containing the image data passed in. """ if not image: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.NOT_IMAGE) image = StringIO.StringIO(image) try: image = Image.open(image) except IOError: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_IMAGE_DATA) img_format = image.format if img_format not in ("BMP", "GIF", "ICO", "JPEG", "PNG", "TIFF"): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.NOT_IMAGE) return image def _ValidateCropArg(self, arg): """Check an argument for the Crop transform. Args: arg: float, argument to Crop transform to check. Raises: apiproxy_errors.ApplicationError on problem with argument. """ if not isinstance(arg, float): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if not (0 <= arg <= 1.0): raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) def _CalculateNewDimensions(self, current_width, current_height, req_width, req_height): """Get new resize dimensions keeping the current aspect ratio. This uses the more restricting of the two requested values to determine the new ratio. Args: current_width: int, current width of the image. current_height: int, current height of the image. req_width: int, requested new width of the image. req_height: int, requested new height of the image. Returns: tuple (width, height) which are both ints of the new ratio. """ width_ratio = float(req_width) / current_width height_ratio = float(req_height) / current_height if req_width == 0 or (width_ratio > height_ratio and req_height != 0): return int(height_ratio * current_width), req_height else: return req_width, int(width_ratio * current_height) def _Resize(self, image, transform): """Use PIL to resize the given image with the given transform. Args: image: PIL.Image.Image object to resize. transform: images_service_pb.Transform to use when resizing. Returns: PIL.Image.Image with transforms performed on it. Raises: BadRequestError if the resize data given is bad. """ width = 0 height = 0 if transform.has_width(): width = transform.width() if width < 0 or 4000 < width: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) if transform.has_height(): height = transform.height() if height < 0 or 4000 < height: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) current_width, current_height = image.size new_width, new_height = self._CalculateNewDimensions(current_width, current_height, width, height) return image.resize((new_width, new_height), Image.ANTIALIAS) def _Rotate(self, image, transform): """Use PIL to rotate the given image with the given transform. Args: image: PIL.Image.Image object to rotate. transform: images_service_pb.Transform to use when rotating. Returns: PIL.Image.Image with transforms performed on it. Raises: BadRequestError if the rotate data given is bad. """ degrees = transform.rotate() if degrees < 0 or degrees % 90 != 0: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) degrees %= 360 degrees = 360 - degrees return image.rotate(degrees) def _Crop(self, image, transform): """Use PIL to crop the given image with the given transform. Args: image: PIL.Image.Image object to crop. transform: images_service_pb.Transform to use when cropping. Returns: PIL.Image.Image with transforms performed on it. Raises: BadRequestError if the crop data given is bad. """ left_x = 0.0 top_y = 0.0 right_x = 1.0 bottom_y = 1.0 if transform.has_crop_left_x(): left_x = transform.crop_left_x() self._ValidateCropArg(left_x) if transform.has_crop_top_y(): top_y = transform.crop_top_y() self._ValidateCropArg(top_y) if transform.has_crop_right_x(): right_x = transform.crop_right_x() self._ValidateCropArg(right_x) if transform.has_crop_bottom_y(): bottom_y = transform.crop_bottom_y() self._ValidateCropArg(bottom_y) width, height = image.size box = (int(transform.crop_left_x() * width), int(transform.crop_top_y() * height), int(transform.crop_right_x() * width), int(transform.crop_bottom_y() * height)) return image.crop(box) def _ProcessTransforms(self, image, transforms): """Execute PIL operations based on transform values. Args: image: PIL.Image.Image instance, image to manipulate. trasnforms: list of ImagesTransformRequest.Transform objects. Returns: PIL.Image.Image with transforms performed on it. Raises: BadRequestError if we are passed more than one of the same type of transform. """ new_image = image if len(transforms) > images.MAX_TRANSFORMS_PER_REQUEST: raise apiproxy_errors.ApplicationError( images_service_pb.ImagesServiceError.BAD_TRANSFORM_DATA) for transform in transforms: if transform.has_width() or transform.has_height(): new_image = self._Resize(new_image, transform) elif transform.has_rotate(): new_image = self._Rotate(new_image, transform) elif transform.has_horizontal_flip(): new_image = new_image.transpose(Image.FLIP_LEFT_RIGHT) elif transform.has_vertical_flip(): new_image = new_image.transpose(Image.FLIP_TOP_BOTTOM) elif (transform.has_crop_left_x() or transform.has_crop_top_y() or transform.has_crop_right_x() or transform.has_crop_bottom_y()): new_image = self._Crop(new_image, transform) elif transform.has_autolevels(): logging.info("I'm Feeling Lucky autolevels will be visible once this " "application is deployed.") else: logging.warn("Found no transformations found to perform.") return new_image