Skip to content

Commit

Permalink
[change] Metric collection: added consent in web UI #372
Browse files Browse the repository at this point in the history
Closes #372
  • Loading branch information
pandafy committed Apr 15, 2024
1 parent d5d5f25 commit 864ab2d
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 5 deletions.
53 changes: 51 additions & 2 deletions openwisp_utils/admin_theme/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import logging

from django.conf import settings
from django.contrib import admin
from django.contrib import admin, messages
from django.shortcuts import render
from django.urls import path
from django.urls import path, reverse
from django.utils.module_loading import import_string
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -35,6 +36,24 @@ def index(self, request, extra_context=None):
context = get_dashboard_context(request)
else:
context = {'dashboard_enabled': False}
if self.is_metric_collection_installed() and request.user.is_superuser:
from ..measurements.models import MetricCollectionConsent

consent_obj = self.get_metric_collection_consent_obj()
if not consent_obj.has_shown_disclaimer:
messages.warning(
request,
mark_safe(
_(
'We collect anonymous usage metrics that helps to improve OpenWISP.'
' You can opt-out from sharing these metrics from the '
'<a href="{url}">System Information page</a>.'
).format(url=reverse('admin:ow-info'))
),
)
# Update the field in DB after showing the message for the
# first time.
MetricCollectionConsent.objects.update(has_shown_disclaimer=True)
return super().index(request, extra_context=context)

def openwisp_info(self, request, *args, **kwargs):
Expand All @@ -45,8 +64,38 @@ def openwisp_info(self, request, *args, **kwargs):
'title': _('System Information'),
'site_title': self.site_title,
}

if self.is_metric_collection_installed():
from ..measurements.admin import MetricCollectionConsentForm

consent_obj = self.get_metric_collection_consent_obj()
if request.POST:
form = MetricCollectionConsentForm(request.POST, instance=consent_obj)
form.full_clean()
form.save()
else:
form = MetricCollectionConsentForm(instance=consent_obj)
context.update(
{
'metric_collection_installed': self.is_metric_collection_installed(),
'metric_consent_form': form,
}
)
return render(request, 'admin/openwisp_info.html', context)

def is_metric_collection_installed(self):
return 'openwisp_utils.measurements' in getattr(settings, 'INSTALLED_APPS', [])

def get_metric_collection_consent_obj(self):
if not self.is_metric_collection_installed():
return None
from ..measurements.models import MetricCollectionConsent

consent_obj = MetricCollectionConsent.objects.first()
if not consent_obj:
consent_obj = MetricCollectionConsent.objects.create()
return consent_obj

def get_urls(self):
autocomplete_view = import_string(app_settings.AUTOCOMPLETE_FILTER_VIEW)
return [
Expand Down
9 changes: 7 additions & 2 deletions openwisp_utils/admin_theme/static/admin/css/openwisp.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ a.section:link,
a.section:visited,
.module th a,
#main .help a,
#main .helptext a,
fieldset.collapsed a.collapse-toggle,
fieldset a.collapse-toggle {
color: #df5d43;
Expand All @@ -137,7 +138,9 @@ a.section:link:focus,
a.section:visited:focus,
.module th a:focus,
#main .help a:hover,
#main .help a:focus {
#main .help a:focus,
#main .helptext a:focus,
#main .helptext a:focus {
text-decoration: underline;
}
#main-content .module caption a {
Expand Down Expand Up @@ -182,7 +185,9 @@ a:focus {
display: inline-block;
}
#main .form-row .help,
#container #main-content .help {
#main .form-row .helptext,
#container #main-content .help,
#container #main-content .helptext {
font-size: 14px !important;
line-height: 20px;
color: rgba(0, 0, 0, 0.45);
Expand Down
27 changes: 26 additions & 1 deletion openwisp_utils/admin_theme/templates/admin/openwisp_info.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "admin/base_site.html" %}
{% load i18n %}
{% load i18n static %}

