##// END OF EJS Templates
Added metadata to the gallery. Added links to search the image by the online search engines
neko259 -
r460:dce9cedd 1.5-dev
parent child Browse files
Show More
@@ -1,29 +1,33 b''
1 1 .ui-button {
2 2 display: none;
3 3 }
4 4
5 5 .ui-dialog-content {
6 6 padding: 0;
7 7 min-height: 0;
8 8 }
9 9
10 10 .mark_btn {
11 11 cursor: pointer;
12 12 }
13 13
14 14 .img-full {
15 15 position: fixed;
16 16 z-index: 9999;
17 17 background-color: #CCC;
18 18 border: 1px solid #000;
19 19 cursor: pointer;
20 20 }
21 21
22 22 .strikethrough {
23 23 text-decoration: line-through;
24 24 }
25 25
26 26 .post_preview {
27 27 z-index: 300;
28 28 position:absolute;
29 29 }
30
31 .gallery_image {
32 display: inline-block;
33 } No newline at end of file
@@ -1,361 +1,368 b''
1 1 html {
2 2 background: #555;
3 3 color: #ffffff;
4 4 }
5 5
6 6 #admin_panel {
7 7 background: #FF0000;
8 8 color: #00FF00
9 9 }
10 10
11 11 .input_field {
12 12
13 13 }
14 14
15 15 .input_field_name {
16 16
17 17 }
18 18
19 19 .input_field_error {
20 20 color: #FF0000;
21 21 }
22 22
23 23 .title {
24 24 font-weight: bold;
25 25 color: #ffcc00;
26 26 font-size: 2ex;
27 27 }
28 28
29 29 .link, a {
30 30 color: #afdcec;
31 31 }
32 32
33 33 .block {
34 34 display: inline-block;
35 35 vertical-align: top;
36 36 }
37 37
38 38 .tag {
39 39 color: #b4cfec;
40 40 }
41 41
42 42 .post_id {
43 43 color: #fff380;
44 44 }
45 45
46 46 .post, .dead_post, #posts-table {
47 47 background: #333;
48 48 margin: 5px;
49 49 padding: 10px;
50 50 border: solid 1px #888;
51 51 clear: left;
52 52 word-wrap: break-word;
53 53 }
54 54
55 55 .metadata {
56 56 padding-top: 5px;
57 57 margin-top: 10px;
58 58 border-top: solid 1px #666;
59 59 font-size: 0.9em;
60 60 color: #ddd;
61 61 }
62 62
63 63 .navigation_panel, .tag_info {
64 64 background: #444;
65 65 margin: 5px;
66 66 padding: 10px;
67 67 border: solid 1px #888;
68 68 color: #eee;
69 69 }
70 70
71 71 .navigation_panel .link {
72 72 border-right: 1px solid #fff;
73 73 font-weight: bold;
74 74 margin-right: 1ex;
75 75 padding-right: 1ex;
76 76 }
77 77 .navigation_panel .link:last-child {
78 78 border-left: 1px solid #fff;
79 79 border-right: none;
80 80 float: right;
81 81 margin-left: 1ex;
82 82 margin-right: 0;
83 83 padding-left: 1ex;
84 84 padding-right: 0;
85 85 }
86 86
87 87 .navigation_panel::after, .post::after {
88 88 clear: both;
89 89 content: ".";
90 90 display: block;
91 91 height: 0;
92 92 line-height: 0;
93 93 visibility: hidden;
94 94 }
95 95
96 96 p {
97 97 margin-top: .5em;
98 98 margin-bottom: .5em;
99 99 }
100 100
101 101 .post-form-w {
102 102 display: table;
103 103 background: #333344;
104 104 border: solid 1px #888;
105 105 color: #fff;
106 106 padding: 10px;
107 107 margin: 5px;
108 108 }
109 109
110 110 .form-row {
111 111 display: table-row;
112 112 }
113 113
114 114 .form-label, .form-input, .form-errors {
115 115 display: table-cell;
116 116 }
117 117
118 118 .form-label {
119 119 padding: .25em 1ex .25em 0;
120 120 vertical-align: top;
121 121 }
122 122
123 123 .form-input {
124 124 padding: .25em 0;
125 125 }
126 126
127 127 .form-errors {
128 128 font-weight: bolder;
129 129 vertical-align: middle;
130 130 }
131 131
132 132 .post-form input, .post-form textarea {
133 133 background: #333;
134 134 color: #fff;
135 135 border: solid 1px;
136 136 padding: 0;
137 137 width: 100%;
138 138 font: medium sans;
139 139 }
140 140
141 141 .form-submit {
142 142 display: table;
143 143 margin-bottom: 1ex;
144 144 }
145 145
146 146 .form-title {
147 147 font-weight: bold;
148 148 font-size: 2.5ex;
149 149 text-decoration: underline;
150 150 }
151 151
152 152 input[type="submit"] {
153 153 background: #222;
154 154 border: solid 2px #fff;
155 155 color: #fff;
156 156 padding: 0.5ex;
157 157 }
158 158
159 159 input[type="submit"]:hover {
160 160 background: #060;
161 161 }
162 162
163 163 blockquote {
164 164 border-left: solid 2px;
165 165 padding-left: 5px;
166 166 color: #B1FB17;
167 167 margin: 0;
168 168 }
169 169
170 170 .post > .image {
171 171 float: left;
172 172 margin: 0 1ex .5ex 0;
173 173 min-width: 1px;
174 174 text-align: center;
175 175 display: table-row;
176 176 }
177 177
178 178 .post > .metadata {
179 179 clear: left;
180 180 }
181 181
182 182 .get {
183 183 font-weight: bold;
184 184 color: #d55;
185 185 }
186 186
187 187 * {
188 188 text-decoration: none;
189 189 }
190 190
191 191 .dead_post {
192 192 background-color: #442222;
193 193 }
194 194
195 195 .mark_btn {
196 196 border: 1px solid;
197 197 min-width: 2ex;
198 198 padding: 2px 2ex;
199 199 }
200 200
201 201 .mark_btn:hover {
202 202 background: #555;
203 203 }
204 204
205 205 .quote {
206 206 color: #92cf38;
207 207 font-style: italic;
208 208 }
209 209
210 210 .spoiler {
211 211 background: white;
212 212 color: white;
213 213 }
214 214
215 215 .spoiler:hover {
216 216 color: black;
217 217 }
218 218
219 219 .comment {
220 220 color: #eb2;
221 221 font-style: italic;
222 222 }
223 223
224 224 a:hover {
225 225 text-decoration: underline;
226 226 }
227 227
228 228 .last-replies {
229 229 margin-left: 3ex;
230 230 }
231 231
232 232 .thread {
233 233 margin-bottom: 3ex;
234 234 }
235 235
236 236 .post:target {
237 237 border: solid 2px white;
238 238 }
239 239
240 240 pre{
241 241 white-space:pre-wrap
242 242 }
243 243
244 244 li {
245 245 list-style-position: inside;
246 246 }
247 247
248 248 .fancybox-skin {
249 249 position: relative;
250 250 background-color: #fff;
251 251 color: #ddd;
252 252 text-shadow: none;
253 253 }
254 254
255 255 .fancybox-image {
256 256 border: 1px solid black;
257 257 }
258 258
259 259 .image-mode-tab {
260 260 background: #444;
261 261 color: #eee;
262 262 display: table;
263 263 margin: 5px;
264 264 padding: 5px;
265 265 border: 1px solid #888;
266 266 }
267 267
268 268 .image-mode-tab > label {
269 269 margin: 0 1ex;
270 270 }
271 271
272 272 .image-mode-tab > label > input {
273 273 margin-right: .5ex;
274 274 }
275 275
276 276 #posts-table {
277 277 margin: 5px;
278 278 }
279 279
280 280 .tag_info {
281 281 display: table;
282 282 }
283 283
284 284 .tag_info > h2 {
285 285 margin: 0;
286 286 }
287 287
288 288 .post-info {
289 289 color: #ddd;
290 290 }
291 291
292 292 .moderator_info {
293 293 color: #e99d41;
294 294 border: dashed 1px;
295 295 padding: 3px;
296 296 }
297 297
298 298 .refmap {
299 299 font-size: 0.9em;
300 300 color: #ccc;
301 301 margin-top: 1em;
302 302 }
303 303
304 304 .fav {
305 305 color: yellow;
306 306 }
307 307
308 308 .not_fav {
309 309 color: #ccc;
310 310 }
311 311
312 312 .role {
313 313 text-decoration: underline;
314 314 }
315 315
316 316 .form-email {
317 317 display: none;
318 318 }
319 319
320 320 .footer {
321 321 margin: 5px;
322 322 }
323 323
324 324 .bar-value {
325 325 background: rgba(50, 55, 164, 0.45);
326 326 font-size: 0.9em;
327 327 height: 1.5em;
328 328 }
329 329
330 330 .bar-bg {
331 331 position: relative;
332 332 border: solid 1px #888;
333 333 margin: 5px;
334 334 overflow: hidden;
335 335 }
336 336
337 337 .bar-text {
338 338 padding: 2px;
339 339 position: absolute;
340 340 left: 0;
341 341 top: 0;
342 342 }
343 343
344 344 .page_link {
345 345 display: table;
346 346 background: #444;
347 347 margin: 5px;
348 348 border: solid 1px #888;
349 349 padding: 5px;
350 350 font-weight: bolder;
351 351 color: #eee;
352 352 }
353 353
354 354 .skipped_replies {
355 355 margin: 5px;
356 356 }
357 357
358 358 .current_page, .current_mode {
359 359 border: solid 1px #afdcec;
360 360 padding: 2px;
361 }
362
363 .gallery_image {
364 border: solid 1px;
365 padding: 0.5ex;
366 margin: 0.5ex;
367 text-align: center;
361 368 } No newline at end of file
@@ -1,353 +1,357 b''
1 1 * {
2 2 font-size: inherit;
3 3 margin: 0;
4 4 padding: 0;
5 5 }
6 6 html {
7 7 background: #fff;
8 8 color: #000;
9 9 font: medium sans-serif;
10 10 }
11 11 a {
12 12 color: inherit;
13 13 text-decoration: underline;
14 14 }
15 15 li {
16 16 list-style-position: inside;
17 17 }
18 18
19 19 #admin_panel {
20 20 background: #182F6F;
21 21 color: #fff;
22 22 padding: .5ex 1ex .5ex 1ex;
23 23 }
24 24
25 25 .navigation_panel {
26 26 background: #182F6F;
27 27 color: #B4CFEC;
28 28 margin-bottom: 1em;
29 29 padding: .5ex 1ex 1ex 1ex;
30 30 }
31 31 .navigation_panel::after {
32 32 clear: both;
33 33 content: ".";
34 34 display: block;
35 35 height: 0;
36 36 line-height: 0;
37 37 visibility: hidden;
38 38 }
39 39
40 40 .navigation_panel a:link, .navigation_panel a:visited, .navigation_panel a:hover {
41 41 text-decoration: none;
42 42 }
43 43
44 44 .navigation_panel .link {
45 45 border-right: 1px solid #fff;
46 46 color: #fff;
47 47 font-weight: bold;
48 48 margin-right: 1ex;
49 49 padding-right: 1ex;
50 50 }
51 51 .navigation_panel .link:last-child {
52 52 border-left: 1px solid #fff;
53 53 border-right: none;
54 54 float: right;
55 55 margin-left: 1ex;
56 56 margin-right: 0;
57 57 padding-left: 1ex;
58 58 padding-right: 0;
59 59 }
60 60
61 61 .navigation_panel .tag {
62 62 color: #fff;
63 63 }
64 64
65 65 .input_field {
66 66
67 67 }
68 68
69 69 .input_field_name {
70 70
71 71 }
72 72
73 73 .input_field_error {
74 74 color: #FF0000;
75 75 }
76 76
77 77
78 78 .title {
79 79 color: #182F6F;
80 80 font-weight: bold;
81 81 }
82 82
83 83 .post-form-w {
84 84 background: #182F6F;
85 85 border-radius: 1ex;
86 86 color: #fff;
87 87 margin: 1em 1ex;
88 88 padding: 1ex;
89 89 }
90 90 .post-form {
91 91 display: table;
92 92 border-collapse: collapse;
93 93 width: 100%;
94 94
95 95 }
96 96 .form-row {
97 97 display: table-row;
98 98 }
99 99 .form-label, .form-input {
100 100 display: table-cell;
101 101 vertical-align: top;
102 102 }
103 103 .form-label {
104 104 padding: .25em 1ex .25em 0;
105 105 }
106 106 .form-input {
107 107 padding: .25em 0;
108 108 }
109 109 .form-input > * {
110 110 background: #fff;
111 111 color: #000;
112 112 border: none;
113 113 padding: 0;
114 114 resize: vertical;
115 115 width: 100%;
116 116 }
117 117 .form-submit {
118 118 border-bottom: 1px solid #666;
119 119 margin-bottom: .5em;
120 120 padding-bottom: .5em;
121 121 }
122 122 .form-title {
123 123 font-weight: bold;
124 124 margin-bottom: .5em;
125 125 }
126 126 .post-form .settings_item {
127 127 margin: .5em 0;
128 128 }
129 129 .form-submit input {
130 130 margin-top: .5em;
131 131 padding: .2em 1ex;
132 132 }
133 133 .form-label {
134 134 text-align: right;
135 135 }
136 136
137 137 .block {
138 138 display: inline-block;
139 139 vertical-align: top;
140 140 }
141 141
142 142 .post_id {
143 143 color: #a00;
144 144 }
145 145
146 146 .post {
147 147 clear: left;
148 148 margin: 0 1ex 1em 1ex;
149 149 overflow-x: auto;
150 150 word-wrap: break-word;
151 151 background: #FFF;
152 152 padding: 1ex;
153 153 border: 1px solid #666;
154 154 box-shadow: 1px 1px 2px 1px #666;
155 155 }
156 156
157 157 #posts > .post:last-child {
158 158 border-bottom: none;
159 159 padding-bottom: 0;
160 160 }
161 161
162 162 .metadata {
163 163 background: #C0E4E8;
164 164 border: 1px solid #7F9699;
165 165 border-radius: .4ex;
166 166 display: table;
167 167 margin-top: .5em;
168 168 padding: .4em;
169 169 }
170 170
171 171 .post ul, .post ol {
172 172 margin: .5em 0 .5em 3ex;
173 173 }
174 174 .post li {
175 175 margin: .2em 0;
176 176 }
177 177 .post p {
178 178 margin: .5em 0;
179 179 }
180 180 .post blockquote {
181 181 border-left: 3px solid #182F6F;
182 182 margin: .5em 0 .5em 3ex;
183 183 padding-left: 1ex;
184 184 }
185 185 .post blockquote > blockquote {
186 186 padding-top: .1em;
187 187 }
188 188
189 189 .post > .image {
190 190 float: left;
191 191 margin-right: 1ex;
192 192 }
193 193 .post > .metadata {
194 194 clear: left;
195 195 }
196 196
197 197 .post > .message .get {
198 198 color: #182F6F; font-weight: bold;
199 199 }
200 200
201 201 .dead_post > .metadata {
202 202 background: #eee;
203 203 }
204 204
205 205 .quote {
206 206 color: #182F6F;
207 207 }
208 208
209 209 .spoiler {
210 210 background: black;
211 211 color: black;
212 212 }
213 213
214 214 .spoiler:hover {
215 215 background: #ffffff;
216 216 }
217 217
218 218 .comment {
219 219 color: #557055;
220 220 }
221 221
222 222 .last-replies {
223 223 margin-left: 6ex;
224 224 }
225 225
226 226 .thread > .post > .message > .post-info {
227 227 border-bottom: 1px solid #ccc;
228 228 padding-bottom: .5em;
229 229 }
230 230
231 231 :target .post_id {
232 232 background: #182F6F;
233 233 color: #FFF;
234 234 text-decoration: none;
235 235 }
236 236
237 237 .image-mode-tab {
238 238 background: #182F6F;
239 239 color: #FFF;
240 240 display: table;
241 241 margin: 1em auto 1em 0;
242 242 padding: .2em .5ex;
243 243 }
244 244
245 245 .image-mode-tab > label {
246 246 margin: 0 1ex;
247 247 }
248 248
249 249 .image-mode-tab > label > input {
250 250 margin-right: .5ex;
251 251 }
252 252
253 253 .tag_info, .page_link {
254 254 margin: 1em 0;
255 255 text-align: center;
256 256 }
257 257
258 258 .form-errors {
259 259 margin-left: 1ex;
260 260 }
261 261
262 262 .moderator_info {
263 263 font-weight: bold;
264 264 float: right;
265 265 }
266 266
267 267 .refmap {
268 268 border: 1px dashed #aaa;
269 269 padding: 0.5em;
270 270 display: table;
271 271 }
272 272
273 273 .fav {
274 274 color: blue;
275 275 }
276 276
277 277 .not_fav {
278 278 color: #ccc;
279 279 }
280 280
281 281 .role {
282 282 text-decoration: underline;
283 283 }
284 284
285 285 .form-email {
286 286 display: none;
287 287 }
288 288
289 289 .bar-value {
290 290 background: #E3E7F2;
291 291 padding: .1em 1ex;
292 292 moz-box-sizing: border-box;
293 293 box-sizing: border-box;
294 294 height: 1.5em;
295 295 }
296 296
297 297 .bar-bg {
298 298 background: #EA4649;
299 299 border: 1px solid #666;
300 300 margin: 0 1ex 1em 1ex;
301 301 position: relative;
302 302 overflow: hidden;
303 303 }
304 304
305 305 .bar-text {
306 306 padding: 2px;
307 307 position: absolute;
308 308 left: 0;
309 309 top: 0;
310 310 }
311 311
312 312 .skipped_replies {
313 313 margin: 1ex;
314 314 }
315 315
316 316 #mark-panel {
317 317 background: #eee;
318 318 border-bottom: 1px solid #182F6F;
319 319 }
320 320
321 321 .mark_btn {
322 322 display: inline-block;
323 323 padding: .2em 1ex;
324 324 border-left: 1px solid #182F6F;
325 325 }
326 326
327 327 .mark_btn:first-child {
328 328 border-left: none;
329 329 }
330 330
331 331 .mark_btn:last-child {
332 332 border-right: 1px solid #182F6F;
333 333 }
334 334
335 335 .current_page {
336 336 border-bottom: 1px solid #FFF;
337 337 padding: 0px 0.5ex;
338 338 }
339 339
340 340 .image-mode-tab a {
341 341 text-decoration: none;
342 342 }
343 343 .image-mode-tab .current_mode::before {
344 344 content: "βœ“ ";
345 345 padding: 0 0 0 .5ex;
346 346 color: #182F6F;
347 347 background: #FFF;
348 348 }
349 349 .image-mode-tab .current_mode {
350 350 padding: 0 .5ex 0 0;
351 351 color: #182F6F;
352 352 background: #FFF;
353 }
354
355 .gallery_image_metadata {
356 margin-bottom: 1em;
353 357 } No newline at end of file
@@ -1,58 +1,66 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load static from staticfiles %}
6 6 {% load board %}
7 7
8 8 {% block head %}
9 9 <title>Neboard - {{ thread.get_opening_post.get_title }}</title>
10 10 {% endblock %}
11 11
12 12 {% block content %}
13 13 {% spaceless %}
14 14 {% get_current_language as LANGUAGE_CODE %}
15 15
16 16 <script src="{% static 'js/thread.js' %}"></script>
17 17
18 18 {% cache 600 thread_gallery_view thread.id thread.last_edit_time LANGUAGE_CODE %}
19 19 <div class="image-mode-tab">
20 20 <a href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
21 21 <a class="current_mode" href="{% url 'thread_mode' thread.get_opening_post.id 'gallery' %}">{% trans 'Gallery mode' %}</a>
22 22 </div>
23 23
24 24 <div id="posts-table">
25 25 {% for post in thread.get_replies %}
26 26 {% if post.image %}
27 <div class="gallery_image">
28 <div>
27 29 <a
28 30 class="thumb"
29 31 href="{{ post.image.url }}"><img
30 32 src="{{ post.image.url_200x150 }}"
31 33 alt="{{ post.id }}"
32 34 width="{{ post.image_pre_width }}"
33 35 height="{{ post.image_pre_height }}"
34 36 data-width="{{ post.image_width }}"
35 37 data-height="{{ post.image_height }}"/>
36 38 </a>
39 </div>
40 <div class="gallery_image_metadata">
41 {{ post.image_width }}x{{ post.image_height }}
42 {% image_actions post.image.url request.get_host %}
43 </div>
44 </div>
37 45 {% endif %}
38 46 {% endfor %}
39 47 </div>
40 48 {% endcache %}
41 49
42 50 {% endspaceless %}
43 51 {% endblock %}
44 52
45 53 {% block metapanel %}
46 54
47 55 {% get_current_language as LANGUAGE_CODE %}
48 56
49 57 <span class="metapanel" data-last-update="{{ last_update }}">
50 58 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
51 59 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
52 60 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
53 61 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
54 62 [<a href="rss/">RSS</a>]
55 63 {% endcache %}
56 64 </span>
57 65
58 66 {% endblock %}
@@ -1,23 +1,49 b''
1 1 from django.core.urlresolvers import reverse
2 2 from django.shortcuts import get_object_or_404
3 3 from boards.models import Post
4 4 from boards.views import thread
5 5 from django import template
6 6
7 7 register = template.Library()
8 8
9 actions = [
10 {
11 'name': 'google',
12 'link': 'http://google.com/searchbyimage?image_url=%s',
13 },
14 {
15 'name': 'iqdb',
16 'link': 'http://iqdb.org/?url=%s',
17 },
18 ]
19
9 20
10 21 @register.simple_tag(name='post_url')
11 22 def post_url(*args, **kwargs):
12 23 post_id = args[0]
13 24
14 25 post = get_object_or_404(Post, id=post_id)
15 26
16 27 if not post.is_opening():
17 28 link = reverse(thread, kwargs={
18 29 'post_id': post.thread_new.get_opening_post().id}) + '#' + str(
19 30 post_id)
20 31 else:
21 32 link = reverse(thread, kwargs={'post_id': post_id})
22 33
23 34 return link
35
36
37 @register.simple_tag(name='image_actions')
38 def image_actions(*args, **kwargs):
39 image_link = args[0]
40 if len(args) > 1:
41 image_link = 'http://' + args[1] + image_link # TODO https?
42
43 result = ''
44
45 for action in actions:
46 result += '[<a href="' + action['link'] % image_link + '">' + \
47 action['name'] + '</a>]'
48
49 return result
@@ -1,558 +1,559 b''
1 1 __author__ = 'neko259'
2 2
3 3 import hashlib
4 4 import string
5 5 import time
6 6 import re
7 7
8 8 from django.core import serializers
9 9 from django.core.urlresolvers import reverse
10 from django.http import HttpResponseRedirect
10 from django.http import HttpResponseRedirect, Http404
11 11 from django.http.response import HttpResponse
12 12 from django.template import RequestContext
13 13 from django.shortcuts import render, redirect, get_object_or_404
14 14 from django.utils import timezone
15 15 from django.db import transaction
16 16 from django.views.decorators.cache import cache_page
17 17 from django.views.i18n import javascript_catalog
18 18
19 19 from boards import forms
20 20 import boards
21 21 from boards import utils
22 22 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
23 23 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
24 24 from boards.models import Post, Tag, Ban, User
25 25 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
26 26 from boards.models.user import RANK_USER
27 27 from boards import authors
28 28 from boards.utils import get_client_ip
29 29 import neboard
30 30
31 31
32 32 BAN_REASON_SPAM = 'Autoban: spam bot'
33 MODE_GALLERY = 'gallery'
34 MODE_NORMAL = 'normal'
33 35
34 36
35 37 def index(request, page=0):
36 38 context = _init_default_context(request)
37 39
38 40 if utils.need_include_captcha(request):
39 41 threadFormClass = ThreadCaptchaForm
40 42 kwargs = {'request': request}
41 43 else:
42 44 threadFormClass = ThreadForm
43 45 kwargs = {}
44 46
45 47 if request.method == 'POST':
46 48 form = threadFormClass(request.POST, request.FILES,
47 49 error_class=PlainErrorList, **kwargs)
48 50 form.session = request.session
49 51
50 52 if form.is_valid():
51 53 return _new_post(request, form)
52 54 if form.need_to_ban:
53 55 # Ban user because he is suspected to be a bot
54 56 _ban_current_user(request)
55 57 else:
56 58 form = threadFormClass(error_class=PlainErrorList, **kwargs)
57 59
58 60 threads = []
59 61 for thread_to_show in Post.objects.get_threads(page=int(page)):
60 62 threads.append(_get_template_thread(thread_to_show))
61 63
62 64 # TODO Make this generic for tag and threads list pages
63 65 context['threads'] = None if len(threads) == 0 else threads
64 66 context['form'] = form
65 67 context['current_page'] = int(page)
66 68
67 69 page_count = Post.objects.get_thread_page_count()
68 70 context['pages'] = range(page_count)
69 71 page = int(page)
70 72 if page < page_count - 1:
71 73 context['next_page'] = str(page + 1)
72 74 if page > 0:
73 75 context['prev_page'] = str(page - 1)
74 76
75 77 return render(request, 'boards/posting_general.html',
76 78 context)
77 79
78 80
79 81 @transaction.atomic
80 82 def _new_post(request, form, opening_post=None):
81 83 """Add a new post (in thread or as a reply)."""
82 84
83 85 ip = get_client_ip(request)
84 86 is_banned = Ban.objects.filter(ip=ip).exists()
85 87
86 88 if is_banned:
87 89 return redirect(you_are_banned)
88 90
89 91 data = form.cleaned_data
90 92
91 93 title = data['title']
92 94 text = data['text']
93 95
94 96 text = _remove_invalid_links(text)
95 97
96 98 if 'image' in data.keys():
97 99 image = data['image']
98 100 else:
99 101 image = None
100 102
101 103 tags = []
102 104
103 105 if not opening_post:
104 106 tag_strings = data['tags']
105 107
106 108 if tag_strings:
107 109 tag_strings = tag_strings.split(' ')
108 110 for tag_name in tag_strings:
109 111 tag_name = string.lower(tag_name.strip())
110 112 if len(tag_name) > 0:
111 113 tag, created = Tag.objects.get_or_create(name=tag_name)
112 114 tags.append(tag)
113 115 post_thread = None
114 116 else:
115 117 post_thread = opening_post.thread_new
116 118
117 119 post = Post.objects.create_post(title=title, text=text, ip=ip,
118 120 thread=post_thread, image=image,
119 121 tags=tags, user=_get_user(request))
120 122
121 123 thread_to_show = (opening_post.id if opening_post else post.id)
122 124
123 125 if opening_post:
124 126 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
125 127 '#' + str(post.id))
126 128 else:
127 129 return redirect(thread, post_id=thread_to_show)
128 130
129 131
130 132 def tag(request, tag_name, page=0):
131 133 """
132 134 Get all tag threads. Threads are split in pages, so some page is
133 135 requested. Default page is 0.
134 136 """
135 137
136 138 tag = get_object_or_404(Tag, name=tag_name)
137 139 threads = []
138 140 for thread_to_show in Post.objects.get_threads(page=int(page), tag=tag):
139 141 threads.append(_get_template_thread(thread_to_show))
140 142
141 143 if request.method == 'POST':
142 144 form = ThreadForm(request.POST, request.FILES,
143 145 error_class=PlainErrorList)
144 146 form.session = request.session
145 147
146 148 if form.is_valid():
147 149 return _new_post(request, form)
148 150 if form.need_to_ban:
149 151 # Ban user because he is suspected to be a bot
150 152 _ban_current_user(request)
151 153 else:
152 154 form = forms.ThreadForm(initial={'tags': tag_name},
153 155 error_class=PlainErrorList)
154 156
155 157 context = _init_default_context(request)
156 158 context['threads'] = None if len(threads) == 0 else threads
157 159 context['tag'] = tag
158 160 context['current_page'] = int(page)
159 161
160 162 page_count = Post.objects.get_thread_page_count(tag=tag)
161 163 context['pages'] = range(page_count)
162 164 page = int(page)
163 165 if page < page_count - 1:
164 166 context['next_page'] = str(page + 1)
165 167 if page > 0:
166 168 context['prev_page'] = str(page - 1)
167 169
168 170 context['form'] = form
169 171
170 172 return render(request, 'boards/posting_general.html',
171 173 context)
172 174
173 175
174 def thread(request, post_id, mode='normal'):
176 def thread(request, post_id, mode=MODE_NORMAL):
175 177 """Get all thread posts"""
176 178
177 179 if utils.need_include_captcha(request):
178 180 postFormClass = PostCaptchaForm
179 181 kwargs = {'request': request}
180 182 else:
181 183 postFormClass = PostForm
182 184 kwargs = {}
183 185
184 186 if request.method == 'POST':
185 187 form = postFormClass(request.POST, request.FILES,
186 188 error_class=PlainErrorList, **kwargs)
187 189 form.session = request.session
188 190
189 191 opening_post = get_object_or_404(Post, id=post_id)
190 192 if form.is_valid():
191 193 return _new_post(request, form, opening_post)
192 194 if form.need_to_ban:
193 195 # Ban user because he is suspected to be a bot
194 196 _ban_current_user(request)
195 197 else:
196 198 form = postFormClass(error_class=PlainErrorList, **kwargs)
197 199
198 200 thread_to_show = get_object_or_404(Post, id=post_id).thread_new
199 201
200 202 context = _init_default_context(request)
201 203
202 204 posts = thread_to_show.get_replies()
203 205 context['form'] = form
204 206 context['bumpable'] = thread_to_show.can_bump()
205 207 if context['bumpable']:
206 208 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts \
207 209 .count()
208 210 context['bumplimit_progress'] = str(
209 211 float(context['posts_left']) /
210 212 neboard.settings.MAX_POSTS_PER_THREAD * 100)
211 213 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
212 214 context["thread"] = thread_to_show
213 215
214 if 'normal' == mode:
216 if MODE_NORMAL == mode:
215 217 document = 'boards/thread.html'
216 elif 'gallery' == mode:
218 elif MODE_GALLERY == mode:
217 219 document = 'boards/thread_gallery.html'
218 220 else:
219 pass
220 # TODO raise 404 error
221 raise Http404
221 222
222 223 return render(request, document, context)
223 224
224 225
225 226 def login(request):
226 227 """Log in with user id"""
227 228
228 229 context = _init_default_context(request)
229 230
230 231 if request.method == 'POST':
231 232 form = LoginForm(request.POST, request.FILES,
232 233 error_class=PlainErrorList)
233 234 form.session = request.session
234 235
235 236 if form.is_valid():
236 237 user = User.objects.get(user_id=form.cleaned_data['user_id'])
237 238 request.session['user_id'] = user.id
238 239 return redirect(index)
239 240
240 241 else:
241 242 form = LoginForm()
242 243
243 244 context['form'] = form
244 245
245 246 return render(request, 'boards/login.html', context)
246 247
247 248
248 249 def settings(request):
249 250 """User's settings"""
250 251
251 252 context = _init_default_context(request)
252 253 user = _get_user(request)
253 254 is_moderator = user.is_moderator()
254 255
255 256 if request.method == 'POST':
256 257 with transaction.atomic():
257 258 if is_moderator:
258 259 form = ModeratorSettingsForm(request.POST,
259 260 error_class=PlainErrorList)
260 261 else:
261 262 form = SettingsForm(request.POST, error_class=PlainErrorList)
262 263
263 264 if form.is_valid():
264 265 selected_theme = form.cleaned_data['theme']
265 266
266 267 user.save_setting('theme', selected_theme)
267 268
268 269 if is_moderator:
269 270 moderate = form.cleaned_data['moderate']
270 271 user.save_setting(SETTING_MODERATE, moderate)
271 272
272 273 return redirect(settings)
273 274 else:
274 275 selected_theme = _get_theme(request)
275 276
276 277 if is_moderator:
277 278 form = ModeratorSettingsForm(initial={'theme': selected_theme,
278 279 'moderate': context['moderator']},
279 280 error_class=PlainErrorList)
280 281 else:
281 282 form = SettingsForm(initial={'theme': selected_theme},
282 283 error_class=PlainErrorList)
283 284
284 285 context['form'] = form
285 286
286 287 return render(request, 'boards/settings.html', context)
287 288
288 289
289 290 def all_tags(request):
290 291 """All tags list"""
291 292
292 293 context = _init_default_context(request)
293 294 context['all_tags'] = Tag.objects.get_not_empty_tags()
294 295
295 296 return render(request, 'boards/tags.html', context)
296 297
297 298
298 299 def jump_to_post(request, post_id):
299 300 """Determine thread in which the requested post is and open it's page"""
300 301
301 302 post = get_object_or_404(Post, id=post_id)
302 303
303 304 if not post.thread:
304 305 return redirect(thread, post_id=post.id)
305 306 else:
306 307 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
307 308 + '#' + str(post.id))
308 309
309 310
310 311 def authors(request):
311 312 """Show authors list"""
312 313
313 314 context = _init_default_context(request)
314 315 context['authors'] = boards.authors.authors
315 316
316 317 return render(request, 'boards/authors.html', context)
317 318
318 319
319 320 @transaction.atomic
320 321 def delete(request, post_id):
321 322 """Delete post"""
322 323
323 324 user = _get_user(request)
324 325 post = get_object_or_404(Post, id=post_id)
325 326
326 327 if user.is_moderator():
327 328 # TODO Show confirmation page before deletion
328 329 Post.objects.delete_post(post)
329 330
330 331 if not post.thread:
331 332 return _redirect_to_next(request)
332 333 else:
333 334 return redirect(thread, post_id=post.thread.id)
334 335
335 336
336 337 @transaction.atomic
337 338 def ban(request, post_id):
338 339 """Ban user"""
339 340
340 341 user = _get_user(request)
341 342 post = get_object_or_404(Post, id=post_id)
342 343
343 344 if user.is_moderator():
344 345 # TODO Show confirmation page before ban
345 346 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
346 347 if created:
347 348 ban.reason = 'Banned for post ' + str(post_id)
348 349 ban.save()
349 350
350 351 return _redirect_to_next(request)
351 352
352 353
353 354 def you_are_banned(request):
354 355 """Show the page that notifies that user is banned"""
355 356
356 357 context = _init_default_context(request)
357 358
358 359 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
359 360 context['ban_reason'] = ban.reason
360 361 return render(request, 'boards/staticpages/banned.html', context)
361 362
362 363
363 364 def page_404(request):
364 365 """Show page 404 (not found error)"""
365 366
366 367 context = _init_default_context(request)
367 368 return render(request, 'boards/404.html', context)
368 369
369 370
370 371 @transaction.atomic
371 372 def tag_subscribe(request, tag_name):
372 373 """Add tag to favorites"""
373 374
374 375 user = _get_user(request)
375 376 tag = get_object_or_404(Tag, name=tag_name)
376 377
377 378 if not tag in user.fav_tags.all():
378 379 user.add_tag(tag)
379 380
380 381 return _redirect_to_next(request)
381 382
382 383
383 384 @transaction.atomic
384 385 def tag_unsubscribe(request, tag_name):
385 386 """Remove tag from favorites"""
386 387
387 388 user = _get_user(request)
388 389 tag = get_object_or_404(Tag, name=tag_name)
389 390
390 391 if tag in user.fav_tags.all():
391 392 user.remove_tag(tag)
392 393
393 394 return _redirect_to_next(request)
394 395
395 396
396 397 def static_page(request, name):
397 398 """Show a static page that needs only tags list and a CSS"""
398 399
399 400 context = _init_default_context(request)
400 401 return render(request, 'boards/staticpages/' + name + '.html', context)
401 402
402 403
403 404 def api_get_post(request, post_id):
404 405 """
405 406 Get the JSON of a post. This can be
406 407 used as and API for external clients.
407 408 """
408 409
409 410 post = get_object_or_404(Post, id=post_id)
410 411
411 412 json = serializers.serialize("json", [post], fields=(
412 413 "pub_time", "_text_rendered", "title", "text", "image",
413 414 "image_width", "image_height", "replies", "tags"
414 415 ))
415 416
416 417 return HttpResponse(content=json)
417 418
418 419
419 420 def get_post(request, post_id):
420 421 """Get the html of a post. Used for popups."""
421 422
422 423 post = get_object_or_404(Post, id=post_id)
423 424 thread = post.thread_new
424 425
425 426 context = RequestContext(request)
426 427 context["post"] = post
427 428 context["can_bump"] = thread.can_bump()
428 429 if "truncated" in request.GET:
429 430 context["truncated"] = True
430 431
431 432 return render(request, 'boards/post.html', context)
432 433
433 434 @cache_page(86400)
434 435 def cached_js_catalog(request, domain='djangojs', packages=None):
435 436 return javascript_catalog(request, domain, packages)
436 437
437 438
438 439 def _get_theme(request, user=None):
439 440 """Get user's CSS theme"""
440 441
441 442 if not user:
442 443 user = _get_user(request)
443 444 theme = user.get_setting('theme')
444 445 if not theme:
445 446 theme = neboard.settings.DEFAULT_THEME
446 447
447 448 return theme
448 449
449 450
450 451 def _init_default_context(request):
451 452 """Create context with default values that are used in most views"""
452 453
453 454 context = RequestContext(request)
454 455
455 456 user = _get_user(request)
456 457 context['user'] = user
457 458 context['tags'] = user.get_sorted_fav_tags()
458 459 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
459 460
460 461 theme = _get_theme(request, user)
461 462 context['theme'] = theme
462 463 context['theme_css'] = 'css/' + theme + '/base_page.css'
463 464
464 465 # This shows the moderator panel
465 466 moderate = user.get_setting(SETTING_MODERATE)
466 467 if moderate == 'True':
467 468 context['moderator'] = user.is_moderator()
468 469 else:
469 470 context['moderator'] = False
470 471
471 472 return context
472 473
473 474
474 475 def _get_user(request):
475 476 """
476 477 Get current user from the session. If the user does not exist, create
477 478 a new one.
478 479 """
479 480
480 481 session = request.session
481 482 if not 'user_id' in session:
482 483 request.session.save()
483 484
484 485 md5 = hashlib.md5()
485 486 md5.update(session.session_key)
486 487 new_id = md5.hexdigest()
487 488
488 489 time_now = timezone.now()
489 490 user = User.objects.create(user_id=new_id, rank=RANK_USER,
490 491 registration_time=time_now)
491 492
492 493 session['user_id'] = user.id
493 494 else:
494 495 user = User.objects.get(id=session['user_id'])
495 496
496 497 return user
497 498
498 499
499 500 def _redirect_to_next(request):
500 501 """
501 502 If a 'next' parameter was specified, redirect to the next page. This is
502 503 used when the user is required to return to some page after the current
503 504 view has finished its work.
504 505 """
505 506
506 507 if 'next' in request.GET:
507 508 next_page = request.GET['next']
508 509 return HttpResponseRedirect(next_page)
509 510 else:
510 511 return redirect(index)
511 512
512 513
513 514 @transaction.atomic
514 515 def _ban_current_user(request):
515 516 """Add current user to the IP ban list"""
516 517
517 518 ip = utils.get_client_ip(request)
518 519 ban, created = Ban.objects.get_or_create(ip=ip)
519 520 if created:
520 521 ban.can_read = False
521 522 ban.reason = BAN_REASON_SPAM
522 523 ban.save()
523 524
524 525
525 526 def _remove_invalid_links(text):
526 527 """
527 528 Replace invalid links in posts so that they won't be parsed.
528 529 Invalid links are links to non-existent posts
529 530 """
530 531
531 532 for reply_number in re.finditer(REGEX_REPLY, text):
532 533 post_id = reply_number.group(1)
533 534 post = Post.objects.filter(id=post_id)
534 535 if not post.exists():
535 536 text = string.replace(text, '>>' + post_id, post_id)
536 537
537 538 return text
538 539
539 540
540 541 def _datetime_to_epoch(datetime):
541 542 return int(time.mktime(timezone.localtime(
542 543 datetime,timezone.get_current_timezone()).timetuple())
543 544 * 1000000 + datetime.microsecond)
544 545
545 546
546 547 def _get_template_thread(thread_to_show):
547 548 """Get template values for thread"""
548 549
549 550 last_replies = thread_to_show.get_last_replies()
550 551 skipped_replies_count = thread_to_show.get_replies().count() \
551 552 - len(last_replies) - 1
552 553 return {
553 554 'thread': thread_to_show,
554 555 'op': thread_to_show.get_replies()[0],
555 556 'bumpable': thread_to_show.can_bump(),
556 557 'last_replies': last_replies,
557 558 'skipped_replies': skipped_replies_count,
558 559 }
General Comments 0
You need to be logged in to leave comments. Login now