1. Jernej Kos
  2. teapoll

Source

teapoll / survey / models.py

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

import datetime
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()
  allow_comment = models.BooleanField(default = False)

  def get_form_class(self, **attrs):
    if self.allow_comment:
      attrs['comment'] = forms.CharField(widget = forms.Textarea, required = False)
    
    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, dict(comment = form.cleaned_data.get('comment', '').strip())

  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, add_fields = super(SimpleChoiceQuestion, self).submit(data)
    add_fields['choice'] = data['choice'].pk if data['choice'] else None
    return add_fields

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, add_fields = 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
    add_fields['rows'] = data
    return add_fields

  def form_data(self, data):
    data = super(ColumnChoiceQuestion, self).form_data(data)
    for rpk, cpk in data['rows'].items():
      data['r%s' % rpk] = cpk
    
    del data['rows']
    return data

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, add_fields = super(MultipleChoiceQuestion, self).submit(data)
    add_fields['choice'] = [x.pk for x in data['choice']]
    return add_fields

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, add_fields = super(NumericRangeQuestion, self).submit(data)
    add_fields['choice'] = data['choice']
    return add_fields

class DateQuestion(Question):
  def get_form(self, **kwargs):
    form = self.get_form_class(
      value = forms.DateField(required = False),
      input_formats = ['%d %m %Y', '%d.%m.%Y', '%d. %m. %Y', '%d-%m-%Y', '%d/%m/%Y']
    )
    return form(**kwargs)
  
  def submit(self, data):
    data, add_fields = super(DateQuestion, self).submit(data)
    date = data['value']
    add_fields['value'] = dict(
      year = date.year,
      month = date.month,
      day = date.day
    ) if date else None
    return add_fields
  
  def form_data(self, data):
    return dict(
      value = datetime.date(
        data['value']['year'],
        data['value']['month'],
        data['value']['day']
      ) if data['value'] else None
    )

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, add_fields = super(NumericEntryQuestion, self).submit(data)
    add_fields['value'] = int(data['value']) if data['value'] else None
    return add_fields

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):
    try:
      sid = transaction.savepoint()
      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()
      
      transaction.savepoint_commit(sid)
    except:
      transaction.savepoint_rollback(sid)
      raise
    
    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 as_html(self, **form_args):
    data = simplejson.loads(self.data)
    return self.question.as_html(initial = self.question.form_data(data), **form_args)