17 """This module contains the GHOP Task Model. |
17 """This module contains the GHOP Task Model. |
18 """ |
18 """ |
19 |
19 |
20 __authors__ = [ |
20 __authors__ = [ |
21 '"Madhusudan.C.S" <madhusudancs@gmail.com>', |
21 '"Madhusudan.C.S" <madhusudancs@gmail.com>', |
|
22 '"Daniel Hans" <daniel.m.hans@gmail.com>', |
22 '"Lennard de Rijk" <ljvderijk@gmail.com>', |
23 '"Lennard de Rijk" <ljvderijk@gmail.com>', |
23 ] |
24 ] |
24 |
25 |
25 |
26 |
26 from google.appengine.ext import db |
27 from google.appengine.ext import db |
41 |
42 |
42 class TaskTag(Tag): |
43 class TaskTag(Tag): |
43 """Model for storing all Task tags. |
44 """Model for storing all Task tags. |
44 """ |
45 """ |
45 |
46 |
46 #: Each task_type tag is scoped under the program. |
|
47 |
|
48 order = db.IntegerProperty(required=True, default=0) |
47 order = db.IntegerProperty(required=True, default=0) |
49 |
|
50 @classmethod |
|
51 def __key_name(cls, scope_path, tag_name): |
|
52 """Create the key_name from program key_name as scope_path and tag_name. |
|
53 """ |
|
54 return scope_path + '/' + tag_name |
|
55 |
|
56 @classmethod |
|
57 def get_by_name(cls, tag_name): |
|
58 """Get the list of tag objects that has the given tag_name. |
|
59 """ |
|
60 tags = db.Query(cls).filter('tag =', tag_name).fetch(1000) |
|
61 return tags |
|
62 |
48 |
63 @classmethod |
49 @classmethod |
64 def get_by_scope(cls, scope): |
50 def get_by_scope(cls, scope): |
65 """Get the list of tag objects that has the given scope. |
51 """Get the list of tag objects that has the given scope and sorts the |
66 """ |
52 result by order values. |
|
53 """ |
|
54 |
67 tags = db.Query(cls).filter('scope =', scope).order('order').fetch(1000) |
55 tags = db.Query(cls).filter('scope =', scope).order('order').fetch(1000) |
68 return tags |
56 return tags |
69 |
57 |
70 @classmethod |
58 @classmethod |
71 def get_highest_order(cls, scope): |
59 def get_highest_order(cls, scope): |
72 """Get a tag with highest order. |
60 """Get a tag with highest order. |
73 """ |
61 """ |
74 tags = db.Query(cls).filter('scope =', scope).order('-order').fetch(1) |
62 |
75 if tags: |
63 tag = db.Query(cls).filter('scope =', scope).order('-order').get() |
76 return tags[0].order |
64 if tag: |
|
65 return tag.order |
77 else: |
66 else: |
78 return -1 |
67 return -1 |
79 |
|
80 @classmethod |
|
81 def get_by_scope_and_name(cls, scope, tag_name): |
|
82 """Get a tag by scope and name. |
|
83 |
|
84 There can be only one such tag. |
|
85 """ |
|
86 |
|
87 tags = db.Query(cls).filter( |
|
88 'scope =', scope).filter('tag =', tag_name).fetch(1) |
|
89 if tags: |
|
90 return tags[0] |
|
91 else: |
|
92 return None |
|
93 |
68 |
94 @classmethod |
69 @classmethod |
95 def update_order(cls, scope, tag_name, order): |
70 def update_order(cls, scope, tag_name, order): |
96 """Updates the order of the tag. |
71 """Updates the order of the tag. |
97 """ |
72 """ |
101 tag.order = order |
76 tag.order = order |
102 tag.put() |
77 tag.put() |
103 |
78 |
104 return tag |
79 return tag |
105 |
80 |
106 @classmethod |
|
107 def copy_tag(cls, scope, tag_name, new_tag_name): |
|
108 """Copy a tag with a given scope and tag_name to another tag with |
|
109 new tag_name. |
|
110 """ |
|
111 tag = cls.get_by_scope_and_name(scope, tag_name) |
|
112 |
|
113 if tag: |
|
114 tag_key_name = cls.__key_name(scope.key().name(), new_tag_name) |
|
115 existing_tag = cls.get_by_key_name(tag_key_name) |
|
116 |
|
117 if existing_tag is None: |
|
118 new_tag = cls(key_name=tag_key_name, tag=new_tag_name, scope=scope, |
|
119 added=tag.added, tagged=tag.tagged, |
|
120 tagged_count=tag.tagged_count) |
|
121 new_tag.put() |
|
122 tag.delete() |
|
123 |
|
124 return new_tag |
|
125 |
|
126 return existing_tag |
|
127 |
|
128 return None |
|
129 |
|
130 @classmethod |
|
131 def delete_tag(cls, scope, tag_name): |
|
132 """Delete a tag with a given scope and tag_name. |
|
133 """ |
|
134 tag = cls.get_by_scope_and_name(scope, tag_name) |
|
135 |
|
136 if tag: |
|
137 tag.delete() |
|
138 return True |
|
139 |
|
140 return False |
|
141 |
|
142 @classmethod |
|
143 def get_or_create(cls, scope, tag_name, order=0): |
|
144 """Get the Tag object that has the tag value given by tag_value. |
|
145 """ |
|
146 |
|
147 if not scope: |
|
148 return None |
|
149 |
|
150 tag_key_name = cls.__key_name(scope.key().name(), tag_name) |
|
151 existing_tag = cls.get_by_key_name(tag_key_name) |
|
152 if existing_tag is None: |
|
153 # the tag does not yet exist, so create it. |
|
154 if not order: |
|
155 order = cls.get_highest_order(scope=scope) + 1 |
|
156 def create_tag_txn(): |
|
157 new_tag = cls(key_name=tag_key_name, tag=tag_name, |
|
158 scope=scope, order=order) |
|
159 new_tag.put() |
|
160 return new_tag |
|
161 existing_tag = db.run_in_transaction(create_tag_txn) |
|
162 return existing_tag |
|
163 |
|
164 |
81 |
165 class TaskTypeTag(TaskTag): |
82 class TaskTypeTag(TaskTag): |
166 """Model for storing of task type tags. |
83 """Model for storing of task type tags. |
167 """ |
84 """ |
168 |
85 |
200 title = db.StringProperty(required=True, |
117 title = db.StringProperty(required=True, |
201 verbose_name=ugettext('Title')) |
118 verbose_name=ugettext('Title')) |
202 title.help_text = ugettext('Title of the task') |
119 title.help_text = ugettext('Title of the task') |
203 |
120 |
204 #: Required field containing the description of the task |
121 #: Required field containing the description of the task |
205 description = db.TextProperty(required=True, |
122 description = db.TextProperty(required=True, |
206 verbose_name=ugettext('Description')) |
123 verbose_name=ugettext('Description')) |
207 description.help_text = ugettext('Complete description of the task') |
124 description.help_text = ugettext('Complete description of the task') |
208 |
125 |
209 #: Field indicating the difficulty level of the Task. This is not |
126 #: Field indicating the difficulty level of the Task. This is not |
210 #: mandatory so the it can be assigned at any later stage. |
127 #: mandatory so the it can be assigned at any later stage. |
211 #: The options are configured by a Program Admin. |
128 #: The options are configured by a Program Admin. |
212 difficulty = tag_property('difficulty') |
129 difficulty = tag_property('difficulty') |
213 |
130 |
214 #: Required field which contains the type of the task. These types are |
131 #: Required field which contains the type of the task. These types are |
215 #: configured by a Program Admin. |
132 #: configured by a Program Admin. |
258 #: Open: This Task is open and ready to be claimed. |
175 #: Open: This Task is open and ready to be claimed. |
259 #: Reopened: This Task has been claimed but never finished and has been |
176 #: Reopened: This Task has been claimed but never finished and has been |
260 #: reopened. |
177 #: reopened. |
261 #: ClaimRequested: A Student has requested to claim this task. |
178 #: ClaimRequested: A Student has requested to claim this task. |
262 #: Claimed: This Task has been claimed and someone is working on it. |
179 #: Claimed: This Task has been claimed and someone is working on it. |
263 #: ActionNeeded: Work on this Task must be submitted for review within |
180 #: ActionNeeded: Work on this Task must be submitted for review within |
264 #: 24 hours. |
181 #: 24 hours. |
265 #: Closed: Work on this Task has been completed to the org's content. |
182 #: Closed: Work on this Task has been completed to the org's content. |
266 #: AwaitingRegistration: Student has completed work on this task, but |
183 #: AwaitingRegistration: Student has completed work on this task, but |
267 #: needs to complete Student registration before this task is closed. |
184 #: needs to complete Student registration before this task is closed. |
268 #: NeedsWork: This work on this Tasks needs a bit more brushing up. This |
185 #: NeedsWork: This work on this Tasks needs a bit more brushing up. This |
270 #: NeedsReview: Student has submitted work for this task and it should |
187 #: NeedsReview: Student has submitted work for this task and it should |
271 #: be reviewed by a Mentor. |
188 #: be reviewed by a Mentor. |
272 #: Invalid: The Task is deleted either by an Org Admin/Mentor |
189 #: Invalid: The Task is deleted either by an Org Admin/Mentor |
273 status = db.StringProperty( |
190 status = db.StringProperty( |
274 required=True, verbose_name=ugettext('Status'), |
191 required=True, verbose_name=ugettext('Status'), |
275 choices=['Unapproved', 'Unpublished', 'Open', 'Reopened', |
192 choices=['Unapproved', 'Unpublished', 'Open', 'Reopened', |
276 'ClaimRequested', 'Claimed', 'ActionNeeded', |
193 'ClaimRequested', 'Claimed', 'ActionNeeded', |
277 'Closed', 'AwaitingRegistration', 'NeedsWork', |
194 'Closed', 'AwaitingRegistration', 'NeedsWork', |
278 'NeedsReview', 'Invalid'], |
195 'NeedsReview', 'Invalid'], |
279 default='Unapproved') |
196 default='Unapproved') |
280 |
197 |
281 #: This field is set to the next deadline that will have consequences for |
198 #: This field is set to the next deadline that will have consequences for |
328 #: model. The subsequent items hold the properties that changed at the |
245 #: model. The subsequent items hold the properties that changed at the |
329 #: timestamp given by the key. |
246 #: timestamp given by the key. |
330 #: Reference properties will be stored by calling str() on their Key. |
247 #: Reference properties will be stored by calling str() on their Key. |
331 history = db.TextProperty(required=False, default='') |
248 history = db.TextProperty(required=False, default='') |
332 |
249 |
333 def __init__(self, parent=None, key_name=None, |
250 def __init__(self, parent=None, key_name=None, |
334 app=None, **entity_values): |
251 app=None, **entity_values): |
335 """Constructor for GHOPTask Model. |
252 """Constructor for GHOPTask Model. |
336 |
253 |
337 Args: |
254 Args: |
338 See Google App Engine APIs. |
255 See Google App Engine APIs. |
339 """ |
256 """ |
340 |
257 |
341 # explicitly call the AppEngine datastore Model constructor |
258 # explicitly call the AppEngine datastore Model constructor |
342 db.Model.__init__(self, parent, key_name, app, **entity_values) |
259 db.Model.__init__(self, parent, key_name, app, **entity_values) |
343 |
260 |
344 # call the Taggable constructor to initialize the tags specified as |
261 # call the Taggable constructor to initialize the tags specified as |
345 # keyword arguments |
262 # keyword arguments |
346 Taggable.__init__(self, task_type=TaskTypeTag, |
263 Taggable.__init__(self, task_type=TaskTypeTag, |
347 difficulty=TaskDifficultyTag, |
264 difficulty=TaskDifficultyTag, |
348 arbit_tag=TaskArbitraryTag) |
265 arbit_tag=TaskArbitraryTag) |