app/soc/models/question.py
changeset 339 b9be44e09530
child 342 72482d8e5b34
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/models/question.py	Wed Oct 15 17:10:27 2008 +0000
@@ -0,0 +1,168 @@
+#!/usr/bin/python2.5
+#
+# Copyright 2008 the Melange authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module contains the Question Model."""
+
+__authors__ = [
+  '"Todd Larsen" <tlarsen@google.com>',
+]
+
+
+from google.appengine.ext import db
+
+import soc.models.work
+
+
+class Question(soc.models.work.Work):
+  """Model of a Question, which is a specialized form of Work.
+
+  Specific types of Questions are actually implemented in subclasses.
+
+  The specific way that the properties and relations inherited from
+  Work are used with a Question are described below.
+
+    work.title:  the title of the Question, used for finding the
+      Question in a list of Questions
+
+    work.abstract:  the Question text, asked to the respondent
+
+    work.authors:  the Authors of the Work referred to by this relation
+      are the authors of the Question
+
+    work.reviews:  even Questions can be "reviewed" (possibly commented
+      on during creation or annotated once put into use).
+
+    work.partial_path: used to scope (and, when combined with
+      work.link_name, uniquely identify) a Question in the same way the
+      property are used with Documents, etc.
+
+    work.link_name: used to identify (and, when combined with
+      work.partial_path, *uniquely* identify) a Question in the same way
+      these properties are used with Documents, etc.
+      
+  In addition to any explicit ReferenceProperties in the Question Model
+  and those inherited as described above, a Question entity participates
+  in these relationships:
+
+    answers)  a 1:many relationship, where each Question has many different
+      Answers associated with it as parts of Responses to Quizzes.  This is
+      implemented as the 'answers' back-reference Query of the Answer model
+      'question' reference.  It is currently unclear how useful this
+      back-reference will be, since the same Question could be used in
+      multiple different Quizzes. Given this, 'answers' currently only
+      exists for completeness.
+      
+    quizzes)  a many:many relationship between Questions and the Quizzes
+      that collect them into a set.  This relation is not explicitly
+      implemented, but can be obtained via a query something like:
+      
+        quizzes_with_a_question = db.GqlQuery(
+            "SELECT * FROM Quiz where questions = :1",
+            a_question.key())
+
+      Such queries are probably only needed when a Question might be
+      altered, in order to find which Quizzes will be affected.
+
+  The properties in this Model do not have verbose_name or help_text,
+  because the dynamic nature of the forms required to create, edit, and
+  use entities of this Model make them pretty useless.
+  
+  ######################################################################
+  # TODO(tlarsen): the following verbose comments can be removed later,
+    when these ideas are implemented in the views and controllers; they
+    are here now so that the concepts will not be lost before that time.
+
+  The recommended use for the combination of work.partial_path and
+  work.link_name is to keep the *same* link_name when copying and
+  modifying an existing Question for a new Program (or instance of a
+  Group that is per-Program), while changing the work.partial_path to
+  represent the Program and Group "ownership" of the Question.  For
+  example, if a Question asking about prior GSoC participation needed
+  to have an additional choice (see the choice_ids and choices properties
+  below), it is desirable to keep the same work.link_name (and also
+  simply append new choice_ids and choices to keep the old answer values
+  compatible).  An existing Question in the above example might be identified
+  as something like:
+    Question:google/gsoc2009/gsoc_past_participation
+    <type>:<Sponsor>/<Program>/<link_name> 
+  To make it possible to query for gsoc_past_participation answers regardless
+  of the Program, the next year, new values are added to choice_ids and
+  choices in a new Question copied from the one above, which would then
+  be named something (still unique) like:
+    Question:google/gsoc2010/gsoc_past_participation
+  Care just needs to be taken to keep the existing choice_ids and choices
+  compatible.
+  
+  Other interesting possibilities also exist, such as asking about GSoC
+  participation of the GHOP participants (some GHOP high school students
+  have actually previously been GSoC mentors, for example).  To produce
+  unique statistics for GHOP that could also be aggregated overall in
+  combination with GSoC, the gsoc_past_participation Question would be
+  duplicated (unaltered) to something like:
+    Question:google/ghop2009/gsoc_past_participation
+  To get the combined results, query on a link_name of
+  gsoc_past_participation.  For more targeted results, include the
+  partial_path to make the query more specific.
+
+  Question creation to permit use cases like the one above is going to
+  be a bit of an "advanced" skill, possibly.  "Doing it wrong" the first
+  time a Question is created will make it difficult to implement stuff
+  like multiple-choice Questions that "grow" new choices year-over-year.
+
+  A dynamic form is most definitely going to be needed to implement the
+  Question creation and editing for multiple-choice questions.
+  """
+  #: db.ListProperty of short, plain-text, "link_name-like" strings
+  #: representing the "encoded" answer choices (must be strings compatible
+  #: with being query arguments and being used in HTML controls and POST
+  #: responses).
+  #:
+  #: If empty (None or an empty list), it is assumed that this Question
+  #: is *not* a multiple choice question.  In that case, the UI should
+  #: display the Question as a textarea in forms and accept any plain-text.
+  #:
+  #: If non-empty, max_answers helps determine how the UI should display
+  #: the Question.  Also, controller logic needs to validate if the
+  #: strings in the 'answers' property of the Answer entity come only
+  #: from this list.
+  #:
+  #: Once Answers to this Question have been stored in the Datastore,
+  #: choice_ids and choices should *not* be modified.  An existing
+  #: Question can be duplicated and then modified (but, it will be a
+  #: different question as a result).
+  choice_ids = db.ListProperty(item_type=str)
+
+  #: db.ListProperty of human-readable choice strings, in the same order
+  #: as, and corresponding to, the "encoded" choices in the choice_ids
+  #: db.ListProperty. 
+  choices = db.ListProperty(item_type=str)
+
+  #: db.IntegerProperty indicating the maximum number of answer values
+  #: permitted for this question.  If 'choices' does not contain a list of
+  #: choice strings, this value is ignored (but should still only be 1).
+  #:
+  #: If there are 'choices' and this value is 1, the UI should render the
+  #: Question in forms as a single-choice control ("radio buttons").
+  #:
+  #: If there are 'choices' and this value is greater than 1, the UI should
+  #: render the question as a list of check-boxes.
+  #:
+  #: max_answers greater than 1 combined with choices enables Questions
+  #: like, for example, "...select the three most important...".
+  max_answers = db.IntegerProperty(default=1)
+
+  #: field storing whether the Answer to a Question is optional
+  is_optional = db.BooleanProperty(default=False)