Skip to main content

Django Survey App

This Django application allows you to create and manage surveys including CSAT, NPS, CES, and Generic Surveys. It supports both anonymous and logged-in user responses with user metadata tracking.

Setup and Installation

1. Install the Application

To install the customersatisfactionmetrics package, run the following command:

pip install customersatisfactionmetrics

2. Update Django Settings

Add customersatisfactionmetrics to the INSTALLED_APPS list in your Django project's settings.py file:

INSTALLED_APPS = [
    # ... other installed apps ...
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "customersatisfactionmetrics",  # Add this line
]

3. Run Migrations

Apply the necessary migrations to your database with the following command:

python manage.py migrate

4. Run the Django Server

Start the Django development server:

python manage.py runserver

Access the admin panel at http://localhost:8000/admin/.

5. Adding Surveys and Questions

  • Navigate to the Django admin site.
  • Use the respective sections to add new surveys and questions.

6. URL Configuration

Include the customersatisfactionmetrics URLs in your project's urls.py file:

from django.urls import path, include

urlpatterns = [
    path('', include('customersatisfactionmetrics.urls')),
    # ... other URL patterns ...
]

Usage

1. Access the Survey

To view a survey, navigate to the URL path survey/slug/<slug> in your web browser, where <slug> is the slug you assigned to the survey in the admin panel. For example:

http://localhost:8000/survey/slug/sample-survey

This URL will display the survey form for users to fill out and submit.

2. Embedding Surveys in Templates

To easily embed a survey in your Django templates, you can use the insert_survey_by_slug template tag. This tag allows you to insert a survey form by its slug directly into a template.

First, load the template tag in your template file:

{% load survey_tags %}

Then, use the insert_survey_by_slug tag to insert a survey form by specifying its slug:

{% insert_survey_by_slug 'your-survey-slug' %}

Replace 'your-survey-slug' with the actual slug of the survey you want to embed. The survey form will be rendered wherever you include this tag in your template.

Example:

<!DOCTYPE html>
<html>
<head>
    <title>Survey Page</title>
</head>
<body>
    <h1>Survey Form</h1>
    {% load survey_tags %}
    {% insert_survey_by_slug 'sample-survey' %}
</body>
</html>

This method provides a flexible way to integrate surveys into various parts of your Django application without the need for additional view logic or URL configurations.

3. Touchpoints: asking only sometimes

insert_survey_by_slug renders a survey every single time the template is rendered. A touchpoint wraps a survey in rules about when it may be shown, so you can leave the tag in a shared template and trust that visitors are not asked repeatedly.

Create a Touchpoint in the admin, point it at a survey, then render it:

{% load survey_tags %}
{% survey_touchpoint 'order-placed' %}

This renders nothing at all when the touchpoint does not exist, is inactive, or the visitor is inside a cooldown, so it is safe to place on any page. Rendering it records an Impression, which is what the cooldown is measured against and what lets you compute a real response rate later.

The tag needs django.template.context_processors.request enabled.

Cooldowns

Each touchpoint carries its own rules:

Field Meaning
cooldown_days How long a respondent is left alone after being shown a survey. Defaults to 84 days (12 weeks).
cooldown_scope GLOBAL means being shown any touchpoint starts the cooldown for all of them; TOUCHPOINT confines it to this one. Defaults to GLOBAL.
scope_cooldown_days An optional second cooldown applied to a shared scope key, so a whole team or tenant is not surveyed in the same week. 0 disables it.
is_active Pause a touchpoint without touching code.

An impression that is neither answered nor dismissed stays reusable for SURVEY_OPEN_IMPRESSION_TTL_HOURS (24 by default). Within that window a page reload shows the same card again rather than it silently vanishing, and no second impression is recorded. After it, the impression counts as ignored.

Scope keys

A scope key is an opaque string that groups respondents, typically an organisation or tenant id. Pass it explicitly:

{% survey_touchpoint 'order-placed' scope_key=organisation.scope_key %}

Or configure a resolver once and let the package call it:

