uchicago-sg/caravel

View on GitHub
caravel/controllers/custom_fields.py

Summary

Maintainability
A
1 hr
Test Coverage
from google.appengine.api import users

import re
import threading

import wtforms
from wtforms import widgets, ValidationError

from markupsafe import Markup
from flask import request, session
from werkzeug import FileStorage

from caravel import model, utils


class OnlyValid(object):

    """Validates that the credential is correct."""

    def __call__(self, form, field):
        """Ensure that the Principal passed in is valid."""

        if not field.data or not field.data.valid:
            raise ValidationError("Invalid credential.")


class MatchesPrincipal(threading.local):

    """Ensures that only the existing Principal is used."""

    def __init__(self):
        self.principal = None

    def __call__(self, form, field):
        """Ensure that only the given user is used for authentication."""

        if not self.principal:
            raise Exception("No principal specified.")

        if not field.data or not field.data.can_act_as(self.principal):
            if not field.data:
                raise ValidationError("Please enter an email.")
            elif field.data.email == self.principal.email:
                raise ValidationError("Please sign in with your CNetID.")
            else:
                raise ValidationError("Please use the same account for edits.")


class AffiliationField(wtforms.SelectField):
    """
    An AffiliationField is one that represents an individual's
    affiliation.
    """

    def __init__(self, label, **kwargs):
        kwargs["choices"] = ([("", "(Select Affiliation...)")] +
                             model.Listing.AFFILIATIONS)
        kwargs["default"] = lambda: session.get("affiliation")

        super(AffiliationField, self).__init__(label, **kwargs)

    def __call__(self, **kwargs):
        kwargs["data-affiliation"] = "value"
        return super(AffiliationField, self).__call__(**kwargs)

    def process_formdata(self, values):
        """Overrides the value sent in the form if asked."""

        super(AffiliationField, self).process_formdata(values)

        if self.data:
            session["affiliation"] = self.data


class PrincipalField(wtforms.StringField):
    """A PrincipalField is one that represents an email address."""

    def __init__(self, label, **kwargs):
        """Initialize this field, adding the requires_validation field."""

        if "validated" in kwargs:
            self.requires_validation = kwargs["requires_validation"]
            del kwargs["requires_validation"]
        else:
            self.requires_validation = False

        return super(PrincipalField, self).__init__(label, **kwargs)

    def process_formdata(self, values):
        """Overrides the value sent in the form if asked."""

        # Extract data into a principal
        email = None
        if not self.requires_validation and values:
            email = values[0]
        self.data = utils.Principal.from_request(request, email)

        # Persist email in a session variable
        if self.data:
            session["email"] = self.data.email

    def _value(self):
        """Returns the user for this user."""
        return self.data.email if self.data else session.get("email", "")

    def __call__(self, **kwargs):
        """Renders a control with a Sign In button."""

        if users.get_current_user():
            kwargs["type"] = "hidden"

            return Markup(
                "<p class='form-control-static'><strong>{}</strong> "
                "(<a href='{}'>Logout</a>)</p>"
                "{}"
            ).format(
                users.get_current_user().email(),
                users.create_logout_url(request.url.encode("utf-8")),
                super(PrincipalField, self).__call__(**kwargs)
            )

        else:
            kwargs["class"] = kwargs.get("class", "") + " inline-user-control"
            kwargs["placeholder"] = "enter email..."

            # Only add -or [ enter email ] if we can.
            alternative = ""
            for validator in self.validators:
                if isinstance(validator, OnlyValid):
                    break
            else:
                alternative = Markup(
                    "<span class='accessibility'> or </span>{}").format(
                    super(PrincipalField, self).__call__(**kwargs), )

            return Markup(
                "<div><a href='{}' class='signin btn btn-success'>Sign in with "
                "Google Apps</a>{}</div>"
            ).format("/oshalogin", alternative)
            # users.create_login_url(request.url)


class PhotoField(wtforms.StringField):

    """A PhotoField represents a photo object stored in Cloud Storage."""

    def process_formdata(self, values):
        """Process an external photo value."""

        for value in values:
            if not value:
                continue

            if not isinstance(value, FileStorage):
                self.data = model.Photo(value)
                break
            elif value.filename:
                self.data = model.Photo.from_image_data(value.read())
                break
        else:
            self.data = None

    def _value(self):
        """Return the photo as a serializable value"""
        return self.data.path if self.data else ""

    def widget(self, field, **kwargs):
        """Render this file input as a removable file upload control."""

        if self.data:
            kwargs["type"] = "hidden"

            return Markup("""
                <div class="thumbnail">
                  {} <img src="{}"/>
                  <div class="caption">
                    <a class="btn btn-danger"
                       onclick="removeThumbnail(this, {!r})">Remove</a>
                  </div>
                </div>
            """).format(
                super(PhotoField, self).widget(field, **kwargs),
                self.data.public_url("large"), str(field.name)
            )
        else:
            kwargs["type"] = "file"
            return super(PhotoField, self).widget(field, **kwargs)