Mike Slinn

Ancient Warmth survey Django-Oscar App

Published 2021-03-03.
Time to read: 1 minutes.

This page is part of the ancientWarmth collection.

Private, for my own use only. Do not publish!

survey Django App

Refer to my initial django-oscar notes.

"""Integrates with surveyjs.io"""

import json
import sys
from inspect import cleandoc
from pathlib import Path
from django.db import models
from django.utils import timezone

def default_questionnaire():
    return '{ "pages": [ { "name": "page1" } ] }'

def group_by(data):
    from collections import defaultdict
    res = defaultdict(list)
    for item in data:
        for key, value in item:
            res[key].append(value)
        result = [ {key:value} for key, value in res.items() ]
    return result

class SurveyQuestionnaire(models.Model):
    name = models.CharField(max_length=50, unique=True)
    questions_json = models.JSONField(default=default_questionnaire)

    @classmethod
    def from_file(cls, file: str):
        return cls.from_path(Path(file))

    @classmethod
    def from_path(cls, path: Path):
        try:
            with open(path) as file:
                dictionary = json.load(file)
                string = json.dumps(dictionary)
                name = dictionary['pages'][0]['name']
                # print(f"{path}: {name}")
                questionnaire = cls.objects.update_or_create(
                    name = name,
                    questions_json = string,
                )
                return questionnaire
        except Exception as exception:
            print(f"SurveyQuestionnaire.from_path: {exception}")
            sys.exit(1)

    @property
    def questions(self) -> list:
        """@return list of question entriess in json format"""
        return self.questions_json

    @property
    def keys(self) -> list:
        """@return ['birthdate', 'compareEpsomDeadSea', 'usedBathSalts' ] """
        return [ x['name'] for x in self.questions ]

    def analytics(self) -> str:
        query_set = list(SurveyResult.objects.filter(questionnaire=self))
        data = [ list(x.result_dict.items()) for x in query_set ]
        result = group_by(data)
        return result

    def entry_for(self, name) -> str:
        """@return question entry as a dict
        Example: {'type': 'text', 'name': 'birthdate', 'title': 'When were you born?', 'inputType': 'date'}
        """
        return [ x for x in self.questions if name==x['name'] ][0]

    def question_for(self, name) -> str:
        """@return 'When were you born?' """
        return self.entry_for(name)['title']

    def type_for(self, name) -> str:
        """@return 'text'"""
        return self.entry_for(name)['type']

    def inputType_for(self, name) -> str:
        """@return 'date'"""
        return self.entry_for(name)['inputType']

    def __str__(self):
        return f"{self.name}; {len(self.questions)} questions"

class SurveyQuestionnaires(models.Model):
    surveys = models.ForeignKey(to=SurveyQuestionnaire, null=True, on_delete=models.CASCADE)

    @classmethod
    def from_files(cls):
        """Saves all json into db"""
        for path in Path("survey/static/questions/").iterdir():
            if path.is_file and path.name.endswith(".json"):
                SurveyQuestionnaire.from_path(path)

    class Meta:
        ordering = ['pk']


def true_false_yes_no(token: str) -> str:
    """Convert True/False to Yes/No"""
    if token == "True":
        return "Yes"
    if token == "False":
        return "No"
    return token

class SurveyResult(models.Model):
    created = models.DateTimeField(default=timezone.now)

    questionnaire = models.ForeignKey(on_delete=models.CASCADE, to=SurveyQuestionnaire)
    result_json = models.JSONField()
    user_id = models.EmailField()

    @property
    def as_html(self):
        lines = [ cleandoc(f"""<b>{self.question_for(key=x)}</b><br>
                               {true_false_yes_no(self.result_dict[x])}<br>
                               """) for x in self.result_dict ]
        return "<br>\n".join(lines)

    @property
    def as_text(self):
        lines = [ f"{self.question_for(key=x)}: {self.result_dict[x]}" for x in self.result_dict ]
        return "\n".join(lines)

    @property
    def result_dict(self) -> dict:
        return json.loads(self.result_json)

    @property
    def keys(self) -> list:
        return list(self.result_dict.keys())

    def email(self, email_address):
        from django.core.mail import send_mail
        # result is set to 1 for success, 0 for failure
        result = send_mail(
            subject = 'Your AncientWarmth Survey Response',
            message = self.as_text,
            html_message = self.as_html,
            from_email = 'mslinn@ancientwarmth.com',
            recipient_list = [ email_address ],
        )
        print(f"Result code for sending email={result}")

    def inputtype_for(self, key):
        """@return 'date'"""
        type_value = self.question_for(key).entry_for(key)['inputType']
        return type_value

    def question_for(self, key):
        """@return question keys"""
        question = self.questionnaire.question_for(key)
        return question

    def result_value_of(self, key: str):
        return self.result_dict[key]

    def type_for(self, key):
        """@return 'text'"""
        type_value = self.question_for(key).entry_for(key)['type']
        return type_value

    def __str__(self):
        return f"{self.user_id} reponse to {self.questionnaire.name} at {self.created}"
