diff --git a/shynet/core/models.py b/shynet/core/models.py index 7d6530a..2ae9d32 100644 --- a/shynet/core/models.py +++ b/shynet/core/models.py @@ -19,6 +19,7 @@ from django.utils.translation import gettext_lazy as _ ACTIVE_USER_TIMEDELTA = timezone.timedelta( milliseconds=settings.SCRIPT_HEARTBEAT_FREQUENCY * 2 ) +RESULTS_LIMIT = 300 def _default_uuid(): @@ -176,7 +177,7 @@ class Service(models.Model): locations = ( hits.values("location") .annotate(count=models.Count("location")) - .order_by("-count") + .order_by("-count")[:RESULTS_LIMIT] ) referrer_ignore = self.get_ignored_referrer_regex() @@ -186,7 +187,7 @@ class Service(models.Model): hits.filter(initial=True) .values("referrer") .annotate(count=models.Count("referrer")) - .order_by("-count") + .order_by("-count")[:RESULTS_LIMIT] ) if not referrer_ignore.match(referrer["referrer"]) ] @@ -194,29 +195,31 @@ class Service(models.Model): countries = ( sessions.values("country") .annotate(count=models.Count("country")) - .order_by("-count") + .order_by("-count")[:RESULTS_LIMIT] ) operating_systems = ( - sessions.values("os").annotate(count=models.Count("os")).order_by("-count") + sessions.values("os") + .annotate(count=models.Count("os")) + .order_by("-count")[:RESULTS_LIMIT] ) browsers = ( sessions.values("browser") .annotate(count=models.Count("browser")) - .order_by("-count") + .order_by("-count")[:RESULTS_LIMIT] ) device_types = ( sessions.values("device_type") .annotate(count=models.Count("device_type")) - .order_by("-count") + .order_by("-count")[:RESULTS_LIMIT] ) devices = ( sessions.values("device") .annotate(count=models.Count("device")) - .order_by("-count") + .order_by("-count")[:RESULTS_LIMIT] ) avg_load_time = hits.aggregate(load_time__avg=models.Avg("load_time"))[ diff --git a/shynet/dashboard/templates/dashboard/pages/service.html b/shynet/dashboard/templates/dashboard/pages/service.html index 5bd40a3..a7998d4 100644 --- a/shynet/dashboard/templates/dashboard/pages/service.html +++ b/shynet/dashboard/templates/dashboard/pages/service.html @@ -135,6 +135,12 @@ {% endfor %} + {% if stats.locations.count == RESULTS_LIMIT %} +
+ + {% trans 'View more locations' %} → + + {% endif %}

diff --git a/shynet/dashboard/templates/dashboard/pages/service_location_list.html b/shynet/dashboard/templates/dashboard/pages/service_location_list.html new file mode 100644 index 0000000..f338060 --- /dev/null +++ b/shynet/dashboard/templates/dashboard/pages/service_location_list.html @@ -0,0 +1,47 @@ +{% extends "dashboard/service_base.html" %} + +{% load i18n a17t_tags pagination humanize helpers %} + +{% block head_title %}{{object.name}} {% trans 'Locations' %}{% endblock %} + +{% block service_actions %} +

{% include 'dashboard/includes/date_range.html' %}
+{% trans 'Analytics' %} → +{% endblock %} + +{% block service_content %} +
+ + + + + + + + + {% for location in object_list %} + + + + + {% empty %} + + + + {% endfor %} + +
{% trans 'Location' %}{% trans 'Hits' %}
+
+ {{location.location|default:"Unknown"|urldisplay}} +
+
+
+ {{location.count|intcomma}} + + ({{location.count|percent:hit_count}}) + +
+
{% trans 'No data yet...' %}
+
+{% pagination page_obj request %} +{% endblock %} diff --git a/shynet/dashboard/templates/dashboard/pages/service_session_list.html b/shynet/dashboard/templates/dashboard/pages/service_session_list.html index 1f86f1d..11eb3d4 100644 --- a/shynet/dashboard/templates/dashboard/pages/service_session_list.html +++ b/shynet/dashboard/templates/dashboard/pages/service_session_list.html @@ -14,4 +14,4 @@ {% include 'dashboard/includes/session_list.html' %}
{% pagination page_obj request %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/shynet/dashboard/urls.py b/shynet/dashboard/urls.py index 7aa917b..8eed38e 100644 --- a/shynet/dashboard/urls.py +++ b/shynet/dashboard/urls.py @@ -26,6 +26,11 @@ urlpatterns = [ views.ServiceSessionView.as_view(), name="service_session", ), + path( + "service//locations/", + views.ServiceLocationsListView.as_view(), + name="service_location_list", + ), path( "api-token-refresh/", views.RefreshApiTokenView.as_view(), diff --git a/shynet/dashboard/views.py b/shynet/dashboard/views.py index ebec65f..60b18c0 100644 --- a/shynet/dashboard/views.py +++ b/shynet/dashboard/views.py @@ -2,21 +2,20 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.messages.views import SuccessMessageMixin from django.core.cache import cache -from django.db.models import Q +from django.db.models import Q, Count from django.shortcuts import get_object_or_404, reverse, redirect from django.views.generic import ( CreateView, DeleteView, DetailView, ListView, - TemplateView, UpdateView, View, ) from rules.contrib.views import PermissionRequiredMixin -from analytics.models import Session -from core.models import Service, _default_api_token +from analytics.models import Session, Hit +from core.models import Service, _default_api_token, RESULTS_LIMIT from .forms import ServiceForm from .mixins import DateRangeMixin @@ -68,6 +67,7 @@ class ServiceView( data = super().get_context_data(**kwargs) data["script_protocol"] = "https://" if settings.SCRIPT_USE_HTTPS else "http://" data["stats"] = self.object.get_core_stats(data["start_date"], data["end_date"]) + data["RESULTS_LIMIT"] = RESULTS_LIMIT data["object_list"] = Session.objects.filter( service=self.get_object(), start_time__lt=self.get_end_date(), @@ -141,6 +141,36 @@ class ServiceSessionsListView( return data +class ServiceLocationsListView( + LoginRequiredMixin, PermissionRequiredMixin, DateRangeMixin, ListView +): + model = Hit + template_name = "dashboard/pages/service_location_list.html" + paginate_by = RESULTS_LIMIT + permission_required = "core.view_service" + + def get_object(self): + return get_object_or_404(Service, pk=self.kwargs.get("pk")) + + def get_queryset(self): + hits = Hit.objects.filter( + service=self.get_object(), + start_time__lt=self.get_end_date(), + start_time__gt=self.get_start_date(), + ) + self.hit_count = hits.count() + + return ( + hits.values("location").annotate(count=Count("location")).order_by("-count") + ) + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + data["object"] = self.get_object() + data["hit_count"] = self.hit_count + return data + + class ServiceSessionView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): model = Session template_name = "dashboard/pages/service_session.html"