hack4impact/maps4all

View on GitHub
app/descriptor/views.py

Summary

Maintainability
D
3 days
Test Coverage
from flask import abort, flash, render_template, redirect, url_for, request
from flask.ext.login import login_required
from sqlalchemy.exc import IntegrityError
from wtforms.fields import SelectField
from flask_wtf.file import InputRequired

from .forms import (
    AddDescriptorOptionValueForm,
    EditDescriptorNameForm,
    EditDescriptorOptionValueForm,
    EditDescriptorSearchableForm,
    FixAllResourceOptionValueForm,
    NewDescriptorForm,
    ChangeRequiredOptionDescriptorForm,
    RequiredOptionDescriptorMissingForm
)
from . import descriptor
from .. import db
from ..models import (
    Descriptor,
    OptionAssociation,
    Resource,
    RequiredOptionDescriptor,
    RequiredOptionDescriptorConstructor
)


@descriptor.route('/')
@login_required
def index():
    """View all resource descriptors."""
    descriptors = Descriptor.query.all()
    return render_template('descriptor/index.html',
                           descriptors=descriptors)


@descriptor.route('/new-descriptor', methods=['GET', 'POST'])
@login_required
def new_descriptor():
    """Create a new descriptor."""
    form = NewDescriptorForm()
    for i in range(10):
        form.option_values.append_entry()
    if form.validate_on_submit():
        values = []
        for v in form.option_values.data:
            if v is not None and len(v) != 0:
                values.append(v)
        descriptor = Descriptor(
            name=form.name.data,
            values=values,
            is_searchable=form.is_searchable.data,
            dtype=form.desc_type.data
        )
        if Descriptor.query.filter(Descriptor.name == form.name.data).first() \
                is not None:
            flash('Descriptor {} already exists.'.format(descriptor.name),
                  'form-error')
        else:
            db.session.add(descriptor)
            try:
                db.session.commit()
                flash('Descriptor {} successfully created'
                      .format(descriptor.name),
                      'form-success')
                return redirect(url_for('descriptor.new_descriptor'))
            except IntegrityError:
                db.session.rollback()
                flash('Database error occurred. Please try again.',
                      'form-error')
    return render_template('descriptor/new_descriptor.html', form=form)


@descriptor.route('/<int:desc_id>', methods=['GET', 'POST'])
@login_required
def descriptor_info(desc_id):
    """Display the descriptor info."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0
    return render_template('descriptor/manage_descriptor.html',
                           desc=descriptor, is_option=is_option)


@descriptor.route('/<int:desc_id>/name', methods=['GET', 'POST'])
@login_required
def edit_name(desc_id):
    """Edit a descriptor's name."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0
    old_name = descriptor.name
    form = EditDescriptorNameForm()
    if form.validate_on_submit():
        if Descriptor.query.filter(Descriptor.name == form.name.data).first() \
                is not None:
            if old_name == form.name.data:
                flash('No change was made', 'form-error')
            else:
                flash('Descriptor {} already exists.'.format(form.name.data),
                    'form-error')
            return render_template('descriptor/manage_descriptor.html',
                                   desc=descriptor, form=form,
                                   is_option=is_option)
        descriptor.name = form.name.data
        db.session.add(descriptor)
        try:
            db.session.commit()
            flash('Name for descriptor {} successfully changed to {}.'.format(old_name, descriptor.name),
                'form-success')
        except IntegrityError:
            db.session.rollback()
            flash('Database error occurred. Please try again.', 'form-error')
        return render_template('descriptor/manage_descriptor.html',
                               desc=descriptor, is_option=is_option)
    form.name.data = descriptor.name
    return render_template('descriptor/manage_descriptor.html',
                           desc=descriptor, form=form, is_option=is_option)


