1
2 from django.db import models
3 from django.contrib.contenttypes.models import ContentType
4 from django.contrib.contenttypes import generic
5
6 from utils import cached_property, mixin_property, mixin_method, mixin, validate_user, UserNotAuthenticated
7 import graph, citation
8
9
10 """
11 @summary:
12
13
14 @author: Daniel Ring
15 @organization: ThoughtAndMemory.org
16 @copyright: ThoughtAndMemoyr.org
17 @contact: lucy@thoughtandmemory.org, dfring@gmail.com
18
19 @version: 0.22a
20 """
21
22 """
23 @attention:
24 #by_user and #by_time methods are not currently implemented in the same
25 way most other connections are. Due to limitations in the Django framework,
26 queries against these fields are either one-shot-and-done or very-very-slow.
27 Incremental query building (QuerySets) doesn't currently work for these models.
28
29 """
30
34
35
37 """
38 @summary:
39
40
41 """
42 score = models.FloatField()
43 time = models.DateTimeField(auto_now_add=True)
44
45 item_type = models.ForeignKey(ContentType)
46 item_id = models.PositiveIntegerField()
47 item = generic.GenericForeignKey('item_type', 'item_id')
48
64
66 """
67 @summary:
68
69
70 @return:
71 @rtype:
72 """
73 nodes = Node.objects.select_related().all()
74 for node in nodes:
75 agg_score = 0
76 for beh_edge in node.behavior_edges.all():
77 agg_score += beh_edge.beh.cached_rating.score * beh_edge.weight
78 agg_score /= node.behavior_edges.count()
79 cr = AggregateRating.make(node, score=agg_score)
80
96
97
98
100 """
101 @summary:
102 An Rating is an individual judgement of some object. This will be most obvious
103 with judgement of B{Behaviors}, but the B{Rating} model itself can be pointed
104 at any object.
105 A Rating is a many-to-one relationship. An object can have an unlimited number
106 of Ratings, but each Rating points at only one object.
107 Users are limited to having one Rating per object.
108 Limits will exist for valid I{scores} for various objects, but because B{Rating}
109 doesn't target a particular model, there is no restriction in place.
110
111 @ivar score: The important part
112 @type score: int
113 @ivar item: The object this B{Rating} references
114 @type item: Any
115 @ivar item_type: The B{ContentType} id of B{item}
116 @type item_type: int, or ContentType
117 @ivar item_id: The id of B{item} in the appropriate table
118 @type item_id: int
119
120 @var RATEABLE_MODELS: The Model classes which will receive the RatingMixin
121 @type RATEABLE_MODELS: list<models.Model>
122
123 @var ACCEPTABLE_RANGE: A dict containing the low, high, and type requirements
124 for scores given each of the Rateable objects.
125 @type ACCEPTABLE_RANGE: dict<key:(low, high, type)>
126 """
127
128 RATEABLE_MODELS = [
129 graph.Node, graph.Edge, graph.Argument,
130 graph.Dimension, graph.Behavior, graph.UPC,
131 citation.Source, citation.Author, citation.Article, citation.Citation ]
132
133 ACCEPTABLE_RANGE = {
134 graph.Node: (0,10,int),
135 graph.Edge: (0,10,int),
136 graph.Argument: (-1,1,int),
137
138 graph.Dimension: (0,10,int),
139 graph.Behavior: (0,10,int),
140 graph.UPC: (0,10,int),
141 citation.Source: (0,10,int),
142 citation.Author: (0,10,int),
143 citation.Article: (0,10,int),
144 citation.Citation: (0,10,int),
145 }
146
147 @classmethod
149 """
150 @summary: Mixes the RatingMixin class into the Model classes
151 specified in Rating.RATEABLE_MODELS
152
153 @return: None
154 @rtype: NoneType
155 """
156 mixin(RatingMixin, Rating.RATEABLE_MODELS)
157
158
159
160
161
162
163
164 score = models.IntegerField()
165
166 item_type = models.ForeignKey(ContentType)
167 item_id = models.PositiveIntegerField()
168 item = generic.GenericForeignKey('item_type', 'item_id')
169
170
171
172
173
174
175
176
177
178
179
180
181 @staticmethod
183 """
184 @summary: Returns a QuerySet containing the single Rating object (if any)
185 made by the specified User on the specified object.
186 @rtype: QuerySet
187 """
188 h = { "user_id" : validate_user(user),
189 "rating_type_id" : ContentType.objects.get_for_model(Rating).id,
190 "item_type_id" : ContentType.objects.get_for_model(item).id,
191 "item_id" : item.id }
192
193
194
195 sql = ("SELECT a.item_id FROM db_action a, db_rating r " +
196 " WHERE a.item_type_id = %(rating_type_id)i " +
197 " AND a.user_id = %(user_id)i " +
198 " AND a.item_id = r.id " +
199 " AND r.item_type_id = %(item_type_id)i " +
200 " AND r.item_id = %(item_id)i " +
201 " ORDER BY a.time DESC") % h
202
203 ratings = Rating.objects.extra(where=["db_rating.id IN (%s)" % (sql,)])
204
205
206
207
208 if len(ratings) == 0:
209 return None
210 elif len(ratings) == 1:
211 return ratings[0]
212 else:
213 return ratings[len(ratings)-1]
214
215
216 @staticmethod
218 """
219 @summary: Returns a QuerySet containing all the Rating objects
220 for the specified item.
221 @rtype: QuerySet
222 """
223 return Rating.objects.filter(item_type=ContentType.objects.get_for_model(item), item_id=item.id)
224
225 @staticmethod
226 - def add(item, score, user=None):
227 """
228 @summary: This method overrides the one in the ModelMixin class,
229 because it must check many more aspects than any other model.
230 @see: L{Rating} for information about parameters and types.
231
232 @return: Returns the new Rating object, if successful.
233 @rtype: Rating
234
235 @raise UserObjectOrIntegerExpected:
236 @raise RatingMixinInvalid:
237 @raise RatingOutOfRange:
238 @raise RatingAlreadyExistsForUser:
239 """
240 user_id = validate_user(user)
241
242
243 if not item.__class__ in Rating.RATEABLE_MODELS:
244 raise RatingMixinInvalid("got %s of type %s." % (item, item.__class__))
245 if not isinstance(item.pk, (int, long)):
246 raise RatingMixinInvalid("Object %s has invalid primary key: '%s'" % (item, item.pk))
247
248
249 range = Rating.ACCEPTABLE_RANGE[item.__class__]
250 if not (score >= range[0] and score <= range[1]):
251 raise RatingOutOfRange(
252 "got %s, but score must be between %s and %s for %s." %
253 (score, range[0], range[1], item.__class__))
254
255
256
257
258
259
260 item_type_id = ContentType.objects.get_for_model(item).id
261 return Rating.objects.create(item_type_id=item_type_id,
262 item_id=item.id,
263 score=score,
264 user=user_id)
265
267 return str(self.score)
270
271
273 """
274 @summary:
275 B{RatingMixin} is a mix-in used to provide common methods, attributes,
276 and hooks to all the models that can be given L{Tag} labels. The list
277 of these objects can be found at L{tag.Tag}.
278
279 At initialization, RatingMixin.B{_apply} is applied to all the models
280 listed in L{rating.Rating}.
281
282 @ivar ratings:
283 @type ratings: RatingMixinManager
284 """
285
286 @property
288 """
289 @summary: Convenience method. Maps to B{Rating.objects.by_item(self)}
290
291 @return: List of Ratings on this object.
292 @rtype: list<Rating>
293 """
294 return Rating.by_item(self)
295
296 @property
307
309 """
310 @summary: Returns a the Rating the specified user put on this object.
311 If the user has not rated this object, then I{None} is returned.
312
313 @param user: User object or an integer to be used as the user_id.
314 Optional - if not specified, the currently logged in user will be used.
315 @type user: None, User, or int
316
317 @return: None, or a single Rating
318 @rtype: NoneType, or Rating
319 """
320 return Rating.by_item_and_user(self, user)
321
322 @property
324 """
325 @summary: Returns an AggregateRating object which contains the rating
326 computed for this object during the last computation cycle. May also
327 return None, if a cycle has not occurred since this item was first
328 rated.
329
330 @return: AggregateRating for this object, or None
331 @rtype: None, or AggregateRating
332 """
333 return AggregateRating.by_item(self)
334
336 """
337 @summary:
338 Adds a Rating associated with this object. Acceptable scores are listed
339 at L{Rating}.
340 If the score is out of range, a RatingOutOfRange exception will be raised.
341 If the specified user (or current user if none is specified) has already
342 rated this object, a RatingAlreadyExistsForUser exception will be raised.
343
344 @return: None, or Rating
345 @rtype: None, or Rating
346
347 @raise RatingOutOfRange: Raised if the I{score} is not within the constraints
348 outline in L{Rating}
349 @raise RatingAlreadyExistsForUser: Raised if the I{user} has already rated
350 this object.
351 """
352 return Rating.add(item=self, score=score)
353