Comment out the 'loadtest' backend in the 'counter' backend sample so that it does...
[gae-samples.git] / overheard / models.py
blobe3d673333ad73f8c853ad137314aeb36d7b7dc8f
1 # Copyright 2008 Google Inc.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
16 """Model classes and utility functions for handling
17 Quotes, Votes and Voters in the Overheard application.
19 """
22 import datetime
23 import hashlib
25 from google.appengine.ext import db
26 from google.appengine.api import memcache
27 from google.appengine.api import users
29 PAGE_SIZE = 20
30 DAY_SCALE = 4
33 class Quote(db.Model):
34 """Storage for a single quote and its metadata
36 Properties
37 quote: The quote as a string
38 uri: An optional URI that is the source of the quotation
39 rank: A calculated ranking based on the number of votes and when the quote was added.
40 created: When the quote was created, recorded in the number of days since the beginning of our local epoch.
41 creation_order: Totally unique index on all quotes in order of their creation.
42 creator: The user that added this quote.
43 """
44 quote = db.StringProperty(required=True, multiline=True)
45 uri = db.StringProperty()
46 rank = db.StringProperty()
47 created = db.IntegerProperty(default=0)
48 creation_order = db.StringProperty(default=" ")
49 votesum = db.IntegerProperty(default=0)
50 creator = db.UserProperty()
53 class Vote(db.Model):
54 """Storage for a single vote by a single user on a single quote.
57 Index
58 key_name: The email address of the user that voted.
59 parent: The quote this is a vote for.
61 Properties
62 vote: The value of 1 for like, -1 for dislike.
63 """
64 vote = db.IntegerProperty(default=0)
67 class Voter(db.Model):
68 """Storage for metadata about each user
70 Properties
71 count: An integer that gets incremented with users addition of a quote.
72 Used to build a unique index for quote creation.
73 hasVoted: Has this user ever voted on a quote.
74 hasAddedQuote: Has this user ever added a quote.
75 """
76 count = db.IntegerProperty(default=0)
77 hasVoted = db.BooleanProperty(default=False)
78 hasAddedQuote = db.BooleanProperty(default=False)
81 def _get_or_create_voter(user):
82 """
83 Find a matching Voter or create a new one with the
84 email as the key_name.
86 Returns a Voter for the given user.
87 """
88 voter = Voter.get_by_key_name(user.email())
89 if voter is None:
90 voter = Voter(key_name=user.email())
91 return voter
94 def get_progress(user):
95 """
96 Returns (hasVoted, hasAddedQuote) for the given user
97 """
98 voter = _get_or_create_voter(user)
99 return voter.hasVoted, voter.hasAddedQuote
102 def _set_progress_hasVoted(user):
104 Sets Voter.hasVoted = True for the given user.
107 def txn():
108 voter = _get_or_create_voter(user)
109 if not voter.hasVoted:
110 voter.hasVoted = True
111 voter.put()
113 db.run_in_transaction(txn)
116 def _unique_user(user):
118 Creates a unique string by using an increasing
119 counter sharded per user. The resulting string
120 is hashed to keep the users email address private.
123 def txn():
124 voter = _get_or_create_voter(user)
125 voter.count += 1
126 voter.hasAddedQuote = True
127 voter.put()
128 return voter.count
130 count = db.run_in_transaction(txn)
132 return hashlib.md5(user.email() + "|" + str(count)).hexdigest()
135 def add_quote(text, user, uri=None, _created=None):
137 Add a new quote to the datastore.
139 Parameters
140 text: The text of the quote
141 user: User who is adding the quote
142 uri: Optional URI pointing to the origin of the quote.
143 _created: Allows the caller to override the calculated created
144 value, used only for testing.
146 Returns
147 The id of the quote or None if the add failed.
149 try:
150 now = datetime.datetime.now()
151 unique_user = _unique_user(user)
152 if _created:
153 created = _created
154 else:
155 created = (now - datetime.datetime(2008, 10, 1)).days
157 q = Quote(
158 quote=text,
159 created=created,
160 creator=user,
161 creation_order = now.isoformat()[:19] + "|" + unique_user,
162 uri=uri
164 q.put()
165 return q.key().id()
166 except db.Error:
167 return None
169 def del_quote(quote_id, user):
171 Remove a quote.
173 User must be the creator of the quote or a site administrator.
175 q = Quote.get_by_id(quote_id)
176 if q is not None and (users.is_current_user_admin() or q.creator == user):
177 q.delete()
180 def get_quote(quote_id):
182 Retrieve a single quote.
184 return Quote.get_by_id(quote_id)
187 def get_quotes_newest(offset=None):
189 Returns 10 quotes per page in created order.
191 Args
192 offset: The id to use to start the page at. This is the value of 'extra'
193 returned from a previous call to this function.
195 Returns
196 (quotes, extra)
198 extra = None
199 if offset is None:
200 quotes = Quote.gql('ORDER BY creation_order DESC').fetch(PAGE_SIZE + 1)
201 else:
202 quotes = Quote.gql("""WHERE creation_order <= :1
203 ORDER BY creation_order DESC""", offset).fetch(PAGE_SIZE + 1)
205 if len(quotes) > PAGE_SIZE:
206 extra = quotes[-1].creation_order
207 quotes = quotes[:PAGE_SIZE]
208 return quotes, extra
211 def set_vote(quote_id, user, newvote):
213 Record 'user' casting a 'vote' for a quote with an id of 'quote_id'.
214 The 'newvote' is usually an integer in [-1, 0, 1].
216 if user is None:
217 return
218 email = user.email()
220 def txn():
221 quote = Quote.get_by_id(quote_id)
222 vote = Vote.get_by_key_name(key_names = user.email(), parent = quote)
223 if vote is None:
224 vote = Vote(key_name = user.email(), parent = quote)
225 if vote.vote == newvote:
226 return
227 quote.votesum = quote.votesum - vote.vote + newvote
228 vote.vote = newvote
229 # See the docstring of main.py for an explanation of
230 # the following formula.
231 quote.rank = "%020d|%s" % (
232 long(quote.created * DAY_SCALE + quote.votesum),
233 quote.creation_order
235 db.put([vote, quote])
236 memcache.set("vote|" + user.email() + "|" + str(quote_id), vote.vote)
238 db.run_in_transaction(txn)
239 _set_progress_hasVoted(user)
242 def get_quotes(page=0):
243 """Returns PAGE_SIZE quotes per page in rank order. Limit to 20 pages."""
244 assert page >= 0
245 assert page < 20
246 extra = None
247 quotes = Quote.gql('ORDER BY rank DESC').fetch(PAGE_SIZE+1, page*PAGE_SIZE)
248 if len(quotes) > PAGE_SIZE:
249 if page < 19:
250 extra = quotes[-1]
251 quotes = quotes[:PAGE_SIZE]
252 return quotes, extra
255 def voted(quote, user):
256 """Returns the value of a users vote on the specified quote, a value in [-1, 0, 1]."""
257 val = 0
258 if user:
259 memcachekey = "vote|" + user.email() + "|" + str(quote.key().id())
260 val = memcache.get(memcachekey)
261 if val is not None:
262 return val
263 vote = Vote.get_by_key_name(key_names = user.email(), parent = quote)
264 if vote is not None:
265 val = vote.vote
266 memcache.set(memcachekey, val)
267 return val