Package hm :: Package db :: Module rating
[hide private]
[frames] | no frames]

Source Code for Module hm.db.rating

  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   
31 -class RatingOutOfRange(RuntimeError): pass
32 -class RatingAlreadyExistsForUser(RuntimeError): pass
33 -class RatingMixinInvalid(RuntimeError): pass
34 35
36 -class AggregateRating(models.Model):
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
50 """ 51 @summary: 52 53 54 @return: 55 @rtype: 56 """ 57 behs = Behavior.objects.select_related().all() 58 for beh in behs: 59 agg_score = 0 60 for rating in beh.ratings.all(): 61 agg_score += rating.score * rating.user.weight 62 agg_score /= beh.ratings.count() 63 cr = AggregateRating.make(beh, score=agg_score)
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
82 """ 83 @summary: 84 85 86 @return: 87 @rtype: 88 """ 89 nodes = Node.objects.select_related().all() 90 for node in nodes: 91 agg_score = 0 92 for child_edge in node.child_edges.all(): 93 agg_score += child_edge.child.cached_rating.score & child_edge.weight 94 agg_score /= node.child_edges.count() 95 cr = AggregateRating.make(node, score=agg_score)
96 97 98
99 -class Rating(models.Model):
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, #graph.BehaviorArgumentLink, 130 graph.Dimension, graph.Behavior, graph.UPC, 131 citation.Source, citation.Author, citation.Article, citation.Citation ] 132 133 ACCEPTABLE_RANGE = { #(LOW,HIGH,type(int,float)) 134 graph.Node: (0,10,int), 135 graph.Edge: (0,10,int), 136 graph.Argument: (-1,1,int), 137 #graph.BehaviorArgumentLink: (0,10,int), 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
148 - def Initialize(klass):
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 #for model_class in Rating.RATEABLE_MODELS: 159 # model_class.add_to_class('cached_rating', models.FloatField(default=1.0)) 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 #@staticmethod 172 #def by_user(user=None): 173 # """ 174 # @summary: 175 # @rtype: QuerySet 176 # """ 177 # user_id = utils.validate_user(user) 178 # return Rating.objects.extra(where=[ 179 # "id IN (SELECT id FROM db_action WHERE user_id = %s)" % (user_id,)]) 180 181 @staticmethod
182 - def by_item_and_user(item, user=None):
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 #item_type_id = ContentType.objects.get_for_model(item).id 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 #ratings = Rating.objects.extra(where=[ 205 # "db_rating.id IN (SELECT a.item_id FROM db_action a, db_rating r WHERE r.item_type_id = %s AND user_id = %s ORDER BY time ASC)" % 206 # (item_type_id, item_id, user_id)]) 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 #raise "Too many ratings!" 215 216 @staticmethod
217 - def by_item(item):
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 #Make sure item is Ratable 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 #Make sure score is within acceptable range 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 #Make sure the user has not already voted. 256 #if Rating.by_item_and_user(item, user_id): 257 # raise RatingAlreadyExistsForUser("User has already rated this item: %s" % (item)) 258 259 #Now make the call. 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
266 - def __unicode__(self):
267 return str(self.score)
268 - class Admin:
269 pass
270 271
272 -class RatingMixin:
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
287 - def ratings(self):
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
297 - def rating_by_current_user(self):
298 try: 299 user = validate_user(None) 300 #print repr(user) 301 #print repr([self, self.id]) 302 r = self.rating_by_user(user) 303 #print repr([r, r.id, r.score, r.item]) 304 return self.rating_by_user(user) 305 except UserNotAuthenticated: 306 return None
307
308 - def rating_by_user(self, user):
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
323 - def aggregate_rating(self):
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
335 - def add_rating(self, score):
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