Skip to content

Store Pageviews in DB #6121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 30 commits into from
May 19, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0998e8a
initial work
dojutsu-user Aug 28, 2019
3e8a99a
fix arguments
dojutsu-user Aug 29, 2019
f5c82cf
update migration file
dojutsu-user Aug 29, 2019
e1e954c
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Aug 30, 2019
9b03012
show top 10 viewed page to the users.
dojutsu-user Sep 3, 2019
152aff0
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Sep 6, 2019
f389476
initial work for showing graphs to the user
dojutsu-user Sep 8, 2019
1717cfc
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Sep 17, 2019
a19823f
show pageviews for a specific page
dojutsu-user Sep 18, 2019
af056c8
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Oct 2, 2019
a1d0a9b
change view to class based view
dojutsu-user Oct 2, 2019
82b3182
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Oct 3, 2019
415a3b3
fix lint
dojutsu-user Oct 3, 2019
2073a82
fix more lint
dojutsu-user Oct 5, 2019
b263c1b
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Oct 14, 2019
3ec59dc
store page_slug instead of page_path
dojutsu-user Oct 15, 2019
108fd5f
little refactor
dojutsu-user Oct 15, 2019
ddc96d9
update test
dojutsu-user Oct 15, 2019
8da0f93
fix tests
dojutsu-user Oct 15, 2019
d0e1317
add test for search tasks
dojutsu-user Oct 16, 2019
3946398
use F expression
dojutsu-user Oct 17, 2019
b06d599
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Oct 22, 2019
11eb6ac
fix tests
dojutsu-user Oct 22, 2019
95e7ee7
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Oct 28, 2019
67d4885
Merge branch 'master' into search-pageviews-sorting
dojutsu-user Oct 31, 2019
9cb2310
Merge branch 'master' into search-pageviews-sorting
davidfischer May 7, 2020
3ce38c8
Index and refactor page view counting
davidfischer May 8, 2020
46747b0
Feedback updates and renames for clarity
davidfischer May 8, 2020
7e5a1f0
Merge branch 'master' into search-pageviews-sorting
davidfischer May 19, 2020
b530bbc
Move pageview models to the analytics app
davidfischer May 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions readthedocs/api/v2/views/footer_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
highest_version,
parse_version_failsafe,
)
from readthedocs.search.tasks import increase_page_view_count


def get_version_compare_data(project, base_version=None):
Expand Down Expand Up @@ -210,6 +211,13 @@ def get(self, request, format=None):
'version_supported': version.supported,
}

# increase the page view count
increase_page_view_count.delay(
project=context['project'],
version=context['version'],
path=context['path'] if context['path'] else 'index.html'
)

# Allow folks to hook onto the footer response for various information
# collection, or to modify the resp_data.
footer_response.send(
Expand Down
4 changes: 4 additions & 0 deletions readthedocs/projects/urls/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@
r'^(?P<project_slug>[-\w]+)/search-analytics/$',
private.search_analytics_view, name='projects_search_analytics',
),
url(
r'^(?P<project_slug>[-\w]+)/page-views/$',
private.page_views, name='projects_page_views',
),
]

domain_urls = [
Expand Down
26 changes: 25 additions & 1 deletion readthedocs/projects/views/private.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
from readthedocs.projects.utils import Echo
from readthedocs.projects.views.base import ProjectAdminMixin, ProjectSpamMixin
from readthedocs.projects.views.mixins import ProjectImportMixin
from readthedocs.search.models import SearchQuery
from readthedocs.search.models import SearchQuery, PageView

from ..tasks import retry_domain_verification

Expand Down Expand Up @@ -1020,3 +1020,27 @@ def _search_analytics_csv_data(request, project_slug):
)
response['Content-Disposition'] = f'attachment; filename="{file_name}"'
return response


def page_views(request, project_slug):
"""View for page views."""

project = get_object_or_404(
Project.objects.for_admin_user(request.user),
slug=project_slug,
)

top_viewed_pages = PageView.get_top_viewed_pages(project)
top_viewed_pages_iter = zip(
top_viewed_pages['pages'],
top_viewed_pages['view_counts']
)

return render(
request,
'projects/project_page_views.html',
{
'project': project,
'top_viewed_pages_iter': top_viewed_pages_iter,
},
)
11 changes: 10 additions & 1 deletion readthedocs/search/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.contrib import admin

from .models import SearchQuery
from .models import SearchQuery, PageView


class SearchQueryAdmin(admin.ModelAdmin):
Expand All @@ -14,4 +14,13 @@ class SearchQueryAdmin(admin.ModelAdmin):
list_select_related = ('project', 'version', 'version__project')


class PageViewAdmin(admin.ModelAdmin):
raw_id_fields = ('project', 'version')
list_display = ('__str__', 'view_count')
search_fields = ('project__slug', 'version__slug', 'path')
readonly_fields = ('created', 'modified')
list_select_related = ('project', 'version', 'version__project')


admin.site.register(SearchQuery, SearchQueryAdmin)
admin.site.register(PageView, PageViewAdmin)
36 changes: 36 additions & 0 deletions readthedocs/search/migrations/0002_create_pageview_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-08-29 18:07
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields


class Migration(migrations.Migration):

