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.
25 from google
.appengine
.ext
import db
26 from google
.appengine
.api
import memcache
27 from google
.appengine
.api
import users
33 class Quote(db
.Model
):
34 """Storage for a single quote and its metadata
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.
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()
54 """Storage for a single vote by a single user on a single quote.
58 key_name: The email address of the user that voted.
59 parent: The quote this is a vote for.
62 vote: The value of 1 for like, -1 for dislike.
64 vote
= db
.IntegerProperty(default
=0)
67 class Voter(db
.Model
):
68 """Storage for metadata about each user
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.
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
):
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.
88 voter
= Voter
.get_by_key_name(user
.email())
90 voter
= Voter(key_name
=user
.email())
94 def get_progress(user
):
96 Returns (hasVoted, hasAddedQuote) for the given user
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.
108 voter
= _get_or_create_voter(user
)
109 if not voter
.hasVoted
:
110 voter
.hasVoted
= True
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.
124 voter
= _get_or_create_voter(user
)
126 voter
.hasAddedQuote
= True
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.
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.
147 The id of the quote or None if the add failed.
150 now
= datetime
.datetime
.now()
151 unique_user
= _unique_user(user
)
155 created
= (now
- datetime
.datetime(2008, 10, 1)).days
161 creation_order
= now
.isoformat()[:19] + "|" + unique_user
,
169 def del_quote(quote_id
, user
):
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
):
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.
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.
200 quotes
= Quote
.gql('ORDER BY creation_order DESC').fetch(PAGE_SIZE
+ 1)
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
]
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].
221 quote
= Quote
.get_by_id(quote_id
)
222 vote
= Vote
.get_by_key_name(key_names
= user
.email(), parent
= quote
)
224 vote
= Vote(key_name
= user
.email(), parent
= quote
)
225 if vote
.vote
== newvote
:
227 quote
.votesum
= quote
.votesum
- 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
),
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."""
247 quotes
= Quote
.gql('ORDER BY rank DESC').fetch(PAGE_SIZE
+1, page
*PAGE_SIZE
)
248 if len(quotes
) > PAGE_SIZE
:
251 quotes
= quotes
[:PAGE_SIZE
]
255 def voted(quote
, user
):
256 """Returns the value of a users vote on the specified quote, a value in [-1, 0, 1]."""
259 memcachekey
= "vote|" + user
.email() + "|" + str(quote
.key().id())
260 val
= memcache
.get(memcachekey
)
263 vote
= Vote
.get_by_key_name(key_names
= user
.email(), parent
= quote
)
266 memcache
.set(memcachekey
, val
)