Show More
@@ -0,0 +1,92 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | from south.utils import datetime_utils as datetime | |||
|
3 | from south.db import db | |||
|
4 | from south.v2 import SchemaMigration | |||
|
5 | from django.db import models | |||
|
6 | ||||
|
7 | ||||
|
8 | class Migration(SchemaMigration): | |||
|
9 | ||||
|
10 | def forwards(self, orm): | |||
|
11 | # Adding field 'Post.image_pre_width' | |||
|
12 | db.add_column(u'boards_post', 'image_pre_width', | |||
|
13 | self.gf('django.db.models.fields.IntegerField')(default=0), | |||
|
14 | keep_default=False) | |||
|
15 | ||||
|
16 | # Adding field 'Post.image_pre_height' | |||
|
17 | db.add_column(u'boards_post', 'image_pre_height', | |||
|
18 | self.gf('django.db.models.fields.IntegerField')(default=0), | |||
|
19 | keep_default=False) | |||
|
20 | ||||
|
21 | ||||
|
22 | def backwards(self, orm): | |||
|
23 | # Deleting field 'Post.image_pre_width' | |||
|
24 | db.delete_column(u'boards_post', 'image_pre_width') | |||
|
25 | ||||
|
26 | # Deleting field 'Post.image_pre_height' | |||
|
27 | db.delete_column(u'boards_post', 'image_pre_height') | |||
|
28 | ||||
|
29 | ||||
|
30 | models = { | |||
|
31 | 'boards.ban': { | |||
|
32 | 'Meta': {'object_name': 'Ban'}, | |||
|
33 | 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), | |||
|
34 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | |||
|
35 | 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | |||
|
36 | 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'}) | |||
|
37 | }, | |||
|
38 | 'boards.post': { | |||
|
39 | 'Meta': {'object_name': 'Post'}, | |||
|
40 | '_text_rendered': ('django.db.models.fields.TextField', [], {}), | |||
|
41 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | |||
|
42 | 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}), | |||
|
43 | 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | |||
|
44 | 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | |||
|
45 | 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | |||
|
46 | 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), | |||
|
47 | 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), | |||
|
48 | 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), | |||
|
49 | 'poster_user_agent': ('django.db.models.fields.TextField', [], {}), | |||
|
50 | 'pub_time': ('django.db.models.fields.DateTimeField', [], {}), | |||
|
51 | 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}), | |||
|
52 | 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}), | |||
|
53 | 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}), | |||
|
54 | 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}), | |||
|
55 | 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}), | |||
|
56 | 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), | |||
|
57 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'}) | |||
|
58 | }, | |||
|
59 | 'boards.setting': { | |||
|
60 | 'Meta': {'object_name': 'Setting'}, | |||
|
61 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | |||
|
62 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), | |||
|
63 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}), | |||
|
64 | 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | |||
|
65 | }, | |||
|
66 | 'boards.tag': { | |||
|
67 | 'Meta': {'object_name': 'Tag'}, | |||
|
68 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | |||
|
69 | 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}), | |||
|
70 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), | |||
|
71 | 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"}) | |||
|
72 | }, | |||
|
73 | 'boards.thread': { | |||
|
74 | 'Meta': {'object_name': 'Thread'}, | |||
|
75 | 'bump_time': ('django.db.models.fields.DateTimeField', [], {}), | |||
|
76 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | |||
|
77 | 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), | |||
|
78 | 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}), | |||
|
79 | 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'}) | |||
|
80 | }, | |||
|
81 | 'boards.user': { | |||
|
82 | 'Meta': {'object_name': 'User'}, | |||
|
83 | 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}), | |||
|
84 | 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}), | |||
|
85 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), | |||
|
86 | 'rank': ('django.db.models.fields.IntegerField', [], {}), | |||
|
87 | 'registration_time': ('django.db.models.fields.DateTimeField', [], {}), | |||
|
88 | 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'}) | |||
|
89 | } | |||
|
90 | } | |||
|
91 | ||||
|
92 | complete_apps = ['boards'] No newline at end of file |
@@ -1,374 +1,379 b'' | |||||
1 | from datetime import datetime, timedelta |
|
1 | from datetime import datetime, timedelta | |
2 | from datetime import time as dtime |
|
2 | from datetime import time as dtime | |
3 | import os |
|
3 | import os | |
4 | from random import random |
|
4 | from random import random | |
5 | import time |
|
5 | import time | |
6 | import math |
|
6 | import math | |
7 | import re |
|
7 | import re | |
8 | from django.core.cache import cache |
|
8 | from django.core.cache import cache | |
9 |
|
9 | |||
10 | from django.db import models |
|
10 | from django.db import models | |
11 | from django.http import Http404 |
|
11 | from django.http import Http404 | |
12 | from django.utils import timezone |
|
12 | from django.utils import timezone | |
13 | from markupfield.fields import MarkupField |
|
13 | from markupfield.fields import MarkupField | |
14 |
|
14 | |||
15 | from neboard import settings |
|
15 | from neboard import settings | |
16 | from boards import thumbs |
|
16 | from boards import thumbs | |
17 |
|
17 | |||
18 | APP_LABEL_BOARDS = 'boards' |
|
18 | APP_LABEL_BOARDS = 'boards' | |
19 |
|
19 | |||
20 | CACHE_KEY_PPD = 'ppd' |
|
20 | CACHE_KEY_PPD = 'ppd' | |
21 |
|
21 | |||
22 | POSTS_PER_DAY_RANGE = range(7) |
|
22 | POSTS_PER_DAY_RANGE = range(7) | |
23 |
|
23 | |||
24 | BAN_REASON_AUTO = 'Auto' |
|
24 | BAN_REASON_AUTO = 'Auto' | |
25 |
|
25 | |||
26 | IMAGE_THUMB_SIZE = (200, 150) |
|
26 | IMAGE_THUMB_SIZE = (200, 150) | |
27 |
|
27 | |||
28 | TITLE_MAX_LENGTH = 50 |
|
28 | TITLE_MAX_LENGTH = 50 | |
29 |
|
29 | |||
30 | DEFAULT_MARKUP_TYPE = 'markdown' |
|
30 | DEFAULT_MARKUP_TYPE = 'markdown' | |
31 |
|
31 | |||
32 | NO_PARENT = -1 |
|
32 | NO_PARENT = -1 | |
33 | NO_IP = '0.0.0.0' |
|
33 | NO_IP = '0.0.0.0' | |
34 | UNKNOWN_UA = '' |
|
34 | UNKNOWN_UA = '' | |
35 | ALL_PAGES = -1 |
|
35 | ALL_PAGES = -1 | |
36 | IMAGES_DIRECTORY = 'images/' |
|
36 | IMAGES_DIRECTORY = 'images/' | |
37 | FILE_EXTENSION_DELIMITER = '.' |
|
37 | FILE_EXTENSION_DELIMITER = '.' | |
38 |
|
38 | |||
39 | SETTING_MODERATE = "moderate" |
|
39 | SETTING_MODERATE = "moderate" | |
40 |
|
40 | |||
41 | REGEX_REPLY = re.compile('>>(\d+)') |
|
41 | REGEX_REPLY = re.compile('>>(\d+)') | |
42 |
|
42 | |||
43 |
|
43 | |||
44 | class PostManager(models.Manager): |
|
44 | class PostManager(models.Manager): | |
45 |
|
45 | |||
46 | def create_post(self, title, text, image=None, thread=None, |
|
46 | def create_post(self, title, text, image=None, thread=None, | |
47 | ip=NO_IP, tags=None, user=None): |
|
47 | ip=NO_IP, tags=None, user=None): | |
48 | """ |
|
48 | """ | |
49 | Create new post |
|
49 | Create new post | |
50 | """ |
|
50 | """ | |
51 |
|
51 | |||
52 | posting_time = timezone.now() |
|
52 | posting_time = timezone.now() | |
53 | if not thread: |
|
53 | if not thread: | |
54 | thread = Thread.objects.create(bump_time=posting_time, |
|
54 | thread = Thread.objects.create(bump_time=posting_time, | |
55 | last_edit_time=posting_time) |
|
55 | last_edit_time=posting_time) | |
56 | else: |
|
56 | else: | |
57 | thread.bump() |
|
57 | thread.bump() | |
58 | thread.last_edit_time = posting_time |
|
58 | thread.last_edit_time = posting_time | |
59 | thread.save() |
|
59 | thread.save() | |
60 |
|
60 | |||
61 | post = self.create(title=title, |
|
61 | post = self.create(title=title, | |
62 | text=text, |
|
62 | text=text, | |
63 | pub_time=posting_time, |
|
63 | pub_time=posting_time, | |
64 | thread_new=thread, |
|
64 | thread_new=thread, | |
65 | image=image, |
|
65 | image=image, | |
66 | poster_ip=ip, |
|
66 | poster_ip=ip, | |
67 | poster_user_agent=UNKNOWN_UA, # TODO Get UA at last! |
|
67 | poster_user_agent=UNKNOWN_UA, # TODO Get UA at last! | |
68 | last_edit_time=posting_time, |
|
68 | last_edit_time=posting_time, | |
69 | user=user) |
|
69 | user=user) | |
70 |
|
70 | |||
71 | thread.replies.add(post) |
|
71 | thread.replies.add(post) | |
72 | if tags: |
|
72 | if tags: | |
73 | linked_tags = [] |
|
73 | linked_tags = [] | |
74 | for tag in tags: |
|
74 | for tag in tags: | |
75 | tag_linked_tags = tag.get_linked_tags() |
|
75 | tag_linked_tags = tag.get_linked_tags() | |
76 | if len(tag_linked_tags) > 0: |
|
76 | if len(tag_linked_tags) > 0: | |
77 | linked_tags.extend(tag_linked_tags) |
|
77 | linked_tags.extend(tag_linked_tags) | |
78 |
|
78 | |||
79 | tags.extend(linked_tags) |
|
79 | tags.extend(linked_tags) | |
80 | map(thread.add_tag, tags) |
|
80 | map(thread.add_tag, tags) | |
81 |
|
81 | |||
82 | self._delete_old_threads() |
|
82 | self._delete_old_threads() | |
83 | self.connect_replies(post) |
|
83 | self.connect_replies(post) | |
84 |
|
84 | |||
85 | return post |
|
85 | return post | |
86 |
|
86 | |||
87 | def delete_post(self, post): |
|
87 | def delete_post(self, post): | |
88 | """ |
|
88 | """ | |
89 | Delete post and update its thread |
|
89 | Delete post and update its thread | |
90 | """ |
|
90 | """ | |
91 |
|
91 | |||
92 | thread = post.thread_new |
|
92 | thread = post.thread_new | |
93 | thread.last_edit_time = timezone.now() |
|
93 | thread.last_edit_time = timezone.now() | |
94 | thread.save() |
|
94 | thread.save() | |
95 |
|
95 | |||
96 | post.delete() |
|
96 | post.delete() | |
97 |
|
97 | |||
98 | def delete_posts_by_ip(self, ip): |
|
98 | def delete_posts_by_ip(self, ip): | |
99 | """ |
|
99 | """ | |
100 | Delete all posts of the author with same IP |
|
100 | Delete all posts of the author with same IP | |
101 | """ |
|
101 | """ | |
102 |
|
102 | |||
103 | posts = self.filter(poster_ip=ip) |
|
103 | posts = self.filter(poster_ip=ip) | |
104 | map(self.delete_post, posts) |
|
104 | map(self.delete_post, posts) | |
105 |
|
105 | |||
106 | # TODO Move this method to thread manager |
|
106 | # TODO Move this method to thread manager | |
107 | def get_threads(self, tag=None, page=ALL_PAGES, |
|
107 | def get_threads(self, tag=None, page=ALL_PAGES, | |
108 | order_by='-bump_time'): |
|
108 | order_by='-bump_time'): | |
109 | if tag: |
|
109 | if tag: | |
110 | threads = tag.threads |
|
110 | threads = tag.threads | |
111 |
|
111 | |||
112 | if not threads.exists(): |
|
112 | if not threads.exists(): | |
113 | raise Http404 |
|
113 | raise Http404 | |
114 | else: |
|
114 | else: | |
115 | threads = Thread.objects.all() |
|
115 | threads = Thread.objects.all() | |
116 |
|
116 | |||
117 | threads = threads.order_by(order_by) |
|
117 | threads = threads.order_by(order_by) | |
118 |
|
118 | |||
119 | if page != ALL_PAGES: |
|
119 | if page != ALL_PAGES: | |
120 | thread_count = threads.count() |
|
120 | thread_count = threads.count() | |
121 |
|
121 | |||
122 | if page < self._get_page_count(thread_count): |
|
122 | if page < self._get_page_count(thread_count): | |
123 | start_thread = page * settings.THREADS_PER_PAGE |
|
123 | start_thread = page * settings.THREADS_PER_PAGE | |
124 | end_thread = min(start_thread + settings.THREADS_PER_PAGE, |
|
124 | end_thread = min(start_thread + settings.THREADS_PER_PAGE, | |
125 | thread_count) |
|
125 | thread_count) | |
126 | threads = threads[start_thread:end_thread] |
|
126 | threads = threads[start_thread:end_thread] | |
127 |
|
127 | |||
128 | return threads |
|
128 | return threads | |
129 |
|
129 | |||
130 | # TODO Move this method to thread manager |
|
130 | # TODO Move this method to thread manager | |
131 | def get_thread_page_count(self, tag=None): |
|
131 | def get_thread_page_count(self, tag=None): | |
132 | if tag: |
|
132 | if tag: | |
133 | threads = Thread.objects.filter(tags=tag) |
|
133 | threads = Thread.objects.filter(tags=tag) | |
134 | else: |
|
134 | else: | |
135 | threads = Thread.objects.all() |
|
135 | threads = Thread.objects.all() | |
136 |
|
136 | |||
137 | return self._get_page_count(threads.count()) |
|
137 | return self._get_page_count(threads.count()) | |
138 |
|
138 | |||
139 | # TODO Move this method to thread manager |
|
139 | # TODO Move this method to thread manager | |
140 | def _delete_old_threads(self): |
|
140 | def _delete_old_threads(self): | |
141 | """ |
|
141 | """ | |
142 | Preserves maximum thread count. If there are too many threads, |
|
142 | Preserves maximum thread count. If there are too many threads, | |
143 | delete the old ones. |
|
143 | delete the old ones. | |
144 | """ |
|
144 | """ | |
145 |
|
145 | |||
146 | # TODO Move old threads to the archive instead of deleting them. |
|
146 | # TODO Move old threads to the archive instead of deleting them. | |
147 | # Maybe make some 'old' field in the model to indicate the thread |
|
147 | # Maybe make some 'old' field in the model to indicate the thread | |
148 | # must not be shown and be able for replying. |
|
148 | # must not be shown and be able for replying. | |
149 |
|
149 | |||
150 | threads = Thread.objects.all() |
|
150 | threads = Thread.objects.all() | |
151 | thread_count = threads.count() |
|
151 | thread_count = threads.count() | |
152 |
|
152 | |||
153 | if thread_count > settings.MAX_THREAD_COUNT: |
|
153 | if thread_count > settings.MAX_THREAD_COUNT: | |
154 | num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT |
|
154 | num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT | |
155 | old_threads = threads[thread_count - num_threads_to_delete:] |
|
155 | old_threads = threads[thread_count - num_threads_to_delete:] | |
156 |
|
156 | |||
157 | map(Thread.delete_with_posts, old_threads) |
|
157 | map(Thread.delete_with_posts, old_threads) | |
158 |
|
158 | |||
159 | def connect_replies(self, post): |
|
159 | def connect_replies(self, post): | |
160 | """ |
|
160 | """ | |
161 | Connect replies to a post to show them as a reflink map |
|
161 | Connect replies to a post to show them as a reflink map | |
162 | """ |
|
162 | """ | |
163 |
|
163 | |||
164 | for reply_number in re.finditer(REGEX_REPLY, post.text.raw): |
|
164 | for reply_number in re.finditer(REGEX_REPLY, post.text.raw): | |
165 | post_id = reply_number.group(1) |
|
165 | post_id = reply_number.group(1) | |
166 | ref_post = self.filter(id=post_id) |
|
166 | ref_post = self.filter(id=post_id) | |
167 | if ref_post.count() > 0: |
|
167 | if ref_post.count() > 0: | |
168 | referenced_post = ref_post[0] |
|
168 | referenced_post = ref_post[0] | |
169 | referenced_post.referenced_posts.add(post) |
|
169 | referenced_post.referenced_posts.add(post) | |
170 | referenced_post.last_edit_time = post.pub_time |
|
170 | referenced_post.last_edit_time = post.pub_time | |
171 | referenced_post.save() |
|
171 | referenced_post.save() | |
172 |
|
172 | |||
173 | def _get_page_count(self, thread_count): |
|
173 | def _get_page_count(self, thread_count): | |
174 | """ |
|
174 | """ | |
175 | Get number of pages that will be needed for all threads |
|
175 | Get number of pages that will be needed for all threads | |
176 | """ |
|
176 | """ | |
177 |
|
177 | |||
178 | return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE))) |
|
178 | return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE))) | |
179 |
|
179 | |||
180 | def get_posts_per_day(self): |
|
180 | def get_posts_per_day(self): | |
181 | """ |
|
181 | """ | |
182 | Get average count of posts per day for the last 7 days |
|
182 | Get average count of posts per day for the last 7 days | |
183 | """ |
|
183 | """ | |
184 |
|
184 | |||
185 | today = datetime.now().date() |
|
185 | today = datetime.now().date() | |
186 | ppd = cache.get(CACHE_KEY_PPD + str(today)) |
|
186 | ppd = cache.get(CACHE_KEY_PPD + str(today)) | |
187 | if ppd: |
|
187 | if ppd: | |
188 | return ppd |
|
188 | return ppd | |
189 |
|
189 | |||
190 | posts_per_days = [] |
|
190 | posts_per_days = [] | |
191 | for i in POSTS_PER_DAY_RANGE: |
|
191 | for i in POSTS_PER_DAY_RANGE: | |
192 | day_end = today - timedelta(i + 1) |
|
192 | day_end = today - timedelta(i + 1) | |
193 | day_start = today - timedelta(i + 2) |
|
193 | day_start = today - timedelta(i + 2) | |
194 |
|
194 | |||
195 | day_time_start = timezone.make_aware(datetime.combine(day_start, |
|
195 | day_time_start = timezone.make_aware(datetime.combine(day_start, | |
196 | dtime()), timezone.get_current_timezone()) |
|
196 | dtime()), timezone.get_current_timezone()) | |
197 | day_time_end = timezone.make_aware(datetime.combine(day_end, |
|
197 | day_time_end = timezone.make_aware(datetime.combine(day_end, | |
198 | dtime()), timezone.get_current_timezone()) |
|
198 | dtime()), timezone.get_current_timezone()) | |
199 |
|
199 | |||
200 | posts_per_days.append(float(self.filter( |
|
200 | posts_per_days.append(float(self.filter( | |
201 | pub_time__lte=day_time_end, |
|
201 | pub_time__lte=day_time_end, | |
202 | pub_time__gte=day_time_start).count())) |
|
202 | pub_time__gte=day_time_start).count())) | |
203 |
|
203 | |||
204 | ppd = (sum(posts_per_day for posts_per_day in posts_per_days) / |
|
204 | ppd = (sum(posts_per_day for posts_per_day in posts_per_days) / | |
205 | len(posts_per_days)) |
|
205 | len(posts_per_days)) | |
206 | cache.set(CACHE_KEY_PPD, ppd) |
|
206 | cache.set(CACHE_KEY_PPD, ppd) | |
207 | return ppd |
|
207 | return ppd | |
208 |
|
208 | |||
209 |
|
209 | |||
210 | class Post(models.Model): |
|
210 | class Post(models.Model): | |
211 | """A post is a message.""" |
|
211 | """A post is a message.""" | |
212 |
|
212 | |||
213 | objects = PostManager() |
|
213 | objects = PostManager() | |
214 |
|
214 | |||
215 | class Meta: |
|
215 | class Meta: | |
216 | app_label = APP_LABEL_BOARDS |
|
216 | app_label = APP_LABEL_BOARDS | |
217 |
|
217 | |||
218 | # TODO Save original file name to some field |
|
218 | # TODO Save original file name to some field | |
219 | def _update_image_filename(self, filename): |
|
219 | def _update_image_filename(self, filename): | |
220 | """Get unique image filename""" |
|
220 | """Get unique image filename""" | |
221 |
|
221 | |||
222 | path = IMAGES_DIRECTORY |
|
222 | path = IMAGES_DIRECTORY | |
223 | new_name = str(int(time.mktime(time.gmtime()))) |
|
223 | new_name = str(int(time.mktime(time.gmtime()))) | |
224 | new_name += str(int(random() * 1000)) |
|
224 | new_name += str(int(random() * 1000)) | |
225 | new_name += FILE_EXTENSION_DELIMITER |
|
225 | new_name += FILE_EXTENSION_DELIMITER | |
226 | new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0] |
|
226 | new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0] | |
227 |
|
227 | |||
228 | return os.path.join(path, new_name) |
|
228 | return os.path.join(path, new_name) | |
229 |
|
229 | |||
230 | title = models.CharField(max_length=TITLE_MAX_LENGTH) |
|
230 | title = models.CharField(max_length=TITLE_MAX_LENGTH) | |
231 | pub_time = models.DateTimeField() |
|
231 | pub_time = models.DateTimeField() | |
232 | text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, |
|
232 | text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, | |
233 | escape_html=False) |
|
233 | escape_html=False) | |
234 |
|
234 | |||
235 | image_width = models.IntegerField(default=0) |
|
235 | image_width = models.IntegerField(default=0) | |
236 | image_height = models.IntegerField(default=0) |
|
236 | image_height = models.IntegerField(default=0) | |
237 |
|
237 | |||
|
238 | image_pre_width = models.IntegerField(default=0) | |||
|
239 | image_pre_height = models.IntegerField(default=0) | |||
|
240 | ||||
238 | image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename, |
|
241 | image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename, | |
239 | blank=True, sizes=(IMAGE_THUMB_SIZE,), |
|
242 | blank=True, sizes=(IMAGE_THUMB_SIZE,), | |
240 | width_field='image_width', |
|
243 | width_field='image_width', | |
241 |
height_field='image_height' |
|
244 | height_field='image_height', | |
|
245 | preview_width_field='image_pre_width', | |||
|
246 | preview_height_field='image_pre_height') | |||
242 |
|
247 | |||
243 | poster_ip = models.GenericIPAddressField() |
|
248 | poster_ip = models.GenericIPAddressField() | |
244 | poster_user_agent = models.TextField() |
|
249 | poster_user_agent = models.TextField() | |
245 |
|
250 | |||
246 | thread = models.ForeignKey('Post', null=True, default=None) |
|
251 | thread = models.ForeignKey('Post', null=True, default=None) | |
247 | thread_new = models.ForeignKey('Thread', null=True, default=None) |
|
252 | thread_new = models.ForeignKey('Thread', null=True, default=None) | |
248 | last_edit_time = models.DateTimeField() |
|
253 | last_edit_time = models.DateTimeField() | |
249 | user = models.ForeignKey('User', null=True, default=None) |
|
254 | user = models.ForeignKey('User', null=True, default=None) | |
250 |
|
255 | |||
251 | referenced_posts = models.ManyToManyField('Post', symmetrical=False, |
|
256 | referenced_posts = models.ManyToManyField('Post', symmetrical=False, | |
252 | null=True, |
|
257 | null=True, | |
253 | blank=True, related_name='rfp+') |
|
258 | blank=True, related_name='rfp+') | |
254 |
|
259 | |||
255 | def __unicode__(self): |
|
260 | def __unicode__(self): | |
256 | return '#' + str(self.id) + ' ' + self.title + ' (' + \ |
|
261 | return '#' + str(self.id) + ' ' + self.title + ' (' + \ | |
257 | self.text.raw[:50] + ')' |
|
262 | self.text.raw[:50] + ')' | |
258 |
|
263 | |||
259 | def get_title(self): |
|
264 | def get_title(self): | |
260 | title = self.title |
|
265 | title = self.title | |
261 | if len(title) == 0: |
|
266 | if len(title) == 0: | |
262 | title = self.text.raw[:20] |
|
267 | title = self.text.raw[:20] | |
263 |
|
268 | |||
264 | return title |
|
269 | return title | |
265 |
|
270 | |||
266 | def get_sorted_referenced_posts(self): |
|
271 | def get_sorted_referenced_posts(self): | |
267 | return self.referenced_posts.order_by('id') |
|
272 | return self.referenced_posts.order_by('id') | |
268 |
|
273 | |||
269 | def is_referenced(self): |
|
274 | def is_referenced(self): | |
270 | return self.referenced_posts.all().exists() |
|
275 | return self.referenced_posts.all().exists() | |
271 |
|
276 | |||
272 | def is_opening(self): |
|
277 | def is_opening(self): | |
273 | return self.thread_new.get_replies()[0] == self |
|
278 | return self.thread_new.get_replies()[0] == self | |
274 |
|
279 | |||
275 |
|
280 | |||
276 | class Thread(models.Model): |
|
281 | class Thread(models.Model): | |
277 |
|
282 | |||
278 | class Meta: |
|
283 | class Meta: | |
279 | app_label = APP_LABEL_BOARDS |
|
284 | app_label = APP_LABEL_BOARDS | |
280 |
|
285 | |||
281 | tags = models.ManyToManyField('Tag') |
|
286 | tags = models.ManyToManyField('Tag') | |
282 | bump_time = models.DateTimeField() |
|
287 | bump_time = models.DateTimeField() | |
283 | last_edit_time = models.DateTimeField() |
|
288 | last_edit_time = models.DateTimeField() | |
284 | replies = models.ManyToManyField('Post', symmetrical=False, null=True, |
|
289 | replies = models.ManyToManyField('Post', symmetrical=False, null=True, | |
285 | blank=True, related_name='tre+') |
|
290 | blank=True, related_name='tre+') | |
286 |
|
291 | |||
287 | def get_tags(self): |
|
292 | def get_tags(self): | |
288 | """ |
|
293 | """ | |
289 | Get a sorted tag list |
|
294 | Get a sorted tag list | |
290 | """ |
|
295 | """ | |
291 |
|
296 | |||
292 | return self.tags.order_by('name') |
|
297 | return self.tags.order_by('name') | |
293 |
|
298 | |||
294 | def bump(self): |
|
299 | def bump(self): | |
295 | """ |
|
300 | """ | |
296 | Bump (move to up) thread |
|
301 | Bump (move to up) thread | |
297 | """ |
|
302 | """ | |
298 |
|
303 | |||
299 | if self.can_bump(): |
|
304 | if self.can_bump(): | |
300 | self.bump_time = timezone.now() |
|
305 | self.bump_time = timezone.now() | |
301 |
|
306 | |||
302 | def get_reply_count(self): |
|
307 | def get_reply_count(self): | |
303 | return self.replies.count() |
|
308 | return self.replies.count() | |
304 |
|
309 | |||
305 | def get_images_count(self): |
|
310 | def get_images_count(self): | |
306 | return self.replies.filter(image_width__gt=0).count() |
|
311 | return self.replies.filter(image_width__gt=0).count() | |
307 |
|
312 | |||
308 | def can_bump(self): |
|
313 | def can_bump(self): | |
309 | """ |
|
314 | """ | |
310 | Check if the thread can be bumped by replying |
|
315 | Check if the thread can be bumped by replying | |
311 | """ |
|
316 | """ | |
312 |
|
317 | |||
313 | post_count = self.get_reply_count() |
|
318 | post_count = self.get_reply_count() | |
314 |
|
319 | |||
315 | return post_count < settings.MAX_POSTS_PER_THREAD |
|
320 | return post_count < settings.MAX_POSTS_PER_THREAD | |
316 |
|
321 | |||
317 | def delete_with_posts(self): |
|
322 | def delete_with_posts(self): | |
318 | """ |
|
323 | """ | |
319 | Completely delete thread and all its posts |
|
324 | Completely delete thread and all its posts | |
320 | """ |
|
325 | """ | |
321 |
|
326 | |||
322 | if self.replies.count() > 0: |
|
327 | if self.replies.count() > 0: | |
323 | map(Post.objects.delete_post, self.replies.all()) |
|
328 | map(Post.objects.delete_post, self.replies.all()) | |
324 |
|
329 | |||
325 | self.delete() |
|
330 | self.delete() | |
326 |
|
331 | |||
327 | def get_last_replies(self): |
|
332 | def get_last_replies(self): | |
328 | """ |
|
333 | """ | |
329 | Get last replies, not including opening post |
|
334 | Get last replies, not including opening post | |
330 | """ |
|
335 | """ | |
331 |
|
336 | |||
332 | if settings.LAST_REPLIES_COUNT > 0: |
|
337 | if settings.LAST_REPLIES_COUNT > 0: | |
333 | reply_count = self.get_reply_count() |
|
338 | reply_count = self.get_reply_count() | |
334 |
|
339 | |||
335 | if reply_count > 0: |
|
340 | if reply_count > 0: | |
336 | reply_count_to_show = min(settings.LAST_REPLIES_COUNT, |
|
341 | reply_count_to_show = min(settings.LAST_REPLIES_COUNT, | |
337 | reply_count - 1) |
|
342 | reply_count - 1) | |
338 | last_replies = self.replies.all().order_by('pub_time')[ |
|
343 | last_replies = self.replies.all().order_by('pub_time')[ | |
339 | reply_count - reply_count_to_show:] |
|
344 | reply_count - reply_count_to_show:] | |
340 |
|
345 | |||
341 | return last_replies |
|
346 | return last_replies | |
342 |
|
347 | |||
343 | def get_replies(self): |
|
348 | def get_replies(self): | |
344 | """ |
|
349 | """ | |
345 | Get sorted thread posts |
|
350 | Get sorted thread posts | |
346 | """ |
|
351 | """ | |
347 |
|
352 | |||
348 | return self.replies.all().order_by('pub_time') |
|
353 | return self.replies.all().order_by('pub_time') | |
349 |
|
354 | |||
350 | def add_tag(self, tag): |
|
355 | def add_tag(self, tag): | |
351 | """ |
|
356 | """ | |
352 | Connect thread to a tag and tag to a thread |
|
357 | Connect thread to a tag and tag to a thread | |
353 | """ |
|
358 | """ | |
354 |
|
359 | |||
355 | self.tags.add(tag) |
|
360 | self.tags.add(tag) | |
356 | tag.threads.add(self) |
|
361 | tag.threads.add(self) | |
357 |
|
362 | |||
358 | def get_opening_post(self): |
|
363 | def get_opening_post(self): | |
359 | """ |
|
364 | """ | |
360 | Get first post of the thread |
|
365 | Get first post of the thread | |
361 | """ |
|
366 | """ | |
362 |
|
367 | |||
363 | return self.get_replies()[0] |
|
368 | return self.get_replies()[0] | |
364 |
|
369 | |||
365 | def __unicode__(self): |
|
370 | def __unicode__(self): | |
366 | return str(self.get_replies()[0].id) |
|
371 | return str(self.get_replies()[0].id) | |
367 |
|
372 | |||
368 | def get_pub_time(self): |
|
373 | def get_pub_time(self): | |
369 | """ |
|
374 | """ | |
370 | Thread does not have its own pub time, so we need to get it from |
|
375 | Thread does not have its own pub time, so we need to get it from | |
371 | the opening post |
|
376 | the opening post | |
372 | """ |
|
377 | """ | |
373 |
|
378 | |||
374 | return self.get_opening_post().pub_time |
|
379 | return self.get_opening_post().pub_time |
@@ -1,358 +1,356 b'' | |||||
1 | html { |
|
1 | html { | |
2 | background: #555; |
|
2 | background: #555; | |
3 | color: #ffffff; |
|
3 | color: #ffffff; | |
4 | } |
|
4 | } | |
5 |
|
5 | |||
6 | #admin_panel { |
|
6 | #admin_panel { | |
7 | background: #FF0000; |
|
7 | background: #FF0000; | |
8 | color: #00FF00 |
|
8 | color: #00FF00 | |
9 | } |
|
9 | } | |
10 |
|
10 | |||
11 | .input_field { |
|
11 | .input_field { | |
12 |
|
12 | |||
13 | } |
|
13 | } | |
14 |
|
14 | |||
15 | .input_field_name { |
|
15 | .input_field_name { | |
16 |
|
16 | |||
17 | } |
|
17 | } | |
18 |
|
18 | |||
19 | .input_field_error { |
|
19 | .input_field_error { | |
20 | color: #FF0000; |
|
20 | color: #FF0000; | |
21 | } |
|
21 | } | |
22 |
|
22 | |||
23 | .title { |
|
23 | .title { | |
24 | font-weight: bold; |
|
24 | font-weight: bold; | |
25 | color: #ffcc00; |
|
25 | color: #ffcc00; | |
26 | font-size: 2ex; |
|
26 | font-size: 2ex; | |
27 | } |
|
27 | } | |
28 |
|
28 | |||
29 | .link, a { |
|
29 | .link, a { | |
30 | color: #afdcec; |
|
30 | color: #afdcec; | |
31 | } |
|
31 | } | |
32 |
|
32 | |||
33 | .block { |
|
33 | .block { | |
34 | display: inline-block; |
|
34 | display: inline-block; | |
35 | vertical-align: top; |
|
35 | vertical-align: top; | |
36 | } |
|
36 | } | |
37 |
|
37 | |||
38 | .tag { |
|
38 | .tag { | |
39 | color: #b4cfec; |
|
39 | color: #b4cfec; | |
40 | } |
|
40 | } | |
41 |
|
41 | |||
42 | .post_id { |
|
42 | .post_id { | |
43 | color: #fff380; |
|
43 | color: #fff380; | |
44 | } |
|
44 | } | |
45 |
|
45 | |||
46 | .post, .dead_post, #posts-table { |
|
46 | .post, .dead_post, #posts-table { | |
47 | background: #333; |
|
47 | background: #333; | |
48 | margin: 5px; |
|
48 | margin: 5px; | |
49 | padding: 10px; |
|
49 | padding: 10px; | |
50 | border: solid 1px #888; |
|
50 | border: solid 1px #888; | |
51 | clear: left; |
|
51 | clear: left; | |
52 | word-wrap: break-word; |
|
52 | word-wrap: break-word; | |
53 | } |
|
53 | } | |
54 |
|
54 | |||
55 | .metadata { |
|
55 | .metadata { | |
56 | padding-top: 5px; |
|
56 | padding-top: 5px; | |
57 | margin-top: 10px; |
|
57 | margin-top: 10px; | |
58 | border-top: solid 1px #666; |
|
58 | border-top: solid 1px #666; | |
59 | font-size: 0.9em; |
|
59 | font-size: 0.9em; | |
60 | color: #ddd; |
|
60 | color: #ddd; | |
61 | } |
|
61 | } | |
62 |
|
62 | |||
63 | .navigation_panel, .tag_info { |
|
63 | .navigation_panel, .tag_info { | |
64 | background: #444; |
|
64 | background: #444; | |
65 | margin: 5px; |
|
65 | margin: 5px; | |
66 | padding: 10px; |
|
66 | padding: 10px; | |
67 | border: solid 1px #888; |
|
67 | border: solid 1px #888; | |
68 | color: #eee; |
|
68 | color: #eee; | |
69 | } |
|
69 | } | |
70 |
|
70 | |||
71 | .navigation_panel .link { |
|
71 | .navigation_panel .link { | |
72 | border-right: 1px solid #fff; |
|
72 | border-right: 1px solid #fff; | |
73 | font-weight: bold; |
|
73 | font-weight: bold; | |
74 | margin-right: 1ex; |
|
74 | margin-right: 1ex; | |
75 | padding-right: 1ex; |
|
75 | padding-right: 1ex; | |
76 | } |
|
76 | } | |
77 | .navigation_panel .link:last-child { |
|
77 | .navigation_panel .link:last-child { | |
78 | border-left: 1px solid #fff; |
|
78 | border-left: 1px solid #fff; | |
79 | border-right: none; |
|
79 | border-right: none; | |
80 | float: right; |
|
80 | float: right; | |
81 | margin-left: 1ex; |
|
81 | margin-left: 1ex; | |
82 | margin-right: 0; |
|
82 | margin-right: 0; | |
83 | padding-left: 1ex; |
|
83 | padding-left: 1ex; | |
84 | padding-right: 0; |
|
84 | padding-right: 0; | |
85 | } |
|
85 | } | |
86 |
|
86 | |||
87 | .navigation_panel::after, .post::after { |
|
87 | .navigation_panel::after, .post::after { | |
88 | clear: both; |
|
88 | clear: both; | |
89 | content: "."; |
|
89 | content: "."; | |
90 | display: block; |
|
90 | display: block; | |
91 | height: 0; |
|
91 | height: 0; | |
92 | line-height: 0; |
|
92 | line-height: 0; | |
93 | visibility: hidden; |
|
93 | visibility: hidden; | |
94 | } |
|
94 | } | |
95 |
|
95 | |||
96 | p { |
|
96 | p { | |
97 | margin-top: .5em; |
|
97 | margin-top: .5em; | |
98 | margin-bottom: .5em; |
|
98 | margin-bottom: .5em; | |
99 | } |
|
99 | } | |
100 |
|
100 | |||
101 | .post-form-w { |
|
101 | .post-form-w { | |
102 | display: table; |
|
102 | display: table; | |
103 | background: #333344; |
|
103 | background: #333344; | |
104 | border: solid 1px #888; |
|
104 | border: solid 1px #888; | |
105 | color: #fff; |
|
105 | color: #fff; | |
106 | padding: 10px; |
|
106 | padding: 10px; | |
107 | margin: 5px; |
|
107 | margin: 5px; | |
108 | } |
|
108 | } | |
109 |
|
109 | |||
110 | .form-row { |
|
110 | .form-row { | |
111 | display: table-row; |
|
111 | display: table-row; | |
112 | } |
|
112 | } | |
113 |
|
113 | |||
114 | .form-label, .form-input, .form-errors { |
|
114 | .form-label, .form-input, .form-errors { | |
115 | display: table-cell; |
|
115 | display: table-cell; | |
116 | } |
|
116 | } | |
117 |
|
117 | |||
118 | .form-label { |
|
118 | .form-label { | |
119 | padding: .25em 1ex .25em 0; |
|
119 | padding: .25em 1ex .25em 0; | |
120 | vertical-align: top; |
|
120 | vertical-align: top; | |
121 | } |
|
121 | } | |
122 |
|
122 | |||
123 | .form-input { |
|
123 | .form-input { | |
124 | padding: .25em 0; |
|
124 | padding: .25em 0; | |
125 | } |
|
125 | } | |
126 |
|
126 | |||
127 | .form-errors { |
|
127 | .form-errors { | |
128 | font-weight: bolder; |
|
128 | font-weight: bolder; | |
129 | vertical-align: middle; |
|
129 | vertical-align: middle; | |
130 | } |
|
130 | } | |
131 |
|
131 | |||
132 | .post-form input, .post-form textarea { |
|
132 | .post-form input, .post-form textarea { | |
133 | background: #333; |
|
133 | background: #333; | |
134 | color: #fff; |
|
134 | color: #fff; | |
135 | border: solid 1px; |
|
135 | border: solid 1px; | |
136 | padding: 0; |
|
136 | padding: 0; | |
137 | width: 100%; |
|
137 | width: 100%; | |
138 | font: medium sans; |
|
138 | font: medium sans; | |
139 | } |
|
139 | } | |
140 |
|
140 | |||
141 | .form-submit { |
|
141 | .form-submit { | |
142 | display: table; |
|
142 | display: table; | |
143 | margin-bottom: 1ex; |
|
143 | margin-bottom: 1ex; | |
144 | } |
|
144 | } | |
145 |
|
145 | |||
146 | .form-title { |
|
146 | .form-title { | |
147 | font-weight: bold; |
|
147 | font-weight: bold; | |
148 | font-size: 2.5ex; |
|
148 | font-size: 2.5ex; | |
149 | text-decoration: underline; |
|
149 | text-decoration: underline; | |
150 | } |
|
150 | } | |
151 |
|
151 | |||
152 | input[type="submit"] { |
|
152 | input[type="submit"] { | |
153 | background: #222; |
|
153 | background: #222; | |
154 | border: solid 2px #fff; |
|
154 | border: solid 2px #fff; | |
155 | color: #fff; |
|
155 | color: #fff; | |
156 | padding: 0.5ex; |
|
156 | padding: 0.5ex; | |
157 | } |
|
157 | } | |
158 |
|
158 | |||
159 | input[type="submit"]:hover { |
|
159 | input[type="submit"]:hover { | |
160 | background: #060; |
|
160 | background: #060; | |
161 | } |
|
161 | } | |
162 |
|
162 | |||
163 | blockquote { |
|
163 | blockquote { | |
164 | border-left: solid 2px; |
|
164 | border-left: solid 2px; | |
165 | padding-left: 5px; |
|
165 | padding-left: 5px; | |
166 | color: #B1FB17; |
|
166 | color: #B1FB17; | |
167 | margin: 0; |
|
167 | margin: 0; | |
168 | } |
|
168 | } | |
169 |
|
169 | |||
170 | .post > .image { |
|
170 | .post > .image { | |
171 | float: left; |
|
171 | float: left; | |
172 | margin: 0 1ex .5ex 0; |
|
172 | margin: 0 1ex .5ex 0; | |
173 | min-width: 1px; |
|
173 | min-width: 1px; | |
174 | text-align: center; |
|
174 | text-align: center; | |
175 | display: table-row; |
|
175 | display: table-row; | |
176 |
|
||||
177 | height: 150px; |
|
|||
178 | } |
|
176 | } | |
179 |
|
177 | |||
180 | .post > .metadata { |
|
178 | .post > .metadata { | |
181 | clear: left; |
|
179 | clear: left; | |
182 | } |
|
180 | } | |
183 |
|
181 | |||
184 | .get { |
|
182 | .get { | |
185 | font-weight: bold; |
|
183 | font-weight: bold; | |
186 | color: #d55; |
|
184 | color: #d55; | |
187 | } |
|
185 | } | |
188 |
|
186 | |||
189 | * { |
|
187 | * { | |
190 | text-decoration: none; |
|
188 | text-decoration: none; | |
191 | } |
|
189 | } | |
192 |
|
190 | |||
193 | .dead_post { |
|
191 | .dead_post { | |
194 | background-color: #442222; |
|
192 | background-color: #442222; | |
195 | } |
|
193 | } | |
196 |
|
194 | |||
197 | .mark_btn { |
|
195 | .mark_btn { | |
198 | border: 1px solid; |
|
196 | border: 1px solid; | |
199 | min-width: 2ex; |
|
197 | min-width: 2ex; | |
200 | padding: 2px 2ex; |
|
198 | padding: 2px 2ex; | |
201 | } |
|
199 | } | |
202 |
|
200 | |||
203 | .mark_btn:hover { |
|
201 | .mark_btn:hover { | |
204 | background: #555; |
|
202 | background: #555; | |
205 | } |
|
203 | } | |
206 |
|
204 | |||
207 | .quote { |
|
205 | .quote { | |
208 | color: #92cf38; |
|
206 | color: #92cf38; | |
209 | font-style: italic; |
|
207 | font-style: italic; | |
210 | } |
|
208 | } | |
211 |
|
209 | |||
212 | .spoiler { |
|
210 | .spoiler { | |
213 | background: white; |
|
211 | background: white; | |
214 | color: white; |
|
212 | color: white; | |
215 | } |
|
213 | } | |
216 |
|
214 | |||
217 | .spoiler:hover { |
|
215 | .spoiler:hover { | |
218 | color: black; |
|
216 | color: black; | |
219 | } |
|
217 | } | |
220 |
|
218 | |||
221 | .comment { |
|
219 | .comment { | |
222 | color: #eb2; |
|
220 | color: #eb2; | |
223 | font-style: italic; |
|
221 | font-style: italic; | |
224 | } |
|
222 | } | |
225 |
|
223 | |||
226 | a:hover { |
|
224 | a:hover { | |
227 | text-decoration: underline; |
|
225 | text-decoration: underline; | |
228 | } |
|
226 | } | |
229 |
|
227 | |||
230 | .last-replies { |
|
228 | .last-replies { | |
231 | margin-left: 3ex; |
|
229 | margin-left: 3ex; | |
232 | } |
|
230 | } | |
233 |
|
231 | |||
234 | .thread { |
|
232 | .thread { | |
235 | margin-bottom: 3ex; |
|
233 | margin-bottom: 3ex; | |
236 | } |
|
234 | } | |
237 |
|
235 | |||
238 | .post:target { |
|
236 | .post:target { | |
239 | border: solid 2px white; |
|
237 | border: solid 2px white; | |
240 | } |
|
238 | } | |
241 |
|
239 | |||
242 | pre{ |
|
240 | pre{ | |
243 | white-space:pre-wrap |
|
241 | white-space:pre-wrap | |
244 | } |
|
242 | } | |
245 |
|
243 | |||
246 | li { |
|
244 | li { | |
247 | list-style-position: inside; |
|
245 | list-style-position: inside; | |
248 | } |
|
246 | } | |
249 |
|
247 | |||
250 | .fancybox-skin { |
|
248 | .fancybox-skin { | |
251 | position: relative; |
|
249 | position: relative; | |
252 | background-color: #fff; |
|
250 | background-color: #fff; | |
253 | color: #ddd; |
|
251 | color: #ddd; | |
254 | text-shadow: none; |
|
252 | text-shadow: none; | |
255 | } |
|
253 | } | |
256 |
|
254 | |||
257 | .fancybox-image { |
|
255 | .fancybox-image { | |
258 | border: 1px solid black; |
|
256 | border: 1px solid black; | |
259 | } |
|
257 | } | |
260 |
|
258 | |||
261 | .image-mode-tab { |
|
259 | .image-mode-tab { | |
262 | background: #444; |
|
260 | background: #444; | |
263 | color: #eee; |
|
261 | color: #eee; | |
264 | display: table; |
|
262 | display: table; | |
265 | margin: 5px; |
|
263 | margin: 5px; | |
266 | padding: 5px; |
|
264 | padding: 5px; | |
267 | border: 1px solid #888; |
|
265 | border: 1px solid #888; | |
268 | } |
|
266 | } | |
269 |
|
267 | |||
270 | .image-mode-tab > label { |
|
268 | .image-mode-tab > label { | |
271 | margin: 0 1ex; |
|
269 | margin: 0 1ex; | |
272 | } |
|
270 | } | |
273 |
|
271 | |||
274 | .image-mode-tab > label > input { |
|
272 | .image-mode-tab > label > input { | |
275 | margin-right: .5ex; |
|
273 | margin-right: .5ex; | |
276 | } |
|
274 | } | |
277 |
|
275 | |||
278 | #posts-table { |
|
276 | #posts-table { | |
279 | margin: 5px; |
|
277 | margin: 5px; | |
280 | } |
|
278 | } | |
281 |
|
279 | |||
282 | .tag_info { |
|
280 | .tag_info { | |
283 | display: table; |
|
281 | display: table; | |
284 | } |
|
282 | } | |
285 |
|
283 | |||
286 | .tag_info > h2 { |
|
284 | .tag_info > h2 { | |
287 | margin: 0; |
|
285 | margin: 0; | |
288 | } |
|
286 | } | |
289 |
|
287 | |||
290 | .post-info { |
|
288 | .post-info { | |
291 | color: #ddd; |
|
289 | color: #ddd; | |
292 | } |
|
290 | } | |
293 |
|
291 | |||
294 | .moderator_info { |
|
292 | .moderator_info { | |
295 | color: #e99d41; |
|
293 | color: #e99d41; | |
296 | border: dashed 1px; |
|
294 | border: dashed 1px; | |
297 | padding: 3px; |
|
295 | padding: 3px; | |
298 | } |
|
296 | } | |
299 |
|
297 | |||
300 | .refmap { |
|
298 | .refmap { | |
301 | font-size: 0.9em; |
|
299 | font-size: 0.9em; | |
302 | color: #ccc; |
|
300 | color: #ccc; | |
303 | margin-top: 1em; |
|
301 | margin-top: 1em; | |
304 | } |
|
302 | } | |
305 |
|
303 | |||
306 | .fav { |
|
304 | .fav { | |
307 | color: yellow; |
|
305 | color: yellow; | |
308 | } |
|
306 | } | |
309 |
|
307 | |||
310 | .not_fav { |
|
308 | .not_fav { | |
311 | color: #ccc; |
|
309 | color: #ccc; | |
312 | } |
|
310 | } | |
313 |
|
311 | |||
314 | .role { |
|
312 | .role { | |
315 | text-decoration: underline; |
|
313 | text-decoration: underline; | |
316 | } |
|
314 | } | |
317 |
|
315 | |||
318 | .form-email { |
|
316 | .form-email { | |
319 | display: none; |
|
317 | display: none; | |
320 | } |
|
318 | } | |
321 |
|
319 | |||
322 | .footer { |
|
320 | .footer { | |
323 | margin: 5px; |
|
321 | margin: 5px; | |
324 | } |
|
322 | } | |
325 |
|
323 | |||
326 | .bar-value { |
|
324 | .bar-value { | |
327 | background: rgba(50, 55, 164, 0.45); |
|
325 | background: rgba(50, 55, 164, 0.45); | |
328 | font-size: 0.9em; |
|
326 | font-size: 0.9em; | |
329 | height: 1.5em; |
|
327 | height: 1.5em; | |
330 | } |
|
328 | } | |
331 |
|
329 | |||
332 | .bar-bg { |
|
330 | .bar-bg { | |
333 | position: relative; |
|
331 | position: relative; | |
334 | border: solid 1px #888; |
|
332 | border: solid 1px #888; | |
335 | margin: 5px; |
|
333 | margin: 5px; | |
336 | overflow: hidden; |
|
334 | overflow: hidden; | |
337 | } |
|
335 | } | |
338 |
|
336 | |||
339 | .bar-text { |
|
337 | .bar-text { | |
340 | padding: 2px; |
|
338 | padding: 2px; | |
341 | position: absolute; |
|
339 | position: absolute; | |
342 | left: 0; |
|
340 | left: 0; | |
343 | top: 0; |
|
341 | top: 0; | |
344 | } |
|
342 | } | |
345 |
|
343 | |||
346 | .page_link { |
|
344 | .page_link { | |
347 | display: table; |
|
345 | display: table; | |
348 | background: #444; |
|
346 | background: #444; | |
349 | margin: 5px; |
|
347 | margin: 5px; | |
350 | border: solid 1px #888; |
|
348 | border: solid 1px #888; | |
351 | padding: 5px; |
|
349 | padding: 5px; | |
352 | font-weight: bolder; |
|
350 | font-weight: bolder; | |
353 | color: #eee; |
|
351 | color: #eee; | |
354 | } |
|
352 | } | |
355 |
|
353 | |||
356 | .skipped_replies { |
|
354 | .skipped_replies { | |
357 | margin: 5px; |
|
355 | margin: 5px; | |
358 | } |
|
356 | } |
@@ -1,344 +1,342 b'' | |||||
1 | html { |
|
1 | html { | |
2 | background: rgb(238, 238, 238); |
|
2 | background: rgb(238, 238, 238); | |
3 | color: rgb(51, 51, 51); |
|
3 | color: rgb(51, 51, 51); | |
4 | } |
|
4 | } | |
5 |
|
5 | |||
6 | #admin_panel { |
|
6 | #admin_panel { | |
7 | background: #FF0000; |
|
7 | background: #FF0000; | |
8 | color: #00FF00 |
|
8 | color: #00FF00 | |
9 | } |
|
9 | } | |
10 |
|
10 | |||
11 | .input_field { |
|
11 | .input_field { | |
12 |
|
12 | |||
13 | } |
|
13 | } | |
14 |
|
14 | |||
15 | .input_field_name { |
|
15 | .input_field_name { | |
16 |
|
16 | |||
17 | } |
|
17 | } | |
18 |
|
18 | |||
19 | .input_field_error { |
|
19 | .input_field_error { | |
20 | color: #FF0000; |
|
20 | color: #FF0000; | |
21 | } |
|
21 | } | |
22 |
|
22 | |||
23 |
|
23 | |||
24 | .title { |
|
24 | .title { | |
25 | font-weight: bold; |
|
25 | font-weight: bold; | |
26 | color: #333; |
|
26 | color: #333; | |
27 | font-size: 2ex; |
|
27 | font-size: 2ex; | |
28 | } |
|
28 | } | |
29 |
|
29 | |||
30 | .link, a { |
|
30 | .link, a { | |
31 | color: rgb(255, 102, 0); |
|
31 | color: rgb(255, 102, 0); | |
32 | } |
|
32 | } | |
33 |
|
33 | |||
34 | .block { |
|
34 | .block { | |
35 | display: inline-block; |
|
35 | display: inline-block; | |
36 | vertical-align: top; |
|
36 | vertical-align: top; | |
37 | } |
|
37 | } | |
38 |
|
38 | |||
39 | .tag { |
|
39 | .tag { | |
40 | color: #222; |
|
40 | color: #222; | |
41 | } |
|
41 | } | |
42 |
|
42 | |||
43 | .post_id:hover { |
|
43 | .post_id:hover { | |
44 | color: #11f; |
|
44 | color: #11f; | |
45 | } |
|
45 | } | |
46 |
|
46 | |||
47 | .post_id { |
|
47 | .post_id { | |
48 | color: #444; |
|
48 | color: #444; | |
49 | } |
|
49 | } | |
50 |
|
50 | |||
51 | .post, .dead_post, #posts-table { |
|
51 | .post, .dead_post, #posts-table { | |
52 | margin: 5px; |
|
52 | margin: 5px; | |
53 | padding: 10px; |
|
53 | padding: 10px; | |
54 | background: rgb(221, 221, 221); |
|
54 | background: rgb(221, 221, 221); | |
55 | border: 1px solid rgb(204, 204, 204); |
|
55 | border: 1px solid rgb(204, 204, 204); | |
56 | border-radius: 5px 5px 5px 5px; |
|
56 | border-radius: 5px 5px 5px 5px; | |
57 | clear: left; |
|
57 | clear: left; | |
58 | word-wrap: break-word; |
|
58 | word-wrap: break-word; | |
59 | display: table; |
|
59 | display: table; | |
60 | } |
|
60 | } | |
61 |
|
61 | |||
62 | .metadata { |
|
62 | .metadata { | |
63 | padding: 5px; |
|
63 | padding: 5px; | |
64 | margin-top: 10px; |
|
64 | margin-top: 10px; | |
65 | border: solid 1px #666; |
|
65 | border: solid 1px #666; | |
66 | font-size: 0.9em; |
|
66 | font-size: 0.9em; | |
67 | display: table; |
|
67 | display: table; | |
68 | } |
|
68 | } | |
69 |
|
69 | |||
70 | .navigation_panel, .tag_info, .page_link { |
|
70 | .navigation_panel, .tag_info, .page_link { | |
71 | margin: 5px; |
|
71 | margin: 5px; | |
72 | padding: 10px; |
|
72 | padding: 10px; | |
73 | border: 1px solid rgb(204, 204, 204); |
|
73 | border: 1px solid rgb(204, 204, 204); | |
74 | border-radius: 5px 5px 5px 5px; |
|
74 | border-radius: 5px 5px 5px 5px; | |
75 | } |
|
75 | } | |
76 |
|
76 | |||
77 | .navigation_panel .link { |
|
77 | .navigation_panel .link { | |
78 | border-right: 1px solid #000; |
|
78 | border-right: 1px solid #000; | |
79 | font-weight: bold; |
|
79 | font-weight: bold; | |
80 | margin-right: 1ex; |
|
80 | margin-right: 1ex; | |
81 | padding-right: 1ex; |
|
81 | padding-right: 1ex; | |
82 | } |
|
82 | } | |
83 | .navigation_panel .link:last-child { |
|
83 | .navigation_panel .link:last-child { | |
84 | border-left: 1px solid #000; |
|
84 | border-left: 1px solid #000; | |
85 | border-right: none; |
|
85 | border-right: none; | |
86 | float: right; |
|
86 | float: right; | |
87 | margin-left: 1ex; |
|
87 | margin-left: 1ex; | |
88 | margin-right: 0; |
|
88 | margin-right: 0; | |
89 | padding-left: 1ex; |
|
89 | padding-left: 1ex; | |
90 | padding-right: 0; |
|
90 | padding-right: 0; | |
91 | } |
|
91 | } | |
92 |
|
92 | |||
93 | .navigation_panel::after, .post::after { |
|
93 | .navigation_panel::after, .post::after { | |
94 | clear: both; |
|
94 | clear: both; | |
95 | content: "."; |
|
95 | content: "."; | |
96 | display: block; |
|
96 | display: block; | |
97 | height: 0; |
|
97 | height: 0; | |
98 | line-height: 0; |
|
98 | line-height: 0; | |
99 | visibility: hidden; |
|
99 | visibility: hidden; | |
100 | } |
|
100 | } | |
101 |
|
101 | |||
102 | p { |
|
102 | p { | |
103 | margin-top: .5em; |
|
103 | margin-top: .5em; | |
104 | margin-bottom: .5em; |
|
104 | margin-bottom: .5em; | |
105 | } |
|
105 | } | |
106 |
|
106 | |||
107 | .post-form-w { |
|
107 | .post-form-w { | |
108 | display: table; |
|
108 | display: table; | |
109 | padding: 10px; |
|
109 | padding: 10px; | |
110 | margin: 5px |
|
110 | margin: 5px | |
111 | } |
|
111 | } | |
112 |
|
112 | |||
113 | .form-row { |
|
113 | .form-row { | |
114 | display: table-row; |
|
114 | display: table-row; | |
115 | } |
|
115 | } | |
116 |
|
116 | |||
117 | .form-label, .form-input, .form-errors { |
|
117 | .form-label, .form-input, .form-errors { | |
118 | display: table-cell; |
|
118 | display: table-cell; | |
119 | } |
|
119 | } | |
120 |
|
120 | |||
121 | .form-label { |
|
121 | .form-label { | |
122 | padding: .25em 1ex .25em 0; |
|
122 | padding: .25em 1ex .25em 0; | |
123 | vertical-align: top; |
|
123 | vertical-align: top; | |
124 | } |
|
124 | } | |
125 |
|
125 | |||
126 | .form-input { |
|
126 | .form-input { | |
127 | padding: .25em 0; |
|
127 | padding: .25em 0; | |
128 | } |
|
128 | } | |
129 |
|
129 | |||
130 | .form-errors { |
|
130 | .form-errors { | |
131 | padding-left: 1ex; |
|
131 | padding-left: 1ex; | |
132 | font-weight: bold; |
|
132 | font-weight: bold; | |
133 | vertical-align: middle; |
|
133 | vertical-align: middle; | |
134 | } |
|
134 | } | |
135 |
|
135 | |||
136 | .post-form input, .post-form textarea { |
|
136 | .post-form input, .post-form textarea { | |
137 | background: #fff; |
|
137 | background: #fff; | |
138 | color: #000; |
|
138 | color: #000; | |
139 | border: solid 1px; |
|
139 | border: solid 1px; | |
140 | padding: 0; |
|
140 | padding: 0; | |
141 | width: 100%; |
|
141 | width: 100%; | |
142 | font: medium sans; |
|
142 | font: medium sans; | |
143 | } |
|
143 | } | |
144 |
|
144 | |||
145 | .form-submit { |
|
145 | .form-submit { | |
146 | border-bottom: 2px solid #ddd; |
|
146 | border-bottom: 2px solid #ddd; | |
147 | margin-bottom: .5em; |
|
147 | margin-bottom: .5em; | |
148 | padding-bottom: .5em; |
|
148 | padding-bottom: .5em; | |
149 | } |
|
149 | } | |
150 |
|
150 | |||
151 | .form-title { |
|
151 | .form-title { | |
152 | font-weight: bold; |
|
152 | font-weight: bold; | |
153 | } |
|
153 | } | |
154 |
|
154 | |||
155 | input[type="submit"] { |
|
155 | input[type="submit"] { | |
156 | background: #fff; |
|
156 | background: #fff; | |
157 | border: solid 1px #000; |
|
157 | border: solid 1px #000; | |
158 | color: #000; |
|
158 | color: #000; | |
159 | } |
|
159 | } | |
160 |
|
160 | |||
161 | blockquote { |
|
161 | blockquote { | |
162 | border-left: solid 2px; |
|
162 | border-left: solid 2px; | |
163 | padding-left: 5px; |
|
163 | padding-left: 5px; | |
164 | color: #B1FB17; |
|
164 | color: #B1FB17; | |
165 | margin: 0; |
|
165 | margin: 0; | |
166 | } |
|
166 | } | |
167 |
|
167 | |||
168 | .post > .image { |
|
168 | .post > .image { | |
169 | float: left; |
|
169 | float: left; | |
170 | margin: 0 1ex .5ex 0; |
|
170 | margin: 0 1ex .5ex 0; | |
171 | min-width: 1px; |
|
171 | min-width: 1px; | |
172 | text-align: center; |
|
172 | text-align: center; | |
173 | display: table-row; |
|
173 | display: table-row; | |
174 |
|
||||
175 | height: 150px; |
|
|||
176 | } |
|
174 | } | |
177 |
|
175 | |||
178 | .post > .metadata { |
|
176 | .post > .metadata { | |
179 | clear: left; |
|
177 | clear: left; | |
180 | } |
|
178 | } | |
181 |
|
179 | |||
182 | .get { |
|
180 | .get { | |
183 | font-weight: bold; |
|
181 | font-weight: bold; | |
184 | color: #d55; |
|
182 | color: #d55; | |
185 | } |
|
183 | } | |
186 |
|
184 | |||
187 | * { |
|
185 | * { | |
188 | text-decoration: none; |
|
186 | text-decoration: none; | |
189 | } |
|
187 | } | |
190 |
|
188 | |||
191 | .dead_post { |
|
189 | .dead_post { | |
192 | background-color: #ecc; |
|
190 | background-color: #ecc; | |
193 | } |
|
191 | } | |
194 |
|
192 | |||
195 | .quote { |
|
193 | .quote { | |
196 | color: #080; |
|
194 | color: #080; | |
197 | font-style: italic; |
|
195 | font-style: italic; | |
198 | } |
|
196 | } | |
199 |
|
197 | |||
200 | .spoiler { |
|
198 | .spoiler { | |
201 | background: white; |
|
199 | background: white; | |
202 | color: white; |
|
200 | color: white; | |
203 | } |
|
201 | } | |
204 |
|
202 | |||
205 | .spoiler:hover { |
|
203 | .spoiler:hover { | |
206 | color: black; |
|
204 | color: black; | |
207 | } |
|
205 | } | |
208 |
|
206 | |||
209 | .comment { |
|
207 | .comment { | |
210 | color: #8B6914; |
|
208 | color: #8B6914; | |
211 | font-style: italic; |
|
209 | font-style: italic; | |
212 | } |
|
210 | } | |
213 |
|
211 | |||
214 | a:hover { |
|
212 | a:hover { | |
215 | text-decoration: underline; |
|
213 | text-decoration: underline; | |
216 | } |
|
214 | } | |
217 |
|
215 | |||
218 | .last-replies { |
|
216 | .last-replies { | |
219 | margin-left: 3ex; |
|
217 | margin-left: 3ex; | |
220 | } |
|
218 | } | |
221 |
|
219 | |||
222 | .thread { |
|
220 | .thread { | |
223 | margin-bottom: 3ex; |
|
221 | margin-bottom: 3ex; | |
224 | } |
|
222 | } | |
225 |
|
223 | |||
226 | .post:target { |
|
224 | .post:target { | |
227 | border: solid 2px black; |
|
225 | border: solid 2px black; | |
228 | } |
|
226 | } | |
229 |
|
227 | |||
230 | pre{ |
|
228 | pre{ | |
231 | white-space:pre-wrap |
|
229 | white-space:pre-wrap | |
232 | } |
|
230 | } | |
233 |
|
231 | |||
234 | li { |
|
232 | li { | |
235 | list-style-position: inside; |
|
233 | list-style-position: inside; | |
236 | } |
|
234 | } | |
237 |
|
235 | |||
238 | .fancybox-skin { |
|
236 | .fancybox-skin { | |
239 | position: relative; |
|
237 | position: relative; | |
240 | background-color: #fff; |
|
238 | background-color: #fff; | |
241 | color: #ddd; |
|
239 | color: #ddd; | |
242 | text-shadow: none; |
|
240 | text-shadow: none; | |
243 | } |
|
241 | } | |
244 |
|
242 | |||
245 | .fancybox-image { |
|
243 | .fancybox-image { | |
246 | border: 1px solid black; |
|
244 | border: 1px solid black; | |
247 | } |
|
245 | } | |
248 |
|
246 | |||
249 | .image-mode-tab { |
|
247 | .image-mode-tab { | |
250 | display: table; |
|
248 | display: table; | |
251 | margin: 5px; |
|
249 | margin: 5px; | |
252 | padding: 5px; |
|
250 | padding: 5px; | |
253 | background: rgb(221, 221, 221); |
|
251 | background: rgb(221, 221, 221); | |
254 | border: 1px solid rgb(204, 204, 204); |
|
252 | border: 1px solid rgb(204, 204, 204); | |
255 | border-radius: 5px 5px 5px 5px; |
|
253 | border-radius: 5px 5px 5px 5px; | |
256 | } |
|
254 | } | |
257 |
|
255 | |||
258 | .image-mode-tab > label { |
|
256 | .image-mode-tab > label { | |
259 | margin: 0 1ex; |
|
257 | margin: 0 1ex; | |
260 | } |
|
258 | } | |
261 |
|
259 | |||
262 | .image-mode-tab > label > input { |
|
260 | .image-mode-tab > label > input { | |
263 | margin-right: .5ex; |
|
261 | margin-right: .5ex; | |
264 | } |
|
262 | } | |
265 |
|
263 | |||
266 | #posts-table { |
|
264 | #posts-table { | |
267 | margin: 5px; |
|
265 | margin: 5px; | |
268 | } |
|
266 | } | |
269 |
|
267 | |||
270 | .tag_info, .page_link { |
|
268 | .tag_info, .page_link { | |
271 | display: table; |
|
269 | display: table; | |
272 | } |
|
270 | } | |
273 |
|
271 | |||
274 | .tag_info > h2 { |
|
272 | .tag_info > h2 { | |
275 | margin: 0; |
|
273 | margin: 0; | |
276 | } |
|
274 | } | |
277 |
|
275 | |||
278 | .moderator_info { |
|
276 | .moderator_info { | |
279 | color: #e99d41; |
|
277 | color: #e99d41; | |
280 | border: dashed 1px; |
|
278 | border: dashed 1px; | |
281 | padding: 3px; |
|
279 | padding: 3px; | |
282 | } |
|
280 | } | |
283 |
|
281 | |||
284 | .refmap { |
|
282 | .refmap { | |
285 | font-size: 0.9em; |
|
283 | font-size: 0.9em; | |
286 | color: #444; |
|
284 | color: #444; | |
287 | margin-top: 1em; |
|
285 | margin-top: 1em; | |
288 | } |
|
286 | } | |
289 |
|
287 | |||
290 | input[type="submit"]:hover { |
|
288 | input[type="submit"]:hover { | |
291 | background: #ccc; |
|
289 | background: #ccc; | |
292 | } |
|
290 | } | |
293 |
|
291 | |||
294 |
|
292 | |||
295 | .fav { |
|
293 | .fav { | |
296 | color: rgb(255, 102, 0); |
|
294 | color: rgb(255, 102, 0); | |
297 | } |
|
295 | } | |
298 |
|
296 | |||
299 | .not_fav { |
|
297 | .not_fav { | |
300 | color: #555; |
|
298 | color: #555; | |
301 | } |
|
299 | } | |
302 |
|
300 | |||
303 | .role { |
|
301 | .role { | |
304 | text-decoration: underline; |
|
302 | text-decoration: underline; | |
305 | } |
|
303 | } | |
306 |
|
304 | |||
307 | .form-email { |
|
305 | .form-email { | |
308 | display: none; |
|
306 | display: none; | |
309 | } |
|
307 | } | |
310 |
|
308 | |||
311 | .mark_btn { |
|
309 | .mark_btn { | |
312 | padding: 2px 2ex; |
|
310 | padding: 2px 2ex; | |
313 | border: 1px solid; |
|
311 | border: 1px solid; | |
314 | } |
|
312 | } | |
315 |
|
313 | |||
316 | .mark_btn:hover { |
|
314 | .mark_btn:hover { | |
317 | background: #ccc; |
|
315 | background: #ccc; | |
318 | } |
|
316 | } | |
319 |
|
317 | |||
320 | .bar-value { |
|
318 | .bar-value { | |
321 | background: rgba(251, 199, 16, 0.61); |
|
319 | background: rgba(251, 199, 16, 0.61); | |
322 | padding: 2px; |
|
320 | padding: 2px; | |
323 | font-size: 0.9em; |
|
321 | font-size: 0.9em; | |
324 | height: 1.5em; |
|
322 | height: 1.5em; | |
325 | } |
|
323 | } | |
326 |
|
324 | |||
327 | .bar-bg { |
|
325 | .bar-bg { | |
328 | position: relative; |
|
326 | position: relative; | |
329 | border: 1px solid rgb(204, 204, 204); |
|
327 | border: 1px solid rgb(204, 204, 204); | |
330 | border-radius: 5px 5px 5px 5px; |
|
328 | border-radius: 5px 5px 5px 5px; | |
331 | margin: 5px; |
|
329 | margin: 5px; | |
332 | overflow: hidden; |
|
330 | overflow: hidden; | |
333 | } |
|
331 | } | |
334 |
|
332 | |||
335 | .bar-text { |
|
333 | .bar-text { | |
336 | padding: 2px; |
|
334 | padding: 2px; | |
337 | position: absolute; |
|
335 | position: absolute; | |
338 | left: 0; |
|
336 | left: 0; | |
339 | top: 0; |
|
337 | top: 0; | |
340 | } |
|
338 | } | |
341 |
|
339 | |||
342 | .skipped_replies { |
|
340 | .skipped_replies { | |
343 | margin: 5px; |
|
341 | margin: 5px; | |
344 | } |
|
342 | } |
@@ -1,244 +1,248 b'' | |||||
1 | {% extends "boards/base.html" %} |
|
1 | {% extends "boards/base.html" %} | |
2 |
|
2 | |||
3 | {% load i18n %} |
|
3 | {% load i18n %} | |
4 | {% load cache %} |
|
4 | {% load cache %} | |
5 | {% load board %} |
|
5 | {% load board %} | |
6 | {% load static %} |
|
6 | {% load static %} | |
7 |
|
7 | |||
8 | {% block head %} |
|
8 | {% block head %} | |
9 | {% if tag %} |
|
9 | {% if tag %} | |
10 | <title>Neboard - {{ tag.name }}</title> |
|
10 | <title>Neboard - {{ tag.name }}</title> | |
11 | {% else %} |
|
11 | {% else %} | |
12 | <title>Neboard</title> |
|
12 | <title>Neboard</title> | |
13 | {% endif %} |
|
13 | {% endif %} | |
14 |
|
14 | |||
15 | {% if prev_page %} |
|
15 | {% if prev_page %} | |
16 | <link rel="next" href=" |
|
16 | <link rel="next" href=" | |
17 | {% if tag %} |
|
17 | {% if tag %} | |
18 | {% url "tag" tag_name=tag page=prev_page %} |
|
18 | {% url "tag" tag_name=tag page=prev_page %} | |
19 | {% else %} |
|
19 | {% else %} | |
20 | {% url "index" page=prev_page %} |
|
20 | {% url "index" page=prev_page %} | |
21 | {% endif %} |
|
21 | {% endif %} | |
22 | " /> |
|
22 | " /> | |
23 | {% endif %} |
|
23 | {% endif %} | |
24 | {% if next_page %} |
|
24 | {% if next_page %} | |
25 | <link rel="next" href=" |
|
25 | <link rel="next" href=" | |
26 | {% if tag %} |
|
26 | {% if tag %} | |
27 | {% url "tag" tag_name=tag page=next_page %} |
|
27 | {% url "tag" tag_name=tag page=next_page %} | |
28 | {% else %} |
|
28 | {% else %} | |
29 | {% url "index" page=next_page %} |
|
29 | {% url "index" page=next_page %} | |
30 | {% endif %} |
|
30 | {% endif %} | |
31 | " /> |
|
31 | " /> | |
32 | {% endif %} |
|
32 | {% endif %} | |
33 |
|
33 | |||
34 | {% endblock %} |
|
34 | {% endblock %} | |
35 |
|
35 | |||
36 | {% block content %} |
|
36 | {% block content %} | |
37 |
|
37 | |||
38 | {% get_current_language as LANGUAGE_CODE %} |
|
38 | {% get_current_language as LANGUAGE_CODE %} | |
39 |
|
39 | |||
40 | {% if tag %} |
|
40 | {% if tag %} | |
41 | <div class="tag_info"> |
|
41 | <div class="tag_info"> | |
42 | <h2> |
|
42 | <h2> | |
43 | {% if tag in user.fav_tags.all %} |
|
43 | {% if tag in user.fav_tags.all %} | |
44 | <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}" |
|
44 | <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}" | |
45 | class="fav">β </a> |
|
45 | class="fav">β </a> | |
46 | {% else %} |
|
46 | {% else %} | |
47 | <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}" |
|
47 | <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}" | |
48 | class="not_fav">β </a> |
|
48 | class="not_fav">β </a> | |
49 | {% endif %} |
|
49 | {% endif %} | |
50 | #{{ tag.name }} |
|
50 | #{{ tag.name }} | |
51 | </h2> |
|
51 | </h2> | |
52 | </div> |
|
52 | </div> | |
53 | {% endif %} |
|
53 | {% endif %} | |
54 |
|
54 | |||
55 | {% if threads %} |
|
55 | {% if threads %} | |
56 | {% if prev_page %} |
|
56 | {% if prev_page %} | |
57 | <div class="page_link"> |
|
57 | <div class="page_link"> | |
58 | <a href=" |
|
58 | <a href=" | |
59 | {% if tag %} |
|
59 | {% if tag %} | |
60 | {% url "tag" tag_name=tag page=prev_page %} |
|
60 | {% url "tag" tag_name=tag page=prev_page %} | |
61 | {% else %} |
|
61 | {% else %} | |
62 | {% url "index" page=prev_page %} |
|
62 | {% url "index" page=prev_page %} | |
63 | {% endif %} |
|
63 | {% endif %} | |
64 | ">{% trans "Previous page" %}</a> |
|
64 | ">{% trans "Previous page" %}</a> | |
65 | </div> |
|
65 | </div> | |
66 | {% endif %} |
|
66 | {% endif %} | |
67 |
|
67 | |||
68 | {% for thread in threads %} |
|
68 | {% for thread in threads %} | |
69 | {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %} |
|
69 | {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %} | |
70 | <div class="thread"> |
|
70 | <div class="thread"> | |
71 | {% if thread.bumpable %} |
|
71 | {% if thread.bumpable %} | |
72 | <div class="post" id="{{ thread.op.id }}"> |
|
72 | <div class="post" id="{{ thread.op.id }}"> | |
73 | {% else %} |
|
73 | {% else %} | |
74 | <div class="post dead_post" id="{{ thread.op.id }}"> |
|
74 | <div class="post dead_post" id="{{ thread.op.id }}"> | |
75 | {% endif %} |
|
75 | {% endif %} | |
76 | {% if thread.op.image %} |
|
76 | {% if thread.op.image %} | |
77 | <div class="image"> |
|
77 | <div class="image"> | |
78 | <a class="thumb" |
|
78 | <a class="thumb" | |
79 | href="{{ thread.op.image.url }}"><img |
|
79 | href="{{ thread.op.image.url }}"><img | |
80 | src="{{ thread.op.image.url_200x150 }}" |
|
80 | src="{{ thread.op.image.url_200x150 }}" | |
81 | alt="{{ thread.op.id }}" |
|
81 | alt="{{ thread.op.id }}" | |
|
82 | width="{{ thread.op.image_pre_width }}" | |||
|
83 | height="{{ thread.op.image_pre_height }}" | |||
82 | data-width="{{ thread.op.image_width }}" |
|
84 | data-width="{{ thread.op.image_width }}" | |
83 | data-height="{{ thread.op.image_height }}"/> |
|
85 | data-height="{{ thread.op.image_height }}"/> | |
84 | </a> |
|
86 | </a> | |
85 | </div> |
|
87 | </div> | |
86 | {% endif %} |
|
88 | {% endif %} | |
87 | <div class="message"> |
|
89 | <div class="message"> | |
88 | <div class="post-info"> |
|
90 | <div class="post-info"> | |
89 | <span class="title">{{ thread.op.title }}</span> |
|
91 | <span class="title">{{ thread.op.title }}</span> | |
90 | <a class="post_id" href="{% url 'thread' thread.op.id %}" |
|
92 | <a class="post_id" href="{% url 'thread' thread.op.id %}" | |
91 | >({{ thread.op.id }})</a> |
|
93 | >({{ thread.op.id }})</a> | |
92 | [{{ thread.op.pub_time }}] |
|
94 | [{{ thread.op.pub_time }}] | |
93 | [<a class="link" href=" |
|
95 | [<a class="link" href=" | |
94 | {% url 'thread' thread.op.id %}#form" |
|
96 | {% url 'thread' thread.op.id %}#form" | |
95 | >{% trans "Reply" %}</a>] |
|
97 | >{% trans "Reply" %}</a>] | |
96 |
|
98 | |||
97 | {% if moderator %} |
|
99 | {% if moderator %} | |
98 | <span class="moderator_info"> |
|
100 | <span class="moderator_info"> | |
99 | [<a href=" |
|
101 | [<a href=" | |
100 | {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}" |
|
102 | {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}" | |
101 | >{% trans 'Delete' %}</a>] |
|
103 | >{% trans 'Delete' %}</a>] | |
102 | ({{ thread.thread.poster_ip }}) |
|
104 | ({{ thread.thread.poster_ip }}) | |
103 | [<a href=" |
|
105 | [<a href=" | |
104 | {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}" |
|
106 | {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}" | |
105 | >{% trans 'Ban IP' %}</a>] |
|
107 | >{% trans 'Ban IP' %}</a>] | |
106 | </span> |
|
108 | </span> | |
107 | {% endif %} |
|
109 | {% endif %} | |
108 | </div> |
|
110 | </div> | |
109 | {% autoescape off %} |
|
111 | {% autoescape off %} | |
110 | {{ thread.op.text.rendered|truncatewords_html:50 }} |
|
112 | {{ thread.op.text.rendered|truncatewords_html:50 }} | |
111 | {% endautoescape %} |
|
113 | {% endautoescape %} | |
112 | {% if thread.op.is_referenced %} |
|
114 | {% if thread.op.is_referenced %} | |
113 | <div class="refmap"> |
|
115 | <div class="refmap"> | |
114 | {% trans "Replies" %}: |
|
116 | {% trans "Replies" %}: | |
115 | {% for ref_post in thread.op.get_sorted_referenced_posts %} |
|
117 | {% for ref_post in thread.op.get_sorted_referenced_posts %} | |
116 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a |
|
118 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a | |
117 | >{% if not forloop.last %},{% endif %} |
|
119 | >{% if not forloop.last %},{% endif %} | |
118 | {% endfor %} |
|
120 | {% endfor %} | |
119 | </div> |
|
121 | </div> | |
120 | {% endif %} |
|
122 | {% endif %} | |
121 | </div> |
|
123 | </div> | |
122 | <div class="metadata"> |
|
124 | <div class="metadata"> | |
123 | {{ thread.thread.get_images_count }} {% trans 'images' %}. |
|
125 | {{ thread.thread.get_images_count }} {% trans 'images' %}. | |
124 | {% if thread.thread.tags %} |
|
126 | {% if thread.thread.tags %} | |
125 | <span class="tags"> |
|
127 | <span class="tags"> | |
126 | {% for tag in thread.thread.get_tags %} |
|
128 | {% for tag in thread.thread.get_tags %} | |
127 | <a class="tag" href=" |
|
129 | <a class="tag" href=" | |
128 | {% url 'tag' tag_name=tag.name %}"> |
|
130 | {% url 'tag' tag_name=tag.name %}"> | |
129 | #{{ tag.name }}</a |
|
131 | #{{ tag.name }}</a | |
130 | >{% if not forloop.last %},{% endif %} |
|
132 | >{% if not forloop.last %},{% endif %} | |
131 | {% endfor %} |
|
133 | {% endfor %} | |
132 | </span> |
|
134 | </span> | |
133 | {% endif %} |
|
135 | {% endif %} | |
134 | </div> |
|
136 | </div> | |
135 | </div> |
|
137 | </div> | |
136 | {% if thread.last_replies.exists %} |
|
138 | {% if thread.last_replies.exists %} | |
137 | {% if thread.skipped_replies %} |
|
139 | {% if thread.skipped_replies %} | |
138 | <div class="skipped_replies"> |
|
140 | <div class="skipped_replies"> | |
139 | <a href="{% url 'thread' thread.op.id %}"> |
|
141 | <a href="{% url 'thread' thread.op.id %}"> | |
140 | {% blocktrans with count=thread.skipped_replies %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %} |
|
142 | {% blocktrans with count=thread.skipped_replies %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %} | |
141 | </a> |
|
143 | </a> | |
142 | </div> |
|
144 | </div> | |
143 | {% endif %} |
|
145 | {% endif %} | |
144 | <div class="last-replies"> |
|
146 | <div class="last-replies"> | |
145 | {% for post in thread.last_replies %} |
|
147 | {% for post in thread.last_replies %} | |
146 | {% if thread.bumpable %} |
|
148 | {% if thread.bumpable %} | |
147 | <div class="post" id="{{ post.id }}"> |
|
149 | <div class="post" id="{{ post.id }}"> | |
148 | {% else %} |
|
150 | {% else %} | |
149 | <div class="post dead_post" id="{{ post.id }}"> |
|
151 | <div class="post dead_post" id="{{ post.id }}"> | |
150 | {% endif %} |
|
152 | {% endif %} | |
151 | {% if post.image %} |
|
153 | {% if post.image %} | |
152 | <div class="image"> |
|
154 | <div class="image"> | |
153 | <a class="thumb" |
|
155 | <a class="thumb" | |
154 | href="{{ post.image.url }}"><img |
|
156 | href="{{ post.image.url }}"><img | |
155 | src=" {{ post.image.url_200x150 }}" |
|
157 | src=" {{ post.image.url_200x150 }}" | |
156 | alt="{{ post.id }}" |
|
158 | alt="{{ post.id }}" | |
|
159 | width="{{ post.image_pre_width }}" | |||
|
160 | height="{{ post.image_pre_height }}" | |||
157 | data-width="{{ post.image_width }}" |
|
161 | data-width="{{ post.image_width }}" | |
158 | data-height="{{ post.image_height }}"/> |
|
162 | data-height="{{ post.image_height }}"/> | |
159 | </a> |
|
163 | </a> | |
160 | </div> |
|
164 | </div> | |
161 | {% endif %} |
|
165 | {% endif %} | |
162 | <div class="message"> |
|
166 | <div class="message"> | |
163 | <div class="post-info"> |
|
167 | <div class="post-info"> | |
164 | <span class="title">{{ post.title }}</span> |
|
168 | <span class="title">{{ post.title }}</span> | |
165 | <a class="post_id" href=" |
|
169 | <a class="post_id" href=" | |
166 | {% url 'thread' thread.op.id %}#{{ post.id }}"> |
|
170 | {% url 'thread' thread.op.id %}#{{ post.id }}"> | |
167 | ({{ post.id }})</a> |
|
171 | ({{ post.id }})</a> | |
168 | [{{ post.pub_time }}] |
|
172 | [{{ post.pub_time }}] | |
169 | </div> |
|
173 | </div> | |
170 | {% autoescape off %} |
|
174 | {% autoescape off %} | |
171 | {{ post.text.rendered|truncatewords_html:50 }} |
|
175 | {{ post.text.rendered|truncatewords_html:50 }} | |
172 | {% endautoescape %} |
|
176 | {% endautoescape %} | |
173 | </div> |
|
177 | </div> | |
174 | {% if post.is_referenced %} |
|
178 | {% if post.is_referenced %} | |
175 | <div class="refmap"> |
|
179 | <div class="refmap"> | |
176 | {% trans "Replies" %}: |
|
180 | {% trans "Replies" %}: | |
177 | {% for ref_post in post.get_sorted_referenced_posts %} |
|
181 | {% for ref_post in post.get_sorted_referenced_posts %} | |
178 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a |
|
182 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a | |
179 | >{% if not forloop.last %},{% endif %} |
|
183 | >{% if not forloop.last %},{% endif %} | |
180 | {% endfor %} |
|
184 | {% endfor %} | |
181 | </div> |
|
185 | </div> | |
182 | {% endif %} |
|
186 | {% endif %} | |
183 | </div> |
|
187 | </div> | |
184 | {% endfor %} |
|
188 | {% endfor %} | |
185 | </div> |
|
189 | </div> | |
186 | {% endif %} |
|
190 | {% endif %} | |
187 | </div> |
|
191 | </div> | |
188 | {% endcache %} |
|
192 | {% endcache %} | |
189 | {% endfor %} |
|
193 | {% endfor %} | |
190 |
|
194 | |||
191 | {% if next_page %} |
|
195 | {% if next_page %} | |
192 | <div class="page_link"> |
|
196 | <div class="page_link"> | |
193 | <a href=" |
|
197 | <a href=" | |
194 | {% if tag %} |
|
198 | {% if tag %} | |
195 | {% url "tag" tag_name=tag page=next_page %} |
|
199 | {% url "tag" tag_name=tag page=next_page %} | |
196 | {% else %} |
|
200 | {% else %} | |
197 | {% url "index" page=next_page %} |
|
201 | {% url "index" page=next_page %} | |
198 | {% endif %} |
|
202 | {% endif %} | |
199 | ">{% trans "Next page" %}</a> |
|
203 | ">{% trans "Next page" %}</a> | |
200 | </div> |
|
204 | </div> | |
201 | {% endif %} |
|
205 | {% endif %} | |
202 | {% else %} |
|
206 | {% else %} | |
203 | <div class="post"> |
|
207 | <div class="post"> | |
204 | {% trans 'No threads exist. Create the first one!' %}</div> |
|
208 | {% trans 'No threads exist. Create the first one!' %}</div> | |
205 | {% endif %} |
|
209 | {% endif %} | |
206 |
|
210 | |||
207 | <div class="post-form-w"> |
|
211 | <div class="post-form-w"> | |
208 | <script src="{% static 'js/panel.js' %}"></script> |
|
212 | <script src="{% static 'js/panel.js' %}"></script> | |
209 | <div class="post-form"> |
|
213 | <div class="post-form"> | |
210 | <div class="form-title">{% trans "Create new thread" %}</div> |
|
214 | <div class="form-title">{% trans "Create new thread" %}</div> | |
211 | <form enctype="multipart/form-data" method="post">{% csrf_token %} |
|
215 | <form enctype="multipart/form-data" method="post">{% csrf_token %} | |
212 | {{ form.as_div }} |
|
216 | {{ form.as_div }} | |
213 | <div class="form-submit"> |
|
217 | <div class="form-submit"> | |
214 | <input type="submit" value="{% trans "Post" %}"/> |
|
218 | <input type="submit" value="{% trans "Post" %}"/> | |
215 | </div> |
|
219 | </div> | |
216 | </form> |
|
220 | </form> | |
217 | <div> |
|
221 | <div> | |
218 | {% trans 'Tags must be delimited by spaces. Text or image is required.' %} |
|
222 | {% trans 'Tags must be delimited by spaces. Text or image is required.' %} | |
219 | </div> |
|
223 | </div> | |
220 | <div><a href="{% url "staticpage" name="help" %}"> |
|
224 | <div><a href="{% url "staticpage" name="help" %}"> | |
221 | {% trans 'Text syntax' %}</a></div> |
|
225 | {% trans 'Text syntax' %}</a></div> | |
222 | </div> |
|
226 | </div> | |
223 | </div> |
|
227 | </div> | |
224 |
|
228 | |||
225 | {% endblock %} |
|
229 | {% endblock %} | |
226 |
|
230 | |||
227 | {% block metapanel %} |
|
231 | {% block metapanel %} | |
228 |
|
232 | |||
229 | <span class="metapanel"> |
|
233 | <span class="metapanel"> | |
230 |
<b><a href="{% url "authors" %}">Neboard</a> 1. |
|
234 | <b><a href="{% url "authors" %}">Neboard</a> 1.5</b> | |
231 | {% trans "Pages:" %} |
|
235 | {% trans "Pages:" %} | |
232 | {% for page in pages %} |
|
236 | {% for page in pages %} | |
233 | [<a href=" |
|
237 | [<a href=" | |
234 | {% if tag %} |
|
238 | {% if tag %} | |
235 | {% url "tag" tag_name=tag page=page %} |
|
239 | {% url "tag" tag_name=tag page=page %} | |
236 | {% else %} |
|
240 | {% else %} | |
237 | {% url "index" page=page %} |
|
241 | {% url "index" page=page %} | |
238 | {% endif %} |
|
242 | {% endif %} | |
239 | ">{{ page }}</a>] |
|
243 | ">{{ page }}</a>] | |
240 | {% endfor %} |
|
244 | {% endfor %} | |
241 | [<a href="rss/">RSS</a>] |
|
245 | [<a href="rss/">RSS</a>] | |
242 | </span> |
|
246 | </span> | |
243 |
|
247 | |||
244 | {% endblock %} |
|
248 | {% endblock %} |
@@ -1,128 +1,130 b'' | |||||
1 | {% extends "boards/base.html" %} |
|
1 | {% extends "boards/base.html" %} | |
2 |
|
2 | |||
3 | {% load i18n %} |
|
3 | {% load i18n %} | |
4 | {% load cache %} |
|
4 | {% load cache %} | |
5 | {% load static from staticfiles %} |
|
5 | {% load static from staticfiles %} | |
6 | {% load board %} |
|
6 | {% load board %} | |
7 |
|
7 | |||
8 | {% block head %} |
|
8 | {% block head %} | |
9 | <title>Neboard - {{ thread.get_replies.0.get_title }}</title> |
|
9 | <title>Neboard - {{ thread.get_replies.0.get_title }}</title> | |
10 | {% endblock %} |
|
10 | {% endblock %} | |
11 |
|
11 | |||
12 | {% block content %} |
|
12 | {% block content %} | |
13 | {% spaceless %} |
|
13 | {% spaceless %} | |
14 | {% get_current_language as LANGUAGE_CODE %} |
|
14 | {% get_current_language as LANGUAGE_CODE %} | |
15 |
|
15 | |||
16 | <script src="{% static 'js/thread_update.js' %}"></script> |
|
16 | <script src="{% static 'js/thread_update.js' %}"></script> | |
17 | <script src="{% static 'js/thread.js' %}"></script> |
|
17 | <script src="{% static 'js/thread.js' %}"></script> | |
18 |
|
18 | |||
19 | {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %} |
|
19 | {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %} | |
20 | {% if bumpable %} |
|
20 | {% if bumpable %} | |
21 | <div class="bar-bg"> |
|
21 | <div class="bar-bg"> | |
22 | <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress"> |
|
22 | <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress"> | |
23 | </div> |
|
23 | </div> | |
24 | <div class="bar-text"> |
|
24 | <div class="bar-text"> | |
25 | <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %} |
|
25 | <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %} | |
26 | </div> |
|
26 | </div> | |
27 | </div> |
|
27 | </div> | |
28 | {% endif %} |
|
28 | {% endif %} | |
29 | <div class="thread"> |
|
29 | <div class="thread"> | |
30 | {% for post in thread.get_replies %} |
|
30 | {% for post in thread.get_replies %} | |
31 | {% if bumpable %} |
|
31 | {% if bumpable %} | |
32 | <div class="post" id="{{ post.id }}"> |
|
32 | <div class="post" id="{{ post.id }}"> | |
33 | {% else %} |
|
33 | {% else %} | |
34 | <div class="post dead_post" id="{{ post.id }}"> |
|
34 | <div class="post dead_post" id="{{ post.id }}"> | |
35 | {% endif %} |
|
35 | {% endif %} | |
36 | {% if post.image %} |
|
36 | {% if post.image %} | |
37 | <div class="image"> |
|
37 | <div class="image"> | |
38 | <a |
|
38 | <a | |
39 | class="thumb" |
|
39 | class="thumb" | |
40 | href="{{ post.image.url }}"><img |
|
40 | href="{{ post.image.url }}"><img | |
41 | src="{{ post.image.url_200x150 }}" |
|
41 | src="{{ post.image.url_200x150 }}" | |
42 | alt="{{ post.id }}" |
|
42 | alt="{{ post.id }}" | |
|
43 | width="{{ post.image_pre_width }}" | |||
|
44 | height="{{ post.image_pre_height }}" | |||
43 | data-width="{{ post.image_width }}" |
|
45 | data-width="{{ post.image_width }}" | |
44 | data-height="{{ post.image_height }}"/> |
|
46 | data-height="{{ post.image_height }}"/> | |
45 | </a> |
|
47 | </a> | |
46 | </div> |
|
48 | </div> | |
47 | {% endif %} |
|
49 | {% endif %} | |
48 | <div class="message"> |
|
50 | <div class="message"> | |
49 | <div class="post-info"> |
|
51 | <div class="post-info"> | |
50 | <span class="title">{{ post.title }}</span> |
|
52 | <span class="title">{{ post.title }}</span> | |
51 | <a class="post_id" href="#{{ post.id }}"> |
|
53 | <a class="post_id" href="#{{ post.id }}"> | |
52 | ({{ post.id }})</a> |
|
54 | ({{ post.id }})</a> | |
53 | [{{ post.pub_time }}] |
|
55 | [{{ post.pub_time }}] | |
54 | [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}') |
|
56 | [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}') | |
55 | ; return false;">>></a>] |
|
57 | ; return false;">>></a>] | |
56 |
|
58 | |||
57 | {% if moderator %} |
|
59 | {% if moderator %} | |
58 | <span class="moderator_info"> |
|
60 | <span class="moderator_info"> | |
59 | [<a href="{% url 'delete' post_id=post.id %}" |
|
61 | [<a href="{% url 'delete' post_id=post.id %}" | |
60 | >{% trans 'Delete' %}</a>] |
|
62 | >{% trans 'Delete' %}</a>] | |
61 | ({{ post.poster_ip }}) |
|
63 | ({{ post.poster_ip }}) | |
62 | [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}" |
|
64 | [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}" | |
63 | >{% trans 'Ban IP' %}</a>] |
|
65 | >{% trans 'Ban IP' %}</a>] | |
64 | </span> |
|
66 | </span> | |
65 | {% endif %} |
|
67 | {% endif %} | |
66 | </div> |
|
68 | </div> | |
67 | {% autoescape off %} |
|
69 | {% autoescape off %} | |
68 | {{ post.text.rendered }} |
|
70 | {{ post.text.rendered }} | |
69 | {% endautoescape %} |
|
71 | {% endautoescape %} | |
70 | {% if post.is_referenced %} |
|
72 | {% if post.is_referenced %} | |
71 | <div class="refmap"> |
|
73 | <div class="refmap"> | |
72 | {% trans "Replies" %}: |
|
74 | {% trans "Replies" %}: | |
73 | {% for ref_post in post.get_sorted_referenced_posts %} |
|
75 | {% for ref_post in post.get_sorted_referenced_posts %} | |
74 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a |
|
76 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a | |
75 | >{% if not forloop.last %},{% endif %} |
|
77 | >{% if not forloop.last %},{% endif %} | |
76 | {% endfor %} |
|
78 | {% endfor %} | |
77 | </div> |
|
79 | </div> | |
78 | {% endif %} |
|
80 | {% endif %} | |
79 | </div> |
|
81 | </div> | |
80 | {% if forloop.first %} |
|
82 | {% if forloop.first %} | |
81 | <div class="metadata"> |
|
83 | <div class="metadata"> | |
82 | <span class="tags"> |
|
84 | <span class="tags"> | |
83 | {% for tag in thread.get_tags %} |
|
85 | {% for tag in thread.get_tags %} | |
84 | <a class="tag" href="{% url 'tag' tag.name %}"> |
|
86 | <a class="tag" href="{% url 'tag' tag.name %}"> | |
85 | #{{ tag.name }}</a |
|
87 | #{{ tag.name }}</a | |
86 | >{% if not forloop.last %},{% endif %} |
|
88 | >{% if not forloop.last %},{% endif %} | |
87 | {% endfor %} |
|
89 | {% endfor %} | |
88 | </span> |
|
90 | </span> | |
89 | </div> |
|
91 | </div> | |
90 | {% endif %} |
|
92 | {% endif %} | |
91 | </div> |
|
93 | </div> | |
92 | {% endfor %} |
|
94 | {% endfor %} | |
93 | </div> |
|
95 | </div> | |
94 | {% endcache %} |
|
96 | {% endcache %} | |
95 |
|
97 | |||
96 | <div class="post-form-w"> |
|
98 | <div class="post-form-w"> | |
97 | <script src="{% static 'js/panel.js' %}"></script> |
|
99 | <script src="{% static 'js/panel.js' %}"></script> | |
98 | <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div> |
|
100 | <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div> | |
99 | <div class="post-form"> |
|
101 | <div class="post-form"> | |
100 | <form id="form" enctype="multipart/form-data" method="post" |
|
102 | <form id="form" enctype="multipart/form-data" method="post" | |
101 | >{% csrf_token %} |
|
103 | >{% csrf_token %} | |
102 | {{ form.as_div }} |
|
104 | {{ form.as_div }} | |
103 | <div class="form-submit"> |
|
105 | <div class="form-submit"> | |
104 | <input type="submit" value="{% trans "Post" %}"/> |
|
106 | <input type="submit" value="{% trans "Post" %}"/> | |
105 | </div> |
|
107 | </div> | |
106 | </form> |
|
108 | </form> | |
107 | <div><a href="{% url "staticpage" name="help" %}"> |
|
109 | <div><a href="{% url "staticpage" name="help" %}"> | |
108 | {% trans 'Text syntax' %}</a></div> |
|
110 | {% trans 'Text syntax' %}</a></div> | |
109 | </div> |
|
111 | </div> | |
110 | </div> |
|
112 | </div> | |
111 |
|
113 | |||
112 | {% endspaceless %} |
|
114 | {% endspaceless %} | |
113 | {% endblock %} |
|
115 | {% endblock %} | |
114 |
|
116 | |||
115 | {% block metapanel %} |
|
117 | {% block metapanel %} | |
116 |
|
118 | |||
117 | {% get_current_language as LANGUAGE_CODE %} |
|
119 | {% get_current_language as LANGUAGE_CODE %} | |
118 |
|
120 | |||
119 | <span class="metapanel" data-last-update="{{ last_update }}"> |
|
121 | <span class="metapanel" data-last-update="{{ last_update }}"> | |
120 | {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %} |
|
122 | {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %} | |
121 | <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %}, |
|
123 | <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %}, | |
122 | <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}. |
|
124 | <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}. | |
123 | {% trans 'Last update: ' %}{{ thread.last_edit_time }} |
|
125 | {% trans 'Last update: ' %}{{ thread.last_edit_time }} | |
124 | [<a href="rss/">RSS</a>] |
|
126 | [<a href="rss/">RSS</a>] | |
125 | {% endcache %} |
|
127 | {% endcache %} | |
126 | </span> |
|
128 | </span> | |
127 |
|
129 | |||
128 | {% endblock %} |
|
130 | {% endblock %} |
@@ -1,173 +1,216 b'' | |||||
1 | # -*- encoding: utf-8 -*- |
|
1 | # -*- encoding: utf-8 -*- | |
2 | """ |
|
2 | """ | |
3 | django-thumbs by Antonio MelΓ© |
|
3 | django-thumbs by Antonio MelΓ© | |
4 | http://django.es |
|
4 | http://django.es | |
5 | """ |
|
5 | """ | |
|
6 | from django.core.files.images import ImageFile | |||
6 | from django.db.models import ImageField |
|
7 | from django.db.models import ImageField | |
7 | from django.db.models.fields.files import ImageFieldFile |
|
8 | from django.db.models.fields.files import ImageFieldFile | |
8 | from PIL import Image |
|
9 | from PIL import Image | |
9 | from django.core.files.base import ContentFile |
|
10 | from django.core.files.base import ContentFile | |
10 | import cStringIO |
|
11 | import cStringIO | |
11 |
|
12 | |||
12 |
|
13 | |||
13 | def generate_thumb(img, thumb_size, format): |
|
14 | def generate_thumb(img, thumb_size, format): | |
14 | """ |
|
15 | """ | |
15 | Generates a thumbnail image and returns a ContentFile object with the thumbnail |
|
16 | Generates a thumbnail image and returns a ContentFile object with the thumbnail | |
16 |
|
17 | |||
17 | Parameters: |
|
18 | Parameters: | |
18 | =========== |
|
19 | =========== | |
19 | img File object |
|
20 | img File object | |
20 |
|
21 | |||
21 | thumb_size desired thumbnail size, ie: (200,120) |
|
22 | thumb_size desired thumbnail size, ie: (200,120) | |
22 |
|
23 | |||
23 | format format of the original image ('jpeg','gif','png',...) |
|
24 | format format of the original image ('jpeg','gif','png',...) | |
24 | (this format will be used for the generated thumbnail, too) |
|
25 | (this format will be used for the generated thumbnail, too) | |
25 | """ |
|
26 | """ | |
26 |
|
27 | |||
27 | img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details |
|
28 | img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details | |
28 | image = Image.open(img) |
|
29 | image = Image.open(img) | |
29 |
|
30 | |||
30 | # get size |
|
31 | # get size | |
31 | thumb_w, thumb_h = thumb_size |
|
32 | thumb_w, thumb_h = thumb_size | |
32 | # If you want to generate a square thumbnail |
|
33 | # If you want to generate a square thumbnail | |
33 | if thumb_w == thumb_h: |
|
34 | if thumb_w == thumb_h: | |
34 | # quad |
|
35 | # quad | |
35 | xsize, ysize = image.size |
|
36 | xsize, ysize = image.size | |
36 | # get minimum size |
|
37 | # get minimum size | |
37 | minsize = min(xsize, ysize) |
|
38 | minsize = min(xsize, ysize) | |
38 | # largest square possible in the image |
|
39 | # largest square possible in the image | |
39 | xnewsize = (xsize - minsize) / 2 |
|
40 | xnewsize = (xsize - minsize) / 2 | |
40 | ynewsize = (ysize - minsize) / 2 |
|
41 | ynewsize = (ysize - minsize) / 2 | |
41 | # crop it |
|
42 | # crop it | |
42 | image2 = image.crop( |
|
43 | image2 = image.crop( | |
43 | (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize)) |
|
44 | (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize)) | |
44 |
# load is necessary after crop |
|
45 | # load is necessary after crop | |
45 | image2.load() |
|
46 | image2.load() | |
46 | # thumbnail of the cropped image (with ANTIALIAS to make it look better) |
|
47 | # thumbnail of the cropped image (with ANTIALIAS to make it look better) | |
47 | image2.thumbnail(thumb_size, Image.ANTIALIAS) |
|
48 | image2.thumbnail(thumb_size, Image.ANTIALIAS) | |
48 | else: |
|
49 | else: | |
49 | # not quad |
|
50 | # not quad | |
50 | image2 = image |
|
51 | image2 = image | |
51 | image2.thumbnail(thumb_size, Image.ANTIALIAS) |
|
52 | image2.thumbnail(thumb_size, Image.ANTIALIAS) | |
52 |
|
53 | |||
53 | io = cStringIO.StringIO() |
|
54 | io = cStringIO.StringIO() | |
54 | # PNG and GIF are the same, JPG is JPEG |
|
55 | # PNG and GIF are the same, JPG is JPEG | |
55 | if format.upper() == 'JPG': |
|
56 | if format.upper() == 'JPG': | |
56 | format = 'JPEG' |
|
57 | format = 'JPEG' | |
57 |
|
58 | |||
58 | image2.save(io, format) |
|
59 | image2.save(io, format) | |
59 | return ContentFile(io.getvalue()) |
|
60 | return ContentFile(io.getvalue()) | |
60 |
|
61 | |||
61 |
|
62 | |||
62 | class ImageWithThumbsFieldFile(ImageFieldFile): |
|
63 | class ImageWithThumbsFieldFile(ImageFieldFile): | |
63 | """ |
|
64 | """ | |
64 | See ImageWithThumbsField for usage example |
|
65 | See ImageWithThumbsField for usage example | |
65 | """ |
|
66 | """ | |
66 |
|
67 | |||
67 | def __init__(self, *args, **kwargs): |
|
68 | def __init__(self, *args, **kwargs): | |
68 | super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs) |
|
69 | super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs) | |
69 | self.sizes = self.field.sizes |
|
70 | self.sizes = self.field.sizes | |
70 |
|
71 | |||
71 | if self.sizes: |
|
72 | if self.sizes: | |
72 | def get_size(self, size): |
|
73 | def get_size(self, size): | |
73 | if not self: |
|
74 | if not self: | |
74 | return '' |
|
75 | return '' | |
75 | else: |
|
76 | else: | |
76 | split = self.url.rsplit('.', 1) |
|
77 | split = self.url.rsplit('.', 1) | |
77 | thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1]) |
|
78 | thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |
78 | return thumb_url |
|
79 | return thumb_url | |
79 |
|
80 | |||
80 | for size in self.sizes: |
|
81 | for size in self.sizes: | |
81 | (w, h) = size |
|
82 | (w, h) = size | |
82 | setattr(self, 'url_%sx%s' % (w, h), get_size(self, size)) |
|
83 | setattr(self, 'url_%sx%s' % (w, h), get_size(self, size)) | |
83 |
|
84 | |||
84 | def save(self, name, content, save=True): |
|
85 | def save(self, name, content, save=True): | |
85 | super(ImageWithThumbsFieldFile, self).save(name, content, save) |
|
86 | super(ImageWithThumbsFieldFile, self).save(name, content, save) | |
86 |
|
87 | |||
87 | if self.sizes: |
|
88 | if self.sizes: | |
88 | for size in self.sizes: |
|
89 | for size in self.sizes: | |
89 | (w, h) = size |
|
90 | (w, h) = size | |
90 | split = self.name.rsplit('.', 1) |
|
91 | split = self.name.rsplit('.', 1) | |
91 | thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) |
|
92 | thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |
92 |
|
93 | |||
93 | # you can use another thumbnailing function if you like |
|
94 | # you can use another thumbnailing function if you like | |
94 | thumb_content = generate_thumb(content, size, split[1]) |
|
95 | thumb_content = generate_thumb(content, size, split[1]) | |
95 |
|
96 | |||
96 | thumb_name_ = self.storage.save(thumb_name, thumb_content) |
|
97 | thumb_name_ = self.storage.save(thumb_name, thumb_content) | |
97 |
|
98 | |||
98 | if not thumb_name == thumb_name_: |
|
99 | if not thumb_name == thumb_name_: | |
99 | raise ValueError( |
|
100 | raise ValueError( | |
100 | 'There is already a file named %s' % thumb_name) |
|
101 | 'There is already a file named %s' % thumb_name) | |
101 |
|
102 | |||
102 | def delete(self, save=True): |
|
103 | def delete(self, save=True): | |
103 | name = self.name |
|
104 | name = self.name | |
104 | super(ImageWithThumbsFieldFile, self).delete(save) |
|
105 | super(ImageWithThumbsFieldFile, self).delete(save) | |
105 | if self.sizes: |
|
106 | if self.sizes: | |
106 | for size in self.sizes: |
|
107 | for size in self.sizes: | |
107 | (w, h) = size |
|
108 | (w, h) = size | |
108 | split = name.rsplit('.', 1) |
|
109 | split = name.rsplit('.', 1) | |
109 | thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) |
|
110 | thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |
110 | try: |
|
111 | try: | |
111 | self.storage.delete(thumb_name) |
|
112 | self.storage.delete(thumb_name) | |
112 | except: |
|
113 | except: | |
113 | pass |
|
114 | pass | |
114 |
|
115 | |||
115 |
|
116 | |||
116 | class ImageWithThumbsField(ImageField): |
|
117 | class ImageWithThumbsField(ImageField): | |
117 | attr_class = ImageWithThumbsFieldFile |
|
118 | attr_class = ImageWithThumbsFieldFile | |
118 | """ |
|
119 | """ | |
119 | Usage example: |
|
120 | Usage example: | |
120 | ============== |
|
121 | ============== | |
121 | photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),) |
|
122 | photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),) | |
122 |
|
123 | |||
123 | To retrieve image URL, exactly the same way as with ImageField: |
|
124 | To retrieve image URL, exactly the same way as with ImageField: | |
124 | my_object.photo.url |
|
125 | my_object.photo.url | |
125 | To retrieve thumbnails URL's just add the size to it: |
|
126 | To retrieve thumbnails URL's just add the size to it: | |
126 | my_object.photo.url_125x125 |
|
127 | my_object.photo.url_125x125 | |
127 | my_object.photo.url_300x200 |
|
128 | my_object.photo.url_300x200 | |
128 |
|
129 | |||
129 | Note: The 'sizes' attribute is not required. If you don't provide it, |
|
130 | Note: The 'sizes' attribute is not required. If you don't provide it, | |
130 | ImageWithThumbsField will act as a normal ImageField |
|
131 | ImageWithThumbsField will act as a normal ImageField | |
131 |
|
132 | |||
132 | How it works: |
|
133 | How it works: | |
133 | ============= |
|
134 | ============= | |
134 | For each size in the 'sizes' atribute of the field it generates a |
|
135 | For each size in the 'sizes' atribute of the field it generates a | |
135 | thumbnail with that size and stores it following this format: |
|
136 | thumbnail with that size and stores it following this format: | |
136 |
|
137 | |||
137 | available_filename.[width]x[height].extension |
|
138 | available_filename.[width]x[height].extension | |
138 |
|
139 | |||
139 | Where 'available_filename' is the available filename returned by the storage |
|
140 | Where 'available_filename' is the available filename returned by the storage | |
140 | backend for saving the original file. |
|
141 | backend for saving the original file. | |
141 |
|
142 | |||
142 | Following the usage example above: For storing a file called "photo.jpg" it saves: |
|
143 | Following the usage example above: For storing a file called "photo.jpg" it saves: | |
143 | photo.jpg (original file) |
|
144 | photo.jpg (original file) | |
144 | photo.125x125.jpg (first thumbnail) |
|
145 | photo.125x125.jpg (first thumbnail) | |
145 | photo.300x200.jpg (second thumbnail) |
|
146 | photo.300x200.jpg (second thumbnail) | |
146 |
|
147 | |||
147 | With the default storage backend if photo.jpg already exists it will use these filenames: |
|
148 | With the default storage backend if photo.jpg already exists it will use these filenames: | |
148 | photo_.jpg |
|
149 | photo_.jpg | |
149 | photo_.125x125.jpg |
|
150 | photo_.125x125.jpg | |
150 | photo_.300x200.jpg |
|
151 | photo_.300x200.jpg | |
151 |
|
152 | |||
152 | Note: django-thumbs assumes that if filename "any_filename.jpg" is available |
|
153 | Note: django-thumbs assumes that if filename "any_filename.jpg" is available | |
153 | filenames with this format "any_filename.[widht]x[height].jpg" will be available, too. |
|
154 | filenames with this format "any_filename.[widht]x[height].jpg" will be available, too. | |
154 |
|
155 | |||
155 | To do: |
|
156 | To do: | |
156 | ====== |
|
157 | ====== | |
157 | Add method to regenerate thubmnails |
|
158 | Add method to regenerate thubmnails | |
158 |
|
159 | |||
159 |
|
160 | |||
160 | """ |
|
161 | """ | |
161 |
|
162 | |||
162 | def __init__(self, verbose_name=None, name=None, width_field=None, |
|
163 | def __init__(self, verbose_name=None, name=None, width_field=None, | |
163 |
height_field=None, sizes=None, |
|
164 | height_field=None, sizes=None, | |
|
165 | preview_width_field=None, preview_height_field=None, | |||
|
166 | **kwargs): | |||
164 | self.verbose_name = verbose_name |
|
167 | self.verbose_name = verbose_name | |
165 | self.name = name |
|
168 | self.name = name | |
166 | self.width_field = width_field |
|
169 | self.width_field = width_field | |
167 | self.height_field = height_field |
|
170 | self.height_field = height_field | |
168 | self.sizes = sizes |
|
171 | self.sizes = sizes | |
169 | super(ImageField, self).__init__(**kwargs) |
|
172 | super(ImageField, self).__init__(**kwargs) | |
170 |
|
173 | |||
|
174 | if sizes is not None and len(sizes) == 1: | |||
|
175 | self.preview_width_field = preview_width_field | |||
|
176 | self.preview_height_field = preview_height_field | |||
|
177 | ||||
|
178 | def update_dimension_fields(self, instance, force=False, *args, **kwargs): | |||
|
179 | """ | |||
|
180 | Update original image dimension fields and thumb dimension fields | |||
|
181 | (only if 1 thumb size is defined) | |||
|
182 | """ | |||
|
183 | ||||
|
184 | super(ImageWithThumbsField, self).update_dimension_fields(instance, | |||
|
185 | force, *args, | |||
|
186 | **kwargs) | |||
|
187 | thumb_width_field = self.preview_width_field | |||
|
188 | thumb_height_field = self.preview_height_field | |||
|
189 | ||||
|
190 | if thumb_width_field is None or thumb_height_field is None \ | |||
|
191 | or len(self.sizes) != 1: | |||
|
192 | return | |||
|
193 | ||||
|
194 | original_width = getattr(instance, self.width_field) | |||
|
195 | original_height = getattr(instance, self.height_field) | |||
|
196 | ||||
|
197 | if original_width > 0 and original_height > 0: | |||
|
198 | thumb_width, thumb_height = self.sizes[0] | |||
|
199 | ||||
|
200 | w_scale = float(thumb_width) / original_width | |||
|
201 | h_scale = float(thumb_height) / original_height | |||
|
202 | scale_ratio = min(w_scale, h_scale) | |||
|
203 | ||||
|
204 | if scale_ratio >= 1: | |||
|
205 | thumb_width_ratio = original_width | |||
|
206 | thumb_height_ratio = original_height | |||
|
207 | else: | |||
|
208 | thumb_width_ratio = int(original_width * scale_ratio) | |||
|
209 | thumb_height_ratio = int(original_height * scale_ratio) | |||
|
210 | ||||
|
211 | setattr(instance, thumb_width_field, thumb_width_ratio) | |||
|
212 | setattr(instance, thumb_height_field, thumb_height_ratio) | |||
|
213 | ||||
171 |
|
214 | |||
172 | from south.modelsinspector import add_introspection_rules |
|
215 | from south.modelsinspector import add_introspection_rules | |
173 | add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"]) |
|
216 | add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"]) |
General Comments 0
You need to be logged in to leave comments.
Login now