@descriptor.route('/<int:desc_id>/searchable', methods=['GET', 'POST'])
@login_required
def edit_searchable(desc_id):
    """Edit a descriptor's searchability."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0
    old_value = descriptor.is_searchable
    form = EditDescriptorSearchableForm()
    if form.validate_on_submit():
        descriptor.is_searchable = form.is_searchable.data
        db.session.add(descriptor)
        try:
            db.session.commit()
            flash('Searchability successfully changed from {} to {}.'
                  .format(old_value, descriptor.is_searchable),
                  'form-success')
            return render_template('descriptor/manage_descriptor.html',
                                   desc=descriptor, is_option=is_option)
        except IntegrityError:
            db.session.rollback()
            flash('Database error occurred. Please try again.', 'form-error')
    form.is_searchable.data = old_value
    return render_template('descriptor/manage_descriptor.html',
                           desc=descriptor, form=form, is_option=is_option)


@descriptor.route('/<int:desc_id>/option-values', methods=['GET', 'POST'])
@login_required
def change_option_values_index(desc_id):
    """Shows the page to add/edit/remove a descriptor's option values."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0
    if not is_option:
        abort(404)
    form = AddDescriptorOptionValueForm()
    if form.validate_on_submit():
        values = descriptor.values[:]
        if form.value.data in values:
            flash('Value {} already exists'.format(form.value.data),
                  'form-error')
            return render_template('descriptor/manage_descriptor.html',
                                   desc=descriptor, is_option=is_option,
                                   desc_id=desc_id, form=form)

        values.append(form.value.data)
        descriptor.values = values
        db.session.add(descriptor)
        try:
            db.session.commit()
            flash('Value {} successfully added.'.format(form.value.data),
                  'form-success')
            form.value.data = ''
        except IntegrityError:
            db.session.rollback()
            flash('Database error occurred. Please try again.', 'form-error')
    return render_template('descriptor/manage_descriptor.html',
                           desc=descriptor, is_option=is_option,
                           desc_id=desc_id, form=form)


@descriptor.route('/<int:desc_id>/option-values/edit/<int:option_index>',
                  methods=['GET', 'POST'])
@login_required
def edit_option_value(desc_id, option_index):
    """Edit a descriptor's selected option value."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0
    form = EditDescriptorOptionValueForm()
    if not is_option:
        abort(404)
    if form.validate_on_submit():
        old_value = descriptor.values[option_index]
        values = descriptor.values[:]
        values[option_index] = form.value.data
        descriptor.values = values
        db.session.add(descriptor)
        try:
            db.session.commit()
            flash('Value {} for descriptor {} successfully changed to {}.'
                  .format(old_value, descriptor.name,
                          descriptor.values[option_index]),
                  'form-success')
            return redirect(url_for('descriptor.descriptor_info',
                                    desc_id=desc_id))
        except IntegrityError:
            db.session.rollback()
            flash('Database error occurred. Please try again.', 'form-error')
    else:
        form.value.data = descriptor.values[option_index]
    return render_template('descriptor/manage_descriptor.html',
                           desc=descriptor, is_option=is_option,
                           desc_id=desc_id, form=form)


@descriptor.route('/<int:desc_id>/option-values/remove/<int:option_index>',
                  methods=['GET', 'POST'])
@login_required
def remove_option_value(desc_id, option_index):
    """Remove a descriptor's selected option value."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None or len(descriptor.values) == 0:
        abort(404)
    old_value = descriptor.values[option_index]

    if len(descriptor.values) == 1:
        flash('Descriptor {} only has one value.'.format(descriptor.name),
              'form-error')
        return redirect(url_for('descriptor.change_option_values_index',
                                desc_id=desc_id))

    option_assocs = OptionAssociation.query.filter(db.and_(
        OptionAssociation.descriptor_id == desc_id,
        OptionAssociation.option == option_index
        )).all()

    choice_names, choices = generate_option_choices(descriptor, option_index)

    # If no resources are affected, just remove the option value.
    if len(option_assocs) == 0:
        remove_value_from_db(descriptor, choice_names, old_value)
        return redirect(url_for('descriptor.descriptor_info', desc_id=desc_id))

    form = FixAllResourceOptionValueForm()

    if form.validate_on_submit():
        for oa in option_assocs:
            db.session.delete(oa)

        if remove_value_from_db(descriptor, choice_names, old_value):
            return redirect(url_for('descriptor.descriptor_info',
                                    desc_id=desc_id))
        else:
            flash('Database error occurred. Please try again', 'form-error')
    return render_template('descriptor/confirm_resources.html',
                           option_assocs=option_assocs, desc_id=desc_id,
                           desc=descriptor, option_index=option_index,
                           form=form)

def generate_option_choices(descriptor, removed_index):
    """Helper function to generate the new options + indices"""
    choice_names = (descriptor.values[:removed_index] +
                    descriptor.values[removed_index + 1:])
    choices = []
    for i in range(len(choice_names)):
        choices.append((i, choice_names[i]))
    return choice_names, choices


def remove_value_from_db(descriptor, values, old_value):
    """Helper function to update the values an option can take."""
    descriptor.values = values
    db.session.add(descriptor)
    try:
        db.session.commit()
        flash('Value {} for descriptor {} successfully removed.'
              .format(old_value, descriptor.name),
              'form-success')
        return True
    except IntegrityError:
        db.session.rollback()
        flash('Database error occurred. Please try again.', 'form-error')
        return False


