Skip to content
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

Add model for per-user-settings #2285

Merged
merged 1 commit into from
May 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 = '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)
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