gedankenstuecke/twitter-analyser

View on GitHub
users/views.py

Summary

Maintainability
A
2 hrs
Test Coverage
import json
import logging
import os
try:
    from urllib2 import HTTPError
except ImportError:
    from urllib.error import HTTPError

from django.conf import settings
from django.contrib.auth import login
from django.shortcuts import redirect, render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

import requests

from tweet_display.helper import get_file_url
from tweet_display.tasks import import_data

from .models import OpenHumansMember
from .forms import UploadFileForm

# Open Humans settings
OH_BASE_URL = settings.OH_BASE_URL
OH_API_BASE = OH_BASE_URL + '/api/direct-sharing'
OH_DELETE_FILES = OH_API_BASE + '/project/files/delete/'
OH_DIRECT_UPLOAD = OH_API_BASE + '/project/files/upload/direct/'
OH_DIRECT_UPLOAD_COMPLETE = OH_API_BASE + '/project/files/upload/complete/'

APP_BASE_URL = os.getenv('APP_BASE_URL', 'http://127.0.0.1:5000/users')
APP_PROJ_PAGE = 'https://www.openhumans.org/activity/twitter-archive-analyzer/'

# Set up logging.
logger = logging.getLogger(__name__)


def oh_get_member_data(token):
    """
    Exchange OAuth2 token for member data.
    """
    req = requests.get(
        '{}/api/direct-sharing/project/exchange-member/'.format(OH_BASE_URL),
        params={'access_token': token})
    if req.status_code == 200:
        return req.json()
    raise Exception('Status code {}'.format(req.status_code))
    return None


def oh_code_to_member(code):
    """
    Exchange code for token, use this to create and return OpenHumansMember.
    If a matching OpenHumansMember already exists in db, update and return it.
    """
    if settings.OH_CLIENT_SECRET and settings.OH_CLIENT_ID and code:
        data = {
            'grant_type': 'authorization_code',
            'redirect_uri': '{}/complete'.format(APP_BASE_URL),
            'code': code,
        }
        req = requests.post(
            '{}/oauth2/token/'.format(OH_BASE_URL),
            data=data,
            auth=requests.auth.HTTPBasicAuth(
                settings.OH_CLIENT_ID,
                settings.OH_CLIENT_SECRET
            ))
        data = req.json()
        if 'access_token' in data:
            oh_id = oh_get_member_data(
                data['access_token'])['project_member_id']
            try:
                oh_member = OpenHumansMember.objects.get(oh_id=oh_id)
                logger.debug('Member {} re-authorized.'.format(oh_id))
                oh_member.access_token = data['access_token']
                oh_member.refresh_token = data['refresh_token']
                oh_member.token_expires = OpenHumansMember.get_expiration(
                    data['expires_in'])
            except OpenHumansMember.DoesNotExist:
                oh_member = OpenHumansMember.create(
                    oh_id=oh_id,
                    access_token=data['access_token'],
                    refresh_token=data['refresh_token'],
                    expires_in=data['expires_in'])
                logger.debug('Member {} created.'.format(oh_id))
            oh_member.save()

            return oh_member
        elif 'error' in req.json():
            logger.debug('Error in token exchange: {}'.format(req.json()))
        else:
            logger.warning('Neither token nor error info in OH response!')
    else:
        logger.error('OH_CLIENT_SECRET or code are unavailable')
    return None


def delete_all_oh_files(oh_member):
    """
    Delete all current project files in Open Humans for this project member.
    """
    requests.post(
        OH_DELETE_FILES,
        params={'access_token': oh_member.get_access_token()},
        data={'project_member_id': oh_member.oh_id,
              'all_files': True})


def upload_file_to_oh(oh_member, filehandle, metadata):
    """
    This demonstrates using the Open Humans "large file" upload process.
    The small file upload process is simpler, but it can time out. This
    alternate approach is required for large files, and still appropriate
    for small files.
    This process is "direct to S3" using three steps: 1. get S3 target URL from
    Open Humans, 2. Perform the upload, 3. Notify Open Humans when complete.
    """
    # Remove any previous file - replace with this one.
    delete_all_oh_files(oh_member)

    # Get the S3 target from Open Humans.
    upload_url = '{}?access_token={}'.format(
        OH_DIRECT_UPLOAD, oh_member.get_access_token())
    req1 = requests.post(
        upload_url,
        data={'project_member_id': oh_member.oh_id,
              'filename': filehandle.name,
              'metadata': json.dumps(metadata)})
    if req1.status_code != 201:
        raise raise_http_error(upload_url, req1,
                               'Bad response when starting file upload.')

    # Upload to S3 target.
    req2 = requests.put(url=req1.json()['url'], data=filehandle)
    if req2.status_code != 200:
        raise raise_http_error(req1.json()['url'], req2,
                               'Bad response when uploading to target.')

    # Report completed upload to Open Humans.
    complete_url = ('{}?access_token={}'.format(
        OH_DIRECT_UPLOAD_COMPLETE, oh_member.get_access_token()))
    req3 = requests.post(
        complete_url,
        data={'project_member_id': oh_member.oh_id,
              'file_id': req1.json()['id']})
    if req3.status_code != 200:
        raise raise_http_error(complete_url, req2,
                               'Bad response when completing upload.')

    # print('Upload done: "{}" for member {}.'.format(
    #    os.path.basename(filehandle.name), oh_member.oh_id))


