class GenericQuestion(BaseModel): ANSWER_TYPES = [(name, name) for name in Answer.answer_types()] identifier = models.CharField(max_length=100, verbose_name='Variable Name') text = models.CharField(max_length=250) answer_type = models.CharField( max_length=100, blank=False, null=False, choices=ANSWER_TYPES) response_validation = models.ForeignKey(ResponseValidation, related_name='%(class)s', null=True, blank=True, verbose_name='Validation Rule') @classmethod def type_name(cls): return cls._meta.verbose_name.title() class Meta: abstract = True def validators(self): return Answer.get_class(self.answer_type).validators() def validator_names(self): return [v.__name__ for v in Answer.get_class(self.answer_type).validators()] def odk_constraint(self): if self.response_validation: return self.response_validation.get_odk_constraint(self) def odk_constraint_msg(self): if self.response_validation: return self.response_validation.dconstraint_message
class QuestionTemplate(BaseModel): ANSWER_TYPES = [(name, name) for name in Answer.answer_types()] identifier = models.CharField(max_length=100, blank=False, null=True, unique=True) group = models.ForeignKey("HouseholdMemberGroup", related_name="question_templates") text = models.CharField( max_length=150, blank=False, null=False, #help_text="To replace the household member's name \ #in the question, please include the variable FAMILY_NAME in curly brackets, e.g. {{ FAMILY_NAME }}. " ) answer_type = models.CharField(max_length=100, blank=False, null=False, choices=ANSWER_TYPES) module = models.ForeignKey("QuestionModule", related_name="question_templates") class Meta: app_label = 'survey' def __unicode__(self): return "%s - %s: (%s)" % (self.identifier, self.text, self.answer_type.upper()) def save(self, *args, **kwargs): if self.answer_type not in [ MultiChoiceAnswer.choice_name(), MultiSelectAnswer.choice_name() ]: self.options.all().delete() return super(QuestionTemplate, self).save(*args, **kwargs)
class Question(BaseModel): ANSWER_TYPES = [(name, name) for name in Answer.answer_types()] identifier = models.CharField(max_length=100, blank=False, null=True, verbose_name='Variable Name') text = models.CharField( max_length=150, blank=False, null=False, #help_text="To replace the household member's name \ #in the question, please include the variable FAMILY_NAME in curly brackets, e.g. {{ FAMILY_NAME }}. " ) answer_type = models.CharField(max_length=100, blank=False, null=False, choices=ANSWER_TYPES) group = models.ForeignKey(HouseholdMemberGroup, related_name='questions') batch = models.ForeignKey('Batch', related_name='batch_questions') module = models.ForeignKey("QuestionModule", related_name="questions", default='') class Meta: app_label = 'survey' unique_together = [ ('identifier', 'batch'), ] def answers(self): return Answer.get_class(self.answer_type).objects.filter(question=self) def total_answers( self ): #just utility to get number of times this question has been answered return Answer.get_class( self.answer_type).objects.filter(question=self).count() def is_loop_start(self): from survey.forms.logic import LogicForm return self.connecting_flows.filter( desc=LogicForm.BACK_TO_ACTION).exists( ) #actually the more correct way is to # check if the next is previous def is_loop_end(self): from survey.forms.logic import LogicForm return self.flows.filter(desc=LogicForm.BACK_TO_ACTION).exists( ) #actually the more correct way is #to check if connecting quest is asked after @property def loop_ender(self): try: from survey.forms.logic import LogicForm return self.connecting_flows.get( desc=LogicForm.BACK_TO_ACTION).question except QuestionFlow.DoesNotExist: inlines = self.batch.questions_inline() @property def looper_flow(self): #if self.is_loop_start() or self.is_loop_end(): return self.batch.get_looper_flow(self) def loop_boundary(self): return self.batch.loop_back_boundaries().get(self.pk, None) # def loop_inlines(self): def delete(self, using=None): ''' Delete related answers before deleting this object :param using: :return: ''' answer_class = Answer.get_class(self.answer_type) answer_class.objects.filter(question=self).delete() return super(Question, self).delete(using=using) def display_text(self, channel=None): text = self.text if channel and channel == USSDAccess.choice_name( ) and self.answer_type == MultiChoiceAnswer.choice_name(): extras = [] #append question options for option in self.options.all().order_by('order'): extras.append(option.to_text) text = '%s\n%s' % (text, '\n'.join(extras)) return text def next_question(self, reply): flows = self.flows.all() answer_class = Answer.get_class(self.answer_type) resulting_flow = None for flow in flows: if flow.validation_test: test_values = [arg.param for arg in flow.text_arguments] if getattr(answer_class, flow.validation_test)(reply, *test_values) == True: resulting_flow = flow break else: resulting_flow = flow if resulting_flow: return resulting_flow.next_question def previous_inlines(self): inlines = self.batch.questions_inline() if self not in inlines: raise ValidationError('%s not inline' % self.identifier) previous = [] for q in inlines: if q.identifier == self.identifier: break else: previous.append(q) return set(previous) def direct_sub_questions(self): from survey.forms.logic import LogicForm sub_flows = self.flows.filter(desc=LogicForm.SUBQUESTION_ACTION, validation_test__isnull=False) return OrderedSet([flow.next_question for flow in sub_flows]) def conditional_flows(self): return self.flows.filter(validation_test__isnull=False) def preceeding_conditional_flows(self): return self.connecting_flows.filter(validation_test__isnull=False) def __unicode__(self): return "%s - %s: (%s)" % (self.identifier, self.text, self.answer_type.upper()) def save(self, *args, **kwargs): if self.answer_type not in [ MultiChoiceAnswer.choice_name(), MultiSelectAnswer.choice_name() ]: self.options.all().delete() return super(Question, self).save(*args, **kwargs) @classmethod def zombies(cls, batch): #these are the batch questions that do not belong to any flow in any way survey_questions = batch.survey_questions return batch.batch_questions.exclude( pk__in=[q.pk for q in survey_questions]) def hierarchical_result_for(self, location_parent, survey): locations = location_parent.get_children().order_by('name')[:10] answers = self.multichoiceanswer.all() return self._format_answer(locations, answers, survey) def _format_answer(self, locations, answers, survey): question_options = self.options.all() data = OrderedDict() for location in locations: households = Household.all_households_in(location, survey) data[location] = { option.text: answers.filter( value=option, interview__householdmember__household__in=households). count() for option in question_options } return data
from django.db import models from django.db.models import Max from survey.models.householdgroups import HouseholdMemberGroup from survey.models.locations import Location from survey.models.surveys import Survey from survey.models.base import BaseModel from survey.utils.views_helper import get_descendants from survey.models.questions import Question, QuestionFlow from survey.models.access_channels import InterviewerAccess # from survey.models.enumeration_area import EnumerationArea from survey.models.interviews import AnswerAccessDefinition, Answer from survey.models.access_channels import ODKAccess from ordered_set import OrderedSet ALL_GROUPS = HouseholdMemberGroup.objects.all() ALL_ANSWERS = Answer.answer_types() class Batch(BaseModel): order = models.PositiveIntegerField(max_length=2, null=True) name = models.CharField(max_length=100, blank=False, null=True) description = models.CharField(max_length=300, blank=True, null=True) survey = models.ForeignKey(Survey, null=True, related_name="batches") # eas = models.ManyToManyField(EnumerationArea, related_name='batches', null=True) #enumeration areas for which this Batch is open # group = models.ForeignKey("HouseholdMemberGroup", null=True, related_name="question_group") start_question = models.OneToOneField(Question, related_name='starter_batch', null=True, blank=True, on_delete=models.SET_NULL) BATCH_IS_OPEN_MESSAGE = "Batch cannot be deleted because it is open in %s." BATCH_HAS_ANSWERS_MESSAGE = "Batch cannot be deleted because it has responses." class Meta: app_label = 'survey' unique_together = [('survey', 'name',) ]
from survey.models.base import BaseModel from survey.utils.views_helper import get_descendants from survey.models.questions import Question, QuestionFlow from survey.forms.logic import LogicForm from survey.models.access_channels import InterviewerAccess # from survey.models.enumeration_area import EnumerationArea from survey.models.interviews import AnswerAccessDefinition, Answer from survey.models.access_channels import ODKAccess from django.core.exceptions import ValidationError from ordered_set import OrderedSet from collections import OrderedDict from cacheops import cached_as from django.conf import settings ALL_GROUPS = HouseholdMemberGroup.objects.all() ALL_ANSWERS = Answer.answer_types() class Batch(BaseModel): order = models.PositiveIntegerField(null=True) name = models.CharField(max_length=100, blank=False, null=True, db_index=True) description = models.CharField(max_length=300, blank=True, null=True) survey = models.ForeignKey(Survey, null=True, related_name="batches") # eas = models.ManyToManyField(EnumerationArea, related_name='batches', null=True) #enumeration areas for which this Batch is open # group = models.ForeignKey("HouseholdMemberGroup", null=True, related_name="question_group") start_question = models.OneToOneField(Question, related_name='starter_batch', null=True,