@descriptor.route('/<int:desc_id>/delete_request')
@login_required
def delete_descriptor_request(desc_id):
    """Shows the page for deletion of a descriptor."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0
    req_opt_desc = RequiredOptionDescriptor.query.all()
    is_required = False
    if req_opt_desc:
        req_opt_desc = req_opt_desc[0]
        is_required = req_opt_desc.descriptor_id == descriptor.id
    return render_template('descriptor/manage_descriptor.html',
                           desc=descriptor, is_option=is_option,
                           is_required=is_required)


@descriptor.route('/<int:desc_id>/delete')
@login_required
def delete_descriptor(desc_id):
    """Deletes a descriptor."""
    descriptor = Descriptor.query.get(desc_id)
    if descriptor is None:
        abort(404)
    is_option = len(descriptor.values) != 0

    db.session.delete(descriptor)
    try:
        db.session.commit()
        flash('Successfully deleted descriptor %s.' % descriptor.name,
              'success')
    except IntegrityError:
            db.session.rollback()
            flash('Database error occurred. Please try again.', 'form-error')
            return render_template('descriptor/manage_descriptor.html',
                                   desc=descriptor, is_option=is_option)
    return redirect(url_for('descriptor.index'))

@descriptor.route('/change-required-option-descriptor', methods=['GET', 'POST'])
@login_required
def change_required_option_descriptor():
    descriptors = Descriptor.query.all()
    choices = []
    for d in descriptors:
        if d.values:
            choices.append((d.name, d.name))
    req_opt_desc = RequiredOptionDescriptor.query.all()[0]
    current_name = ""
    if req_opt_desc.descriptor_id != -1:
        descriptor = Descriptor.query.filter_by(
            id=req_opt_desc.descriptor_id
        ).first()
        if descriptor is not None:
            current_name = descriptor.name
    if current_name != "":
        setattr(
            ChangeRequiredOptionDescriptorForm,
            'descriptor',
            SelectField(
                'Option Descriptor',
                choices=choices,
                validators=[InputRequired()],
                default=current_name)
        )
        form = ChangeRequiredOptionDescriptorForm()
        if form.validate_on_submit():
            RequiredOptionDescriptorConstructor.query.delete()
            db.session.commit()
            desc = Descriptor.query.filter_by(
                name=form.descriptor.data
            ).first()
            if desc is not None:
                req_opt_desc_const = RequiredOptionDescriptorConstructor(name=desc.name, values=desc.values)
                db.session.add(req_opt_desc_const)
                db.session.commit()
            return redirect(url_for('descriptor.review_required_option_descriptor'))
    else:
        form = None
    return render_template(
            'descriptor/change_required_option_descriptor.html',
            form=form
    )

@descriptor.route('/review-required-option-descriptor', methods=['GET', 'POST'])
@login_required
def review_required_option_descriptor():
    req_opt_desc_const = RequiredOptionDescriptorConstructor.query.all()[0]
    form = RequiredOptionDescriptorMissingForm()
    missing_resources = []
    resources = Resource.query.all()
    descriptor = Descriptor.query.filter_by(
                    name=req_opt_desc_const.name
                 ).first()
    for r in resources:
        if descriptor is None:
            missing_resources.append(r.name)
        else:
            option_association = OptionAssociation.query.filter_by(
                                    resource_id = r.id,
                                    descriptor_id=descriptor.id
                                 ).first()
            if option_association is None:
                missing_resources.append(r.name)
    if request.method == 'POST':
        if len(form.resources.data) < len(missing_resources):
            flash('Error: You must choose an option for each resource. Please try again.', 'form-error')
        else:
            for j, r_name in enumerate(missing_resources):
                resource = Resource.query.filter_by(
                    name=r_name
                ).first()
                if resource is not None:
                    for val in form.resources.data[j]:
                        new_association = OptionAssociation(
                                            resource_id=resource.id,
                                            descriptor_id=descriptor.id,
                                            option=descriptor.values.index(val),
                                            resource=resource,
                                            descriptor=descriptor)
                        db.session.add(new_association)
            RequiredOptionDescriptor.query.delete()
            req_opt_desc = RequiredOptionDescriptor(descriptor_id=descriptor.id)
            db.session.add(req_opt_desc)
            db.session.commit()
            return redirect(url_for('descriptor.index'))
    for j, r_name in enumerate(missing_resources):
        form.resources.append_entry()
        form.resources[j].label = r_name
        form.resources[j].choices = [(v, v) for v in req_opt_desc_const.values]
    return render_template('descriptor/review_required_option_descriptor.html', form=form)