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

survey Django App

Refer to my initial django-oscar notes.

models.py
"""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;