##// END OF EJS Templates
Removed tag popularity ratings, they are too slow. Added API to get tag...
neko259 -
r651:582b2d85 default
parent child Browse files
Show More
@@ -1,192 +1,192 b''
1 from django.core.urlresolvers import reverse
2 1 import markdown
3 from markdown.inlinepatterns import Pattern
2 from markdown.inlinepatterns import Pattern, SubstituteTagPattern
4 3 from markdown.util import etree
5 4 import boards
6 5
7 6 __author__ = 'neko259'
8 7
9 8
10 9 AUTOLINK_PATTERN = r'(https?://\S+)'
11 10 QUOTE_PATTERN = r'^(?<!>)(>[^>].*)$'
12 11 REFLINK_PATTERN = r'((>>)(\d+))'
13 12 SPOILER_PATTERN = r'%%([^(%%)]+)%%'
14 13 COMMENT_PATTERN = r'^(//(.+))'
15 14 STRIKETHROUGH_PATTERN = r'~(.+)~'
16 15
17 16
18 17 class TextFormatter():
19 18 """
20 19 An interface for formatter that can be used in the text format panel
21 20 """
22 21
23 22 name = ''
24 23
25 24 # Left and right tags for the button preview
26 25 preview_left = ''
27 26 preview_right = ''
28 27
29 28 # Left and right characters for the textarea input
30 29 format_left = ''
31 30 format_right = ''
32 31
33 32
34 33 class AutolinkPattern(Pattern):
35 34 def handleMatch(self, m):
36 35 link_element = etree.Element('a')
37 36 href = m.group(2)
38 37 link_element.set('href', href)
39 38 link_element.text = href
40 39
41 40 return link_element
42 41
43 42
44 43 class QuotePattern(Pattern, TextFormatter):
45 44 name = ''
46 45 preview_left = '<span class="quote">&gt; '
47 46 preview_right = '</span>'
48 47
49 48 format_left = '&gt;'
50 49
51 50 def handleMatch(self, m):
52 51 quote_element = etree.Element('span')
53 52 quote_element.set('class', 'quote')
54 53 quote_element.text = m.group(2)
55 54
56 55 return quote_element
57 56
58 57
59 58 class ReflinkPattern(Pattern):
60 59 def handleMatch(self, m):
61 60 post_id = m.group(4)
62 61
63 62 posts = boards.models.Post.objects.filter(id=post_id)
64 63 if posts.count() > 0:
65 64 ref_element = etree.Element('a')
66 65
67 66 post = posts[0]
68 67
69 68 ref_element.set('href', post.get_url())
70 69 ref_element.text = m.group(2)
71 70
72 71 return ref_element
73 72
74 73
75 74 class SpoilerPattern(Pattern, TextFormatter):
76 75 name = 's'
77 76 preview_left = '<span class="spoiler">'
78 77 preview_right = '</span>'
79 78
80 79 format_left = '%%'
81 80 format_right = '%%'
82 81
83 82 def handleMatch(self, m):
84 83 quote_element = etree.Element('span')
85 84 quote_element.set('class', 'spoiler')
86 85 quote_element.text = m.group(2)
87 86
88 87 return quote_element
89 88
90 89
91 90 class CommentPattern(Pattern, TextFormatter):
92 91 name = ''
93 92 preview_left = '<span class="comment">// '
94 93 preview_right = '</span>'
95 94
96 95 format_left = '//'
97 96
98 97 def handleMatch(self, m):
99 98 quote_element = etree.Element('span')
100 99 quote_element.set('class', 'comment')
101 100 quote_element.text = '//' + m.group(3)
102 101
103 102 return quote_element
104 103
105 104
106 105 class StrikeThroughPattern(Pattern, TextFormatter):
107 106 name = 's'
108 107 preview_left = '<span class="strikethrough">'
109 108 preview_right = '</span>'
110 109
111 110 format_left = '~'
112 111 format_right = '~'
113 112
114 113 def handleMatch(self, m):
115 114 quote_element = etree.Element('span')
116 115 quote_element.set('class', 'strikethrough')
117 116 quote_element.text = m.group(2)
118 117
119 118 return quote_element
120 119
121 120
122 121 class ItalicPattern(TextFormatter):
123 122 name = 'i'
124 123 preview_left = '<i>'
125 124 preview_right = '</i>'
126 125
127 126 format_left = '_'
128 127 format_right = '_'
129 128
130 129
131 130 class BoldPattern(TextFormatter):
132 131 name = 'b'
133 132 preview_left = '<b>'
134 133 preview_right = '</b>'
135 134
136 135 format_left = '__'
137 136 format_right = '__'
138 137
139 138
140 139 class CodePattern(TextFormatter):
141 140 name = 'code'
142 141 preview_left = '<code>'
143 142 preview_right = '</code>'
144 143
145 144 format_left = ' '
146 145
147 146
148 147 class NeboardMarkdown(markdown.Extension):
149 148 def extendMarkdown(self, md, md_globals):
150 149 self._add_neboard_patterns(md)
151 150 self._delete_patterns(md)
152 151
153 152 def _delete_patterns(self, md):
154 153 del md.parser.blockprocessors['quote']
155 154
156 155 del md.inlinePatterns['image_link']
157 156 del md.inlinePatterns['image_reference']
158 157
159 158 def _add_neboard_patterns(self, md):
160 159 autolink = AutolinkPattern(AUTOLINK_PATTERN, md)
161 160 quote = QuotePattern(QUOTE_PATTERN, md)
162 161 reflink = ReflinkPattern(REFLINK_PATTERN, md)
163 162 spoiler = SpoilerPattern(SPOILER_PATTERN, md)
164 163 comment = CommentPattern(COMMENT_PATTERN, md)
165 164 strikethrough = StrikeThroughPattern(STRIKETHROUGH_PATTERN, md)
166 165
167 166 md.inlinePatterns[u'autolink_ext'] = autolink
168 167 md.inlinePatterns[u'spoiler'] = spoiler
169 168 md.inlinePatterns[u'strikethrough'] = strikethrough
170 169 md.inlinePatterns[u'comment'] = comment
171 170 md.inlinePatterns[u'reflink'] = reflink
172 171 md.inlinePatterns[u'quote'] = quote
173 172
174 173
175 174 def make_extension(configs=None):
176 175 return NeboardMarkdown(configs=configs)
177 176
178 177 neboard_extension = make_extension()
179 178
180 179
181 180 def markdown_extended(markup):
182 return markdown.markdown(markup, [neboard_extension], safe_mode=True)
181 return markdown.markdown(markup, [neboard_extension, 'nl2br'],
182 safe_mode=True)
183 183
184 184 formatters = [
185 185 QuotePattern,
186 186 SpoilerPattern,
187 187 ItalicPattern,
188 188 BoldPattern,
189 189 CommentPattern,
190 190 StrikeThroughPattern,
191 191 CodePattern,
192 192 ]
@@ -1,27 +1,27 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5
6 6 {% block head %}
7 7 <title>Neboard - {% trans "Tags" %}</title>
8 8 {% endblock %}
9 9
10 10 {% block content %}
11 11
12 12 {% cache 600 all_tags_list %}
13 13 <div class="post">
14 14 {% if all_tags %}
15 15 {% for tag in all_tags %}
16 <div class="tag_item" style="opacity: {{ tag.get_font_value }}">
16 <div class="tag_item">
17 17 <a class="tag" href="{% url 'tag' tag.name %}">
18 18 #{{ tag.name }}</a>
19 19 </div>
20 20 {% endfor %}
21 21 {% else %}
22 22 {% trans 'No tags found.' %}
23 23 {% endif %}
24 24 </div>
25 25 {% endcache %}
26 26
27 27 {% endblock %}
@@ -1,83 +1,85 b''
1 1 from django.conf.urls import patterns, url, include
2 2 from boards import views
3 3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4 4 from boards.views import api, tag_threads, all_threads, archived_threads, \
5 5 login, settings, all_tags
6 6 from boards.views.authors import AuthorsView
7 7 from boards.views.delete_post import DeletePostView
8 8 from boards.views.ban import BanUserView
9 9 from boards.views.static import StaticPageView
10 10 from boards.views.post_admin import PostAdminView
11 11
12 12 js_info_dict = {
13 13 'packages': ('boards',),
14 14 }
15 15
16 16 urlpatterns = patterns('',
17 17
18 18 # /boards/
19 19 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
20 20 # /boards/page/
21 21 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
22 22 name='index'),
23 23
24 24 url(r'^archive/$', archived_threads.ArchiveView.as_view(), name='archive'),
25 25 url(r'^archive/page/(?P<page>\w+)/$',
26 26 archived_threads.ArchiveView.as_view(), name='archive'),
27 27
28 28 # login page
29 29 url(r'^login/$', login.LoginView.as_view(), name='login'),
30 30
31 31 # /boards/tag/tag_name/
32 32 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
33 33 name='tag'),
34 34 # /boards/tag/tag_id/page/
35 35 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
36 36 tag_threads.TagView.as_view(), name='tag'),
37 37
38 38 # /boards/thread/
39 39 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
40 40 name='thread'),
41 41 url(r'^thread/(?P<post_id>\w+)/mode/(?P<mode>\w+)/$', views.thread.ThreadView
42 42 .as_view(), name='thread_mode'),
43 43
44 44 # /boards/post_admin/
45 45 url(r'^post_admin/(?P<post_id>\w+)/$', PostAdminView.as_view(),
46 46 name='post_admin'),
47 47
48 48 url(r'^settings/$', settings.SettingsView.as_view(), name='settings'),
49 49 url(r'^tags/$', all_tags.AllTagsView.as_view(), name='tags'),
50 50 url(r'^captcha/', include('captcha.urls')),
51 51 url(r'^authors/$', AuthorsView.as_view(), name='authors'),
52 52 url(r'^delete/(?P<post_id>\w+)/$', DeletePostView.as_view(),
53 53 name='delete'),
54 54 url(r'^ban/(?P<post_id>\w+)/$', BanUserView.as_view(), name='ban'),
55 55
56 56 url(r'^banned/$', views.banned.BannedView.as_view(), name='banned'),
57 57 url(r'^staticpage/(?P<name>\w+)/$', StaticPageView.as_view(),
58 58 name='staticpage'),
59 59
60 60 # RSS feeds
61 61 url(r'^rss/$', AllThreadsFeed()),
62 62 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
63 63 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
64 64 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
65 65 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
66 66
67 67 # i18n
68 68 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict,
69 69 name='js_info_dict'),
70 70
71 71 # API
72 72 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
73 73 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
74 74 api.api_get_threaddiff, name="get_thread_diff"),
75 75 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
76 76 name='get_threads'),
77 77 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
78 78 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
79 79 name='get_thread'),
80 80 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
81 81 name='add_post'),
82 url(r'api/get_tag_popularity/(?P<tag_name>\w+)$', api.get_tag_popularity,
83 name='get_tag_popularity'),
82 84
83 85 )
@@ -1,239 +1,248 b''
1 1 from datetime import datetime
2 2 import json
3 3 import logging
4 4 from django.db import transaction
5 5 from django.http import HttpResponse
6 6 from django.shortcuts import get_object_or_404, render
7 7 from django.template import RequestContext
8 8 from django.utils import timezone
9 9 from django.core import serializers
10 10
11 11 from boards.forms import PostForm, PlainErrorList
12 12 from boards.models import Post, Thread, Tag
13 13 from boards.utils import datetime_to_epoch
14 14 from boards.views.thread import ThreadView
15 15
16 16 __author__ = 'neko259'
17 17
18 18 PARAMETER_TRUNCATED = 'truncated'
19 19 PARAMETER_TAG = 'tag'
20 20 PARAMETER_OFFSET = 'offset'
21 21 PARAMETER_DIFF_TYPE = 'type'
22 22
23 23 DIFF_TYPE_HTML = 'html'
24 24 DIFF_TYPE_JSON = 'json'
25 25
26 26 STATUS_OK = 'ok'
27 27 STATUS_ERROR = 'error'
28 28
29 29 logger = logging.getLogger(__name__)
30 30
31 31
32 32 @transaction.atomic
33 33 def api_get_threaddiff(request, thread_id, last_update_time):
34 34 """
35 35 Gets posts that were changed or added since time
36 36 """
37 37
38 38 thread = get_object_or_404(Post, id=thread_id).get_thread()
39 39
40 40 logger.info('Getting thread #%s diff since %s' % (thread_id,
41 41 last_update_time))
42 42
43 43 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
44 44 timezone.get_current_timezone())
45 45
46 46 json_data = {
47 47 'added': [],
48 48 'updated': [],
49 49 'last_update': None,
50 50 }
51 51 added_posts = Post.objects.filter(thread_new=thread,
52 52 pub_time__gt=filter_time) \
53 53 .order_by('pub_time')
54 54 updated_posts = Post.objects.filter(thread_new=thread,
55 55 pub_time__lte=filter_time,
56 56 last_edit_time__gt=filter_time)
57 57
58 58 diff_type = DIFF_TYPE_HTML
59 59 if PARAMETER_DIFF_TYPE in request.GET:
60 60 diff_type = request.GET[PARAMETER_DIFF_TYPE]
61 61
62 62 for post in added_posts:
63 63 json_data['added'].append(_get_post_data(post.id, diff_type, request))
64 64 for post in updated_posts:
65 65 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
66 66 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
67 67
68 68 return HttpResponse(content=json.dumps(json_data))
69 69
70 70
71 71 def api_add_post(request, opening_post_id):
72 72 """
73 73 Adds a post and return the JSON response for it
74 74 """
75 75
76 76 opening_post = get_object_or_404(Post, id=opening_post_id)
77 77
78 78 logger.info('Adding post via api...')
79 79
80 80 status = STATUS_OK
81 81 errors = []
82 82
83 83 if request.method == 'POST':
84 84 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
85 85 form.session = request.session
86 86
87 87 if form.need_to_ban:
88 88 # Ban user because he is suspected to be a bot
89 89 # _ban_current_user(request)
90 90 status = STATUS_ERROR
91 91 if form.is_valid():
92 92 post = ThreadView().new_post(request, form, opening_post,
93 93 html_response=False)
94 94 if not post:
95 95 status = STATUS_ERROR
96 96 else:
97 97 logger.info('Added post #%d via api.' % post.id)
98 98 else:
99 99 status = STATUS_ERROR
100 100 errors = form.as_json_errors()
101 101
102 102 response = {
103 103 'status': status,
104 104 'errors': errors,
105 105 }
106 106
107 107 return HttpResponse(content=json.dumps(response))
108 108
109 109
110 110 def get_post(request, post_id):
111 111 """
112 112 Gets the html of a post. Used for popups. Post can be truncated if used
113 113 in threads list with 'truncated' get parameter.
114 114 """
115 115
116 116 logger.info('Getting post #%s' % post_id)
117 117
118 118 post = get_object_or_404(Post, id=post_id)
119 119
120 120 context = RequestContext(request)
121 121 context['post'] = post
122 122 if PARAMETER_TRUNCATED in request.GET:
123 123 context[PARAMETER_TRUNCATED] = True
124 124
125 125 return render(request, 'boards/api_post.html', context)
126 126
127 127
128 128 # TODO Test this
129 129 def api_get_threads(request, count):
130 130 """
131 131 Gets the JSON thread opening posts list.
132 132 Parameters that can be used for filtering:
133 133 tag, offset (from which thread to get results)
134 134 """
135 135
136 136 if PARAMETER_TAG in request.GET:
137 137 tag_name = request.GET[PARAMETER_TAG]
138 138 if tag_name is not None:
139 139 tag = get_object_or_404(Tag, name=tag_name)
140 140 threads = tag.threads.filter(archived=False)
141 141 else:
142 142 threads = Thread.objects.filter(archived=False)
143 143
144 144 if PARAMETER_OFFSET in request.GET:
145 145 offset = request.GET[PARAMETER_OFFSET]
146 146 offset = int(offset) if offset is not None else 0
147 147 else:
148 148 offset = 0
149 149
150 150 threads = threads.order_by('-bump_time')
151 151 threads = threads[offset:offset + int(count)]
152 152
153 153 opening_posts = []
154 154 for thread in threads:
155 155 opening_post = thread.get_opening_post()
156 156
157 157 # TODO Add tags, replies and images count
158 158 opening_posts.append(_get_post_data(opening_post.id,
159 159 include_last_update=True))
160 160
161 161 return HttpResponse(content=json.dumps(opening_posts))
162 162
163 163
164 164 # TODO Test this
165 165 def api_get_tags(request):
166 166 """
167 167 Gets all tags or user tags.
168 168 """
169 169
170 170 # TODO Get favorite tags for the given user ID
171 171
172 172 tags = Tag.objects.get_not_empty_tags()
173 173 tag_names = []
174 174 for tag in tags:
175 175 tag_names.append(tag.name)
176 176
177 177 return HttpResponse(content=json.dumps(tag_names))
178 178
179 179
180 180 # TODO The result can be cached by the thread last update time
181 181 # TODO Test this
182 182 def api_get_thread_posts(request, opening_post_id):
183 183 """
184 184 Gets the JSON array of thread posts
185 185 """
186 186
187 187 opening_post = get_object_or_404(Post, id=opening_post_id)
188 188 thread = opening_post.get_thread()
189 189 posts = thread.get_replies()
190 190
191 191 json_data = {
192 192 'posts': [],
193 193 'last_update': None,
194 194 }
195 195 json_post_list = []
196 196
197 197 for post in posts:
198 198 json_post_list.append(_get_post_data(post.id))
199 199 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
200 200 json_data['posts'] = json_post_list
201 201
202 202 return HttpResponse(content=json.dumps(json_data))
203 203
204 204
205 205 def api_get_post(request, post_id):
206 206 """
207 207 Gets the JSON of a post. This can be
208 208 used as and API for external clients.
209 209 """
210 210
211 211 post = get_object_or_404(Post, id=post_id)
212 212
213 213 json = serializers.serialize("json", [post], fields=(
214 214 "pub_time", "_text_rendered", "title", "text", "image",
215 215 "image_width", "image_height", "replies", "tags"
216 216 ))
217 217
218 218 return HttpResponse(content=json)
219 219
220 220
221 def get_tag_popularity(request, tag_name):
222 tag = get_object_or_404(Tag, name=tag_name)
223
224 json_data = []
225 json_data['popularity'] = tag.get_popularity()
226
227 return HttpResponse(content=json.dumps(json_data))
228
229
221 230 # TODO Add pub time and replies
222 231 def _get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
223 232 include_last_update=False):
224 233 if format_type == DIFF_TYPE_HTML:
225 234 return get_post(request, post_id).content.strip()
226 235 elif format_type == DIFF_TYPE_JSON:
227 236 post = get_object_or_404(Post, id=post_id)
228 237 post_json = {
229 238 'id': post.id,
230 239 'title': post.title,
231 240 'text': post.text.rendered,
232 241 }
233 242 if post.image:
234 243 post_json['image'] = post.image.url
235 244 post_json['image_preview'] = post.image.url_200x150
236 245 if include_last_update:
237 246 post_json['bump_time'] = datetime_to_epoch(
238 247 post.thread_new.bump_time)
239 248 return post_json
General Comments 0
You need to be logged in to leave comments. Login now