{% block content %}
{% if openwisp_version %}
Expand All @@ -15,4 +15,29 @@ <h2>{% trans "OS Information" %}</h2>
<p><strong>{% trans "OS version" %}:</strong> {{ system_info.os_version }}</p>
<p><strong>{% trans "Kernel version" %}:</strong> {{ system_info.kernel_version }}</p>
<p><strong>{% trans "Hardware platform" %}:</strong> {{ system_info.hardware_platform }}</p>

{% block metric_collect_consent %}
{% if metric_collection_installed %}
<h2>Metric collection</h2>
<form method="POST" id="id_metric_collection_consent_form">
{% csrf_token %}
{{ metric_consent_form }}
</form>
{% endif %}
{% endblock metric_collect_consent %}
{% endblock content %}

{% block extrahead %}
{{ block.super }}
{% if 'jquery' not in block.super and not media.js %}
<script type="text/javascript" src="{% static 'admin/js/vendor/jquery/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'admin/js/jquery.init.js' %}"></script>
{% endif %}
{% endblock %}

{% block footer %}
{{ block.super }}
{% if metric_collection_installed %}
<script src="{% static 'admin/js/metric-collection-consent.js' %}"></script>
{% endif %}
{% endblock footer %}
10 changes: 10 additions & 0 deletions openwisp_utils/measurements/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django import forms

from .models import MetricCollectionConsent


class MetricCollectionConsentForm(forms.ModelForm):
class Meta:
model = MetricCollectionConsent
widgets = {'user_consented': forms.CheckboxInput(attrs={'class': 'bold'})}
fields = ['user_consented']
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.7 on 2024-04-15 05:54

from django.db import migrations, models
import django.utils.timezone
import model_utils.fields
import uuid


class Migration(migrations.Migration):

dependencies = [
("measurements", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="MetricCollectionConsent",
fields=[
(
"id",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
("has_shown_disclaimer", models.BooleanField(default=False)),
(
"user_consented",
models.BooleanField(
default=True,
help_text='Allow OpenWISP to collect and share anonymous usage metrics to improve the software. Before opting-out kindly consider reading <a href="https://github.com/openwisp/openwisp-utils?tab=readme-ov-file#collection-of-usage-metrics" target="_blank">why we collect metrics</a>.',
verbose_name="Allow collecting anonymous usage metrics",
),
),
],
options={
"abstract": False,
},
),
]
30 changes: 30 additions & 0 deletions openwisp_utils/measurements/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from openwisp_utils.base import TimeStampedEditableModel
from packaging.version import parse as parse_version

Expand Down Expand Up @@ -53,3 +54,32 @@ def log_module_version_changes(cls, current_versions):
OpenwispVersion.objects.create(module_version=current_versions)
return False, True
return False, False


class MetricCollectionConsent(TimeStampedEditableModel):
"""
This model stores information about the superuser's consent to collect
anonymous usage metrics. The ``has_shown_disclaimer`` field is used to
track whether a disclaimer for metric collection has been shown to the
superuser on their first login. The ``user_consented`` field stores
whether the superuser has opted-in to collecting anonymous usage
metrics.
"""

has_shown_disclaimer = models.BooleanField(
default=False,
)
# Metric collection is opt-out. By default, metric collection is enabled.
# Disabling it is a one-time action by the superuser. Whenever a superuser
# disables metric collection, they are opting-out to share anymore anonymous
# usage metrics with the OpenWISP project.
user_consented = models.BooleanField(
default=True,
verbose_name=_('Allow collecting anonymous usage metrics'),
help_text=_(
'Allow OpenWISP to collect and share anonymous usage metrics to improve'
' the software. Before opting-out kindly consider reading'
' <a href="https://github.com/openwisp/openwisp-utils?tab=readme-ov-file'
'#collection-of-usage-metrics" target="_blank">why we collect metrics</a>.'
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

django.jQuery(document).ready(function($) {
$('#id_user_consented').change(function() {
$('#id_metric_collection_consent_form').submit();
});
});
3 changes: 3 additions & 0 deletions tests/openwisp2/urls.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import include, path
from django.views.generic import TemplateView

Expand All @@ -13,3 +14,5 @@
name='menu-test-view',
),
]

urlpatterns += staticfiles_urlpatterns()

0 comments on commit 864ab2d

Please sign in to comment.