def raise_http_error(url, response, message):
    raise HTTPError(url, response.status_code, message, hdrs=None, fp=None)


def index(request):
    """
    Starting page for app.
    """
    context = {'client_id': settings.OH_CLIENT_ID,
               'oh_proj_page': settings.OH_ACTIVITY_PAGE,
               'inactive_project': settings.INACTIVE_PROJECT,
               'redirect_uri': settings.OH_REDIRECT_URI}
    if request.user.is_authenticated:
        return redirect('dashboard')
    return render(request, 'users/index.html', context=context)


def complete(request):
    """
    Receive user from Open Humans. Store data, start data upload task.
    """
    logger.debug("Received user returning from Open Humans.")

    form = None

    if request.method == 'GET':
        # Exchange code for token.
        # This creates an OpenHumansMember and associated User account.
        code = request.GET.get('code', '')
        oh_member = oh_code_to_member(code=code)
        if oh_member:
            # Log in the user.
            user = oh_member.user
            login(request, user,
                  backend='django.contrib.auth.backends.ModelBackend')
        elif not request.user.is_authenticated:
            logger.debug('Invalid code exchange. User returned to start page.')
            return redirect('/')
        else:
            oh_member = request.user.openhumansmember

        if get_file_url(oh_member.oh_id) is not None:
            return redirect('dashboard')

        form = UploadFileForm()
        context = {'oh_id': oh_member.oh_id,
                   'oh_member': oh_member,
                   'oh_proj_page': settings.OH_ACTIVITY_PAGE,
                   'form': form}
        return render(request, 'users/complete.html',
                      context=context)

    elif request.method == 'POST':
        form = UploadFileForm(request.POST, request.FILES)
        if form.is_valid():
            metadata = {'tags': ['twitter', 'twitter-archive'],
                        'description': 'Twitter achive file.'}
            upload_file_to_oh(
                request.user.openhumansmember,
                request.FILES['file'],
                metadata)
        else:
            logger.debug('INVALID FORM')
        import_data.delay(request.user.openhumansmember.oh_id)
        return redirect('dashboard')


def public_data(request):
    public_user_list = OpenHumansMember.objects.filter(
                        public=True).order_by(
                        'oh_id')
    paginator = Paginator(public_user_list, 20)  # Show 20 contacts per page
    page = request.GET.get('page')
    try:
        public_users = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        public_users = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        public_users = paginator.page(paginator.num_pages)
    return render(request, 'users/public_data.html',
                  {'public_users': public_users,
                   'section': 'public_data'})


def dashboard(request):
    """
    Give options to delete account, make data public/private,
    reupload archive, trigger new parsing of archive.
    """
    if request.user.is_authenticated:
        oh_member = request.user.openhumansmember
        has_data = bool(get_file_url(oh_member.oh_id))
        context = {'client_id': settings.OH_CLIENT_ID,
                   'oh_proj_page': settings.OH_ACTIVITY_PAGE,
                   'oh_member': oh_member,
                   'has_data': has_data,
                   'section': 'home'}

        return render(request, 'users/dashboard.html', context=context)
    return redirect("/")


def delete_account(request):
    if request.user.is_authenticated:
        oh_member = request.user.openhumansmember
        oh_member.delete()
        request.user.delete()
    return redirect("/")


def access_switch(request):
    if request.user.is_authenticated:
        oh_member = request.user.openhumansmember
        if oh_member.public:
            oh_member.public = False
        else:
            oh_member.public = True
        oh_member.save()
    return redirect('dashboard')


def regenerate_graphs(request):
    if request.method == 'POST' and request.user.is_authenticated:
        import_data.delay(request.user.openhumansmember.oh_id)
    return redirect('dashboard')


def upload_old(request):
    if request.user.is_authenticated:
        return render(request, 'users/upload_old.html')
    return redirect('dashboard')