Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Topic bookmark is basically working.

  • Loading branch information...
commit 7413256769b014af39d15e4de1af01280de89bf9 1 parent c24d883
@livid authored
View
8 app.yaml.example
@@ -144,6 +144,14 @@ handlers:
- url: /my/(.*)
script: my.py
+- url: /add/star/(.*)
+ script: queue.py
+ login: admin
+
+- url: /minus/star/(.*)
+ script: queue.py
+ login: admin
+
- url: .*
script: main.py
View
55 favorite.py
@@ -14,6 +14,7 @@
from google.appengine.ext import db
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template
+from google.appengine.api.labs import taskqueue
from v2ex.babel import Member
from v2ex.babel import Counter
@@ -23,19 +24,16 @@
from v2ex.babel import Reply
from v2ex.babel import Note
from v2ex.babel import NodeBookmark
+from v2ex.babel import TopicBookmark
from v2ex.babel import SYSTEM_VERSION
from v2ex.babel.security import *
from v2ex.babel.ua import *
from v2ex.babel.da import *
-from v2ex.babel.ext.cookies import Cookies
-
-template.register_template_library('v2ex.templatetags.filters')
class FavoriteNodeHandler(webapp.RequestHandler):
def get(self, node_name):
- self.response.out.write('FUCK')
if 'Referer' in self.request.headers:
go = self.request.headers['Referer']
else:
@@ -58,7 +56,6 @@ def get(self, node_name):
class UnfavoriteNodeHandler(webapp.RequestHandler):
def get(self, node_name):
- self.response.out.write('FUCK')
if 'Referer' in self.request.headers:
go = self.request.headers['Referer']
else:
@@ -77,10 +74,56 @@ def get(self, node_name):
memcache.delete(n)
self.redirect(go)
+class FavoriteTopicHandler(webapp.RequestHandler):
+ def get(self, topic_num):
+ if 'Referer' in self.request.headers:
+ go = self.request.headers['Referer']
+ else:
+ go = '/'
+ member = CheckAuth(self)
+ if member:
+ topic = GetKindByNum('Topic', int(topic_num))
+ if topic is not False:
+ q = db.GqlQuery("SELECT * FROM TopicBookmark WHERE topic = :1 AND member = :2", topic, member)
+ if q.count() == 0:
+ bookmark = TopicBookmark(parent=member)
+ bookmark.topic = topic
+ bookmark.member = member
+ bookmark.put()
+ member.favorited_topics = member.favorited_topics + 1
+ member.put()
+ n = 'r/t' + str(topic.num) + '/m' + str(member.num)
+ memcache.set(n, True, 86400 * 14)
+ taskqueue.add(url='/add/star/topic/' + str(topic.key()))
+ self.redirect(go)
+
+class UnfavoriteTopicHandler(webapp.RequestHandler):
+ def get(self, topic_num):
+ if 'Referer' in self.request.headers:
+ go = self.request.headers['Referer']
+ else:
+ go = '/'
+ member = CheckAuth(self)
+ if member:
+ topic = GetKindByNum('Topic', int(topic_num))
+ if topic is not False:
+ q = db.GqlQuery("SELECT * FROM TopicBookmark WHERE topic = :1 AND member = :2", topic, member)
+ if q.count() > 0:
+ bookmark = q[0]
+ bookmark.delete()
+ member.favorited_topics = member.favorited_topics - 1
+ member.put()
+ n = 'r/t' + str(topic.num) + '/m' + str(member.num)
+ memcache.delete(n)
+ taskqueue.add(url='/minus/star/topic/' + str(topic.key()))
+ self.redirect(go)
+
def main():
application = webapp.WSGIApplication([
('/favorite/node/([a-zA-Z0-9]+)', FavoriteNodeHandler),
- ('/unfavorite/node/([a-zA-Z0-9]+)', UnfavoriteNodeHandler)
+ ('/unfavorite/node/([a-zA-Z0-9]+)', UnfavoriteNodeHandler),
+ ('/favorite/topic/([0-9]+)', FavoriteTopicHandler),
+ ('/unfavorite/topic/([0-9]+)', UnfavoriteTopicHandler)
],
debug=True)
util.run_wsgi_app(application)
View
6 index.yaml
@@ -137,3 +137,9 @@ indexes:
- name: node_num
- name: last_touched
direction: desc
+
+- kind: TopicBookmark
+ properties:
+ - name: member
+ - name: created
+ direction: desc
View
4 main.py
@@ -208,10 +208,6 @@ def get(self):
template_values['c'] = c
path = os.path.join(os.path.dirname(__file__), 'tpl', 'desktop', 'index.html')
output = template.render(path, template_values)
- expires_date = datetime.datetime.utcnow() + datetime.timedelta(seconds=20)
- expires_str = expires_date.strftime("%d %b %Y %H:%M:%S GMT")
- self.response.headers.add_header("Expires", expires_str)
- self.response.headers['Cache-Control'] = 'max-age=20, must-revalidate'
self.response.out.write(output)
class RecentHandler(webapp.RequestHandler):
View
27 my.py
@@ -60,9 +60,34 @@ def get(self):
else:
self.redirect('/')
+class MyTopicsHandler(webapp.RequestHandler):
+ def get(self):
+ member = CheckAuth(self)
+ if member:
+ site = GetSite()
+ l10n = GetMessages(self, member, site)
+ template_values = {}
+ template_values['site'] = site
+ template_values['member'] = member
+ template_values['l10n'] = l10n
+ template_values['page_title'] = site.title + u' › 我收藏的主题'
+ template_values['rnd'] = random.randrange(1, 100)
+ if member.favorited_topics > 0:
+ template_values['has_topics'] = True
+ q = db.GqlQuery("SELECT * FROM TopicBookmark WHERE member = :1 ORDER BY created DESC", member)
+ template_values['bookmarks'] = q
+ else:
+ template_values['has_topics'] = False
+ path = os.path.join(os.path.dirname(__file__), 'tpl', 'desktop', 'my_topics.html')
+ output = template.render(path, template_values)
+ self.response.out.write(output)
+ else:
+ self.redirect('/')
+
def main():
application = webapp.WSGIApplication([
- ('/my/nodes', MyNodesHandler)
+ ('/my/nodes', MyNodesHandler),
+ ('/my/topics', MyTopicsHandler)
],
debug=True)
util.run_wsgi_app(application)
View
60 queue.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+import os
+import re
+import time
+import datetime
+import hashlib
+import string
+import random
+
+from google.appengine.ext import webapp
+from google.appengine.api import memcache
+from google.appengine.ext import db
+from google.appengine.ext.webapp import util
+from google.appengine.ext.webapp import template
+
+from v2ex.babel import Member
+from v2ex.babel import Counter
+from v2ex.babel import Section
+from v2ex.babel import Node
+from v2ex.babel import Topic
+from v2ex.babel import Reply
+from v2ex.babel import Note
+
+from v2ex.babel import SYSTEM_VERSION
+
+from v2ex.babel.security import *
+from v2ex.babel.ua import *
+from v2ex.babel.da import *
+from v2ex.babel.l10n import *
+
+class AddStarTopicHandler(webapp.RequestHandler):
+ def post(self, topic_key):
+ topic = db.get(db.Key(topic_key))
+ if topic:
+ topic.stars = topic.stars + 1
+ topic.put()
+ memcache.set('Topic_' + str(topic.num), topic, 86400)
+
+class MinusStarTopicHandler(webapp.RequestHandler):
+ def post(self, topic_key):
+ topic = db.get(db.Key(topic_key))
+ if topic:
+ topic.stars = topic.stars - 1
+ topic.put()
+ memcache.set('Topic_' + str(topic.num), topic, 86400)
+
+
+def main():
+ application = webapp.WSGIApplication([
+ ('/add/star/topic/(.*)', AddStarTopicHandler),
+ ('/minus/star/topic/(.*)', MinusStarTopicHandler)
+ ],
+ debug=True)
+ util.run_wsgi_app(application)
+
+
+if __name__ == '__main__':
+ main()
View
8 static/css/desktop/style.css
@@ -757,8 +757,8 @@ a.tiny_label:hover {
a.op:link, a.op:visited, a.op:active {
background-color: #dde;
- font-size: 9px;
- line-height: 9px;
+ font-size: 10px;
+ line-height: 10px;
display: inline-block;
padding: 2px 4px 2px 4px;
-moz-border-radius: 2px;
@@ -775,8 +775,8 @@ a.op:hover {
a.opo:link, a.opo:visited, a.opo:active {
background-color: #334;
color: #fff;
- font-size: 9px;
- line-height: 9px;
+ font-size: 10px;
+ line-height: 10px;
display: inline-block;
padding: 2px 4px 2px 4px;
-moz-border-radius: 2px;
View
5 topic.py
@@ -403,6 +403,11 @@ def get(self, topic_num):
r = template.render(path, template_values)
memcache.set(r_tag, r, 86400)
template_values['r'] = r
+ if topic and member:
+ if member.hasFavorited(topic):
+ template_values['favorited'] = True
+ else:
+ template_values['favorited'] = False
if browser['ios']:
path = os.path.join(os.path.dirname(__file__), 'tpl', 'mobile', 'topic.html')
else:
View
2  tpl/desktop/my_nodes.html
@@ -12,7 +12,7 @@
</div>
<div id="Content">
<div class="box">
- <div class="cell"><span class="bigger"><a href="/">{{ site.title }}</a> {{ l10n.chevron }} 我收藏的节点</span></div>
+ <div class="cell"><span class="fade"><a href="/">{{ site.title }}</a> {{ l10n.chevron }} 我收藏的节点</span></div>
<div class="inner">
{% if has_nodes %}
<table cellpadding="0" cellspacing="0" border="0" width="100%">
View
66 tpl/desktop/my_topics.html
@@ -0,0 +1,66 @@
+{% include 'common/head.html' %}
+<body>
+ {% include 'common/top.html' %}
+ <div id="Wrapper">
+ <div id="Main">
+ <div id="Sidebar">
+ </div>
+ <div id="Rightbar">
+ {% include 'rightbar/user.html' %}
+ <div class="sep20"></div>
+ {% include 'rightbar/ads.html' %}
+ </div>
+ <div id="Content">
+ <div class="box">
+ <div class="cell"><span class="fade"><a href="/">{{ site.title }}</a> {{ l10n.chevron }} 我收藏的主题</span></div>
+ {% if has_topics %}
+ {% for bookmark in bookmarks %}
+ <div class="cell from_{{ bookmark.topic.member.num }}">
+ <table cellpadding="0" cellspacing="0" border="0" width="100%"><tr>
+ <td valign="top" class="avatar"><a href="/member/{{ bookmark.topic.created_by }}">{{ bookmark.topic.member|avatar:"normal" }}</a></td>
+ <td style="padding-left: 12px" valign="top">
+ {% ifequal bookmark.topic.replies 0 %}{% else %}
+ <div class="fr">
+ {% ifequal member.num bookmark.topic.member_num %}
+ <a href="/t/{{ bookmark.topic.num }}#reply{{ bookmark.topic.replies }}" class="count_orange">{{ bookmark.topic.replies }}</a>
+ {% else %}
+ <a href="/t/{{ bookmark.topic.num }}#reply{{ bookmark.topic.replies }}" class="count_livid">{{ bookmark.topic.replies }}</a>
+ {% endifequal %}
+ </div>
+ {% endifequal %}
+ <div class="sep3"></div>
+ <span class="bigger" style="font-size: 16px">{% if site.use_topic_types %}{% if bookmark.topic.type %}<span style="display: inline-block; background-color: {{ bookmark.topic.type_color }}; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; color: #fff; padding: 0px 8px 0px 8px; font-size: 13px;">{{ bookmark.topic.type }}</span> &nbsp;{% endif %}{% endif %}<a href="/t/{{ bookmark.topic.num }}#reply{{ bookmark.topic.replies }}">{{ bookmark.topic.title|escape }}</a></span>
+ <div class="sep5"></div>
+ <span class="fade" style="font-size: 10px;">By <strong><a href="/member/{{ bookmark.topic.created_by|escape }}" class="dark">{{ bookmark.topic.created_by|escape }}</a></strong>{% if bookmark.topic.node_name %} in <strong><a href="/go/{{ bookmark.topic.node_name }}" class="dark">{{ bookmark.topic.node_title|escape }}</a></strong>{% endif %}<br />
+ <span class="created">{{ bookmark.topic.last_touched|timesince }} ago{% if bookmark.topic.last_reply_by %} replied by <a href="/member/{{ bookmark.topic.last_reply_by }}" class="dark">{{ bookmark.topic.last_reply_by }}</a>{% endif %}</span>
+ </td>
+ </tr>
+ </table>
+ </div>
+ {% endfor %}
+ <script type="text/javascript">
+ blocked = [{{ blocked }}];
+ $("#topics_index").children('.cell').each( function(index) {
+ for (i in blocked) {
+ if ($(this).hasClass('from_' + blocked[i])) {
+ $(this).css('display', 'none');
+ }
+ }
+ });
+ </script>
+ {% else %}
+ <div class="inner">
+ <div class="sep20"></div>
+ <div align="center"><h2 class="snow">目前你还没有收藏任何主题</h2></div>
+ <div class="sep20"></div>
+ </div>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ <div class="c"></div>
+ </div>
+ </div>
+ {% include 'common/bottom.html' %}
+</body>
+</html>
View
2  tpl/desktop/rightbar/user.html
@@ -19,7 +19,7 @@
<table cellpadding="0" cellspacing="0" border="0" width="100%">
<tr>
<td width="33%" align="center"><a href="/my/nodes" class="dark" style="display: block;"><span class="bigger">{{ member.favorited_nodes }}</span><div class="sep3"></div><span class="fade">节点收藏</span></a></td>
- <td width="34%" style="border-left: 1px solid #e2e2e2; border-right: 1px solid #e2e2e2;" align="center"></td>
+ <td width="34%" style="border-left: 1px solid #e2e2e2; border-right: 1px solid #e2e2e2;" align="center"><a href="/my/topics" class="dark" style="display: block;"><span class="bigger">{{ member.favorited_topics }}</span><div class="sep3"></div><span class="fade">主题收藏</span></a></td>
<td width="33%" align="center"></td>
</tr>
</table>
View
33 tpl/desktop/topic.html
@@ -39,12 +39,12 @@
<div class="fr snow"><a href="/member/{{ topic.member.username }}">{{ topic.member|avatar:"large" }}</a></div>
<span class="bigger"><a href="/">{{ site.title }}</a> {{ l10n.chevron }} <a href="/go/{{ node.name}}">{{ node.title|escape }}</a></span>
<h1>{{ topic.title|escape }}</h1>
- <strong><small class="fade">{% if site.use_topic_types %}{% if topic.type %}<span style="display: inline-block; background-color: {{ topic.type_color }}; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; color: #fff; padding: 0px 8px 0px 8px; font-size: 11px;">{{ topic.type }}</span> &nbsp;{% endif %}{% endif %}By <a href="/member/{{ topic.member.username }}" class="dark">{{ topic.created_by }}</a>{% if topic.source %} via {{ topic.source }}{% endif %} at {{ topic.created|timesince }} ago, {{ topic.hits }} hits{% ifequal member.num 1 %} &nbsp; <a href="#;" onclick="if (confirm('{{ l10n.sure_to_delete }} #{{ topic.num }} - {{ topic.title|escape }}')) { location = '/delete/topic/{{ topic.num }}'; }" class="op">DELETE</a>{% endifequal %}{% if can_edit %} <a href="/edit/topic/{{ topic.num }}" class="op">EDIT</a> <a href="/backstage/tidy/topic/{{ topic.num }}" class="op">TIDY</a>{% endif %}</small></strong>
+ <small class="fade">{% if site.use_topic_types %}{% if topic.type %}<span style="display: inline-block; background-color: {{ topic.type_color }}; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; color: #fff; padding: 0px 8px 0px 8px; font-size: 11px;">{{ topic.type }}</span> &nbsp;{% endif %}{% endif %}By <a href="/member/{{ topic.member.username }}" class="dark">{{ topic.created_by }}</a>{% if topic.source %} via {{ topic.source }}{% endif %} at {{ topic.created|timesince }} ago, {{ topic.hits }} hits{% ifequal member.num 1 %} &nbsp; <a href="#;" onclick="if (confirm('{{ l10n.sure_to_delete }} #{{ topic.num }} - {{ topic.title|escape }}')) { location = '/delete/topic/{{ topic.num }}'; }" class="op">DELETE</a>{% endifequal %}{% if can_edit %} <a href="/edit/topic/{{ topic.num }}" class="op">EDIT</a> <a href="/backstage/tidy/topic/{{ topic.num }}" class="op">TIDY</a>{% endif %}</small>
</div>
<div class="inner">
<div class="content topic_content">{{ topic.content_rendered }}</div>
</div>
- <div class="inner"><div class="fr"><a href="/favorite/topic/{{ topic.num }}" class="op">加入收藏</a></div>
+ {% if member %}<div class="inner"><div class="fr" align="right">{% if topic.stars %}<span style="font-size: 10px; line-height: 10px; color: #99a;">已有 {{ topic.stars }} 人收藏此主题 &nbsp; </span>{% endif %}{% if favorited %}<a href="/unfavorite/topic/{{ topic.num }}" class="op">取消收藏</a>{% else %}<a href="/favorite/topic/{{ topic.num }}" class="op">加入收藏</a>{% endif %}</div>{% endif %}
&nbsp;
</div>
</div>
@@ -69,22 +69,25 @@
</div>
{% if member %}
<script type="text/javascript">
- blocked = [{{ blocked }}];
- $("#replies").children('.cell').each( function(index) {
- for (i in blocked) {
- if ($(this).hasClass('from_' + blocked[i])) {
- $(this).css('display', 'none');
+ function a() {
+ blocked = [{{ blocked }}];
+ $("#replies").children('.cell').each( function(index) {
+ for (i in blocked) {
+ if ($(this).hasClass('from_' + blocked[i])) {
+ $(this).css('display', 'none');
+ }
+ }
+ });
+ i = 0;
+ $("#replies").children('.cell').each( function(index) {
+ i++;
+ if (is_admin) {
+ $(this).find("span.ops").html(' &nbsp; <a href="/backstage/tidy/reply/' + replies_ids[index] + '" class="op">TIDY</a> <a href="/edit/reply/' + replies_ids[index] + '" class="op">EDIT</a> <a href="#;" onclick="if (confirm(\'{{ l10n.sure_to_delete }} #' + i + '\')) { location.href = \'/backstage/remove/reply/' + replies_keys[index] + '\'; }" class="op">DEL</a> &nbsp;<img src="/static/img/' + (replies_parents[index] ? 'empty.png' : 'dot_red.png') + '" align="absmiddle" />');
}
}
- });
- i = 0;
- $("#replies").children('.cell').each( function(index) {
- i++;
- if (is_admin) {
- $(this).find("span.ops").html(' &nbsp; <a href="/backstage/tidy/reply/' + replies_ids[index] + '" class="op">TIDY</a> <a href="/edit/reply/' + replies_ids[index] + '" class="op">EDIT</a> <a href="#;" onclick="if (confirm(\'{{ l10n.sure_to_delete }} #' + i + '\')) { location.href = \'/backstage/remove/reply/' + replies_keys[index] + '\'; }" class="op">DEL</a> &nbsp;<img src="/static/img/' + (replies_parents[index] ? 'empty.png' : 'dot_red.png') + '" align="absmiddle" />');
- }
+ );
}
- );
+ setTimeout("a()", 100);
</script>
{% endif %}
</div>
View
24 v2ex/babel/__init__.py
@@ -1,4 +1,4 @@
-SYSTEM_VERSION = '2.3.17'
+SYSTEM_VERSION = '2.3.18'
import datetime
import hashlib
@@ -64,7 +64,21 @@ def hasFavorited(self, something):
memcache.set(n, False, 86400 * 14)
return False
else:
- return False
+ if type(something).__name__ == 'Topic':
+ n = 'r/t' + str(something.num) + '/m' + str(self.num)
+ r = memcache.get(n)
+ if r:
+ return r
+ else:
+ q = db.GqlQuery("SELECT * FROM TopicBookmark WHERE topic =:1 AND member = :2", something, self)
+ if q.count() > 0:
+ memcache.set(n, True, 86400 * 14)
+ return True
+ else:
+ memcache.set(n, False, 86400 * 14)
+ return False
+ else:
+ return False
class Counter(db.Model):
name = db.StringProperty(required=False, indexed=True)
@@ -110,6 +124,7 @@ class Topic(db.Model):
content_rendered = db.TextProperty(required=False)
content_length = db.IntegerProperty(default=0)
hits = db.IntegerProperty(default=0)
+ stars = db.IntegerProperty(required=True, default=0)
replies = db.IntegerProperty(default=0)
created_by = db.StringProperty(required=False, indexed=True)
last_reply_by = db.StringProperty(required=False, indexed=True)
@@ -219,4 +234,9 @@ class Page(db.Model):
class NodeBookmark(db.Model):
node = db.ReferenceProperty(Node)
member = db.ReferenceProperty(Member)
+ created = db.DateTimeProperty(auto_now_add=True)
+
+class TopicBookmark(db.Model):
+ topic = db.ReferenceProperty(Topic)
+ member = db.ReferenceProperty(Member)
created = db.DateTimeProperty(auto_now_add=True)
Please sign in to comment.
Something went wrong with that request. Please try again.