|
1 #!/usr/bin/python2.5 |
|
2 # |
|
3 # Copyright 2008 the Melange authors. |
|
4 # |
|
5 # Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 # you may not use this file except in compliance with the License. |
|
7 # You may obtain a copy of the License at |
|
8 # |
|
9 # http://www.apache.org/licenses/LICENSE-2.0 |
|
10 # |
|
11 # Unless required by applicable law or agreed to in writing, software |
|
12 # distributed under the License is distributed on an "AS IS" BASIS, |
|
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 # See the License for the specific language governing permissions and |
|
15 # limitations under the License. |
|
16 |
|
17 """This module contains the Question Model.""" |
|
18 |
|
19 __authors__ = [ |
|
20 '"Todd Larsen" <tlarsen@google.com>', |
|
21 ] |
|
22 |
|
23 |
|
24 from google.appengine.ext import db |
|
25 |
|
26 import soc.models.work |
|
27 |
|
28 |
|
29 class Question(soc.models.work.Work): |
|
30 """Model of a Question, which is a specialized form of Work. |
|
31 |
|
32 Specific types of Questions are actually implemented in subclasses. |
|
33 |
|
34 The specific way that the properties and relations inherited from |
|
35 Work are used with a Question are described below. |
|
36 |
|
37 work.title: the title of the Question, used for finding the |
|
38 Question in a list of Questions |
|
39 |
|
40 work.abstract: the Question text, asked to the respondent |
|
41 |
|
42 work.authors: the Authors of the Work referred to by this relation |
|
43 are the authors of the Question |
|
44 |
|
45 work.reviews: even Questions can be "reviewed" (possibly commented |
|
46 on during creation or annotated once put into use). |
|
47 |
|
48 work.partial_path: used to scope (and, when combined with |
|
49 work.link_name, uniquely identify) a Question in the same way the |
|
50 property are used with Documents, etc. |
|
51 |
|
52 work.link_name: used to identify (and, when combined with |
|
53 work.partial_path, *uniquely* identify) a Question in the same way |
|
54 these properties are used with Documents, etc. |
|
55 |
|
56 In addition to any explicit ReferenceProperties in the Question Model |
|
57 and those inherited as described above, a Question entity participates |
|
58 in these relationships: |
|
59 |
|
60 answers) a 1:many relationship, where each Question has many different |
|
61 Answers associated with it as parts of Responses to Quizzes. This is |
|
62 implemented as the 'answers' back-reference Query of the Answer model |
|
63 'question' reference. It is currently unclear how useful this |
|
64 back-reference will be, since the same Question could be used in |
|
65 multiple different Quizzes. Given this, 'answers' currently only |
|
66 exists for completeness. |
|
67 |
|
68 quizzes) a many:many relationship between Questions and the Quizzes |
|
69 that collect them into a set. This relation is not explicitly |
|
70 implemented, but can be obtained via a query something like: |
|
71 |
|
72 quizzes_with_a_question = db.GqlQuery( |
|
73 "SELECT * FROM Quiz where questions = :1", |
|
74 a_question.key()) |
|
75 |
|
76 Such queries are probably only needed when a Question might be |
|
77 altered, in order to find which Quizzes will be affected. |
|
78 |
|
79 The properties in this Model do not have verbose_name or help_text, |
|
80 because the dynamic nature of the forms required to create, edit, and |
|
81 use entities of this Model make them pretty useless. |
|
82 |
|
83 ###################################################################### |
|
84 # TODO(tlarsen): the following verbose comments can be removed later, |
|
85 when these ideas are implemented in the views and controllers; they |
|
86 are here now so that the concepts will not be lost before that time. |
|
87 |
|
88 The recommended use for the combination of work.partial_path and |
|
89 work.link_name is to keep the *same* link_name when copying and |
|
90 modifying an existing Question for a new Program (or instance of a |
|
91 Group that is per-Program), while changing the work.partial_path to |
|
92 represent the Program and Group "ownership" of the Question. For |
|
93 example, if a Question asking about prior GSoC participation needed |
|
94 to have an additional choice (see the choice_ids and choices properties |
|
95 below), it is desirable to keep the same work.link_name (and also |
|
96 simply append new choice_ids and choices to keep the old answer values |
|
97 compatible). An existing Question in the above example might be identified |
|
98 as something like: |
|
99 Question:google/gsoc2009/gsoc_past_participation |
|
100 <type>:<Sponsor>/<Program>/<link_name> |
|
101 To make it possible to query for gsoc_past_participation answers regardless |
|
102 of the Program, the next year, new values are added to choice_ids and |
|
103 choices in a new Question copied from the one above, which would then |
|
104 be named something (still unique) like: |
|
105 Question:google/gsoc2010/gsoc_past_participation |
|
106 Care just needs to be taken to keep the existing choice_ids and choices |
|
107 compatible. |
|
108 |
|
109 Other interesting possibilities also exist, such as asking about GSoC |
|
110 participation of the GHOP participants (some GHOP high school students |
|
111 have actually previously been GSoC mentors, for example). To produce |
|
112 unique statistics for GHOP that could also be aggregated overall in |
|
113 combination with GSoC, the gsoc_past_participation Question would be |
|
114 duplicated (unaltered) to something like: |
|
115 Question:google/ghop2009/gsoc_past_participation |
|
116 To get the combined results, query on a link_name of |
|
117 gsoc_past_participation. For more targeted results, include the |
|
118 partial_path to make the query more specific. |
|
119 |
|
120 Question creation to permit use cases like the one above is going to |
|
121 be a bit of an "advanced" skill, possibly. "Doing it wrong" the first |
|
122 time a Question is created will make it difficult to implement stuff |
|
123 like multiple-choice Questions that "grow" new choices year-over-year. |
|
124 |
|
125 A dynamic form is most definitely going to be needed to implement the |
|
126 Question creation and editing for multiple-choice questions. |
|
127 """ |
|
128 #: db.ListProperty of short, plain-text, "link_name-like" strings |
|
129 #: representing the "encoded" answer choices (must be strings compatible |
|
130 #: with being query arguments and being used in HTML controls and POST |
|
131 #: responses). |
|
132 #: |
|
133 #: If empty (None or an empty list), it is assumed that this Question |
|
134 #: is *not* a multiple choice question. In that case, the UI should |
|
135 #: display the Question as a textarea in forms and accept any plain-text. |
|
136 #: |
|
137 #: If non-empty, max_answers helps determine how the UI should display |
|
138 #: the Question. Also, controller logic needs to validate if the |
|
139 #: strings in the 'answers' property of the Answer entity come only |
|
140 #: from this list. |
|
141 #: |
|
142 #: Once Answers to this Question have been stored in the Datastore, |
|
143 #: choice_ids and choices should *not* be modified. An existing |
|
144 #: Question can be duplicated and then modified (but, it will be a |
|
145 #: different question as a result). |
|
146 choice_ids = db.ListProperty(item_type=str) |
|
147 |
|
148 #: db.ListProperty of human-readable choice strings, in the same order |
|
149 #: as, and corresponding to, the "encoded" choices in the choice_ids |
|
150 #: db.ListProperty. |
|
151 choices = db.ListProperty(item_type=str) |
|
152 |
|
153 #: db.IntegerProperty indicating the maximum number of answer values |
|
154 #: permitted for this question. If 'choices' does not contain a list of |
|
155 #: choice strings, this value is ignored (but should still only be 1). |
|
156 #: |
|
157 #: If there are 'choices' and this value is 1, the UI should render the |
|
158 #: Question in forms as a single-choice control ("radio buttons"). |
|
159 #: |
|
160 #: If there are 'choices' and this value is greater than 1, the UI should |
|
161 #: render the question as a list of check-boxes. |
|
162 #: |
|
163 #: max_answers greater than 1 combined with choices enables Questions |
|
164 #: like, for example, "...select the three most important...". |
|
165 max_answers = db.IntegerProperty(default=1) |
|
166 |
|
167 #: field storing whether the Answer to a Question is optional |
|
168 is_optional = db.BooleanProperty(default=False) |