# settings.py
SURVEY_SCOPE_RESOLVER = "myapp.utils.current_organisation_key"
# myapp/utils.py
def current_organisation_key(request):
    organisation = getattr(request, "organisation", None)
    return f"org:{organisation.id}" if organisation else ""

The package never imports your models; it only ever sees the string you return.

Measuring

Because impressions are recorded separately from responses, you can distinguish "never asked" from "asked and ignored":

from customersatisfactionmetrics.models import Impression

shown = Impression.objects.filter(touchpoint__slug="order-placed")
response_rate = shown.filter(responded_at__isnull=False).count() / shown.count()

Linking feedback to an object

Knowing who answered is often not enough, and sometimes impossible: a visitor filling in a public form has no user account at all. Every Response and Impression can therefore point at any object in your application through a generic relation, so an answer stays traceable to the thing it was about.

Pass it to the tag:

{% survey_touchpoint 'order-placed' subject=order %}

The subject is stored on the impression and the answers inherit it on submission, read back from the impression rather than posted with the form, so a respondent cannot relabel their feedback as being about somebody else's object.

For a survey rendered outside a touchpoint, pass it when processing the form:

from customersatisfactionmetrics.views.survey_view import process_form_submission

process_form_submission(request, form, survey, subject=order)

Then query it back:

from customersatisfactionmetrics.models import Response, subject_filter

Response.objects.filter(**subject_filter(order))

Primary keys are stored as text, so integer, UUID and slug primary keys all work. The subject is optional, and deleting the object it points at leaves the answer readable: the recorded content_type and object_id survive.

This requires django.contrib.contenttypes in INSTALLED_APPS, which Django enables by default.

Ratings with a comment

A scored survey (CSAT, NPS, CES) fixes the bounds of its INT questions (1-5, or 0-10 for NPS) but leaves TEXT and BOOL questions alone. That is what lets a short survey pair a rating with an optional comment, which is the usual shape of an in-product survey: one required score, one optional free text.

Marking the comment optional matters. Requiring free text is the single most common reason in-product surveys collect almost nothing.

Features

  • Supports various survey types: CSAT, NPS, CES, and Generic.
  • Allows both anonymous and logged-in user responses.
  • Tracks user metadata like IP address and user agent.
  • Touchpoints with per respondent and per scope cooldowns, so surveys are shown only sometimes rather than on every page render.
  • Impression tracking, which distinguishes "never asked" from "asked and ignored" and makes a real response rate computable.
  • Generic subject links, so a response can be tied to any object in your application even when the respondent is anonymous.

Contributing

Contributions to this project are welcome. Please fork the repository and submit a pull request.

License

This project is licensed under MIT License. See the LICENSE file in the respective folders for more details.

Contact

For any queries or further information, please contact us at devops@pescheck.nl.

Thank you for your interest in our Django Survey Project!

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

customersatisfactionmetrics-1.1.2.tar.gz (34.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

customersatisfactionmetrics-1.1.2-py3-none-any.whl (46.5 kB view details)

Uploaded Python 3

File details

Details for the file customersatisfactionmetrics-1.1.2.tar.gz.

File metadata

File hashes

Hashes for customersatisfactionmetrics-1.1.2.tar.gz
Algorithm Hash digest
SHA256 530912b6fdc61b62f5361bc8500dbd59a0c873a803c932d8db276741a4bc82f3
MD5 f9547af6b85295074db092bc03d34903
BLAKE2b-256 c39ad2d64699152b9b5007f61f6c6b1456be99a6621e72d0d640c2a10706556b

See more details on using hashes here.

File details

Details for the file customersatisfactionmetrics-1.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for customersatisfactionmetrics-1.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 3941ab77cd38cc59e772345603575cb9858a0ca1dd7dc299cb4cabf0feb4aba0
MD5 09c459e8a71e00a8b2a0a93c616cac42
BLAKE2b-256 e0358429b173ca6de930c022e2a494338860a2a7381dc02d2ce2c638381702c4

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.1.2 This release

2 files

1.1.0

2 files

1.0.9

2 files

1.0.8

2 files

1.0.7

2 files

1.0.6

2 files

1.0.5

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page