README.md edited
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,444 @@
|
||||
# encoding: utf-8
|
||||
import datetime
|
||||
import json
|
||||
from django import forms
|
||||
try:
|
||||
from django.core.urlresolvers import reverse
|
||||
except ImportError: # Django 1.11
|
||||
from django.urls import reverse
|
||||
|
||||
from django.forms import Widget
|
||||
from django.utils import formats
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
from googleapiclient.discovery import build
|
||||
import httplib2
|
||||
from jet.dashboard.modules import DashboardModule
|
||||
from oauth2client.client import flow_from_clientsecrets, OAuth2Credentials, AccessTokenRefreshError, Storage
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
try:
|
||||
from django.utils.encoding import force_unicode
|
||||
except ImportError:
|
||||
from django.utils.encoding import force_text as force_unicode
|
||||
|
||||
try:
|
||||
from django.forms.utils import flatatt
|
||||
except ImportError:
|
||||
from django.forms.util import flatatt
|
||||
|
||||
JET_MODULE_GOOGLE_ANALYTICS_CLIENT_SECRETS_FILE = getattr(
|
||||
settings,
|
||||
'JET_MODULE_GOOGLE_ANALYTICS_CLIENT_SECRETS_FILE',
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
class ModuleCredentialStorage(Storage):
|
||||
def __init__(self, module):
|
||||
super(ModuleCredentialStorage, self).__init__()
|
||||
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,
|
||||
prompt='consent'
|
||||
)
|
||||
|
||||
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, e
|
||||
|
||||
|
||||
class 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')
|
||||
@@ -0,0 +1,58 @@
|
||||
try:
|
||||
from django.core.urlresolvers import reverse
|
||||
except ImportError: # Django 1.11
|
||||
from django.urls import reverse
|
||||
|
||||
from django.conf.urls import url
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect
|
||||
from httplib2 import ServerNotFoundError
|
||||
from jet.dashboard.dashboard_modules.google_analytics import GoogleAnalyticsClient, ModuleCredentialStorage
|
||||
from jet.dashboard.models import UserDashboardModule
|
||||
from jet.dashboard import dashboard
|
||||
from django.http import HttpResponse
|
||||
from oauth2client.client import FlowExchangeError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
def google_analytics_grant_view(request, pk):
|
||||
redirect_uri = request.build_absolute_uri(reverse('jet-dashboard:google-analytics-callback'))
|
||||
client = GoogleAnalyticsClient(redirect_uri=redirect_uri)
|
||||
return redirect(client.get_oauth_authorize_url(pk))
|
||||
|
||||
|
||||
def google_analytics_revoke_view(request, pk):
|
||||
try:
|
||||
module = UserDashboardModule.objects.get(pk=pk)
|
||||
ModuleCredentialStorage(module).delete()
|
||||
return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
|
||||
except UserDashboardModule.DoesNotExist:
|
||||
return HttpResponse(_('Module not found'))
|
||||
|
||||
|
||||
def google_analytics_callback_view(request):
|
||||
module = None
|
||||
|
||||
try:
|
||||
state = request.GET['state']
|
||||
module = UserDashboardModule.objects.get(pk=state)
|
||||
|
||||
redirect_uri = request.build_absolute_uri(reverse('jet-dashboard:google-analytics-callback'))
|
||||
client = GoogleAnalyticsClient(redirect_uri=redirect_uri)
|
||||
client.set_credential_from_request(request)
|
||||
|
||||
ModuleCredentialStorage(module).put(client.credential)
|
||||
except (FlowExchangeError, ValueError, ServerNotFoundError):
|
||||
messages.error(request, _('API request failed.'))
|
||||
except KeyError:
|
||||
return HttpResponse(_('Bad arguments'))
|
||||
except UserDashboardModule.DoesNotExist:
|
||||
return HttpResponse(_('Module not found'))
|
||||
|
||||
return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
|
||||
|
||||
dashboard.urls.register_urls([
|
||||
url(r'^google-analytics/grant/(?P<pk>\d+)/$', google_analytics_grant_view, name='google-analytics-grant'),
|
||||
url(r'^google-analytics/revoke/(?P<pk>\d+)/$', google_analytics_revoke_view, name='google-analytics-revoke'),
|
||||
url(r'^google-analytics/callback/', google_analytics_callback_view, name='google-analytics-callback'),
|
||||
])
|
||||
@@ -0,0 +1,369 @@
|
||||
# encoding: utf-8
|
||||
import datetime
|
||||
import json
|
||||
from django import forms
|
||||
try:
|
||||
from django.core.urlresolvers import reverse
|
||||
except ImportError: # Django 1.11
|
||||
from django.urls import reverse
|
||||
|
||||
from django.forms import Widget
|
||||
from django.utils import formats
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import capfirst
|
||||
from jet.dashboard.modules import DashboardModule
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
try:
|
||||
from urllib import request
|
||||
from urllib.parse import urlencode
|
||||
from urllib.error import URLError, HTTPError
|
||||
except ImportError:
|
||||
import urllib2 as request
|
||||
from urllib2 import URLError, HTTPError
|
||||
from urllib import urlencode
|
||||
|
||||
JET_MODULE_YANDEX_METRIKA_CLIENT_ID = getattr(settings, 'JET_MODULE_YANDEX_METRIKA_CLIENT_ID', '')
|
||||
JET_MODULE_YANDEX_METRIKA_CLIENT_SECRET = getattr(settings, 'JET_MODULE_YANDEX_METRIKA_CLIENT_SECRET', '')
|
||||
|
||||
|
||||
class YandexMetrikaClient:
|
||||
OAUTH_BASE_URL = 'https://oauth.yandex.ru/'
|
||||
API_BASE_URL = 'https://api-metrika.yandex.ru/'
|
||||
CLIENT_ID = JET_MODULE_YANDEX_METRIKA_CLIENT_ID
|
||||
CLIENT_SECRET = JET_MODULE_YANDEX_METRIKA_CLIENT_SECRET
|
||||
|
||||
def __init__(self, access_token=None):
|
||||
self.access_token = access_token
|
||||
|
||||
def request(self, base_url, url, data=None, headers=None):
|
||||
url = '%s%s' % (base_url, url)
|
||||
|
||||
if data is not None:
|
||||
data = urlencode(data).encode()
|
||||
|
||||
if headers is None:
|
||||
headers = {}
|
||||
|
||||
req = request.Request(url, data, headers)
|
||||
|
||||
try:
|
||||
f = request.urlopen(req)
|
||||
result = f.read().decode('utf8')
|
||||
result = json.loads(result)
|
||||
except URLError as e:
|
||||
return None, e
|
||||
|
||||
return result, None
|
||||
|
||||
def get_oauth_authorize_url(self, state=''):
|
||||
return '%sauthorize' \
|
||||
'?response_type=code' \
|
||||
'&state=%s' \
|
||||
'&client_id=%s' % (self.OAUTH_BASE_URL, state, self.CLIENT_ID)
|
||||
|
||||
def oauth_request(self, url, data=None):
|
||||
return self.request(self.OAUTH_BASE_URL, url, data)
|
||||
|
||||
def oath_token_request(self, code):
|
||||
data = {
|
||||
'grant_type': 'authorization_code',
|
||||
'code': code,
|
||||
'client_id': self.CLIENT_ID,
|
||||
'client_secret': self.CLIENT_SECRET
|
||||
}
|
||||
return self.oauth_request('token', data)
|
||||
|
||||
def api_request(self, url, data=None):
|
||||
headers = None
|
||||
if self.access_token is not None:
|
||||
headers = {'Authorization': 'OAuth %s' % self.access_token}
|
||||
return self.request(self.API_BASE_URL, url, data, headers)
|
||||
|
||||
def api_counters_request(self):
|
||||
return self.api_request('counters.json')
|
||||
|
||||
def api_stat_traffic_summary(self, counter, date1, date2, group=None):
|
||||
if group is None:
|
||||
group = 'day'
|
||||
return self.api_request('stat/traffic/summary.json?id=%s&date1=%s&date2=%s&group=%s' % (
|
||||
counter,
|
||||
date1.strftime('%Y%m%d'),
|
||||
date2.strftime('%Y%m%d'),
|
||||
group
|
||||
))
|
||||
|
||||
|
||||
class AccessTokenWidget(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:yandex-metrika-revoke', kwargs={'pk': self.module.model.pk}),
|
||||
force_text(_('Revoke access'))
|
||||
)
|
||||
else:
|
||||
link = '<a href="%s">%s</a>' % (
|
||||
reverse('jet-dashboard:yandex-metrika-grant', kwargs={'pk': self.module.model.pk}),
|
||||
force_text(_('Grant access'))
|
||||
)
|
||||
|
||||
if value is None:
|
||||
value = ''
|
||||
|
||||
return format_html('%s<input type="hidden" name="access_token" value="%s">' % (link, value))
|
||||
|
||||
|
||||
class YandexMetrikaSettingsForm(forms.Form):
|
||||
access_token = forms.CharField(label=_('Access'), widget=AccessTokenWidget)
|
||||
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['access_token'].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['site']), counters))
|
||||
else:
|
||||
label = force_text(_('grant access first')) if module.access_token is None else force_text(_('counters loading failed'))
|
||||
self.fields['counter'].choices = (('', '-- %s -- ' % label),)
|
||||
|
||||
|
||||
class YandexMetrikaChartSettingsForm(YandexMetrikaSettingsForm):
|
||||
show = forms.ChoiceField(label=_('Show'), choices=(
|
||||
('visitors', capfirst(_('visitors'))),
|
||||
('visits', capfirst(_('visits'))),
|
||||
('page_views', capfirst(_('views'))),
|
||||
))
|
||||
group = forms.ChoiceField(label=_('Group'), choices=(
|
||||
('day', _('By day')),
|
||||
('week', _('By week')),
|
||||
('month', _('By month')),
|
||||
))
|
||||
|
||||
|
||||
class YandexMetrikaPeriodVisitorsSettingsForm(YandexMetrikaSettingsForm):
|
||||
group = forms.ChoiceField(label=_('Group'), choices=(
|
||||
('day', _('By day')),
|
||||
('week', _('By week')),
|
||||
('month', _('By month')),
|
||||
))
|
||||
|
||||
|
||||
class YandexMetrikaBase(DashboardModule):
|
||||
settings_form = YandexMetrikaSettingsForm
|
||||
ajax_load = True
|
||||
contrast = True
|
||||
period = None
|
||||
access_token = None
|
||||
expires_in = None
|
||||
token_type = None
|
||||
counter = None
|
||||
error = None
|
||||
|
||||
def settings_dict(self):
|
||||
return {
|
||||
'period': self.period,
|
||||
'access_token': self.access_token,
|
||||
'expires_in': self.expires_in,
|
||||
'token_type': self.token_type,
|
||||
'counter': self.counter
|
||||
}
|
||||
|
||||
def load_settings(self, settings):
|
||||
try:
|
||||
self.period = int(settings.get('period'))
|
||||
except TypeError:
|
||||
self.period = 0
|
||||
self.access_token = settings.get('access_token')
|
||||
self.expires_in = settings.get('expires_in')
|
||||
self.token_type = settings.get('token_type')
|
||||
self.counter = settings.get('counter')
|
||||
|
||||
def init_with_context(self, context):
|
||||
raise NotImplementedError('subclasses of YandexMetrika must provide a init_with_context() method')
|
||||
|
||||
def counters(self):
|
||||
client = YandexMetrikaClient(self.access_token)
|
||||
counters, exception = client.api_counters_request()
|
||||
|
||||
if counters is not None:
|
||||
return counters['counters']
|
||||
else:
|
||||
return None
|
||||
|
||||
def format_grouped_date(self, date, group):
|
||||
if group == 'week':
|
||||
date = u'%s — %s' % (
|
||||
(date - datetime.timedelta(days=7)).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.access_token is None:
|
||||
self.error = mark_safe(_('Please <a href="%s">attach Yandex account and choose Yandex Metrika 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 Yandex Metrika counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def api_stat_traffic_summary(self, group=None):
|
||||
if self.counter_attached():
|
||||
date1 = datetime.datetime.now() - datetime.timedelta(days=self.period)
|
||||
date2 = datetime.datetime.now()
|
||||
|
||||
client = YandexMetrikaClient(self.access_token)
|
||||
result, exception = client.api_stat_traffic_summary(self.counter, date1, date2, group)
|
||||
|
||||
if exception is not None:
|
||||
error = _('API request failed.')
|
||||
if isinstance(exception, HTTPError) and exception.code == 403:
|
||||
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)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
class YandexMetrikaVisitorsTotals(YandexMetrikaBase):
|
||||
"""
|
||||
Yandex Metrika widget that shows total number of visitors, visits and viewers for a particular period of time.
|
||||
Period may be following: Today, Last week, Last month, Last quarter, Last year
|
||||
"""
|
||||
|
||||
title = _('Yandex Metrika visitors totals')
|
||||
template = 'jet.dashboard/modules/yandex_metrika_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(YandexMetrikaVisitorsTotals, self).__init__(title, **kwargs)
|
||||
|
||||
def init_with_context(self, context):
|
||||
result = self.api_stat_traffic_summary()
|
||||
|
||||
if result is not None:
|
||||
try:
|
||||
self.children.append({'title': _('visitors'), 'value': result['totals']['visitors']})
|
||||
self.children.append({'title': _('visits'), 'value': result['totals']['visits']})
|
||||
self.children.append({'title': _('views'), 'value': result['totals']['page_views']})
|
||||
except KeyError:
|
||||
self.error = _('Bad server response')
|
||||
|
||||
|
||||
class YandexMetrikaVisitorsChart(YandexMetrikaBase):
|
||||
"""
|
||||
Yandex Metrika widget that shows visitors/visits/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 = _('Yandex Metrika visitors chart')
|
||||
template = 'jet.dashboard/modules/yandex_metrika_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: ``visitors``, ``visits``, ``page_views``
|
||||
show = None
|
||||
|
||||
#: Sets grouping of data. Possible values: ``day``, ``week``, ``month``
|
||||
group = None
|
||||
settings_form = YandexMetrikaChartSettingsForm
|
||||
|
||||
class Media:
|
||||
js = ('jet.dashboard/vendor/chart.js/Chart.min.js', 'jet.dashboard/dashboard_modules/yandex_metrika.js')
|
||||
|
||||
def __init__(self, title=None, period=None, show=None, group=None, **kwargs):
|
||||
kwargs.update({'period': period, 'show': show, 'group': group})
|
||||
super(YandexMetrikaVisitorsChart, self).__init__(title, **kwargs)
|
||||
|
||||
def settings_dict(self):
|
||||
settings = super(YandexMetrikaVisitorsChart, self).settings_dict()
|
||||
settings['show'] = self.show
|
||||
settings['group'] = self.group
|
||||
return settings
|
||||
|
||||
def load_settings(self, settings):
|
||||
super(YandexMetrikaVisitorsChart, self).load_settings(settings)
|
||||
self.show = settings.get('show')
|
||||
self.group = settings.get('group')
|
||||
|
||||
def init_with_context(self, context):
|
||||
result = self.api_stat_traffic_summary(self.group)
|
||||
|
||||
if result is not None:
|
||||
try:
|
||||
for data in result['data']:
|
||||
date = datetime.datetime.strptime(data['date'], '%Y%m%d')
|
||||
key = self.show if self.show is not None else 'visitors'
|
||||
self.children.append((date, data[key]))
|
||||
except KeyError:
|
||||
self.error = _('Bad server response')
|
||||
|
||||
|
||||
class YandexMetrikaPeriodVisitors(YandexMetrikaBase):
|
||||
"""
|
||||
Yandex Metrika widget that shows visitors, visits 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 = _('Yandex Metrika period visitors')
|
||||
template = 'jet.dashboard/modules/yandex_metrika_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 = YandexMetrikaPeriodVisitorsSettingsForm
|
||||
|
||||
def __init__(self, title=None, period=None, group=None, **kwargs):
|
||||
kwargs.update({'period': period, 'group': group})
|
||||
super(YandexMetrikaPeriodVisitors, self).__init__(title, **kwargs)
|
||||
|
||||
def settings_dict(self):
|
||||
settings = super(YandexMetrikaPeriodVisitors, self).settings_dict()
|
||||
settings['group'] = self.group
|
||||
return settings
|
||||
|
||||
def load_settings(self, settings):
|
||||
super(YandexMetrikaPeriodVisitors, self).load_settings(settings)
|
||||
self.group = settings.get('group')
|
||||
|
||||
def init_with_context(self, context):
|
||||
result = self.api_stat_traffic_summary(self.group)
|
||||
|
||||
if result is not None:
|
||||
try:
|
||||
for data in reversed(result['data']):
|
||||
date = datetime.datetime.strptime(data['date'], '%Y%m%d')
|
||||
date = self.format_grouped_date(date, self.group)
|
||||
self.children.append((date, data))
|
||||
except KeyError:
|
||||
self.error = _('Bad server response')
|
||||
@@ -0,0 +1,68 @@
|
||||
from django.conf.urls import url
|
||||
from django.contrib import messages
|
||||
try:
|
||||
from django.core.urlresolvers import reverse
|
||||
except ImportError: # Django 1.11
|
||||
from django.urls import reverse
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from jet.dashboard.dashboard_modules.yandex_metrika import YandexMetrikaClient
|
||||
from jet.dashboard.models import UserDashboardModule
|
||||
from jet.dashboard import dashboard
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
def yandex_metrika_grant_view(request, pk):
|
||||
client = YandexMetrikaClient()
|
||||
return redirect(client.get_oauth_authorize_url(pk))
|
||||
|
||||
|
||||
def yandex_metrika_revoke_view(request, pk):
|
||||
try:
|
||||
module = UserDashboardModule.objects.get(pk=pk)
|
||||
module.pop_settings(('access_token', 'expires_in', 'token_type', 'counter'))
|
||||
return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
|
||||
except UserDashboardModule.DoesNotExist:
|
||||
return HttpResponse(_('Module not found'))
|
||||
|
||||
|
||||
def yandex_metrika_callback_view(request):
|
||||
try:
|
||||
state = request.GET['state']
|
||||
code = request.GET['code']
|
||||
|
||||
module = UserDashboardModule.objects.get(pk=state)
|
||||
|
||||
client = YandexMetrikaClient()
|
||||
result, exception = client.oath_token_request(code)
|
||||
|
||||
if result is None:
|
||||
messages.error(request, _('API request failed.'))
|
||||
else:
|
||||
module.update_settings(result)
|
||||
|
||||
return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
|
||||
except KeyError:
|
||||
return HttpResponse(_('Bad arguments'))
|
||||
except UserDashboardModule.DoesNotExist:
|
||||
return HttpResponse(_('Module not found'))
|
||||
|
||||
|
||||
dashboard.urls.register_urls([
|
||||
url(
|
||||
r'^yandex-metrika/grant/(?P<pk>\d+)/$',
|
||||
yandex_metrika_grant_view,
|
||||
name='yandex-metrika-grant'
|
||||
),
|
||||
url(
|
||||
r'^yandex-metrika/revoke/(?P<pk>\d+)/$',
|
||||
yandex_metrika_revoke_view,
|
||||
name='yandex-metrika-revoke'
|
||||
),
|
||||
url(
|
||||
r'^yandex-metrika/callback/$',
|
||||
yandex_metrika_callback_view,
|
||||
name='yandex-metrika-callback'
|
||||
),
|
||||
])
|
||||
Reference in New Issue
Block a user