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

Fixes and slight UI/documentation improvements #194

Merged
merged 18 commits into from
May 11, 2022
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 .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ jobs:
strategy:
max-parallel: 5
matrix:
python-version: [2.7, 3.5, 3.6, pypy2, pypy3]
python-version: ['3.5', '3.6', '3.7', '3.8', '3.9', '3.10', 'pypy-3.8', 'pypy-3.9']
fail-fast: false

steps:
- uses: actions/checkout@v2
Expand Down
20 changes: 19 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,21 @@ Include 'django.contrib.humanize' as above if not already included.

Include the app URLconf in your urls.py file:

.. code-block:: python

url(r'experiments/', include('experiments.urls')),

We haven't configured our goals yet, we'll do that in a bit. Please ensure
you have correctly configured your STATIC_URL setting.


Include following JS libraries to your base template:

.. code-block:: html

<script src="{% static 'experiments/js/experiments.js' %}"></script>
<script src="{% static 'experiments/js/jquery.cookie.js' %}"></script>

OPTIONAL:
If you want to use the built in retention goals you will need to include the retention middleware:

Expand Down Expand Up @@ -263,7 +273,15 @@ This will be fired when the user loads the page. This is not the only way of fir

<button onclick="experiments.goal('registration')">Complete Registration</button>

(Please note, this requires CSRF authentication. Please see the `Django Docs <https://docs.djangoproject.com/en/1.4/ref/contrib/csrf/#ajax>`_)
(Please note, this requires CSRF authentication. Please see the `Django Docs <https://docs.djangoproject.com/en/3.2/ref/csrf/>`_)
The CSRF code would be something like:

.. code-block:: javascript

$.ajaxSetup({
headers:
{ 'X-CSRFToken': Cookies.get('csrftoken') }
});

4. **Cookies**:

Expand Down
50 changes: 28 additions & 22 deletions example_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,23 +92,8 @@
SECRET_KEY = 'gfjo;2r3l;hjropjf30j3fl;m234nc9p;o2mnpfnpfj'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)

TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.request",
"django.contrib.messages.context_processors.messages",
)

MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
Expand All @@ -119,18 +104,39 @@

ROOT_URLCONF = 'example_project.urls'

TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
os.path.join(PROJECT_ROOT, 'templates'),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(PROJECT_ROOT, 'templates'),
],
'APP_DIRS': False,
'OPTIONS': {
'context_processors': {
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.request",
"django.contrib.messages.context_processors.messages",
},
'loaders': (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
},
},
]


INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.humanize',
'django.contrib.messages',
'django.contrib.staticfiles',
'experiments',
# Uncomment the next line to enable the admin:
Expand Down
2 changes: 1 addition & 1 deletion example_project/templates/test_page.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% load experiments %}
{% load staticfiles %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
Expand Down
2 changes: 1 addition & 1 deletion example_project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

urlpatterns = [
url(r'experiments/', include('experiments.urls')),
url(r'admin/', include(admin.site.urls)),
url(r'admin/', admin.site.urls),
url(r'^$', TemplateView.as_view(template_name="test_page.html"), name="test_page"),
url(r'^goal/$', TemplateView.as_view(template_name="goal.html"), name="goal"),
]
Expand Down
1 change: 0 additions & 1 deletion experiments/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
default_app_config = 'experiments.apps.ExperimentsConfig'
4 changes: 4 additions & 0 deletions experiments/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ def process_response(self, request, response):
experiment_user = participant(request)
experiment_user.visit()

# record cookie goal
goal_name = request.COOKIES.get('experiments_goal')
participant(request).goal(goal_name)

return response
2 changes: 1 addition & 1 deletion experiments/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
class Experiment(models.Model):
name = models.CharField(primary_key=True, max_length=128)
description = models.TextField(default="", blank=True, null=True)
alternatives = models.JSONField(default={}, blank=True)
alternatives = JSONField(default=dict, blank=True)
relevant_chi2_goals = models.TextField(default="", null=True, blank=True)
relevant_mwu_goals = models.TextField(default="", null=True, blank=True)

Expand Down
2 changes: 1 addition & 1 deletion experiments/static/experiments/js/experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ experiments = function() {
$.post("/experiments/confirm_human/");
},
goal: function(goal_name) {
$.post("/experiments/goal/" + goal_name);
$.post(f"/experiments/goal/{goal_name}/");
}
};
}();
Expand Down
6 changes: 3 additions & 3 deletions experiments/templates/admin/experiments/results_table.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
<th id="experiment-toggle-goals" class="experiment-toggle-goals" data-shown="false">Toggle All Goals</th>
<th data-alternative="control"{% if user_alternative == 'control' %} class="experiment-selected-alternative"{% endif %}>
control <small>({{ control_participants|intcomma }})</small>
&dash; <span class="experiment-alternative-enrolled">Enrolled</span><span class="experiment-alternative-join">Click to join</span>
<span class="experiment-alternative-enrolled button">Shown</span><span class="experiment-alternative-join button" disabled>Show for me</span>
</th>
{% for alternative, participants in alternatives %}
{% if alternative != 'control' %}
<th colspan="3" data-alternative="{{ alternative }}"{% if user_alternative == alternative %} class="experiment-selected-alternative"{% endif %}>
{{ alternative }} <small>({{ participants|intcomma }})</small>
&dash; <span class="experiment-alternative-enrolled">Enrolled</span><span class="experiment-alternative-join">Click to join</span>
<span class="experiment-alternative-enrolled button">Shown</span><span class="experiment-alternative-join button disabled" disabled>Show for me</span>
</th>
{% endif %}
{% endfor %}
Expand Down Expand Up @@ -71,7 +71,7 @@
{% endif %}
{% endwith %}
{% if data.mwu %}
MWU: {{ results.mann_whitney_confidence|floatformat:2 }}%
<span title="Mann-Whitney U test">MWU:</span> {{ results.mann_whitney_confidence|floatformat:2 }}%
{% endif %}
</td>
{% endif %}
Expand Down
3 changes: 2 additions & 1 deletion experiments/templatetags/experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def experiment_goal(goal_name):
@register.inclusion_tag('experiments/confirm_human.html', takes_context=True)
def experiments_confirm_human(context):
request = context.get('request')
return {'confirmed_human': request.session.get(conf.CONFIRM_HUMAN_SESSION_KEY, False)}
if request:
return {'confirmed_human': request.session.get(conf.CONFIRM_HUMAN_SESSION_KEY, False)}


