Skip to content

Commit

Permalink
Merge pull request #2285 from kobotoolbox/1804-per-user-settings
Browse files Browse the repository at this point in the history
Add model for per-user-settings
  • Loading branch information
jnm authored May 30, 2019
2 parents 0c49d3a + 8e33563 commit f466a97
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 9 deletions.
3 changes: 2 additions & 1 deletion hub/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
25 changes: 25 additions & 0 deletions hub/migrations/0005_perusersetting.py
Original file line number Diff line number Diff line change
@@ -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)),
],
),
]
42 changes: 39 additions & 3 deletions hub/models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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'
Expand Down
60 changes: 60 additions & 0 deletions hub/tests/test_perusersetting.py
Original file line number Diff line number Diff line change
@@ -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 = '[email protected]',
)
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)
1 change: 0 additions & 1 deletion kobo/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 7 additions & 3 deletions kpi/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import constance
from django.conf import settings

from hub.models import ConfigurationFile
from hub.models import ConfigurationFile, PerUserSetting
from hub.utils.i18n import I18nUtils


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


Expand Down
2 changes: 1 addition & 1 deletion kpi/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@

{% if intercom_app_id %}
<script>
window.IntercomAppId = '{{intercom_app_id}}';
window.IntercomAppId = '{{ intercom_app_id|safe }}';
</script>
{% endif %}
</head>
Expand Down

0 comments on commit f466a97

Please sign in to comment.