##// END OF EJS Templates
Added missing migration, changed logs format
neko259 -
r742:03c0f4a4 2.0-dev
parent child Browse files
Show More
@@ -0,0 +1,73 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 # Deleting field 'Tag.linked'
12 db.delete_column(u'boards_tag', 'linked_id')
13
14
15 def backwards(self, orm):
16 # Adding field 'Tag.linked'
17 db.add_column(u'boards_tag', 'linked',
18 self.gf('django.db.models.fields.related.ForeignKey')(to=orm['boards.Tag'], null=True, blank=True),
19 keep_default=False)
20
21
22 models = {
23 'boards.ban': {
24 'Meta': {'object_name': 'Ban'},
25 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
26 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
27 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
28 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
29 },
30 'boards.post': {
31 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
32 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
35 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
36 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
37 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
38 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
39 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
40 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
41 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
42 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'bbcode'", 'max_length': '30'}),
43 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
44 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
45 },
46 'boards.postimage': {
47 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
48 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
49 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
50 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
51 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
52 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
53 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
54 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
55 },
56 'boards.tag': {
57 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
58 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
60 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
61 },
62 'boards.thread': {
63 'Meta': {'object_name': 'Thread'},
64 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
65 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
66 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
68 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
69 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
70 }
71 }
72
73 complete_apps = ['boards'] No newline at end of file
@@ -1,344 +1,345 b''
1 1 from datetime import datetime, timedelta, date
2 2 from datetime import time as dtime
3 3 import logging
4 4 import re
5 5
6 6 from django.core.cache import cache
7 7 from django.core.urlresolvers import reverse
8 8 from django.db import models, transaction
9 9 from django.template.loader import render_to_string
10 10 from django.utils import timezone
11 11 from markupfield.fields import MarkupField
12 12
13 13 from boards.models import PostImage
14 14 from boards.models.base import Viewable
15 15 from boards.models.thread import Thread
16 16
17 17
18 18 APP_LABEL_BOARDS = 'boards'
19 19
20 20 CACHE_KEY_PPD = 'ppd'
21 21 CACHE_KEY_POST_URL = 'post_url'
22 22
23 23 POSTS_PER_DAY_RANGE = range(7)
24 24
25 25 BAN_REASON_AUTO = 'Auto'
26 26
27 27 IMAGE_THUMB_SIZE = (200, 150)
28 28
29 29 TITLE_MAX_LENGTH = 200
30 30
31 31 DEFAULT_MARKUP_TYPE = 'bbcode'
32 32
33 33 # TODO This should be removed
34 34 NO_IP = '0.0.0.0'
35 35
36 36 # TODO Real user agent should be saved instead of this
37 37 UNKNOWN_UA = ''
38 38
39 39 REGEX_REPLY = re.compile(r'>>(\d+)')
40 40
41 41 logger = logging.getLogger(__name__)
42 42
43 43
44 44 class PostManager(models.Manager):
45 45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
46 46 tags=None):
47 47 """
48 48 Creates new post
49 49 """
50 50
51 51 posting_time = timezone.now()
52 52 if not thread:
53 53 thread = Thread.objects.create(bump_time=posting_time,
54 54 last_edit_time=posting_time)
55 55 new_thread = True
56 56 else:
57 57 thread.bump()
58 58 thread.last_edit_time = posting_time
59 59 thread.save()
60 60 new_thread = False
61 61
62 62 post = self.create(title=title,
63 63 text=text,
64 64 pub_time=posting_time,
65 65 thread_new=thread,
66 66 poster_ip=ip,
67 67 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
68 68 # last!
69 69 last_edit_time=posting_time)
70 70
71 71 if image:
72 72 post_image = PostImage.objects.create(image=image)
73 73 post.images.add(post_image)
74 74 logger.info('Created image #%d for post #%d' % (post_image.id,
75 75 post.id))
76 76
77 77 thread.replies.add(post)
78 78 if tags:
79 79 map(thread.add_tag, tags)
80 80
81 81 if new_thread:
82 82 Thread.objects.process_oldest_threads()
83 83 self.connect_replies(post)
84 84
85 logger.info('Created post #%d' % post.id)
85 logger.info('Created post #%d with title %s' % (post.id,
86 post.get_title()))
86 87
87 88 return post
88 89
89 90 def delete_post(self, post):
90 91 """
91 92 Deletes post and update or delete its thread
92 93 """
93 94
94 95 post_id = post.id
95 96
96 97 thread = post.get_thread()
97 98
98 99 if post.is_opening():
99 100 thread.delete()
100 101 else:
101 102 thread.last_edit_time = timezone.now()
102 103 thread.save()
103 104
104 105 post.delete()
105 106
106 logger.info('Deleted post #%d' % post_id)
107 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
107 108
108 109 def delete_posts_by_ip(self, ip):
109 110 """
110 111 Deletes all posts of the author with same IP
111 112 """
112 113
113 114 posts = self.filter(poster_ip=ip)
114 115 map(self.delete_post, posts)
115 116
116 117 def connect_replies(self, post):
117 118 """
118 119 Connects replies to a post to show them as a reflink map
119 120 """
120 121
121 122 for reply_number in re.finditer(REGEX_REPLY, post.text.rendered):
122 123 post_id = reply_number.group(1)
123 124 ref_post = self.filter(id=post_id)
124 125 if ref_post.count() > 0:
125 126 referenced_post = ref_post[0]
126 127 referenced_post.referenced_posts.add(post)
127 128 referenced_post.last_edit_time = post.pub_time
128 129 referenced_post.build_refmap()
129 130 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
130 131
131 132 referenced_thread = referenced_post.get_thread()
132 133 referenced_thread.last_edit_time = post.pub_time
133 134 referenced_thread.save(update_fields=['last_edit_time'])
134 135
135 136 def get_posts_per_day(self):
136 137 """
137 138 Gets average count of posts per day for the last 7 days
138 139 """
139 140
140 141 today = date.today()
141 142 ppd = cache.get(CACHE_KEY_PPD + str(today))
142 143 if ppd:
143 144 return ppd
144 145
145 146 posts_per_days = []
146 147 for i in POSTS_PER_DAY_RANGE:
147 148 day_end = today - timedelta(i + 1)
148 149 day_start = today - timedelta(i + 2)
149 150
150 151 day_time_start = timezone.make_aware(datetime.combine(
151 152 day_start, dtime()), timezone.get_current_timezone())
152 153 day_time_end = timezone.make_aware(datetime.combine(
153 154 day_end, dtime()), timezone.get_current_timezone())
154 155
155 156 posts_per_days.append(float(self.filter(
156 157 pub_time__lte=day_time_end,
157 158 pub_time__gte=day_time_start).count()))
158 159
159 160 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
160 161 len(posts_per_days))
161 162 cache.set(CACHE_KEY_PPD + str(today), ppd)
162 163 return ppd
163 164
164 165
165 166 class Post(models.Model, Viewable):
166 167 """A post is a message."""
167 168
168 169 objects = PostManager()
169 170
170 171 class Meta:
171 172 app_label = APP_LABEL_BOARDS
172 173 ordering = ('id',)
173 174
174 175 title = models.CharField(max_length=TITLE_MAX_LENGTH)
175 176 pub_time = models.DateTimeField()
176 177 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
177 178 escape_html=False)
178 179
179 180 images = models.ManyToManyField(PostImage, null=True, blank=True,
180 181 related_name='ip+', db_index=True)
181 182
182 183 poster_ip = models.GenericIPAddressField()
183 184 poster_user_agent = models.TextField()
184 185
185 186 thread_new = models.ForeignKey('Thread', null=True, default=None,
186 187 db_index=True)
187 188 last_edit_time = models.DateTimeField()
188 189
189 190 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
190 191 null=True,
191 192 blank=True, related_name='rfp+',
192 193 db_index=True)
193 194 refmap = models.TextField(null=True, blank=True)
194 195
195 196 def __unicode__(self):
196 197 return '#' + str(self.id) + ' ' + self.title + ' (' + \
197 198 self.text.raw[:50] + ')'
198 199
199 200 def get_title(self):
200 201 """
201 202 Gets original post title or part of its text.
202 203 """
203 204
204 205 title = self.title
205 206 if not title:
206 207 title = self.text.rendered
207 208
208 209 return title
209 210
210 211 def build_refmap(self):
211 212 """
212 213 Builds a replies map string from replies list. This is a cache to stop
213 214 the server from recalculating the map on every post show.
214 215 """
215 216 map_string = ''
216 217
217 218 first = True
218 219 for refpost in self.referenced_posts.all():
219 220 if not first:
220 221 map_string += ', '
221 222 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
222 223 refpost.id)
223 224 first = False
224 225
225 226 self.refmap = map_string
226 227
227 228 def get_sorted_referenced_posts(self):
228 229 return self.refmap
229 230
230 231 def is_referenced(self):
231 232 return len(self.refmap) > 0
232 233
233 234 def is_opening(self):
234 235 """
235 236 Checks if this is an opening post or just a reply.
236 237 """
237 238
238 239 return self.get_thread().get_opening_post_id() == self.id
239 240
240 241 @transaction.atomic
241 242 def add_tag(self, tag):
242 243 edit_time = timezone.now()
243 244
244 245 thread = self.get_thread()
245 246 thread.add_tag(tag)
246 247 self.last_edit_time = edit_time
247 248 self.save(update_fields=['last_edit_time'])
248 249
249 250 thread.last_edit_time = edit_time
250 251 thread.save(update_fields=['last_edit_time'])
251 252
252 253 @transaction.atomic
253 254 def remove_tag(self, tag):
254 255 edit_time = timezone.now()
255 256
256 257 thread = self.get_thread()
257 258 thread.remove_tag(tag)
258 259 self.last_edit_time = edit_time
259 260 self.save(update_fields=['last_edit_time'])
260 261
261 262 thread.last_edit_time = edit_time
262 263 thread.save(update_fields=['last_edit_time'])
263 264
264 265 def get_url(self, thread=None):
265 266 """
266 267 Gets full url to the post.
267 268 """
268 269
269 270 cache_key = CACHE_KEY_POST_URL + str(self.id)
270 271 link = cache.get(cache_key)
271 272
272 273 if not link:
273 274 if not thread:
274 275 thread = self.get_thread()
275 276
276 277 opening_id = thread.get_opening_post_id()
277 278
278 279 if self.id != opening_id:
279 280 link = reverse('thread', kwargs={
280 281 'post_id': opening_id}) + '#' + str(self.id)
281 282 else:
282 283 link = reverse('thread', kwargs={'post_id': self.id})
283 284
284 285 cache.set(cache_key, link)
285 286
286 287 return link
287 288
288 289 def get_thread(self):
289 290 """
290 291 Gets post's thread.
291 292 """
292 293
293 294 return self.thread_new
294 295
295 296 def get_referenced_posts(self):
296 297 return self.referenced_posts.only('id', 'thread_new')
297 298
298 299 def get_text(self):
299 300 return self.text
300 301
301 302 def get_view(self, moderator=False, need_open_link=False,
302 303 truncated=False, *args, **kwargs):
303 304 if 'is_opening' in kwargs:
304 305 is_opening = kwargs['is_opening']
305 306 else:
306 307 is_opening = self.is_opening()
307 308
308 309 if 'thread' in kwargs:
309 310 thread = kwargs['thread']
310 311 else:
311 312 thread = self.get_thread()
312 313
313 314 if 'can_bump' in kwargs:
314 315 can_bump = kwargs['can_bump']
315 316 else:
316 317 can_bump = thread.can_bump()
317 318
318 319 if is_opening:
319 320 opening_post_id = self.id
320 321 else:
321 322 opening_post_id = thread.get_opening_post_id()
322 323
323 324 return render_to_string('boards/post.html', {
324 325 'post': self,
325 326 'moderator': moderator,
326 327 'is_opening': is_opening,
327 328 'thread': thread,
328 329 'bumpable': can_bump,
329 330 'need_open_link': need_open_link,
330 331 'truncated': truncated,
331 332 'opening_post_id': opening_post_id,
332 333 })
333 334
334 335 def get_first_image(self):
335 336 return self.images.earliest('id')
336 337
337 338 def delete(self, using=None):
338 339 """
339 340 Deletes all post images and the post itself.
340 341 """
341 342
342 343 self.images.all().delete()
343 344
344 345 super(Post, self).delete(using)
General Comments 0
You need to be logged in to leave comments. Login now