From 8e33563efe269ac98cf466d243bd9f8f9d769608 Mon Sep 17 00:00:00 2001 From: "John N. Milner" Date: Wed, 29 May 2019 15:12:51 -0400 Subject: [PATCH] Add model for per-user-settings allowing us to selectively enable Intercom. Closes #1804 --- hub/admin.py | 3 +- hub/migrations/0005_perusersetting.py | 25 +++++++++++ hub/models.py | 42 +++++++++++++++++-- hub/tests/test_perusersetting.py | 60 +++++++++++++++++++++++++++ kobo/settings/base.py | 1 - kpi/context_processors.py | 10 +++-- kpi/templates/index.html | 2 +- 7 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 hub/migrations/0005_perusersetting.py create mode 100644 hub/tests/test_perusersetting.py diff --git a/hub/admin.py b/hub/admin.py index 5384cabbd6..050baa6ed4 100644 --- a/hub/admin.py +++ b/hub/admin.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin -from models import SitewideMessage, ConfigurationFile +from models import SitewideMessage, ConfigurationFile, PerUserSetting from actions import delete_related_objects, remove_from_kobocat class UserDeleteKludgeAdmin(UserAdmin): @@ -35,5 +35,6 @@ def get_actions(self, request): admin.site.register(SitewideMessage) admin.site.register(ConfigurationFile) +admin.site.register(PerUserSetting) admin.site.unregister(User) admin.site.register(User, UserDeleteKludgeAdmin) diff --git a/hub/migrations/0005_perusersetting.py b/hub/migrations/0005_perusersetting.py new file mode 100644 index 0000000000..9b055fee3b --- /dev/null +++ b/hub/migrations/0005_perusersetting.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import jsonbfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('hub', '0004_configurationfile'), + ] + + operations = [ + migrations.CreateModel( + name='PerUserSetting', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('user_queries', jsonbfield.fields.JSONField(help_text='A JSON representation of a *list* of Django queries, e.g. `[{"email__endswith": "@kobotoolbox.org"}, {"email__endswith": "@kbtdev.org"}]`. A matching user is one who would be returned by ANY of the queries in the list.')), + ('name', models.CharField(unique=True, max_length=255)), + ('value_when_matched', models.CharField(max_length=2048, blank=True)), + ('value_when_not_matched', models.CharField(max_length=2048, blank=True)), + ], + ), + ] diff --git a/hub/models.py b/hub/models.py index 7e292cf433..6135f7b0cf 100644 --- a/hub/models.py +++ b/hub/models.py @@ -1,11 +1,14 @@ -from django.db import models -from django.db.models.signals import post_save from django.conf import settings +from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse +from django.db import models +from django.db.models.signals import post_save from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 -from markitup.fields import MarkupField +from django.utils.translation import ugettext_lazy as _ +from jsonbfield.fields import JSONField as JSONBField from jsonfield import JSONField +from markitup.fields import MarkupField class SitewideMessage(models.Model): @@ -46,6 +49,39 @@ def url(self): return reverse('configurationfile', kwargs={'slug': self.slug}) +class PerUserSetting(models.Model): + """ + A configuration setting that has different values depending on whether not + a user matches certain criteria + """ + user_queries = JSONBField( + help_text=_('A JSON representation of a *list* of Django queries, ' + 'e.g. `[{"email__endswith": "@kobotoolbox.org"}, ' + '{"email__endswith": "@kbtdev.org"}]`. ' + 'A matching user is one who would be returned by ANY of ' + 'the queries in the list.') + ) + name = models.CharField(max_length=255, unique=True, + default='INTERCOM_APP_ID') # The only one for now! + value_when_matched = models.CharField(max_length=2048, blank=True) + value_when_not_matched = models.CharField(max_length=2048, blank=True) + + def user_matches(self, user): + manager = user._meta.model.objects + queryset = manager.none() + for user_query in self.user_queries: + queryset |= manager.filter(**user_query) + return queryset.filter(pk=user.pk).exists() + + def get_for_user(self, user): + if self.user_matches(user): + return self.value_when_matched + else: + return self.value_when_not_matched + + def __str__(self): + return self.name + class FormBuilderPreference(models.Model): KPI = 'K' DKOBO = 'D' diff --git a/hub/tests/test_perusersetting.py b/hub/tests/test_perusersetting.py new file mode 100644 index 0000000000..2cd965f5cc --- /dev/null +++ b/hub/tests/test_perusersetting.py @@ -0,0 +1,60 @@ +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.test import TestCase + +from hub.models import PerUserSetting + + +class PerUserSettingTestCase(TestCase): + + def setUp(self): + self.user_for_username_match = User.objects.create( + username = 'match_me') + self.user_for_email_match = User.objects.create( + username='no_match_here', + email = 'foundme@matchthis.int', + ) + self.non_matching_user = User.objects.create(username='leave_me_alone') + self.setting = PerUserSetting.objects.create( + name='test', + user_queries=[{"username__icontains": "Match"}, + {"email__iendswith": "MatchThis.int"}], + value_when_matched='great!', + value_when_not_matched='okay...', + ) + + def test_matching_user(self): + for u in [self.user_for_username_match, self.user_for_email_match]: + self.assertTrue(self.setting.user_matches(u)) + self.assertEqual(self.setting.get_for_user(u), 'great!') + + def test_non_matching_user(self): + u = self.non_matching_user + self.assertFalse(self.setting.user_matches(u)) + self.assertEqual(self.setting.get_for_user(u), 'okay...') + + +class IntercomConfigurationTestCase(TestCase): + fixtures = ['test_data'] + + def setUp(self): + self.setting = PerUserSetting.objects.create( + name='INTERCOM_APP_ID', + user_queries=[{"username": "someuser"}], + value_when_matched='arm&leg', + value_when_not_matched='', + ) + + def test_intercom_for_matching_user(self): + self.assertTrue(self.client.login(username='someuser', + password='someuser')) + response = self.client.get(reverse('kpi-root')) + lines = [line.strip() for line in response.content.split('\n')] + self.assertTrue("window.IntercomAppId = 'arm&leg';" in lines) + + def test_no_intercom_for_non_matching_user(self): + self.assertTrue(self.client.login(username='anotheruser', + password='anotheruser')) + response = self.client.get(reverse('kpi-root')) + lines = [line.strip() for line in response.content.split('\n')] + self.assertFalse("window.IntercomAppId = 'arm&leg';" in lines) diff --git a/kobo/settings/base.py b/kobo/settings/base.py index 0a632f2d6c..b985505fac 100644 --- a/kobo/settings/base.py +++ b/kobo/settings/base.py @@ -331,7 +331,6 @@ def __init__(self, *args, **kwargs): # STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' GOOGLE_ANALYTICS_TOKEN = os.environ.get('GOOGLE_ANALYTICS_TOKEN') -INTERCOM_APP_ID = os.environ.get('INTERCOM_APP_ID') RAVEN_JS_DSN = os.environ.get('RAVEN_JS_DSN') # replace this with the pointer to the kobocat server, if it exists diff --git a/kpi/context_processors.py b/kpi/context_processors.py index 7c8ece8306..17db5ee795 100644 --- a/kpi/context_processors.py +++ b/kpi/context_processors.py @@ -1,7 +1,7 @@ import constance from django.conf import settings -from hub.models import ConfigurationFile +from hub.models import ConfigurationFile, PerUserSetting from hub.utils.i18n import I18nUtils @@ -9,10 +9,14 @@ def external_service_tokens(request): out = {} if settings.GOOGLE_ANALYTICS_TOKEN: out['google_analytics_token'] = settings.GOOGLE_ANALYTICS_TOKEN - if settings.INTERCOM_APP_ID: - out['intercom_app_id'] = settings.INTERCOM_APP_ID if settings.RAVEN_JS_DSN: out['raven_js_dsn'] = settings.RAVEN_JS_DSN + try: + intercom_setting = PerUserSetting.objects.get(name='INTERCOM_APP_ID') + except PerUserSetting.DoesNotExist: + pass + else: + out['intercom_app_id'] = intercom_setting.get_for_user(request.user) return out diff --git a/kpi/templates/index.html b/kpi/templates/index.html index fbf31d1897..fe6b00df75 100644 --- a/kpi/templates/index.html +++ b/kpi/templates/index.html @@ -51,7 +51,7 @@ {% if intercom_app_id %} {% endif %}