##// END OF EJS Templates
Fixed medadata design when an image is present. Added a method to determine if the tag is empty (has no attached threads).
neko259 -
r32:e74f9cc7 default
parent child Browse files
Show More
@@ -1,186 +1,190 b''
1 import os
1 import os
2 import re
2 import re
3 from django.db import models
3 from django.db import models
4 from django.utils import timezone
4 from django.utils import timezone
5 import time
5 import time
6
6
7 from neboard import settings
7 from neboard import settings
8
8
9 import thumbs
9 import thumbs
10
10
11 NO_PARENT = -1
11 NO_PARENT = -1
12 NO_IP = '0.0.0.0'
12 NO_IP = '0.0.0.0'
13 UNKNOWN_UA = ''
13 UNKNOWN_UA = ''
14
14
15
15
16 def update_image_filename(instance, filename):
16 def update_image_filename(instance, filename):
17 """Get unique image filename"""
17 """Get unique image filename"""
18
18
19 path = 'images/'
19 path = 'images/'
20 new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename
20 new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename
21 return os.path.join(path, new_name)
21 return os.path.join(path, new_name)
22
22
23
23
24 class PostManager(models.Manager):
24 class PostManager(models.Manager):
25 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
25 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
26 ip=NO_IP, tags=None):
26 ip=NO_IP, tags=None):
27 post = self.create(title=title,
27 post = self.create(title=title,
28 text=text,
28 text=text,
29 pub_time=timezone.now(),
29 pub_time=timezone.now(),
30 parent=parent_id,
30 parent=parent_id,
31 image=image,
31 image=image,
32 poster_ip=ip,
32 poster_ip=ip,
33 poster_user_agent=UNKNOWN_UA,
33 poster_user_agent=UNKNOWN_UA,
34 last_edit_time=timezone.now())
34 last_edit_time=timezone.now())
35
35
36 if tags:
36 if tags:
37 for tag in tags:
37 for tag in tags:
38 post.tags.add(tag)
38 post.tags.add(tag)
39
39
40 if parent_id != NO_PARENT:
40 if parent_id != NO_PARENT:
41 parent = self.get(id=parent_id)
41 parent = self.get(id=parent_id)
42 parent.last_edit_time=timezone.now()
42 parent.last_edit_time=timezone.now()
43 parent.save()
43 parent.save()
44 else:
44 else:
45 self._delete_old_threads()
45 self._delete_old_threads()
46
46
47 return post
47 return post
48
48
49 def delete_post(self, post):
49 def delete_post(self, post):
50 children = self.filter(parent=post.id)
50 children = self.filter(parent=post.id)
51 for child in children:
51 for child in children:
52 self.delete_post(child)
52 self.delete_post(child)
53 post.delete()
53 post.delete()
54
54
55 def delete_posts_by_ip(self, ip):
55 def delete_posts_by_ip(self, ip):
56 posts = self.filter(poster_ip=ip)
56 posts = self.filter(poster_ip=ip)
57 for post in posts:
57 for post in posts:
58 self.delete_post(post)
58 self.delete_post(post)
59
59
60 def get_threads(self, tag=None):
60 def get_threads(self, tag=None):
61 if tag:
61 if tag:
62 threads = self.filter(parent=NO_PARENT, tags=tag)
62 threads = self.filter(parent=NO_PARENT, tags=tag)
63 else:
63 else:
64 threads = self.filter(parent=NO_PARENT)
64 threads = self.filter(parent=NO_PARENT)
65 threads = list(threads.order_by('-last_edit_time'))
65 threads = list(threads.order_by('-last_edit_time'))
66
66
67 return threads
67 return threads
68
68
69 def get_thread(self, opening_post_id):
69 def get_thread(self, opening_post_id):
70 opening_post = self.get(id=opening_post_id)
70 opening_post = self.get(id=opening_post_id)
71 replies = self.filter(parent=opening_post_id)
71 replies = self.filter(parent=opening_post_id)
72
72
73 thread = [opening_post]
73 thread = [opening_post]
74 thread.extend(replies)
74 thread.extend(replies)
75
75
76 return thread
76 return thread
77
77
78 def exists(self, post_id):
78 def exists(self, post_id):
79 posts = self.filter(id=post_id)
79 posts = self.filter(id=post_id)
80
80
81 return len(posts) > 0
81 return len(posts) > 0
82
82
83 def _delete_old_threads(self):
83 def _delete_old_threads(self):
84 """
84 """
85 Preserves maximum thread count. If there are too many threads,
85 Preserves maximum thread count. If there are too many threads,
86 delete the old ones.
86 delete the old ones.
87 """
87 """
88
88
89 # TODO Try to find a better way to get the active thread count.
89 # TODO Try to find a better way to get the active thread count.
90
90
91 # TODO Move old threads to the archive instead of deleting them.
91 # TODO Move old threads to the archive instead of deleting them.
92 # Maybe make some 'old' field in the model to indicate the thread
92 # Maybe make some 'old' field in the model to indicate the thread
93 # must not be shown and be able for replying.
93 # must not be shown and be able for replying.
94
94
95 threads = self.get_threads()
95 threads = self.get_threads()
96 thread_count = len(threads)
96 thread_count = len(threads)
97
97
98 if thread_count > settings.MAX_THREAD_COUNT:
98 if thread_count > settings.MAX_THREAD_COUNT:
99 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
99 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
100 old_threads = threads[-num_threads_to_delete:]
100 old_threads = threads[-num_threads_to_delete:]
101
101
102 for thread in old_threads:
102 for thread in old_threads:
103 self.delete_post(thread)
103 self.delete_post(thread)
104
104
105
105
106 class Tag(models.Model):
106 class Tag(models.Model):
107 """
107 """
108 A tag is a text node assigned to the post. The tag serves as a board
108 A tag is a text node assigned to the post. The tag serves as a board
109 section. There can be multiple tags for each message
109 section. There can be multiple tags for each message
110 """
110 """
111
111
112 name = models.CharField(max_length=100)
112 name = models.CharField(max_length=100)
113 # TODO Connect the tag to its posts to check the number of threads for
113 # TODO Connect the tag to its posts to check the number of threads for
114 # the tag.
114 # the tag.
115
115
116 def __unicode__(self):
116 def __unicode__(self):
117 return self.name
117 return self.name
118
118
119 def is_empty(self):
120 posts_with_tag = Post.objects.get_threads(tag=self)
121 return len(posts_with_tag) == 0
122
119
123
120 class Post(models.Model):
124 class Post(models.Model):
121 """A post is a message."""
125 """A post is a message."""
122
126
123 objects = PostManager()
127 objects = PostManager()
124
128
125 title = models.CharField(max_length=50)
129 title = models.CharField(max_length=50)
126 pub_time = models.DateTimeField()
130 pub_time = models.DateTimeField()
127 text = models.TextField()
131 text = models.TextField()
128 image = thumbs.ImageWithThumbsField(upload_to=update_image_filename,
132 image = thumbs.ImageWithThumbsField(upload_to=update_image_filename,
129 blank=True, sizes=((200, 150),))
133 blank=True, sizes=((200, 150),))
130 poster_ip = models.IPAddressField()
134 poster_ip = models.IPAddressField()
131 poster_user_agent = models.TextField()
135 poster_user_agent = models.TextField()
132 parent = models.BigIntegerField()
136 parent = models.BigIntegerField()
133 tags = models.ManyToManyField(Tag)
137 tags = models.ManyToManyField(Tag)
134 last_edit_time = models.DateTimeField()
138 last_edit_time = models.DateTimeField()
135
139
136 def __unicode__(self):
140 def __unicode__(self):
137 return self.title + ' (' + self.text + ')'
141 return self.title + ' (' + self.text + ')'
138
142
139 def _get_replies(self):
143 def _get_replies(self):
140 return Post.objects.filter(parent=self.id)
144 return Post.objects.filter(parent=self.id)
141
145
142 def get_reply_count(self):
146 def get_reply_count(self):
143 return len(self._get_replies())
147 return len(self._get_replies())
144
148
145 def get_images_count(self):
149 def get_images_count(self):
146 images_count = 1 if self.image else 0
150 images_count = 1 if self.image else 0
147 for reply in self._get_replies():
151 for reply in self._get_replies():
148 if reply.image:
152 if reply.image:
149 images_count += 1
153 images_count += 1
150
154
151 return images_count
155 return images_count
152
156
153 def get_gets_count(self):
157 def get_gets_count(self):
154 gets_count = 1 if self.is_get() else 0
158 gets_count = 1 if self.is_get() else 0
155 for reply in self._get_replies():
159 for reply in self._get_replies():
156 if reply.is_get():
160 if reply.is_get():
157 gets_count += 1
161 gets_count += 1
158
162
159 return gets_count
163 return gets_count
160
164
161 def is_get(self):
165 def is_get(self):
162 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
166 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
163
167
164 # TODO Make a better algorithm for describing gets
168 # TODO Make a better algorithm for describing gets
165 return self.id == 1 or self.id % 10 == 0
169 return self.id == 1 or self.id % 10 == 0
166
170
167 def get_parsed_text(self):
171 def get_parsed_text(self):
168 text = self.text
172 text = self.text
169
173
170 # TODO Implement parsing
174 # TODO Implement parsing
171 # Make //text colored
175 # Make //text colored
172 # Make >>12 links to posts
176 # Make >>12 links to posts
173
177
174 return text
178 return text
175
179
176
180
177 class Admin(models.Model):
181 class Admin(models.Model):
178 """
182 """
179 Model for admin users
183 Model for admin users
180 """
184 """
181 name = models.CharField(max_length=100)
185 name = models.CharField(max_length=100)
182 password = models.CharField(max_length=100)
186 password = models.CharField(max_length=100)
183
187
184 def __unicode__(self):
188 def __unicode__(self):
185 return self.name + '/' + '*' * len(self.password)
189 return self.name + '/' + '*' * len(self.password)
186
190
@@ -1,80 +1,79 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 .title {
11 .title {
12 font-weight: bold;
12 font-weight: bold;
13 color: #ffcc00;
13 color: #ffcc00;
14 }
14 }
15
15
16 .post-form {
16 .post-form {
17 text-align: left;
17 text-align: left;
18 display: table;
18 display: table;
19 border-radius: 5px;
19 border-radius: 5px;
20 padding: 5px;
20 padding: 5px;
21 margin: 5px;
21 margin: 5px;
22 background: #334;
22 background: #334;
23 }
23 }
24
24
25 .form-row {
25 .form-row {
26 display: table-row;
26 display: table-row;
27 }
27 }
28
28
29 .form-input {
29 .form-input {
30 display: table-cell;
30 display: table-cell;
31 }
31 }
32
32
33 .link {
33 .link {
34 color: #afdcec;
34 color: #afdcec;
35 }
35 }
36
36
37 .link:hover {
37 .link:hover {
38 color: #fff380;
38 color: #fff380;
39 }
39 }
40
40
41 .post_id {
41 .post_id {
42 color: #ffffff;
42 color: #ffffff;
43 }
43 }
44
44
45 .block {
45 .block {
46 display: inline-block;
46 display: inline-block;
47 vertical-align: top;
47 vertical-align: top;
48 }
48 }
49
49
50 .tag {
50 .tag {
51 color: #b4cfec;
51 color: #b4cfec;
52 }
52 }
53
53
54 .tag:hover {
54 .tag:hover {
55 color: #d0edb4;
55 color: #d0edb4;
56 }
56 }
57
57
58 .post_id {
58 .post_id {
59 color: #fff380;
59 color: #fff380;
60 }
60 }
61
61
62 .post {
62 .post {
63 background: #333;
63 background: #333;
64 margin: 5px;
64 margin: 5px;
65 padding: 10px;
65 padding: 10px;
66 border-radius: 5px;
66 border-radius: 5px;
67 }
67 }
68
68
69 .form-title {
69 .form-title {
70 font-weight: bolder;
70 font-weight: bolder;
71 }
71 }
72
72
73 .metadata {
73 .metadata {
74 float: left;
75 padding: 2px;
74 padding: 2px;
76 margin-top: 10px;
75 margin-top: 10px;
77 border: solid 1px #666;
76 border: solid 1px #666;
78 font-size: 0.9em;
77 font-size: 0.9em;
79 color: #ddd
78 color: #ddd
80 } No newline at end of file
79 }
@@ -1,72 +1,72 b''
1 {% extends "base.html" %}
1 {% extends "base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block content %}
5 {% block content %}
6
6
7 {% if threads %}
7 {% if threads %}
8 {% for thread in threads %}
8 {% for thread in threads %}
9 <div class="post">
9 <div class="post">
10 {% if thread.image %}
10 {% if thread.image %}
11 <div class="block">
11 <div class="block">
12 <a href="{{ thread.image.url }}"><img
12 <a href="{{ thread.image.url }}"><img
13 src="{{ thread.image.url_200x150 }}" />
13 src="{{ thread.image.url_200x150 }}" />
14 </a>
14 </a>
15 </div>
15 </div>
16 {% endif %}
16 {% endif %}
17 <div class="block">
17 <div class="block">
18 <span class="title">{{ thread.title }}</span>
18 <span class="title">{{ thread.title }}</span>
19 <span class="post_id">(#{{ thread.id }})</span>
19 <span class="post_id">(#{{ thread.id }})</span>
20 [{{ thread.pub_time }}]
20 [{{ thread.pub_time }}]
21 [<a class="link" href="{% url 'thread' thread.id %}"
21 [<a class="link" href="{% url 'thread' thread.id %}"
22 >{% trans "View" %}</a>]<br />
22 >{% trans "View" %}</a>]<br />
23 {{ thread.get_parsed_text|linebreaksbr|truncatechars:300 }}<br
23 {{ thread.get_parsed_text|linebreaksbr|truncatechars:300 }}<br
24 />
24 />
25 </div>
25 <div class="metadata">
26 <div class="metadata">
26 {{ thread.get_reply_count }} {% trans 'replies' %},
27 {{ thread.get_reply_count }} {% trans 'replies' %},
27 {{ thread.get_images_count }} {% trans 'images' %},
28 {{ thread.get_images_count }} {% trans 'images' %},
28 {{ thread.get_gets_count }} {% trans 'gets' %}.
29 {{ thread.get_gets_count }} {% trans 'gets' %}.
29 {% if thread.tags.all %}
30 {% if thread.tags.all %}
30 <span class="tags">{% trans 'Tags' %}:
31 <span class="tags">{% trans 'Tags' %}:
31 {% for tag in thread.tags.all %}
32 {% for tag in thread.tags.all %}
32 <a class="tag" href="
33 <a class="tag" href="
33 {% url 'tag' tag_name=tag.name %}">
34 {% url 'tag' tag_name=tag.name %}">
34 {{ tag.name }}</a>
35 {{ tag.name }}</a>
35 {% endfor %}
36 {% endfor %}
36 </span>
37 </span>
37 {% endif %}
38 {% endif %}
38 </div>
39 </div>
39 </div>
40 </div>
40 </div>
41 {% endfor %}
41 {% endfor %}
42 {% else %}
42 {% else %}
43 No threads found.
43 No threads found.
44 <hr />
44 <hr />
45 {% endif %}
45 {% endif %}
46
46
47 <div class="post-form">
47 <div class="post-form">
48 <span class="form-title">{% trans "Create new thread" %}</span>
48 <span class="form-title">{% trans "Create new thread" %}</span>
49 <form enctype="multipart/form-data" method="post">{% csrf_token %}
49 <form enctype="multipart/form-data" method="post">{% csrf_token %}
50 <div class="form-row">
50 <div class="form-row">
51 <div class="form-input">{% trans 'Title' %}</div>
51 <div class="form-input">{% trans 'Title' %}</div>
52 <div class="form-input">{{ form.title }}</div>
52 <div class="form-input">{{ form.title }}</div>
53 </div>
53 </div>
54 <div class="form-row">
54 <div class="form-row">
55 <div class="form-input">{% trans 'Text' %}</div>
55 <div class="form-input">{% trans 'Text' %}</div>
56 <div class="form-input">{{ form.text }}</div>
56 <div class="form-input">{{ form.text }}</div>
57 </div>
57 </div>
58 <div class="form-row">
58 <div class="form-row">
59 <div class="form-input">{% trans 'Image' %}</div>
59 <div class="form-input">{% trans 'Image' %}</div>
60 <div class="form-input">{{ form.image }}</div>
60 <div class="form-input">{{ form.image }}</div>
61 </div>
61 </div>
62 <div class="form-row">
62 <div class="form-row">
63 <div class="form-input">{% trans 'Tags' %}</div>
63 <div class="form-input">{% trans 'Tags' %}</div>
64 <div class="form-input">{{ form.tags }}</div>
64 <div class="form-input">{{ form.tags }}</div>
65 </div>
65 </div>
66 <input type="submit" value="{% trans 'Post' %}" />
66 <input type="submit" value="{% trans 'Post' %}" />
67 <hr />
67 <hr />
68 {% trans "Tags must be delimited by spaces. Text or image is required." %}
68 {% trans "Tags must be delimited by spaces. Text or image is required." %}
69 </form>
69 </form>
70 </div>
70 </div>
71
71
72 {% endblock %} No newline at end of file
72 {% endblock %}
@@ -1,61 +1,61 b''
1 {% extends "base.html" %}
1 {% extends "base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block content %}
5 {% block content %}
6
6
7 {% if posts %}
7 {% if posts %}
8 {% for post in posts %}
8 {% for post in posts %}
9 <a name="{{ post.id }}"></a>
9 <a name="{{ post.id }}"></a>
10 <div class="post">
10 <div class="post">
11 {% if post.image %}
11 {% if post.image %}
12 <div class="block">
12 <div class="block">
13 <a href="{{ post.image.url }}"><img
13 <a href="{{ post.image.url }}"><img
14 src="{{ post.image.url_200x150 }}" />
14 src="{{ post.image.url_200x150 }}" />
15 </a>
15 </a>
16 </div>
16 </div>
17 {% endif %}
17 {% endif %}
18 <div class="block">
18 <div class="block">
19 <span class="title">{{ post.title }}</span>
19 <span class="title">{{ post.title }}</span>
20 <a class="post_id" href="#{{ post.id }}">
20 <a class="post_id" href="#{{ post.id }}">
21 (#{{ post.id }})</a>
21 (#{{ post.id }})</a>
22 [{{ post.pub_time }}]<br />
22 [{{ post.pub_time }}]<br />
23 {{ post.get_parsed_text|linebreaksbr }}<br />
23 {{ post.get_parsed_text|linebreaksbr }}<br />
24 {% if post.tags.all %}
25 <div class="metadata">
26 <span class="tags">{% trans 'Tags' %}:
27 {% for tag in post.tags.all %}
28 <a class="tag" href="{% url 'tag' tag.name %}">
29 {{ tag.name }}</a>
30 {% endfor %}
31 </span>
32 </div>
33 {% endif %}
34 </div>
24 </div>
25 {% if post.tags.all %}
26 <div class="metadata">
27 <span class="tags">{% trans 'Tags' %}:
28 {% for tag in post.tags.all %}
29 <a class="tag" href="{% url 'tag' tag.name %}">
30 {{ tag.name }}</a>
31 {% endfor %}
32 </span>
33 </div>
34 {% endif %}
35 </div>
35 </div>
36 {% endfor %}
36 {% endfor %}
37 {% else %}
37 {% else %}
38 No threads found.
38 No threads found.
39 <hr />
39 <hr />
40 {% endif %}
40 {% endif %}
41
41
42 <div class="post-form">
42 <div class="post-form">
43 <span class="form-title">{% trans "Reply to the thread" %}</span>
43 <span class="form-title">{% trans "Reply to the thread" %}</span>
44 <form enctype="multipart/form-data" method="post">{% csrf_token %}
44 <form enctype="multipart/form-data" method="post">{% csrf_token %}
45 <div class="form-row">
45 <div class="form-row">
46 <div class="form-input">{% trans 'Title' %}</div>
46 <div class="form-input">{% trans 'Title' %}</div>
47 <div class="form-input">{{ form.title }}</div>
47 <div class="form-input">{{ form.title }}</div>
48 </div>
48 </div>
49 <div class="form-row">
49 <div class="form-row">
50 <div class="form-input">{% trans 'Text' %}</div>
50 <div class="form-input">{% trans 'Text' %}</div>
51 <div class="form-input">{{ form.text }}</div>
51 <div class="form-input">{{ form.text }}</div>
52 </div>
52 </div>
53 <div class="form-row">
53 <div class="form-row">
54 <div class="form-input">{% trans 'Image' %}</div>
54 <div class="form-input">{% trans 'Image' %}</div>
55 <div class="form-input">{{ form.image }}</div>
55 <div class="form-input">{{ form.image }}</div>
56 </div>
56 </div>
57 <input type="submit" value="{% trans 'Post' %}" />
57 <input type="submit" value="{% trans 'Post' %}" />
58 </form>
58 </form>
59 </div>
59 </div>
60
60
61 {% endblock %} No newline at end of file
61 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now