class ExperimentNode(template.Node):
Expand Down
4 changes: 2 additions & 2 deletions experiments/tests/test_significance.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def test_equal(self):
self.assertChiSquareCorrect(((100, 100, 100), (200, 200, 200), (300, 300, 300)), 0, 1)

def test_error(self):
self.assertRaises(TypeError, chi_square_p_value((1,)))
self.assertRaises(TypeError, chi_square_p_value(((1,2,3))))
self.assertEqual(chi_square_p_value((1,)), None)
self.assertEqual(chi_square_p_value(((0,2,3))), None)

def test_is_none(self):
self.assertEqual(chi_square_p_value(((1, 1), (1, -1))), (None, None), "Negative numbers should not be allowed")
Expand Down
2 changes: 1 addition & 1 deletion experiments/tests/test_webuser.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def test_transfer_enrollments(self):
# if the participant cache hasn't been wiped appropriately then the
# session experiment user will be impacted instead of the authenticated
# experiment user
ExperimentsRetentionMiddleware().process_response(request, HttpResponse())
ExperimentsRetentionMiddleware(request).process_response(request, HttpResponse())
self.assertIsNotNone(Enrollment.objects.all()[0].last_seen)


Expand Down
2 changes: 1 addition & 1 deletion experiments/tests/test_webuser_incorporate.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def _login(self):
def test_visit_incorporate(self):
alternative = participant(self.request).enroll(self.experiment.name, ['alternative'])

ExperimentsRetentionMiddleware().process_response(self.request, HttpResponse())
ExperimentsRetentionMiddleware(self.request).process_response(self.request, HttpResponse())

self.assertEqual(
dict(self.experiment_counter.participant_goal_frequencies(self.experiment,
Expand Down
4 changes: 2 additions & 2 deletions experiments/tests/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from experiments.urls import urlpatterns
from django.conf.urls import url
from django.urls import path
from django.contrib import admin


urlpatterns += [
url(r'^admin/', admin.site.urls),
path('admin/', admin.site.urls),
]
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
license='MIT',
install_requires=[
'django>=1.11',
'django-modeldict-yplan>=1.5.0,<2',
'django-modeldict-yplan>=1.5.0',
'redis>=2.4.9',
],
tests_require=[
Expand Down
7 changes: 5 additions & 2 deletions testrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ def runtests():
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'django.contrib.messages',
'experiments',),
ROOT_URLCONF='experiments.tests.urls',
MIDDLEWARE_CLASSES = (
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware'
),
SECRET_KEY="foobarbaz",
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
Expand All @@ -36,6 +38,7 @@ def runtests():
'OPTIONS': {
'context_processors': [
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
Expand Down
25 changes: 19 additions & 6 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,33 @@ minversion=2.7
skip_missing_interpreters=True

envlist =
{py27,py35,py36,pypy2,pypy3}-django1.11
{py35,py36,py37,py38,pypy38,pypy39}-django{2.2}
{py36,py37,py38,py39,pypy38,pypy39}-django{3.0,3.1}
{py36,py37,py38,py39,py310,pypy38,pypy39}-django{3.2}
{py38,py39,py310,pypy38,pypy39}-django{4.0}

[gh-actions]
python =
2.7: py27
3.5: py35
3.6: py36
pypy2: pypy2
pypy3: pypy3
3.7: py37
3.8: py38
3.9: py39
3.10: py310
pypy-3.8: pypy38
pypy-3.9: pypy39

[testenv]
commands =
python testrunner.py
python -W error::DeprecationWarning testrunner.py

deps =
mock
django1.11: Django==1.11
django2.2: Django==2.2.*
django2.2: jsonfield>=1.0.3,<3
django3.0: Django==3.0.*
django3.0: jsonfield>=1.0.3,<3
django3.1: Django==3.1.*
django3.1: jsonfield>=1.0.3,<3
django3.2: Django==3.2.*
django4.0: Django==4.0.*