Skip to content

3 · Identifying participants

About 15 minutes

Five iMac G3 computers in the translucent colours Apple called flavours

The five "flavours" of the iMac G3, 1999. Machines like these filled university labs around the turn of the century, running experiments through packages such as PsyScope and SuperLab. Or DMDX, if you were more of a PC user like me back then.
Image: Stephen Hackett, CC BY-SA 4.0, via Wikimedia Commons.
Remember DMDX and PsyScope?

If you were in a lab around 2000, one of these was probably how you built a study:

  • DMDX on Windows, written by Jonathan and Kenneth Forster and still in use, which got millisecond timing out of a PC by talking to the display hardware (Forster & Forster, 2003)
  • PsyScope on the Mac, where you assembled a trial by dragging boxes around instead of writing code. It was remarkable for 1993, and the reason a generation of psychologists owned a Mac (Cohen, MacWhinney, Flatt & Provost, 1993)
  • SuperLab and E-Prime, the commercial options. Both are still sold today

By the end of this chapter the study will know who is doing it and which condition they're in, read from the URL 🔗.

Two kinds of URL parameter

Look at a real study link:

/study/flanker/?participant_id=p01&condition=A

There are two things going on in this URL:

  • flanker is a path parameter. It's part of the address itself, and it's how the URL pattern picks the study. This is the <slug:slug> we encountered in the last chapter
  • participant_id and condition are query parameters, the key=value pairs after the ?. They carry extra information about this particular visit

Recruitment tools like Prolific add such query parameters to your study URL (unique for each participant), allowing you to know who has done your study and thus who to pay!

Read query parameters on the server

You could read query parameters in the participant's browser via JavaScript (URLSearchParams). I'd rather achieve the same thing in the backend using Python, as we already need to do related tasks here. Keeping similar logic in the same place reduces complexity (see the KISS computer science principle), which is always a good thing.

The study_detail view you copied in chapter 2 already reads these query parameters. Here it is again, so we can look at those lines:

def study_detail(request, slug):
    """Serve a single study to a participant.

    The study is identified by a *path* parameter (``slug``, matched by the URL
    pattern); the participant and condition arrive as *query* parameters, e.g.
    ``/study/flanker/?participant_id=p01&condition=A``. We read them here on the
    server and pass them into the template.
    """
    study = get_object_or_404(Study, slug=slug)

    # ?preview=1 runs the study but skips saving (see the template). Match "1" exactly, so
    # ?preview=0 doesn't accidentally turn it on.
    preview = request.GET.get("preview") == "1"
    participant_id = request.GET.get("participant_id", "")
    condition = request.GET.get("condition", "")

    context = {
        "study": study,

        # maybe we want to give a collaborator our draft study but we dont
        # want their data to save. Preview lets us do this.
        "preview": preview,

        # Every plugin. The researcher's pasted code can use any trial type
        # without telling us which, so the page is given them all (see study.jspsych).
        "jspsych_plugins": plugin_files(),

        # Who is doing the study, and which condition they're in, read from the query string
        # so the front-end never has to parse the URL itself.
        "participant_id": participant_id,
        "condition": condition,
    }
    return render(request, "study/study_detail.html", context)

study/views.py on GitHub

See how request.GET holds various key pieces of information. From there we can look up participant_id and condition (defaulting to an empty string if they're missing) and put each one in the context as its own variable.

Hand the extracted query parameters as variables to the front-end

See how in the template the python variables we just defined are passed to JavaScript variables. Add these two lines to study/templates/study/study_detail.html, inside the <script> block and above the const jsPsych = ... line:

const PARTICIPANT_ID = "{{ participant_id|escapejs }}";
const CONDITION = "{{ condition|escapejs }}";

study/templates/study/study_detail.html on GitHub

What escapejs is doing there

Both of those values originate from the URL, so they came from whoever wrote the link, and they could have been manipulated to run dodgy code. escapejs rewrites quotes, angle brackets, backslashes and newlines as \uXXXX escapes, so a value can't break out of its JavaScript string and run as code.

Where the Prolific IDs come from

When you set your study up on Prolific, you paste your study URL with Prolific's placeholders, and Prolific fills them in per participant:

https://your-site.example/study/flanker/?participant_id={{%PROLIFIC_PID%}}&condition=A

Prolific swaps {{%PROLIFIC_PID%}} for each participant's real ID. In chapter 4b, at the end of the study we send participants back to Prolific so their submission is recorded as complete.

Prolific also passes {{%STUDY_ID%}} and {{%SESSION_ID%}}. We only read participant_id here. Note that SESSION_ID offers a security measure to check if participants manipulated the URLs provided by Prolific (for more, see Secure external URL).

Checkpoint

With the server running, open http://127.0.0.1:8000/study/flanker/?participant_id=p01&condition=A. The study looks exactly as before. To see the difference, have a look at the page source (right-click, View Page Source) and find your two new lines:

const PARTICIPANT_ID = "p01";
const CONDITION = "A";

No data is saved yet. The page knows who the participant is, but when the study finishes the data just disappears. In the next chapter we sort this out!