teapoll / survey / models.py

from django import forms, template
from django.db import models
from django.contrib.auth.models import User

import polymorphic
import random
import simplejson
import string

class ValidationError(Exception):
  pass

class Question(polymorphic.PolymorphicModel):
  class Meta:
    ordering = ['order']

  survey = models.ForeignKey('Survey', related_name = 'questions')
  order = models.IntegerField()
  description = models.TextField()

  def get_form_class(self, **attrs):
    new_cls = type("_%s_Form" % self.__class__.__name__, (forms.Form,), attrs)
    return new_cls

  def submit(self, data):
    form = self.get_form(data = data, prefix = 'q%s' % self.pk)
    if not form.is_valid():
      raise ValidationError
    return form.cleaned_data

  def interpret_answer(self, data):
    return data

  def form_data(self, data):
    return data

  def as_html(self, **form_args):
    form = self.get_form(prefix = 'q%s' % self.pk, **form_args)
    tpl = template.loader.get_template('survey/questions/%s.html' % \
      self._meta.verbose_name.replace(' ', '_').replace('_question', ''))
    return tpl.render(template.Context({ "form" : form, "question" : self }))

  def __unicode__(self):
    if len(self.description) > 70:
      return self.description[:67] + u"..."
    
    return self.description

class SeparatorQuestion(Question):
  def get_form(self, **kwargs): pass
  def submit(self, data): pass

class SimpleChoice(models.Model):
  class Meta:
    ordering = ['question', 'number']

  question = models.ForeignKey('SimpleChoiceQuestion', related_name = 'choices')
  text = models.TextField()
  number = models.IntegerField()

  def __unicode__(self):
    return u"%d - %s" % (self.number, self.text)

class SimpleChoiceQuestion(Question):
  def get_form(self, **kwargs):
    form = self.get_form_class(
      choice = forms.ModelChoiceField(
        queryset = self.choices.all(),
        empty_label = u"(brez odgovora)",
        required = False
      )
    )
    return form(**kwargs)

  def submit(self, data):
    data = super(SimpleChoiceQuestion, self).submit(data)
    return data['choice'].pk if data['choice'] else None

  def interpret_answer(self, data):
    return self.choices.get(pk = data).number if data else None

  def form_data(self, data):
    return dict(choice = data)

class ChoiceColumn(models.Model):
  class Meta:
    ordering = ['question', 'number']

  question = models.ForeignKey('ColumnChoiceQuestion', related_name = 'columns')
  text = models.TextField()
  number = models.IntegerField()

  def __unicode__(self):
    return u"%d - %s" % (self.number, self.text)

class ChoiceRow(models.Model):
  class Meta:
    ordering = ['question', 'order']

  question = models.ForeignKey('ColumnChoiceQuestion', related_name = 'rows')
  text = models.TextField()
  order = models.IntegerField()

  def __unicode__(self):
    return u"%s) %s" % (string.lowercase[self.order - 1], self.text)

class ColumnChoiceQuestion(Question):
  def get_form(self, **kwargs):
    fields = {}
    for row in self.rows.all():
      fields['r%s' % row.pk] = forms.ModelChoiceField(
        queryset = self.columns.all(),
        label = unicode(row),
        empty_label = u"(brez odgovora)",
        required = False
      )

    form = self.get_form_class(**fields)
    return form(**kwargs)

  def submit(self, data):
    raw_data = super(ColumnChoiceQuestion, self).submit(data)
    data = {}
    for row in self.rows.all():
      datum = raw_data['r%s' % row.pk]
      data[row.pk] = datum.pk if datum else None
    return data

  def interpret_answer(self, data):
    idata = {}
    for rpk, cpk in data.items():
      idata[rpk] = self.columns.get(pk = cpk).number if cpk else None
    return idata

  def form_data(self, data):
    fdata = {}
    for rpk, cpk in data.items():
      fdata['r%s' % rpk] = cpk
    return fdata

class MultipleChoice(models.Model):
  class Meta:
    ordering = ['question', 'number']

  question = models.ForeignKey('MultipleChoiceQuestion', related_name = 'choices')
  text = models.TextField()
  number = models.IntegerField()

  def __unicode__(self):
    return u"%d - %s" % (self.number, self.text)

class MultipleChoiceQuestion(Question):
  def get_form(self, **kwargs):
    form = self.get_form_class(
      choice = forms.ModelMultipleChoiceField(
        queryset = self.choices.all(),
        required = False
      )
    )
    return form(**kwargs)

  def submit(self, data):
    data = super(MultipleChoiceQuestion, self).submit(data)
    return [x.pk for x in data['choice']]

  def interpret_answer(self, data):
    return [self.choices.get(pk = x).number for x in data]

  def form_data(self, data):
    return dict(choice = data)

class NumericRangeQuestion(Question):
  min_answer = models.PositiveIntegerField()
  max_answer = models.PositiveIntegerField()

  def get_form(self, **kwargs):
    form = self.get_form_class(
      choice = forms.TypedChoiceField(
        choices = [('', u"(brez odgovora)")] + \
          [(x, x) for x in xrange(self.min_answer, self.max_answer + 1)],
        coerce = int,
        empty_value = None,
        required = False
      )
    )
    return form(**kwargs)

  def submit(self, data):
    data = super(NumericRangeQuestion, self).submit(data)
    return data['choice']

  def form_data(self, data):
    return dict(choice = data)

class NumericEntryQuestion(Question):
  def get_form(self, **kwargs):
    form = self.get_form_class(
      value = forms.IntegerField(required = False),
    )
    return form(**kwargs)

  def submit(self, data):
    data = super(NumericEntryQuestion, self).submit(data)
    return int(data['value']) if data['value'] else None

  def form_data(self, data):
    return dict(value = data)

def generate_sid():
  return "".join([random.choice(string.ascii_letters + string.digits) for i in xrange(10)])

class Survey(models.Model):
  class Meta:
    ordering = ['sid']

  sid = models.CharField(primary_key = True, max_length = 15, editable = False, default = generate_sid)
  title = models.CharField(max_length = 200)

  def save(self, *args, **kwargs):
    if self.sid is None:
      self.sid = self.generate_sid()

    super(Survey, self).save(*args, **kwargs)

  def submit(self, data, subject = None, user = None):
    if subject is None:
      subject = Subject(created_by = user)
      subject.save()

    for question in self.questions.all():
      if question.get_form() is None:
        continue
      
      try:
        answer = Answer.objects.get(subject = subject, survey = self, question = question)
      except Answer.DoesNotExist:
        answer = Answer(subject = subject, survey = self, question = question)

      answer.data = simplejson.dumps(question.submit(data))
      answer.save()

    return subject

  def get_subjects(self):
    return Subject.objects.filter(answers__survey = self).distinct()

  def __unicode__(self):
    return self.title

class Subject(models.Model):
  class Meta:
    ordering = ['sid']

  sid = models.AutoField(primary_key = True)
  created_at = models.DateTimeField(auto_now_add = True)
  created_by = models.ForeignKey(User, null = True)

class Answer(models.Model):
  subject = models.ForeignKey(Subject, related_name = 'answers')
  survey = models.ForeignKey(Survey, related_name = 'subject_answers')
  question = models.ForeignKey(Question, related_name = 'subject_answers')
  data = models.TextField()

  def interpret(self):
    data = simplejson.loads(self.data)
    return self.question.interpret_answer(data)

  def as_html(self):
    data = simplejson.loads(self.data)
    return self.question.as_html(initial = self.question.form_data(data))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.