##// END OF EJS Templates
Fixed get computing. Visually differing get posts in thread.
neko259 -
r42:37e52c98 default
parent child Browse files
Show More
@@ -1,212 +1,212 b''
1 1 import os
2 2 import re
3 3 from django.db import models
4 4 from django.utils import timezone
5 5 import time
6 6
7 7 from neboard import settings
8 8 from markupfield.fields import MarkupField
9 9
10 10 import thumbs
11 11
12 12 NO_PARENT = -1
13 13 NO_IP = '0.0.0.0'
14 14 UNKNOWN_UA = ''
15 15
16 16
17 17 def update_image_filename(instance, filename):
18 18 """Get unique image filename"""
19 19
20 20 path = 'images/'
21 21 new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename
22 22 return os.path.join(path, new_name)
23 23
24 24
25 25 class PostManager(models.Manager):
26 26 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
27 27 ip=NO_IP, tags=None):
28 28 post = self.create(title=title,
29 29 text=text,
30 30 pub_time=timezone.now(),
31 31 parent=parent_id,
32 32 image=image,
33 33 poster_ip=ip,
34 34 poster_user_agent=UNKNOWN_UA,
35 35 last_edit_time=timezone.now())
36 36
37 37 if tags:
38 38 for tag in tags:
39 39 post.tags.add(tag)
40 40
41 41 if parent_id != NO_PARENT:
42 42 self._bump_thread(parent_id)
43 43 else:
44 44 self._delete_old_threads()
45 45
46 46 return post
47 47
48 48 def delete_post(self, post):
49 49 children = self.filter(parent=post.id)
50 50 for child in children:
51 51 self.delete_post(child)
52 52 post.delete()
53 53
54 54 def delete_posts_by_ip(self, ip):
55 55 posts = self.filter(poster_ip=ip)
56 56 for post in posts:
57 57 self.delete_post(post)
58 58
59 59 def get_threads(self, tag=None):
60 60 if tag:
61 61 threads = self.filter(parent=NO_PARENT, tags=tag)
62 62 else:
63 63 threads = self.filter(parent=NO_PARENT)
64 64 threads = list(threads.order_by('-last_edit_time'))
65 65
66 66 return threads
67 67
68 68 def get_thread(self, opening_post_id):
69 69 opening_post = self.get(id=opening_post_id)
70 70
71 71 if opening_post.parent == NO_PARENT:
72 72 replies = self.filter(parent=opening_post_id)
73 73
74 74 thread = [opening_post]
75 75 thread.extend(replies)
76 76
77 77 return thread
78 78
79 79 def exists(self, post_id):
80 80 posts = self.filter(id=post_id)
81 81
82 82 return len(posts) > 0
83 83
84 84 def _delete_old_threads(self):
85 85 """
86 86 Preserves maximum thread count. If there are too many threads,
87 87 delete the old ones.
88 88 """
89 89
90 90 # TODO Try to find a better way to get the active thread count.
91 91
92 92 # TODO Move old threads to the archive instead of deleting them.
93 93 # Maybe make some 'old' field in the model to indicate the thread
94 94 # must not be shown and be able for replying.
95 95
96 96 threads = self.get_threads()
97 97 thread_count = len(threads)
98 98
99 99 if thread_count > settings.MAX_THREAD_COUNT:
100 100 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
101 101 old_threads = threads[-num_threads_to_delete:]
102 102
103 103 for thread in old_threads:
104 104 self.delete_post(thread)
105 105
106 106 def _bump_thread(self, thread_id):
107 107 thread = self.get(id=thread_id)
108 108
109 109 replies_count = len(self.get_thread(thread_id))
110 110 if replies_count <= settings.MAX_POSTS_PER_THREAD:
111 111 thread.last_edit_time = timezone.now()
112 112 thread.save()
113 113
114 114
115 115 class TagManager(models.Manager):
116 116 def get_not_empty_tags(self):
117 117 all_tags = self.all().order_by('name')
118 118 tags = []
119 119 for tag in all_tags:
120 120 if not tag.is_empty():
121 121 tags.append(tag)
122 122
123 123 return tags
124 124
125 125
126 126 class Tag(models.Model):
127 127 """
128 128 A tag is a text node assigned to the post. The tag serves as a board
129 129 section. There can be multiple tags for each message
130 130 """
131 131
132 132 objects = TagManager()
133 133
134 134 name = models.CharField(max_length=100)
135 135 # TODO Connect the tag to its posts to check the number of threads for
136 136 # the tag.
137 137
138 138 def __unicode__(self):
139 139 return self.name
140 140
141 141 def is_empty(self):
142 142 return self.get_post_count() == 0
143 143
144 144 def get_post_count(self):
145 145 posts_with_tag = Post.objects.get_threads(tag=self)
146 146 return len(posts_with_tag)
147 147
148 148
149 149 class Post(models.Model):
150 150 """A post is a message."""
151 151
152 152 objects = PostManager()
153 153
154 154 title = models.CharField(max_length=50)
155 155 pub_time = models.DateTimeField()
156 156 text = MarkupField(default_markup_type='markdown', escape_html=True)
157 157 image = thumbs.ImageWithThumbsField(upload_to=update_image_filename,
158 158 blank=True, sizes=((200, 150),))
159 159 poster_ip = models.IPAddressField()
160 160 poster_user_agent = models.TextField()
161 161 parent = models.BigIntegerField()
162 162 tags = models.ManyToManyField(Tag)
163 163 last_edit_time = models.DateTimeField()
164 164
165 165 def __unicode__(self):
166 166 return self.title + ' (' + self.text.raw + ')'
167 167
168 168 def _get_replies(self):
169 169 return Post.objects.filter(parent=self.id)
170 170
171 171 def get_reply_count(self):
172 172 return len(self._get_replies())
173 173
174 174 def get_images_count(self):
175 175 images_count = 1 if self.image else 0
176 176 for reply in self._get_replies():
177 177 if reply.image:
178 178 images_count += 1
179 179
180 180 return images_count
181 181
182 182 def get_gets_count(self):
183 183 gets_count = 1 if self.is_get() else 0
184 184 for reply in self._get_replies():
185 185 if reply.is_get():
186 186 gets_count += 1
187 187
188 188 return gets_count
189 189
190 190 def is_get(self):
191 191 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
192 192
193 193 first = self.id == 1
194 194
195 195 # TODO Compile regexes
196 196
197 197 id_str = str(self.id)
198 pretty = re.match(r'\d0+', id_str)
199 same_digits = re.match(r'^(.)\1{1,}$', id_str)
198 pretty = re.match(r'^\d(0)+$', id_str)
199 same_digits = re.match(r'^(.)\1+$', id_str)
200 200 return first or pretty or same_digits
201 201
202 202
203 203 class Admin(models.Model):
204 204 """
205 205 Model for admin users
206 206 """
207 207 name = models.CharField(max_length=100)
208 208 password = models.CharField(max_length=100)
209 209
210 210 def __unicode__(self):
211 211 return self.name + '/' + '*' * len(self.password)
212 212
@@ -1,162 +1,168 b''
1 1 html {
2 2 background: #555;
3 3 color: #ffffff;
4 4 }
5 5
6 6 #admin_panel {
7 7 background: #FF0000;
8 8 color: #00FF00
9 9 }
10 10
11 11 .title {
12 12 font-weight: bold;
13 13 color: #ffcc00;
14 14 }
15 15
16 16 .link, a {
17 17 color: #afdcec;
18 18 }
19 19
20 20 .link:hover {
21 21 color: #fff380;
22 22 }
23 23
24 24 .post_id {
25 25 color: #ffffff;
26 26 }
27 27
28 28 .block {
29 29 display: inline-block;
30 30 vertical-align: top;
31 31 }
32 32
33 33 .tag {
34 34 color: #b4cfec;
35 35 }
36 36
37 37 .tag:hover {
38 38 color: #d0edb4;
39 39 }
40 40
41 41 .post_id {
42 42 color: #fff380;
43 43 }
44 44
45 45 .post {
46 46 background: #333;
47 47 margin: 5px;
48 48 padding: 10px;
49 49 border-radius: 5px;
50 50 clear: left;
51 51 }
52 52
53 53 .metadata {
54 padding: 2px;
54 padding: 5px;
55 55 margin-top: 10px;
56 56 border: solid 1px #666;
57 57 font-size: 0.9em;
58 58 color: #ddd;
59 display: table;
59 60 }
60 61
61 62 #navigation_panel {
62 63 background: #444;
63 64 margin: 5px;
64 65 padding: 10px;
65 66 border-radius: 5px;
66 67 color: #eee;
67 68 }
68 69
69 70 #navigation_panel .link {
70 71 border-right: 1px solid #fff;
71 72 font-weight: bold;
72 73 margin-right: 1ex;
73 74 padding-right: 1ex;
74 75 }
75 76 #navigation_panel .link:last-child {
76 77 border-left: 1px solid #fff;
77 78 border-right: none;
78 79 float: right;
79 80 margin-left: 1ex;
80 81 margin-right: 0;
81 82 padding-left: 1ex;
82 83 padding-right: 0;
83 84 }
84 85
85 86 #navigation_panel::after, .post::after {
86 87 clear: both;
87 88 content: ".";
88 89 display: block;
89 90 height: 0;
90 91 line-height: 0;
91 92 visibility: hidden;
92 93 }
93 94
94 95 p {
95 96 margin-top: .5em;
96 97 margin-bottom: .5em;
97 98 }
98 99
99 100 .post-form-w {
100 101 display: table;
101 102 background: #333344;
102 103 border-radius: 5px;
103 104 color: #fff;
104 105 padding: 10px;
105 106 margin: 5px
106 107 }
107 108
108 109 .form-row {
109 110 display: table-row;
110 111 }
111 112
112 113 .form-label, .form-input {
113 114 display: table-cell;
114 115 }
115 116
116 117 .form-label {
117 118 padding: .25em 1ex .25em 0;
118 119 vertical-align: top;
119 120 }
120 121
121 122 .form-input {
122 123 padding: .25em 0;
123 124 }
124 125
125 126 .post-form input, .post-form textarea {
126 127 background: #333;
127 128 color: #fff;
128 129 border: solid 1px;
129 130 padding: 0;
130 131 width: 100%;
131 132 }
132 133
133 134 .form-submit {
134 135 border-bottom: 2px solid #ddd;
135 136 margin-bottom: .5em;
136 137 padding-bottom: .5em;
137 138 }
138 139
139 140 .form-title {
140 141 font-weight: bold;
141 142 }
142 143
143 144 input[type="submit"] {
144 145 background: #222;
145 146 border: solid 1px #fff;
146 147 color: #fff;
147 148 }
148 149
149 150 blockquote {
150 151 border-left: solid 2px;
151 152 padding-left: 5px;
152 153 color: #B1FB17;
153 154 margin: 0;
154 155 }
155 156
156 157 .post > .image {
157 158 float: left; margin: 0 1ex .5ex 0;
158 159 }
159 160
160 161 .post > .metadata {
161 162 clear: left;
163 }
164
165 .get {
166 font-weight: bold;
167 color: #d55;
162 168 } No newline at end of file
@@ -1,174 +1,179 b''
1 1 * {
2 2 font-size: inherit;
3 3 margin: 0;
4 4 padding: 0;
5 5 }
6 6 html {
7 7 background: #fff;
8 8 color: #000;
9 9 font: medium sans-serif;
10 10 }
11 11 a {
12 12 color: inherit;
13 13 text-decoration: underline;
14 14 }
15 15
16 16 #admin_panel {
17 17 background: #182F6F;
18 18 color: #fff;
19 19 padding: .5ex 1ex .5ex 1ex;
20 20 }
21 21
22 22 #navigation_panel {
23 23 background: #182F6F;
24 24 color: #B4CFEC;
25 25 margin-bottom: 1em;
26 26 padding: .5ex 1ex 1ex 1ex;
27 27 }
28 28 #navigation_panel::after {
29 29 clear: both;
30 30 content: ".";
31 31 display: block;
32 32 height: 0;
33 33 line-height: 0;
34 34 visibility: hidden;
35 35 }
36 36
37 37 #navigation_panel a:link, #navigation_panel a:visited, #navigation_panel a:hover {
38 38 text-decoration: none;
39 39 }
40 40
41 41 #navigation_panel .link {
42 42 border-right: 1px solid #fff;
43 43 color: #fff;
44 44 font-weight: bold;
45 45 margin-right: 1ex;
46 46 padding-right: 1ex;
47 47 }
48 48 #navigation_panel .link:last-child {
49 49 border-left: 1px solid #fff;
50 50 border-right: none;
51 51 float: right;
52 52 margin-left: 1ex;
53 53 margin-right: 0;
54 54 padding-left: 1ex;
55 55 padding-right: 0;
56 56 }
57 57
58 58 #navigation_panel .tag {
59 59 color: #fff;
60 60 }
61 61
62 62 .title {
63 63 color: #182F6F;
64 64 font-weight: bold;
65 65 }
66 66
67 67 .post-form-w {
68 68 background: #182F6F;
69 69 border-radius: 1ex;
70 70 color: #fff;
71 71 margin: 1em 1ex;
72 72 padding: 1ex;
73 73 }
74 74 .post-form {
75 75 display: table;
76 76 border-collapse: collapse;
77 77 width: 100%;
78 78
79 79 }
80 80 .form-row {
81 81 display: table-row;
82 82 }
83 83 .form-label, .form-input {
84 84 display: table-cell;
85 85 vertical-align: top;
86 86 }
87 87 .form-label {
88 88 padding: .25em 1ex .25em 0;
89 89 }
90 90 .form-input {
91 91 padding: .25em 0;
92 92 }
93 93 .form-input > * {
94 94 background: #fff;
95 95 color: #000;
96 96 border: none;
97 97 padding: 0;
98 98 resize: vertical;
99 99 width: 100%;
100 100 }
101 101 .form-submit {
102 102 border-bottom: 1px solid #666;
103 103 margin-bottom: .5em;
104 104 padding-bottom: .5em;
105 105 }
106 106 .form-title {
107 107 font-weight: bold;
108 108 margin-bottom: .5em;
109 109 }
110 110 .post-form .settings_item {
111 111 margin: .5em 0;
112 112 }
113 113 .form-submit input {
114 114 margin-top: .5em;
115 115 padding: .2em 1ex;
116 116 }
117 117 .form-label {
118 118 text-align: right;
119 119 }
120 120
121 121 .block {
122 122 display: inline-block;
123 123 vertical-align: top;
124 124 }
125 125
126 126 .post_id {
127 127 color: #a00;
128 128 }
129 129
130 130 .post {
131 131 background: #FFF;
132 132 border-bottom: 1px solid #182F6F;
133 133 margin: 0 1ex 1em 1ex;
134 134 overflow-x: auto;
135 135 padding-bottom: 1em;
136 136 }
137 137
138 138 .metadata {
139 139 background: #C0E4E8;
140 140 border: 1px solid #7F9699;
141 141 border-radius: .4ex;
142 142 display: table;
143 143 margin-top: .5em;
144 144 padding: .4em;
145 145 }
146 146
147 147 .post ul, .post ol {
148 148 margin: .5em 0 .5em 3ex;
149 149 }
150 150 .post li {
151 151 margin: .2em 0;
152 152 }
153 153 .post p {
154 154 margin: .5em 0;
155 155 }
156 156 .post blockquote {
157 157 border-left: 3px solid #182F6F;
158 158 margin: .5em 0 .5em 3ex;
159 159 padding-left: 1ex;
160 160 }
161 161 .post blockquote > blockquote {
162 162 padding-top: .1em;
163 163 }
164 164
165 165 .post > .image {
166 166 float: left;
167 167 margin-right: 1ex;
168 168 }
169 169 .post > .metadata {
170 170 clear: left;
171 171 }
172
172 173 .post {
173 174 clear: left;
174 175 }
176
177 .post > .message .get {
178 color: #182F6F; font-weight: bold;
179 } No newline at end of file
@@ -1,76 +1,81 b''
1 1 {% extends "base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load markup %}
5 5
6 6 {% block head %}
7 7 <title>Neboard</title>
8 8 {% endblock %}
9 9
10 10 {% block content %}
11 11
12 12 {% if posts %}
13 13 {% for post in posts %}
14 14 <a name="{{ post.id }}"></a>
15 15 <div class="post">
16 16 {% if post.image %}
17 17 <div class="image">
18 18 <a href="{{ post.image.url }}"><img
19 19 src="{{ post.image.url_200x150 }}" />
20 20 </a>
21 21 </div>
22 22 {% endif %}
23 23 <div class="message">
24 24 <span class="title">{{ post.title }}</span>
25 25 <a class="post_id" href="#{{ post.id }}">
26 26 (#{{ post.id }})</a>
27 27 [{{ post.pub_time }}]
28 {% if post.is_get %}
29 <span class="get">
30 {% trans "Get!" %}
31 </span>
32 {% endif %}
28 33 {% autoescape off %}
29 34 {{ post.text.rendered }}
30 35 {% endautoescape %}
31 36 </div>
32 37 {% if post.tags.all %}
33 38 <div class="metadata">
34 39 <span class="tags">{% trans 'Tags' %}:
35 40 {% for tag in post.tags.all %}
36 41 <a class="tag" href="{% url 'tag' tag.name %}">
37 42 {{ tag.name }}</a>
38 43 {% endfor %}
39 44 </span>
40 45 </div>
41 46 {% endif %}
42 47 </div>
43 48 {% endfor %}
44 49 {% else %}
45 50 No threads found.
46 51 <hr />
47 52 {% endif %}
48 53
49 54 <form enctype="multipart/form-data" method="post">{% csrf_token %}
50 55 <div class="post-form-w">
51 56 <div class="form-title">{% trans "Reply to thread" %}</div>
52 57 <div class="post-form">
53 58 <div class="form-row">
54 59 <div class="form-label">{% trans 'Title' %}</div>
55 60 <div class="form-input">{{ form.title }}</div>
56 61 </div>
57 62 <div class="form-row">
58 63 <div class="form-label">{% trans 'Text' %}</div>
59 64 <div class="form-input">{{ form.text }}</div>
60 65 </div>
61 66 <div class="form-row">
62 67 <div class="form-label">{% trans 'Image' %}</div>
63 68 <div class="form-input">{{ form.image }}</div>
64 69 </div>
65 70 </div>
66 71 <div class="form-submit"><input type="submit"
67 72 value="{% trans "Post" %}"/></div>
68 73 <div>Use <a
69 74 href="http://daringfireball.net/projects/markdown/basics">
70 75 markdown</a> syntax for posting.</div>
71 76 <div>Example: *<i>italic</i>*, **<b>bold</b>**</div>
72 77 <div>Insert quotes with "&gt;"</div>
73 78 </div>
74 79 </form>
75 80
76 81 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now