| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 | # encoding: utf-8import datetimeimport jsonfrom django import formsfrom django.core.urlresolvers import reversefrom django.forms import Widgetfrom django.utils import formatsfrom django.utils.html import format_htmlfrom django.utils.safestring import mark_safefrom django.utils.text import capfirstfrom googleapiclient.discovery import buildimport httplib2from jet.dashboard.modules import DashboardModulefrom oauth2client.client import flow_from_clientsecrets, OAuth2Credentials, AccessTokenRefreshError, Storagefrom django.utils.translation import ugettext_lazy as _from django.conf import settingsfrom django.utils.encoding import force_texttry:    from django.utils.encoding import force_unicodeexcept ImportError:    from django.utils.encoding import force_text as force_unicodetry:    from django.forms.utils import flatattexcept ImportError:    from django.forms.util import flatattJET_MODULE_GOOGLE_ANALYTICS_CLIENT_SECRETS_FILE = getattr(    settings,    'JET_MODULE_GOOGLE_ANALYTICS_CLIENT_SECRETS_FILE',    '')class ModuleCredentialStorage(Storage):    def __init__(self, module):        self.module = module    def locked_get(self):        pass    def locked_put(self, credentials):        pass    def locked_delete(self):        pass    def get(self):        try:            settings = json.loads(self.module.settings)            credential = settings['credential']            return OAuth2Credentials.from_json(credential)        except (ValueError, KeyError):            return None    def put(self, credentials):        self.module.update_settings({'credential': credentials.to_json()})    def delete(self):        self.module.pop_settings(('credential',))class GoogleAnalyticsClient:    credential = None    analytics_service = None    def __init__(self, storage=None, redirect_uri=None):        self.FLOW = flow_from_clientsecrets(            JET_MODULE_GOOGLE_ANALYTICS_CLIENT_SECRETS_FILE,            scope='https://www.googleapis.com/auth/analytics.readonly',            redirect_uri=redirect_uri        )        if storage is not None:            credential = storage.get()            credential.set_store(storage)            self.set_credential(credential)    def get_oauth_authorize_url(self, state=''):        self.FLOW.params['state'] = state        authorize_url = self.FLOW.step1_get_authorize_url()        return authorize_url    def set_credential(self, credential):        self.credential = credential        self.set_analytics_service(self.credential)    def set_credential_from_request(self, request):        self.set_credential(self.FLOW.step2_exchange(request.GET))    def set_analytics_service(self, credential):        http = httplib2.Http()        http = credential.authorize(http)        self.analytics_service = build('analytics', 'v3', http=http)    def api_profiles(self):        if self.analytics_service is None:            return None, None        try:            profiles = self.analytics_service.management().profiles().list(                accountId='~all',                webPropertyId='~all'            ).execute()            return profiles['items'], None        except (TypeError, KeyError) as e:            return None, e    def api_ga(self, profile_id, date1, date2, group=None):        if self.analytics_service is None:            return None, None        if group == 'day':            dimensions = 'ga:date'        elif group == 'week':            dimensions = 'ga:year,ga:week'        elif group == 'month':            dimensions = 'ga:year,ga:month'        else:            dimensions = ''        try:            data = self.analytics_service.data().ga().get(                ids='ga:' + profile_id,                start_date=date1.strftime('%Y-%m-%d'),                end_date=date2.strftime('%Y-%m-%d'),                metrics='ga:users,ga:sessions,ga:pageviews',                dimensions=dimensions            ).execute()            return data, None        except TypeError as e:            return None, eclass CredentialWidget(Widget):    module = None    def render(self, name, value, attrs=None):        if value and len(value) > 0:            link = '<a href="%s">%s</a>' % (                reverse('jet-dashboard:google-analytics-revoke', kwargs={'pk': self.module.model.pk}),                force_text(_('Revoke access'))            )        else:            link = '<a href="%s">%s</a>' % (                reverse('jet-dashboard:google-analytics-grant', kwargs={'pk': self.module.model.pk}),                force_text(_('Grant access'))            )        attrs = self.build_attrs({            'type': 'hidden',            'name': 'credential',        })        attrs['value'] = force_unicode(value) if value else ''        return format_html('%s<input{} />' % link, flatatt(attrs))class GoogleAnalyticsSettingsForm(forms.Form):    credential = forms.CharField(label=_('Access'), widget=CredentialWidget)    counter = forms.ChoiceField(label=_('Counter'))    period = forms.ChoiceField(label=_('Statistics period'), choices=(        (0, _('Today')),        (6, _('Last week')),        (30, _('Last month')),        (31 * 3 - 1, _('Last quarter')),        (364, _('Last year')),    ))    def set_module(self, module):        self.fields['credential'].widget.module = module        self.set_counter_choices(module)    def set_counter_choices(self, module):        counters = module.counters()        if counters is not None:            self.fields['counter'].choices = (('', '-- %s --' % force_text(_('none'))),)            self.fields['counter'].choices.extend(map(lambda x: (x['id'], x['websiteUrl']), counters))        else:            label = force_text(_('grant access first')) if module.credential is None else force_text(_('counters loading failed'))            self.fields['counter'].choices = (('', '-- %s -- ' % label),)class GoogleAnalyticsChartSettingsForm(GoogleAnalyticsSettingsForm):    show = forms.ChoiceField(label=_('Show'), choices=(        ('ga:users', capfirst(_('users'))),        ('ga:sessions', capfirst(_('sessions'))),        ('ga:pageviews', capfirst(_('views'))),    ))    group = forms.ChoiceField(label=_('Group'), choices=(        ('day', _('By day')),        ('week', _('By week')),        ('month', _('By month')),    ))class GoogleAnalyticsPeriodVisitorsSettingsForm(GoogleAnalyticsSettingsForm):    group = forms.ChoiceField(label=_('Group'), choices=(        ('day', _('By day')),        ('week', _('By week')),        ('month', _('By month')),    ))class GoogleAnalyticsBase(DashboardModule):    settings_form = GoogleAnalyticsSettingsForm    ajax_load = True    contrast = True    period = None    credential = None    counter = None    error = None    storage = None    def __init__(self, title=None, period=None, **kwargs):        kwargs.update({'period': period})        super(GoogleAnalyticsBase, self).__init__(title, **kwargs)    def settings_dict(self):        return {            'period': self.period,            'credential': self.credential,            'counter': self.counter        }    def load_settings(self, settings):        try:            self.period = int(settings.get('period'))        except TypeError:            self.period = 0        self.credential = settings.get('credential')        self.storage = ModuleCredentialStorage(self.model)        self.counter = settings.get('counter')    def init_with_context(self, context):        raise NotImplementedError('subclasses of GoogleAnalytics must provide a init_with_context() method')    def counters(self):        try:            client = GoogleAnalyticsClient(self.storage)            profiles, exception = client.api_profiles()            return profiles        except Exception:            return None    def get_grouped_date(self, data, group):        if group == 'week':            date = datetime.datetime.strptime(                '%s-%s-%s' % (data['ga_year'], data['ga_week'], '0'),                '%Y-%W-%w'            )        elif group == 'month':            date = datetime.datetime.strptime(data['ga_year'] + data['ga_month'], '%Y%m')        else:            date = datetime.datetime.strptime(data['ga_date'], '%Y%m%d')        return date    def format_grouped_date(self, data, group):        date = self.get_grouped_date(data, group)        if group == 'week':            date = u'%s — %s' % (                (date - datetime.timedelta(days=6)).strftime('%d.%m'),                date.strftime('%d.%m')            )        elif group == 'month':            date = date.strftime('%b, %Y')        else:            date = formats.date_format(date, 'DATE_FORMAT')        return date    def counter_attached(self):        if self.credential is None:            self.error = mark_safe(_('Please <a href="%s">attach Google account and choose Google Analytics counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))            return False        elif self.counter is None:            self.error = mark_safe(_('Please <a href="%s">select Google Analytics counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))            return False        else:            return True    def api_ga(self, group=None):        if self.counter_attached():            date1 = datetime.datetime.now() - datetime.timedelta(days=self.period)            date2 = datetime.datetime.now()            try:                client = GoogleAnalyticsClient(self.storage)                result, exception = client.api_ga(self.counter, date1, date2, group)                if exception is not None:                        raise exception                return result            except Exception as e:                error = _('API request failed.')                if isinstance(e, AccessTokenRefreshError):                    error += _(' Try to <a href="%s">revoke and grant access</a> again') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk})                self.error = mark_safe(error)class GoogleAnalyticsVisitorsTotals(GoogleAnalyticsBase):    """    Google Analytics widget that shows total number of users, sessions and viewers for a particular period of time.    Period may be following: Today, Last week, Last month, Last quarter, Last year    """    title = _('Google Analytics visitors totals')    template = 'jet.dashboard/modules/google_analytics_visitors_totals.html'    #: Which period should be displayed. Allowed values - integer of days    period = None    def __init__(self, title=None, period=None, **kwargs):        kwargs.update({'period': period})        super(GoogleAnalyticsVisitorsTotals, self).__init__(title, **kwargs)    def init_with_context(self, context):        result = self.api_ga()        if result is not None:            try:                self.children.append({'title': _('users'), 'value': result['totalsForAllResults']['ga:users']})                self.children.append({'title': _('sessions'), 'value': result['totalsForAllResults']['ga:sessions']})                self.children.append({'title': _('views'), 'value': result['totalsForAllResults']['ga:pageviews']})            except KeyError:                self.error = _('Bad server response')class GoogleAnalyticsVisitorsChart(GoogleAnalyticsBase):    """    Google Analytics widget that shows users/sessions/viewer chart for a particular period of time.    Data is grouped by day, week or month    Period may be following: Today, Last week, Last month, Last quarter, Last year    """    title = _('Google Analytics visitors chart')    template = 'jet.dashboard/modules/google_analytics_visitors_chart.html'    style = 'overflow-x: auto;'    #: Which period should be displayed. Allowed values - integer of days    period = None    #: What data should be shown. Possible values: ``ga:users``, ``ga:sessions``, ``ga:pageviews``    show = None    #: Sets grouping of data. Possible values: ``day``, ``week``, ``month``    group = None    settings_form = GoogleAnalyticsChartSettingsForm    class Media:        js = ('jet.dashboard/vendor/chart.js/Chart.min.js', 'jet.dashboard/dashboard_modules/google_analytics.js')    def __init__(self, title=None, period=None, show=None, group=None, **kwargs):        kwargs.update({'period': period, 'show': show, 'group': group})        super(GoogleAnalyticsVisitorsChart, self).__init__(title, **kwargs)    def settings_dict(self):        settings = super(GoogleAnalyticsVisitorsChart, self).settings_dict()        settings['show'] = self.show        settings['group'] = self.group        return settings    def load_settings(self, settings):        super(GoogleAnalyticsVisitorsChart, self).load_settings(settings)        self.show = settings.get('show')        self.group = settings.get('group')    def init_with_context(self, context):        result = self.api_ga(self.group)        if result is not None:            try:                for data in result['rows']:                    row_data = {}                    i = 0                    for column in result['columnHeaders']:                        row_data[column['name'].replace(':', '_')] = data[i]                        i += 1                    date = self.get_grouped_date(row_data, self.group)                    self.children.append((date, row_data[self.show.replace(':', '_')]))            except KeyError:                self.error = _('Bad server response')class GoogleAnalyticsPeriodVisitors(GoogleAnalyticsBase):    """    Google Analytics widget that shows users, sessions and viewers for a particular period of time.    Data is grouped by day, week or month    Period may be following: Today, Last week, Last month, Last quarter, Last year    """    title = _('Google Analytics period visitors')    template = 'jet.dashboard/modules/google_analytics_period_visitors.html'    #: Which period should be displayed. Allowed values - integer of days    period = None    #: Sets grouping of data. Possible values: ``day``, ``week``, ``month``    group = None    contrast = False    settings_form = GoogleAnalyticsPeriodVisitorsSettingsForm    def __init__(self, title=None, period=None, group=None, **kwargs):        kwargs.update({'period': period, 'group': group})        super(GoogleAnalyticsPeriodVisitors, self).__init__(title, **kwargs)    def settings_dict(self):        settings = super(GoogleAnalyticsPeriodVisitors, self).settings_dict()        settings['group'] = self.group        return settings    def load_settings(self, settings):        super(GoogleAnalyticsPeriodVisitors, self).load_settings(settings)        self.group = settings.get('group')    def init_with_context(self, context):        result = self.api_ga(self.group)        if result is not None:            try:                for data in reversed(result['rows']):                    row_data = {}                    i = 0                    for column in result['columnHeaders']:                        row_data[column['name'].replace(':', '_')] = data[i]                        i += 1                    date = self.format_grouped_date(row_data, self.group)                    self.children.append((date, row_data))            except KeyError:                self.error = _('Bad server response')
 |