Shell
$ ./manage.py makemigrations survey
Migrations for 'pricing':
  pricing/migrations/0001_initial.py
    - Create model Bather
    - Create model Category
    - Create model Chemical
    - Create model Color
    - Create model Design
    - Create model Designer
    - Create model EssentialOil
    - Create model Scent
    - Create model Rating
    - Create model Package
    - Create model Mixture
    - Create model Ingredient
    - Add field designer to design
    - Add field mixture to design 
Shell
$ ./manage.py sqlmigrate pricing 0001
BEGIN;
--
-- Create model Bather
--
CREATE TABLE "aw_pricing_bather" ("user_id" integer NOT NULL PRIMARY KEY);
--
-- Create model Category
--
CREATE TABLE "aw_pricing_category" ("id" serial NOT NULL PRIMARY KEY, "name" text NOT NULL);
--
-- Create model Chemical
--
CREATE TABLE "aw_pricing_chemical" ("id" serial NOT NULL PRIMARY KEY, "alt_names" text NULL, "full_description" text NOT NULL, "name" text NOT NULL, "short_description" text NOT NULL);
--
-- Create model Color
--
CREATE TABLE "aw_pricing_color" ("id" serial NOT NULL PRIMARY KEY, "kid_friendly" boolean NOT NULL, "name" text NOT NULL);
--
-- Create model Design
--
CREATE TABLE "aw_pricing_design" ("id" serial NOT NULL PRIMARY KEY, "created" timestamp with time zone NOT NULL, "graphics" varchar(100) NOT NULL, "name" text NOT NULL, "category_id" integer NOT NULL UNIQUE, "color_id" integer NOT NULL UNIQUE);
--
-- Create model Designer
--
CREATE TABLE "aw_pricing_designer" ("user_id" integer NOT NULL PRIMARY KEY);
--
-- Create model EssentialOil
--
CREATE TABLE "aw_pricing_essentialoil" ("id" serial NOT NULL PRIMARY KEY, "kid_friendly" boolean NOT NULL, "full_description" text NOT NULL, "name" text NOT NULL, "short_description" text NOT NULL);
--
-- Create model Scent
--
CREATE TABLE "aw_pricing_scent" ("id" serial NOT NULL PRIMARY KEY, "ml_per_liter" integer NOT NULL CHECK ("ml_per_liter" >= 0), "design_id" integer NOT NULL, "essential_oil_id" integer NOT NULL UNIQUE);
--
-- Create model Rating
--
CREATE TABLE "aw_pricing_rating" ("id" serial NOT NULL PRIMARY KEY, "value" integer NOT NULL CHECK ("value" >= 0), "bather_id" integer NOT NULL, "design_id" integer NOT NULL);
--
-- Create model Package
--
CREATE TABLE "aw_pricing_package" ("id" serial NOT NULL PRIMARY KEY, "grams" integer NOT NULL CHECK ("grams" >= 0), "bather_id" integer NOT NULL, "design_id" integer NOT NULL UNIQUE);
--
-- Create model Mixture
--
CREATE TABLE "aw_pricing_mixture" ("id" serial NOT NULL PRIMARY KEY, "created" timestamp with time zone NOT NULL, "name" text NOT NULL, "designer_id" integer NOT NULL UNIQUE);
--
-- Create model Ingredient
--
CREATE TABLE "aw_pricing_ingredient" ("id" serial NOT NULL PRIMARY KEY, "parts" integer NOT NULL CHECK ("parts" >= 0), "chemical_id" integer NOT NULL UNIQUE, "mixture_id" integer NOT NULL);
--
-- Add field designer to design
--
ALTER TABLE "aw_pricing_design" ADD COLUMN "designer_id" integer NOT NULL CONSTRAINT "aw_pricing_design_designer_id_0be65681_fk_aw_pricin" REFERENCES "aw_pricing_designer"("user_id") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS "aw_pricing_design_designer_id_0be65681_fk_aw_pricin" IMMEDIATE;
--
-- Add field mixture to design
--
ALTER TABLE "aw_pricing_design" ADD COLUMN "mixture_id" integer NOT NULL UNIQUE CONSTRAINT "aw_pricing_design_mixture_id_93091820_fk_aw_pricing_mixture_id" REFERENCES "aw_pricing_mixture"("id") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS "aw_pricing_design_mixture_id_93091820_fk_aw_pricing_mixture_id" IMMEDIATE;
ALTER TABLE "aw_pricing_bather" ADD CONSTRAINT "aw_pricing_bather_user_id_6390db20_fk_auth_user_id" FOREIGN KEY ("user_id") REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_design" ADD CONSTRAINT "aw_pricing_design_category_id_c794e2d1_fk_aw_pricin" FOREIGN KEY ("category_id") REFERENCES "aw_pricing_category" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_design" ADD CONSTRAINT "aw_pricing_design_color_id_a8f9e074_fk_aw_pricing_color_id" FOREIGN KEY ("color_id") REFERENCES "aw_pricing_color" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_designer" ADD CONSTRAINT "aw_pricing_designer_user_id_8b9aabdf_fk_auth_user_id" FOREIGN KEY ("user_id") REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_scent" ADD CONSTRAINT "aw_pricing_scent_design_id_2ab15a89_fk_aw_pricing_design_id" FOREIGN KEY ("design_id") REFERENCES "aw_pricing_design" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_scent" ADD CONSTRAINT "aw_pricing_scent_essential_oil_id_700d88e4_fk_aw_pricin" FOREIGN KEY ("essential_oil_id") REFERENCES "aw_pricing_essentialoil" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "aw_pricing_scent_design_id_2ab15a89" ON "aw_pricing_scent" ("design_id");
ALTER TABLE "aw_pricing_rating" ADD CONSTRAINT "aw_pricing_rating_bather_id_c8957e24_fk_aw_pricin" FOREIGN KEY ("bather_id") REFERENCES "aw_pricing_bather" ("user_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_rating" ADD CONSTRAINT "aw_pricing_rating_design_id_17651e3b_fk_aw_pricing_design_id" FOREIGN KEY ("design_id") REFERENCES "aw_pricing_design" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "aw_pricing_rating_bather_id_c8957e24" ON "aw_pricing_rating" ("bather_id");
CREATE INDEX "aw_pricing_rating_design_id_17651e3b" ON "aw_pricing_rating" ("design_id");
ALTER TABLE "aw_pricing_package" ADD CONSTRAINT "aw_pricing_package_bather_id_d0441e4f_fk_aw_pricin" FOREIGN KEY ("bather_id") REFERENCES "aw_pricing_bather" ("user_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_package" ADD CONSTRAINT "aw_pricing_package_design_id_90fc01b7_fk_aw_pricing_design_id" FOREIGN KEY ("design_id") REFERENCES "aw_pricing_design" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "aw_pricing_package_bather_id_d0441e4f" ON "aw_pricing_package" ("bather_id");
ALTER TABLE "aw_pricing_mixture" ADD CONSTRAINT "aw_pricing_mixture_designer_id_be94523d_fk_aw_pricin" FOREIGN KEY ("designer_id") REFERENCES "aw_pricing_designer" ("user_id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_ingredient" ADD CONSTRAINT "aw_pricing_ingredien_chemical_id_2a2fd6ae_fk_aw_pricin" FOREIGN KEY ("chemical_id") REFERENCES "aw_pricing_chemical" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "aw_pricing_ingredient" ADD CONSTRAINT "aw_pricing_ingredien_mixture_id_cd5b0b60_fk_aw_pricin" FOREIGN KEY ("mixture_id") REFERENCES "aw_pricing_mixture" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "aw_pricing_ingredient_mixture_id_cd5b0b60" ON "aw_pricing_ingredient" ("mixture_id");
CREATE INDEX "aw_pricing_design_designer_id_0be65681" ON "aw_pricing_design" ("designer_id");
COMMIT; 


* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.