def connections(org_label, project_label, connection_label):
    """ connection configuration management
    /organizations/aquaya/projects/water-quality/connections
        : viewing a list of this project's connections
    /organizations/aquaya/projects/water-quality/connections?create=true
        : create a new connection config, immediately redirect to editing
    /organizations/aquaya/projects/water-quality/connections/ivrhub
        : view the 'ivrhub' connection config
    /organizations/aquaya/projects/water-quality/connections/ivrhub?edit=true
        : edit a connection config; accepts GET or POST
    """
    user = User.objects(email=session["email"])[0]

    orgs = Organization.objects(label=org_label)
    if not orgs:
        flash('Organization "%s" not found, sorry!' % org_label, "warning")
        return redirect(url_for("organizations"))
    org = orgs[0]

    # permission-check
    if org not in user.organizations and not user.admin_rights:
        app.logger.error(
            "%s tried to view a project but was \
            denied for want of admin rights"
            % session["email"]
        )
        abort(404)

    # find the project
    projects = Project.objects(label=project_label, organization=org)
    if not projects:
        flash('Project "%s" not found, sorry!' % project_label, "warning")
        return redirect(url_for("organizations", org_label=org.label))
    project = projects[0]

    if request.method == "POST":
        # we have a connection_label
        connections = Connection.objects(label=connection_label, project=project)
        if not connections:
            abort(404)
        connection = connections[0]

        form_type = request.form.get("form_type", "")

        if form_type == "commcare_configuration":
            connection.credentials = {
                "username": request.form.get("username", ""),
                "password": request.form.get("password", ""),
            }
            connection.domain = request.form.get("domain", "")
            connection.export_tag = request.form.get("export_tag", "")

            include_admin = request.form.get("include_admin", "")
            if include_admin == "true":
                connection.include_admin = True
            else:
                connection.include_admin = False

            include_metadata = request.form.get("include_metadata", "")
            if include_metadata == "true":
                connection.include_metadata = True
            else:
                connection.include_metadata = False

            connection.save()
            flash("CommCare settings saved successfully.", "success")
            url = url_for(
                "connections",
                org_label=org.label,
                project_label=project.label,
                connection_label=connection.label,
                edit="true",
            )
            return redirect(url + "#commcare")

        elif form_type == "schedule":
            old_interval = connection.schedule.interval
            new_interval = {
                "type": request.form.get("schedule_type", ""),
                "at": request.form.get("send_at", ""),
                "on_day": request.form.get("send_on_day", ""),
            }

            if old_interval != new_interval:
                repeating_task = connection.schedule
                # update the interval
                repeating_task.interval = new_interval
                repeating_task.save()
                # invalidate the running job and schedule the new one
                update_scheduled_connection(connection)

                flash("Connection interval saved successfully.", "success")

            url = url_for(
                "connections",
                org_label=org.label,
                project_label=project.label,
                connection_label=connection.label,
                edit="true",
            )
            return redirect(url + "#schedule")

        elif form_type == "admin":
            # delete the connection
            # keeping the associated messages as they're an important log
            name = connection.name
            utilities.delete_connection(connection, session["email"])

            # see if, given this deletion, the dupes are still dupes
            unduped_count = utilities.recheck_duplicate_entries(project)
            if unduped_count:
                flash(
                    "%s entries were automatically added to the project \
                    as they are no longer duplicates"
                    % unduped_count,
                    "info",
                )

            flash('Connection "%s" was deleted successfully' % name, "success")
            return redirect(url_for("connections", org_label=org.label, project_label=project.label))

        else:
            # bad 'form_type'
            abort(404)

        try:
            connection.save()
            flash("Changes saved successfully.", "success")
            return redirect(
                url_for(
                    "connections",
                    org_label=org.label,
                    project_label=project.label,
                    connection_label=connection.label,
                    edit="true",
                )
            )
        except:
            app.logger.error("%s experienced an error saving info about %s" % (session["email"], request.form["name"]))
            flash(
                "Error saving changes - make sure connection names are \
                unique.",
                "error",
            )
            return redirect(
                url_for(
                    "connections",
                    org_label=org.label,
                    project_label=project.label,
                    connection_label=connection_label,
                    edit="true",
                )
            )

    if request.method == "GET":
        if connection_label:
            connections = Connection.objects(label=connection_label, project=project)
            if not connections:
                app.logger.error(
                    "%s tried to access a connection that does \
                    not exist"
                    % session["email"]
                )
                flash('Connection "%s" not found, sorry!' % connection_label, "warning")
                return redirect(url_for("connections", org_label=org.label, project_label=project.label))
            connection = connections[0]

            if request.args.get("edit", "") == "true":
                current_time = datetime.datetime.utcnow()

                return render_template("connection_edit.html", connection=connection, current_time=current_time)

            elif request.args.get("fire", "") == "true":
                if ready_to_connect(connection):
                    # enqueue the job immediately
                    redis_config = app.config["REDIS_CONFIG"]
                    use_connection(Redis(redis_config["host"], redis_config["port"], password=redis_config["password"]))
                    queue = Queue()
                    job = queue.enqueue_call(func=connect_to_source, args=(str(connection.id),), timeout=900)
                    flash(
                        "Connecting to data source, this may take a few \
                        moments.  Check the connection log for updates.",
                        "info",
                    )

                    # save the id of this job
                    connection.schedule.update(set__next_task_id=job.id)

                else:
                    flash("This connection is not properly configured.", "error")

                return redirect(
                    url_for(
                        "connections",
                        org_label=org.label,
                        project_label=project.label,
                        connection_label=connection_label,
                    )
                )

            else:
                connection_log = ConnectionLogEntry.objects(project=project).order_by("-completion_time")

                return render_template("connection.html", connection=connection, connection_log=connection_log)

        if request.args.get("create", "") == "true":
            # create a new connection
            # first create a repeating task object
            new_repeater = RepeatingTask()
            new_repeater.save()

            service = request.args.get("service", "")

            if service == "commcare":
                # see if one already exists
                cxns = CommCareConnection.objects(project=project)
                if cxns:
                    flash(
                        "A data connection to CommCare already exists for \
                        this project.  Create a new project if you want to \
                        connect to another CommCare source."
                    )
                    return redirect(url_for("connections", org_label=org.label, project_label=project.label))

                # create a new one
                new_connection = CommCareConnection(
                    description="A connection to data at commcarehq.org",
                    schedule=new_repeater,
                    label="commcare",
                    project=project,
                    name="CommCare",
                    service_type="commcare",
                )
                new_connection.save()
                app.logger.info("CommCare connection created by %s" % session["email"])
                flash(
                    "CommCare connection initialized.  Please change the \
                    default values to complete the link.",
                    "info",
                )

            else:
                flash("We are still working on connecting to that service.", "info")
                return redirect(url_for("connections", org_label=org.label, project_label=project.label))

            # redirect to the editing screen
            return redirect(
                url_for(
                    "connections",
                    org_label=org.label,
                    project_label=project.label,
                    connection_label=new_connection.label,
                    edit="true",
                )
            )

        # no connection in particular was specified so show them all
        cxns = CommCareConnection.objects(project=project)
        commcare_connection = None
        if cxns:
            commcare_connection = cxns[0]

        return render_template("project_connections.html", project=project, commcare_connection=commcare_connection)
