Source code for map_app.lib

from __future__ import absolute_import

import json
import logging
import os
from abc import ABCMeta, abstractmethod
from contextlib import contextmanager
try:
    from functools import lru_cache
except ImportError:
    from repoze.lru import lru_cache
from tempfile import NamedTemporaryFile

from django.conf import settings
from django.shortcuts import get_object_or_404
from googleapiclient.discovery import build
from httplib2 import Http
from oauth2client import client
from oauth2client.contrib import xsrfutil
from oauth2client.contrib.django_util.storage import DjangoORMStorage

from .models import UserTokens, CredentialsModel

log = logging.getLogger(__name__)


def _build_service(http, service_name='fusiontables', version='v1'):
    return build(service_name, version, http=http,
                 developerKey=settings.GOOGLE_FUSION_TABLE_API_KEY)


def store_user_tokens(user, access_token, refresh_token):
    return UserTokens.objects.get_or_create(
        user=user,
        access_token=access_token,
        refresh_token=refresh_token,
    )


[docs]@contextmanager def verify_client_id_json(filename): """ Verify the required client_id.json values are set. A context manager to parse the client_id.json and set the missing values with the env vars. :param filename: Client id json filename. :type filename: str """ if not os.path.exists(filename): raise OSError('Path specified doesn\'t exists: ' + filename) fp = open(filename) client_id = json.load(fp) required_keys = ['client_id', 'project_id', 'client_secret'] for k in required_keys: if client_id['web'][k] == '': env_var = k.upper() if not os.environ.get(env_var): raise ValueError("Client ID json required key value" " not set in {} or missing env var {}" .format(filename, env_var)) else: client_id['web'][k] = os.environ.get(env_var) f = NamedTemporaryFile(mode='w', dir=os.path.dirname(filename), suffix='.json', delete=False) json.dump(client_id, f) f.seek(0) f.close() try: yield f.name finally: try: os.unlink(f.name) except OSError: pass
[docs]class FlowClient(object): """ FlowClient to manage credentials. """ def __init__(self, request, client_secret_json=settings.GOOGLE_OAUTH2_CLIENT_SECRETS_JSON, scope=settings.FUSION_TABLE_SCOPE, redirect_url=settings.OAUTH2_CLIENT_REDIRECT_PATH, ): with verify_client_id_json(client_secret_json) as client_secret: self.flow = client.flow_from_clientsecrets( filename=client_secret, scope=scope, redirect_uri=redirect_url) self.request = request self.user = self.request.user self.http = Http() def get_credential(self): # Read credentials storage = DjangoORMStorage( CredentialsModel, 'id', self.user, 'credential') return storage.get() def generate_token(self): token = xsrfutil.generate_token( settings.CLIENT_SECRET, self.user.id) self.flow.params['state'] = token return token def update_user_token_model(self, token, authorization_url): user_token = UserTokens(user=self.user, access_token=token, authorized_url=authorization_url) user_token.save() def credential_is_valid(self): credential = self.get_credential() # Credential not valid if None or credential.invalid == True if credential: return not credential.access_token_expired return False def get_authorization_url(self): if not self.credential_is_valid(): token = self.generate_token() authorization_url = self.flow.step1_get_authorize_url() self.update_user_token_model(token, authorization_url) return authorization_url def get_user_token(self): user_token = get_object_or_404(UserTokens, pk=self.user.id) return user_token.access_token def update_user_credential(self): # Create credential request = self.request credential = self.flow.step2_exchange(request.GET) storage = DjangoORMStorage(CredentialsModel, 'id', request.user, 'credential') # Write credential storage.put(credential) cred, created = CredentialsModel.objects.update_or_create( id=request.user, credential=credential) return cred, created def _authorize_http(self): if self.credential_is_valid(): credential = self.get_credential() self.http = credential.authorize(self.http) @lru_cache(maxsize=None) def get_service_and_table_id(self): self._authorize_http() # http is authorized with the user's Credentials and can be # used in API calls table_id = settings.FUSION_TABLE_ID service = _build_service(self.http) return service, table_id
[docs]class FusionTableMixin(object): """ Mixin to manage Interactions with google fusion table. """ __metaclass__ = ABCMeta @staticmethod def select_all_rows(service, table_id): return (service.query() .sql(sql='SELECT ROWID, address,' ' latitude, longitude,' ' computed_address FROM %s' % table_id).execute()) @staticmethod def select_all_addresses(service, table_id): return (service.query() .sql(sql='SELECT address FROM %s' % table_id).execute()) @staticmethod def delete_all_addresses(service, table_id): return service.query().sql( sql='DELETE FROM %s;' % table_id).execute() @staticmethod def delete_address_at_row(service, table_id, row_id): return ( service.query() .sql(sql='DELETE FROM %s WHERE ROWID = %d' % (table_id, row_id,)) .execute()) @classmethod def bulk_delete(cls, results, service, table_id): delete_query = 'DELETE FROM %s WHERE ROWID IN ({row_ids});' % table_id row_ids = [row.get('rowid') for row in results] return (service.query() .sql(sql=delete_query.format(row_ids=row_ids)) .execute()) @classmethod def bulk_save(cls, addresses, service, table_id): # limit insert to 60 rows for query in list(cls.generate_values(addresses, table_id))[:60]: service.query().sql(sql=query).execute() @classmethod def generate_values(cls, addresses, table_id): values_dict = {'table_id': table_id} insert_query = ("INSERT INTO {table_id} " "(address, latitude, longitude," " computed_address) VALUES " "\"('{address}', {latitude}," " {longitude}, '{computed_address}')\"") for address in addresses: values_dict.update(cls.address_model_to_dict(address)) yield insert_query.format(**values_dict) @classmethod def get_style(cls, service, table_id, style_id=1): style = (service.style() .get(tableId=table_id, styleId=style_id) .execute()) or {} return json.dumps(style) @classmethod def save(cls, address, service, table_id): values_dict = cls.address_model_to_dict(address) values_dict.update({'table_id': table_id}) if not cls.address_exists(address, service, table_id): return (service.query() .sql(sql="INSERT INTO {table_id} " "(address, latitude, longitude," " computed_address)" "VALUES \"('{address}', {latitude}, " "{longitude}, '{computed_address}')\"" .format(**values_dict)).execute()) else: log.debug("Saving already existing address.") @classmethod def address_exists(cls, address, service, table_id): query_dict = {'table_id': table_id, 'address': address.address} results = (service.query() .sql(sql="SELECT address FROM {table_id} " "WHERE address LIKE '%{address}%'" .format(**query_dict)).execute()) try: return next(cls._process_result(results)) except StopIteration: pass return None @classmethod @abstractmethod def get_service_and_table_id(cls): # This should be removed. # http = get_http_auth(settings.GOOGLE_SERVICE_ACCOUNT_KEY_FILE) # http is authorized with the user's Credentials and can be # used in API calls # table_id = settings.FUSION_TABLE_ID # service = _build_service(http) # return service, table_id pass @staticmethod def get_columns(results): return results.get('columns', []) @classmethod def _process_result(cls, results, columns=None, excludes=None): rows = results.get('rows', []) columns = columns or cls.get_columns(results) for row in rows: ret_dict = dict(zip(columns, row)) if excludes: for key in excludes: if key in ret_dict: del ret_dict[key] yield ret_dict
[docs] @classmethod def address_model_to_dict(cls, address): """ Convert the ``map_app.models.Address`` instance to a dict. :param address: The address model instance. :type address: ``map_app.models.Address`` :rtype: dict """ keys = ['address', 'latitude', 'longitude', 'computed_address'] values_dict = {} for key in keys: try: values_dict[key] = getattr(address, key, '') or '' except AttributeError: values_dict[key] = address.get(key, '') return values_dict