dependencies = [
('projects', '0044_auto_20190703_1300'),
('builds', '0010_add-description-field-to-automation-rule'),
('search', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='PageView',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', django_extensions.db.fields.CreationDateTimeField(auto_now_add=True, verbose_name='created')),
('modified', django_extensions.db.fields.ModificationDateTimeField(auto_now=True, verbose_name='modified')),
('path', models.CharField(max_length=4096)),
('view_count', models.PositiveIntegerField(default=1)),
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_views', to='projects.Project')),
('version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='page_views', to='builds.Version', verbose_name='Version')),
],
options={
'ordering': ('-modified', '-created'),
'get_latest_by': 'modified',
'abstract': False,
},
),
]
59 changes: 58 additions & 1 deletion readthedocs/search/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Search Queries."""

from django.db import models
from django.db.models import Count
from django.db.models import Count, Sum
from django.db.models.functions import TruncDate
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
Expand Down Expand Up @@ -137,3 +137,60 @@ def generate_distribution_of_top_queries(cls, project_slug, n):
final_data['int_data'].append(count_of_other)

return final_data


class PageView(TimeStampedModel):
project = models.ForeignKey(
Project,
related_name='page_views',
on_delete=models.CASCADE,
)
version = models.ForeignKey(
Version,
verbose_name=_('Version'),
related_name='page_views',
on_delete=models.CASCADE,
)
path = models.CharField(max_length=4096)
view_count = models.PositiveIntegerField(default=1)

def __str__(self):
return f'[{self.project.slug}:{self.version.slug}]: {self.path}'

@classmethod
def get_top_viewed_pages(cls, project):
"""
Returns top 10 pages according to view counts.

Structure of returned data is compatible to make graphs.
Sample returned data::
{
'pages': ['index.html', 'contribute.html', 'sponsors.html'],
'view_counts': [150, 200, 143]
}
This data shows that `index.html` is the most viewed page having 150 total views,
followed by `contribute.html` and `sponsors.html` having 200 and
143 total page views respectively.
"""
qs = (
cls.objects
.filter(project=project)
.values_list('path')
.annotate(total_views=Sum('view_count'))
.values_list('path', 'total_views')
.order_by('-total_views')[:10]
)

pages = []
view_counts = []

for data in qs.iterator():
pages.append(data[0])
view_counts.append(data[1])

final_data = {
'pages': pages,
'view_counts': view_counts,
}

return final_data
23 changes: 22 additions & 1 deletion readthedocs/search/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from readthedocs.builds.models import Version
from readthedocs.projects.models import Project
from readthedocs.search.models import SearchQuery
from readthedocs.search.models import SearchQuery, PageView
from readthedocs.worker import app
from .utils import _get_index, _get_document

Expand Down Expand Up @@ -212,3 +212,24 @@ def record_search_query(project_slug, version_slug, query, total_results, time_s
)
obj.created = time
obj.save()


@app.task(queue='web')
def increase_page_view_count(project, version, path):
today_date = timezone.now().date()
page_view_obj = PageView.objects.filter(
project=project,
version=version,
path=path,
created__date=today_date,
).first()

if page_view_obj:
page_view_obj.view_count += 1
page_view_obj.save()
else:
PageView.objects.create(
project=project,
version=version,
path=path,
)
1 change: 1 addition & 0 deletions readthedocs/templates/projects/project_edit_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<li class="{% block project-environment-variables-active %}{% endblock %}"><a href="{% url "projects_environmentvariables" project.slug %}">{% trans "Environment Variables" %}</a></li>
<li class="{% block project-notifications-active %}{% endblock %}"><a href="{% url "projects_notifications" project.slug %}">{% trans "Notifications" %}</a></li>
<li class="{% block project-search-analytics-active %}{% endblock %}"><a href="{% url "projects_search_analytics" project.slug %}">{% trans "Search Analytics" %}</a></li>
<li class="{% block project-page-views-active %}{% endblock %}"><a href="{% url "projects_page_views" project.slug %}">{% trans "Page Views" %}</a></li>
{% if USE_PROMOS %}
<li class="{% block project-ads-active %}{% endblock %}"><a href="{% url "projects_advertising" project.slug %}">{% trans "Advertising" %} </a></li>
{% endif %}
Expand Down
52 changes: 52 additions & 0 deletions readthedocs/templates/projects/project_page_views.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{% extends "projects/project_edit_base.html" %}

{% load i18n %}
{% load static %}

{% block title %}{% trans "Page Views" %}{% endblock %}

{% block nav-dashboard %} class="active"{% endblock %}

{% block project-page-views-active %}active{% endblock %}
{% block project_edit_content_header %}{% trans "Page Views" %}{% endblock %}

{% block project_edit_content %}
<h3>{% trans "Top Viewed Pages" %}</h3>
<div class="module-list">
<div class="module-list-wrapper">
<ul class="long-list-overflow">
{% for page, count in top_viewed_pages_iter %}
<li class="module-item">
{{ page }}
<span class="right quiet">
{{ count }} {{ view|pluralize:"s" }}
</span>
</li>
{% empty %}
<li class="module-item">
<p class="quiet">
{% trans 'No date available.' %}
</p>
</li>
{% endfor %}
</ul>
</div>
</div>

<br/>

<h2>{% trans "Page Views Per Page" %}</h2>
<canvas id="page-views-per-page" width="400" height="150"></canvas>

{% endblock %}

{% block extra_scripts %}
{% endblock %}

{% block extra_links %}
<!-- Chart.js v2.8.0 -->
<link rel="stylesheet" href="{% static 'vendor/chartjs/chartjs.min.css' %}">
{% endblock %}

{% block footerjs %}
{% endblock %}