Example #2
0
def uploads(o_label, p_label, u_label):
    ''' uploadin
    /organizations/aquaya/projects/water-quality/uploads?create=true
        : create a new upload, immediately redirect to editing
    /organizations/aquaya/projects/water-quality/uploads/may-2012
        : view an upload
    /organizations/aquaya/projects/water-quality/uploads/may-2012?edit=true
        : edit an upload; accepts GET or POST
    '''
    # can't wrap the line through a route or spaces are injected
    # hence this silliness:
    org_label = o_label
    project_label = p_label
    upload_label = u_label

    user = User.objects(email=session['email'])[0]

    orgs = Organization.objects(label=org_label)
    if not orgs:
        flash('Organization "%s" not found, sorry!' % org_label, 'warning')
        return redirect(url_for('organizations'))
    org = orgs[0]
    
    # permission-check
    if org not in user.organizations and not user.admin_rights:
        app.logger.error('%s tried to view a project but was \
            denied for want of admin rights' % session['email'])
        abort(404)
    
    projects = Project.objects(label=project_label, organization=org) 
    if not projects:
        flash('Project "%s" not found, sorry!' % project_label, 'warning')
        return redirect(url_for('organizations', org_label=org.label))
    project = projects[0]

    if request.method == 'GET':
        if not upload_label and request.args.get('create', '') == 'true':
            # create a new upload

            # CSRF validation
            token = request.args.get('token', '')
            if not verify_token(token):
                abort(403)

            try:
                name = 'upl-%s' % utilities.generate_random_string(6)
                new_upload = Upload(
                    label = name.lower()
                    , name = name
                    , project = project
                )
                new_upload.save()

                project.update(push__uploads=new_upload)
                app.logger.info('upload created by %s' % session['email'])
                flash('please select a file and make other edits', 'warning')
            except:
                app.logger.error('upload creation failed for %s' % \
                    session['email'])
                flash('There was an error, sorry :/', 'error')
                return redirect(url_for('projects', org_label=org.label
                    , project_label=project.label))
            
            # redirect to the editing screen
            return redirect(url_for('uploads', o_label=org.label
                , p_label=project.label, u_label=new_upload.label
                , edit='true'))

        elif not upload_label:
            # could show projects uploads but punting for now
            unique_entries = Entry.objects(project=project, unique=True
                , visible=True)
            duplicate_entries = Entry.objects(project=project
                , unique=False)
            hidden_entries = Entry.objects(project=project, visible=False
                , unique=True)
            
            upload_entry_counts = {}
            for upload in project.uploads:
                upload_entry_counts[upload.id] = Entry.objects(upload=upload).count()
            
            return render_template('project_uploads.html', project=project
                , unique_entries=unique_entries
                , duplicate_entries=duplicate_entries
                , upload_entry_counts=upload_entry_counts
                , hidden_entries=hidden_entries)
        
        else:
            # we have an upload label
            uploads = Upload.objects(label=upload_label, project=project) 
            if not uploads:
                flash('Upload "%s" not found, sorry!' % upload_label
                    , 'warning')
                return redirect(url_for('projects', org_label=org.label
                    , project_label=project.label))
            upload = uploads[0]

            if request.args.get('edit', '') == 'true':
                # show the editor

                # queue up any flashed messages
                # these get set during the worker's processing of a file
                worker_messages = upload.worker_messages
                for message in worker_messages:
                    flash(message['message'], message['status'])
                upload.update(set__worker_messages = [])

                return render_template('upload_edit.html', upload=upload
                        , number_of_pages=0)

            else:
                if not upload.filename:
                    # need to add one so redirect to editing
                    flash('please specify a file for this upload', 'warning')
                    return redirect(url_for('uploads', o_label=org.label
                        , p_label=project.label, u_label=upload.label
                        , edit='true'))
                else:

                    # things are still in progress
                    if not upload.s3_key:
                        flash('Your file is being processed in the '
                            'background.  Refresh the page to see updates.'
                            , 'info')
                        return render_template('upload.html', upload=upload
                                , number_of_pages=0)

                    # otherwise upload is complete, show the data
                    # pagination
                    total_entries = Entry.objects(project=project
                        , upload=upload).count()
                    entries_per_page = 10
                    pages = utilities.calculate_pages(total_entries
                        , entries_per_page=entries_per_page)

                    # validate the requested page
                    current_page = utilities.validate_page_request(
                        request.args.get('page', 1), pages)

                    # get the sorted entries
                    entries = utilities.query_entries(project
                        , upload = upload
                        , skip = (entries_per_page * (current_page - 1))
                        , limit = entries_per_page)
                    
                    hidden_entries_count = Entry.objects(upload=upload
                        , visible=False).count()

                    # queue up any flashed messages
                    # these get set during the worker's processing of a file
                    worker_messages = upload.worker_messages
                    for message in worker_messages:
                        flash(message['message'], message['status'])
                    upload.update(set__worker_messages = [])

                    return render_template('upload.html'
                        , upload=upload
                        , entries=entries
                        , total_entries = total_entries
                        , hidden_entries_count=hidden_entries_count
                        , current_page = current_page
                        , number_of_pages = pages)

    elif request.method == 'POST':
        # we have an upload label
        uploads = Upload.objects(label=upload_label, project=project)
        if not uploads:
            abort(404)
        upload = uploads[0]

        form_type = request.form.get('form_type', '')
        if form_type == 'info':

            if upload.name != request.form.get('name', ''):
                name = request.form.get('name', '')
                upload.update(set__name = name)

                uploads = Upload.objects(project=project).only('label')
                labels = [u.label for u in uploads]
                upload.update(set__label = 
                        utilities.generate_label(name, labels))

                upload.project.update(set__update_time =
                        datetime.datetime.utcnow())
                upload.reload()
            
            if upload.description != request.form.get('description', ''):
                upload.update(set__description =
                        request.form.get('description', ''))
                upload.project.update(set__update_time =
                        datetime.datetime.utcnow())

            if request.files.get('data_file', ''):
                data_file = request.files.get('data_file')
                
                try:
                    filename = uploaded_data.save(data_file)
                    if '..' in filename or filename.startswith('/'):
                        app.logger.info('%s tried to upload a malicious file \
                            "%s"' % (session['email'], filename))
                        flash('bad filename, sorry :/', 'error')
                        return redirect(url_for('uploads'
                            , o_label=org.label, p_label=project.label
                            , u_label=upload.label, edit='true'))

                    absolute_filename = uploaded_data.path(filename)
                
                except UploadNotAllowed:
                    app.logger.info('%s tried to upload an unsupported file'
                        % session['email'])
                    flash('we currently only support .xls files, sorry :/'
                        , 'error')
                    return redirect(url_for('uploads'
                        , o_label=org.label, p_label=project.label
                        , u_label=upload.label, edit='true'))
                
                upload.update(set__filename = 
                        os.path.basename(absolute_filename))
                upload.update(set__uploaded_by = user)

                # enqueue upload-processing
                redis_config = app.config['REDIS_CONFIG']
                use_connection(Redis(redis_config['host'], redis_config['port']
                        , password=redis_config['password']))
                queue = Queue()
                queue.enqueue(process_uploaded_file, upload.id,
                        absolute_filename)
                    
        elif form_type == 'admin':
            # delete the upload
            name = upload.name
            # pull out the upload from the parent project first
            project.update(pull__uploads=upload)
            
            # delete associated entries, remove from system, remove from s3
            deleted_entries_count = utilities.delete_upload(upload
                , session['email'])
            flash('upload "%s" was deleted successfully; %s entries deleted \
                as well' % (name, deleted_entries_count), 'success')
            
            # see if, given this deletion, the dupes are still dupes
            unduped_count = utilities.recheck_duplicate_entries(project)
            if unduped_count:
                flash('%s entries were automatically added to the project \
                    as they are no longer duplicates' % unduped_count, 'info')

            return redirect(url_for('uploads', o_label=org.label
                , p_label=project.label))
        
        else:
            # bad 'form_type'
            abort(404)
       
        try:
            upload.save()
            project.save()
        except:
            app.logger.error('%s experienced an error saving info about the \
                upload %s' % (session['email'], request.form['name']))
            flash('error, make sure names are unique', 'error')
            return redirect(url_for('projects'
                , org_label=upload.project.organization.label
                , project_label=upload.project.label))
        
        return redirect(url_for('uploads'
            , o_label=upload.project.organization.label, p_label=project.label
            , u_label=upload.label))