##// END OF EJS Templates
Split up post model into post and thread to normalise models. Still need some refactoring
neko259 -
r398:b2448061 default
parent child Browse files
Show More
@@ -0,0 +1,115 b''
1 # -*- coding: utf-8 -*-
2 from south.db import db
3 from south.v2 import SchemaMigration
4 from django.db import models
5
6
7 class Migration(SchemaMigration):
8
9 def forwards(self, orm):
10 # Adding model 'Thread'
11 db.create_table(u'boards_thread', (
12 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
13 ('bump_time', self.gf('django.db.models.fields.DateTimeField')()),
14 ('last_edit_time', self.gf('django.db.models.fields.DateTimeField')()),
15 ))
16 db.send_create_signal('boards', ['Thread'])
17
18 # Adding M2M table for field tags on 'Thread'
19 m2m_table_name = db.shorten_name(u'boards_thread_tags')
20 db.create_table(m2m_table_name, (
21 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
22 ('thread', models.ForeignKey(orm['boards.thread'], null=False)),
23 ('tag', models.ForeignKey(orm['boards.tag'], null=False))
24 ))
25 db.create_unique(m2m_table_name, ['thread_id', 'tag_id'])
26
27 db.delete_table(db.shorten_name(u'boards_tag_threads'))
28 # Adding M2M table for field threads on 'Tag'
29 m2m_table_name = db.shorten_name(u'boards_tag_threads')
30 db.create_table(m2m_table_name, (
31 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
32 ('thread', models.ForeignKey(orm['boards.thread'], null=False)),
33 ('tag', models.ForeignKey(orm['boards.tag'], null=False))
34 ))
35 db.create_unique(m2m_table_name, ['thread_id', 'tag_id'])
36
37 # Adding M2M table for field replies on 'Thread'
38 m2m_table_name = db.shorten_name(u'boards_thread_replies')
39 db.create_table(m2m_table_name, (
40 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
41 ('thread', models.ForeignKey(orm['boards.thread'], null=False)),
42 ('post', models.ForeignKey(orm['boards.post'], null=False))
43 ))
44 db.create_unique(m2m_table_name, ['thread_id', 'post_id'])
45
46 db.add_column(u'boards_post', 'thread_new',
47 self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['boards.Thread'], null=True),
48 keep_default=False)
49
50 def backwards(self, orm):
51 pass
52
53 models = {
54 'boards.ban': {
55 'Meta': {'object_name': 'Ban'},
56 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
57 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
58 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
59 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
60 },
61 'boards.post': {
62 'Meta': {'object_name': 'Post'},
63 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
64 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
65 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
67 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
68 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
69 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
70 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
71 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
72 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
73 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
74 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'re+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
75 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'}),
76 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
77 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
78 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
79 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
80 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
81 },
82 'boards.setting': {
83 'Meta': {'object_name': 'Setting'},
84 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
85 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
86 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
87 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
88 },
89 'boards.tag': {
90 'Meta': {'object_name': 'Tag'},
91 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
93 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
94 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
95 },
96 'boards.thread': {
97 'Meta': {'object_name': 'Thread'},
98 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
99 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
101 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
102 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
103 },
104 'boards.user': {
105 'Meta': {'object_name': 'User'},
106 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
107 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
108 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
109 'rank': ('django.db.models.fields.IntegerField', [], {}),
110 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
111 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
112 }
113 }
114
115 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,98 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 DataMigration
5 from django.db import models
6 from boards import views
7
8
9 class Migration(DataMigration):
10
11 def forwards(self, orm):
12 for post in orm.Post.objects.filter(thread=None):
13 thread = orm.Thread.objects.create(
14 bump_time=post.bump_time,
15 last_edit_time=post.last_edit_time)
16
17 thread.replies.add(post)
18 post.thread_new = thread
19 post.save()
20 print str(post.thread_new.id)
21
22 for reply in post.replies.all():
23 thread.replies.add(reply)
24 reply.thread_new = thread
25 reply.save()
26
27 for tag in post.tags.all():
28 thread.tags.add(tag)
29 tag.threads.add(thread)
30
31 def backwards(self, orm):
32 pass
33
34 models = {
35 'boards.ban': {
36 'Meta': {'object_name': 'Ban'},
37 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
38 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
39 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
40 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
41 },
42 'boards.post': {
43 'Meta': {'object_name': 'Post'},
44 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
45 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
46 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
48 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
49 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
50 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
51 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
52 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
53 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
54 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
55 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'re+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
56 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'}),
57 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
58 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
59 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}),
60 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
61 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
62 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
63 },
64 'boards.setting': {
65 'Meta': {'object_name': 'Setting'},
66 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
68 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
69 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
70 },
71 'boards.tag': {
72 'Meta': {'object_name': 'Tag'},
73 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
74 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
75 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
76 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
77 },
78 'boards.thread': {
79 'Meta': {'object_name': 'Thread'},
80 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
81 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
82 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
83 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
84 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
85 },
86 'boards.user': {
87 'Meta': {'object_name': 'User'},
88 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
89 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
90 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
91 'rank': ('django.db.models.fields.IntegerField', [], {}),
92 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
93 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
94 }
95 }
96
97 complete_apps = ['boards']
98 symmetrical = True
@@ -0,0 +1,110 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 'Post.bump_time'
12 db.delete_column(u'boards_post', 'bump_time')
13
14 # Removing M2M table for field tags on 'Post'
15 db.delete_table(db.shorten_name(u'boards_post_tags'))
16
17 # Removing M2M table for field replies on 'Post'
18 db.delete_table(db.shorten_name(u'boards_post_replies'))
19
20
21 def backwards(self, orm):
22
23 # User chose to not deal with backwards NULL issues for 'Post.bump_time'
24 raise RuntimeError("Cannot reverse this migration. 'Post.bump_time' and its values cannot be restored.")
25
26 # The following code is provided here to aid in writing a correct migration # Adding field 'Post.bump_time'
27 db.add_column(u'boards_post', 'bump_time',
28 self.gf('django.db.models.fields.DateTimeField')(),
29 keep_default=False)
30
31 # Adding M2M table for field tags on 'Post'
32 m2m_table_name = db.shorten_name(u'boards_post_tags')
33 db.create_table(m2m_table_name, (
34 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
35 ('post', models.ForeignKey(orm['boards.post'], null=False)),
36 ('tag', models.ForeignKey(orm['boards.tag'], null=False))
37 ))
38 db.create_unique(m2m_table_name, ['post_id', 'tag_id'])
39
40 # Adding M2M table for field replies on 'Post'
41 m2m_table_name = db.shorten_name(u'boards_post_replies')
42 db.create_table(m2m_table_name, (
43 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
44 ('from_post', models.ForeignKey(orm['boards.post'], null=False)),
45 ('to_post', models.ForeignKey(orm['boards.post'], null=False))
46 ))
47 db.create_unique(m2m_table_name, ['from_post_id', 'to_post_id'])
48
49
50 models = {
51 'boards.ban': {
52 'Meta': {'object_name': 'Ban'},
53 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
54 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
56 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
57 },
58 'boards.post': {
59 'Meta': {'object_name': 'Post'},
60 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
61 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
62 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
63 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
64 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
65 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
66 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
67 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
68 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
69 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
70 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
71 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
72 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}),
73 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
74 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
75 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
76 },
77 'boards.setting': {
78 'Meta': {'object_name': 'Setting'},
79 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
80 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
81 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
82 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
83 },
84 'boards.tag': {
85 'Meta': {'object_name': 'Tag'},
86 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
87 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
88 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
89 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
90 },
91 'boards.thread': {
92 'Meta': {'object_name': 'Thread'},
93 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
94 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
95 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
96 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
97 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
98 },
99 'boards.user': {
100 'Meta': {'object_name': 'User'},
101 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
102 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
103 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
104 'rank': ('django.db.models.fields.IntegerField', [], {}),
105 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
106 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
107 }
108 }
109
110 complete_apps = ['boards'] No newline at end of file
@@ -5,7 +5,7 b' from boards.models import Post, Tag, Use'
5 class PostAdmin(admin.ModelAdmin):
5 class PostAdmin(admin.ModelAdmin):
6
6
7 list_display = ('id', 'title', 'text')
7 list_display = ('id', 'title', 'text')
8 list_filter = ('pub_time', 'tags')
8 list_filter = ('pub_time', 'thread_new')
9 search_fields = ('id', 'title', 'text')
9 search_fields = ('id', 'title', 'text')
10
10
11
11
@@ -14,6 +14,7 b" SPOILER_PATTERN = r'%%(.+)%%'"
14 COMMENT_PATTERN = r'^(//(.+))'
14 COMMENT_PATTERN = r'^(//(.+))'
15 STRIKETHROUGH_PATTERN = r'~(.+)~'
15 STRIKETHROUGH_PATTERN = r'~(.+)~'
16
16
17
17 class AutolinkPattern(Pattern):
18 class AutolinkPattern(Pattern):
18 def handleMatch(self, m):
19 def handleMatch(self, m):
19 link_element = etree.Element('a')
20 link_element = etree.Element('a')
@@ -41,17 +42,17 b' class ReflinkPattern(Pattern):'
41 if posts.count() > 0:
42 if posts.count() > 0:
42 ref_element = etree.Element('a')
43 ref_element = etree.Element('a')
43
44
44 post = posts[0]
45 post = posts[0]
45 if post.thread:
46 if post.thread:
46 link = reverse(boards.views.thread, kwargs={'post_id': post.thread.id}) \
47 link = reverse(boards.views.thread, kwargs={'post_id': post.thread.id}) \
47 + '#' + post_id
48 + '#' + post_id
48 else:
49 else:
49 link = reverse(boards.views.thread, kwargs={'post_id': post_id})
50 link = reverse(boards.views.thread, kwargs={'post_id': post_id})
50
51
51 ref_element.set('href', link)
52 ref_element.set('href', link)
52 ref_element.text = m.group(2)
53 ref_element.text = m.group(2)
53
54
54 return ref_element
55 return ref_element
55
56
56
57
57 class SpoilerPattern(Pattern):
58 class SpoilerPattern(Pattern):
@@ -1,6 +1,7 b''
1 __author__ = 'neko259'
1 __author__ = 'neko259'
2
2
3 from boards.models.post import Post
3 from boards.models.post import Post
4 from boards.models.post import Thread
4 from boards.models.tag import Tag
5 from boards.models.tag import Tag
5 from boards.models.user import Ban
6 from boards.models.user import Ban
6 from boards.models.user import Setting
7 from boards.models.user import Setting
@@ -38,18 +38,25 b' class PostManager(models.Manager):'
38 def create_post(self, title, text, image=None, thread=None,
38 def create_post(self, title, text, image=None, thread=None,
39 ip=NO_IP, tags=None, user=None):
39 ip=NO_IP, tags=None, user=None):
40 posting_time = timezone.now()
40 posting_time = timezone.now()
41 if not thread:
42 thread = Thread.objects.create(bump_time=posting_time,
43 last_edit_time=posting_time)
44 else:
45 thread.bump()
46 thread.last_edit_time = posting_time
47 thread.save()
41
48
42 post = self.create(title=title,
49 post = self.create(title=title,
43 text=text,
50 text=text,
44 pub_time=posting_time,
51 pub_time=posting_time,
45 thread=thread,
52 thread_new=thread,
46 image=image,
53 image=image,
47 poster_ip=ip,
54 poster_ip=ip,
48 poster_user_agent=UNKNOWN_UA,
55 poster_user_agent=UNKNOWN_UA,
49 last_edit_time=posting_time,
56 last_edit_time=posting_time,
50 bump_time=posting_time,
51 user=user)
57 user=user)
52
58
59 thread.replies.add(post)
53 if tags:
60 if tags:
54 linked_tags = []
61 linked_tags = []
55 for tag in tags:
62 for tag in tags:
@@ -58,31 +65,18 b' class PostManager(models.Manager):'
58 linked_tags.extend(tag_linked_tags)
65 linked_tags.extend(tag_linked_tags)
59
66
60 tags.extend(linked_tags)
67 tags.extend(linked_tags)
61 map(post.tags.add, tags)
68 map(thread.add_tag, tags)
62 for tag in tags:
63 tag.threads.add(post)
64
69
65 if thread:
70 self._delete_old_threads()
66 thread.replies.add(post)
67 thread.bump()
68 thread.last_edit_time = posting_time
69 thread.save()
70 else:
71 self._delete_old_threads()
72
73 self.connect_replies(post)
71 self.connect_replies(post)
74
72
75 return post
73 return post
76
74
75 # TODO Remove this method after migration
77 def delete_post(self, post):
76 def delete_post(self, post):
78 if post.replies.count() > 0:
77 thread = post.thread_new
79 map(self.delete_post, post.replies.all())
78 thread.last_edit_time = timezone.now()
80
79 thread.save()
81 # Update thread's last edit time
82 thread = post.thread
83 if thread:
84 thread.last_edit_time = timezone.now()
85 thread.save()
86
80
87 post.delete()
81 post.delete()
88
82
@@ -90,15 +84,16 b' class PostManager(models.Manager):'
90 posts = self.filter(poster_ip=ip)
84 posts = self.filter(poster_ip=ip)
91 map(self.delete_post, posts)
85 map(self.delete_post, posts)
92
86
87 # TODO Remove this method after migration
93 def get_threads(self, tag=None, page=ALL_PAGES,
88 def get_threads(self, tag=None, page=ALL_PAGES,
94 order_by='-bump_time'):
89 order_by='-bump_time'):
95 if tag:
90 if tag:
96 threads = tag.threads
91 threads = tag.threads
97
92
98 if threads.count() == 0:
93 if not threads.exists():
99 raise Http404
94 raise Http404
100 else:
95 else:
101 threads = self.filter(thread=None)
96 threads = Thread.objects.all()
102
97
103 threads = threads.order_by(order_by)
98 threads = threads.order_by(order_by)
104
99
@@ -113,26 +108,25 b' class PostManager(models.Manager):'
113
108
114 return threads
109 return threads
115
110
111 # TODO Remove this method after migration
116 def get_thread(self, opening_post_id):
112 def get_thread(self, opening_post_id):
117 try:
113 try:
118 opening_post = self.get(id=opening_post_id, thread=None)
114 opening_post = self.get(id=opening_post_id)
119 except Post.DoesNotExist:
115 except Post.DoesNotExist:
120 raise Http404
116 raise Http404
121
117
122 if opening_post.replies:
118 return opening_post.thread_new
123 thread = [opening_post]
124 thread.extend(opening_post.replies.all().order_by('pub_time'))
125
119
126 return thread
120 # TODO Move this method to thread manager
127
128 def get_thread_page_count(self, tag=None):
121 def get_thread_page_count(self, tag=None):
129 if tag:
122 if tag:
130 threads = self.filter(thread=None, tags=tag)
123 threads = Thread.objects.filter(tags=tag)
131 else:
124 else:
132 threads = self.filter(thread=None)
125 threads = Thread.objects.all()
133
126
134 return self._get_page_count(threads.count())
127 return self._get_page_count(threads.count())
135
128
129 # TODO Move this method to thread manager
136 def _delete_old_threads(self):
130 def _delete_old_threads(self):
137 """
131 """
138 Preserves maximum thread count. If there are too many threads,
132 Preserves maximum thread count. If there are too many threads,
@@ -143,14 +137,14 b' class PostManager(models.Manager):'
143 # Maybe make some 'old' field in the model to indicate the thread
137 # Maybe make some 'old' field in the model to indicate the thread
144 # must not be shown and be able for replying.
138 # must not be shown and be able for replying.
145
139
146 threads = self.get_threads()
140 threads = Thread.objects.all()
147 thread_count = threads.count()
141 thread_count = threads.count()
148
142
149 if thread_count > settings.MAX_THREAD_COUNT:
143 if thread_count > settings.MAX_THREAD_COUNT:
150 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
144 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
151 old_threads = threads[thread_count - num_threads_to_delete:]
145 old_threads = threads[thread_count - num_threads_to_delete:]
152
146
153 map(self.delete_post, old_threads)
147 map(Thread.delete_with_posts, old_threads)
154
148
155 def connect_replies(self, post):
149 def connect_replies(self, post):
156 """Connect replies to a post to show them as a refmap"""
150 """Connect replies to a post to show them as a refmap"""
@@ -204,13 +198,10 b' class Post(models.Model):'
204 poster_user_agent = models.TextField()
198 poster_user_agent = models.TextField()
205
199
206 thread = models.ForeignKey('Post', null=True, default=None)
200 thread = models.ForeignKey('Post', null=True, default=None)
207 tags = models.ManyToManyField('Tag')
201 thread_new = models.ForeignKey('Thread', null=True, default=None)
208 last_edit_time = models.DateTimeField()
202 last_edit_time = models.DateTimeField()
209 bump_time = models.DateTimeField()
210 user = models.ForeignKey('User', null=True, default=None)
203 user = models.ForeignKey('User', null=True, default=None)
211
204
212 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
213 blank=True, related_name='re+')
214 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
205 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
215 null=True,
206 null=True,
216 blank=True, related_name='rfp+')
207 blank=True, related_name='rfp+')
@@ -226,14 +217,40 b' class Post(models.Model):'
226
217
227 return title
218 return title
228
219
220 def get_sorted_referenced_posts(self):
221 return self.referenced_posts.order_by('id')
222
223 def is_referenced(self):
224 return self.referenced_posts.all().exists()
225
226
227 class Thread(models.Model):
228
229 class Meta:
230 app_label = 'boards'
231
232 tags = models.ManyToManyField('Tag')
233 bump_time = models.DateTimeField()
234 last_edit_time = models.DateTimeField()
235 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
236 blank=True, related_name='tre+')
237
238 def get_tags(self):
239 """Get a sorted tag list"""
240
241 return self.tags.order_by('name')
242
243 def bump(self):
244 """Bump (move to up) thread"""
245
246 if self.can_bump():
247 self.bump_time = timezone.now()
248
229 def get_reply_count(self):
249 def get_reply_count(self):
230 return self.replies.count()
250 return self.replies.count()
231
251
232 def get_images_count(self):
252 def get_images_count(self):
233 images_count = 1 if self.image else 0
253 return self.replies.filter(image_width__gt=0).count()
234 images_count += self.replies.filter(image_width__gt=0).count()
235
236 return images_count
237
254
238 def can_bump(self):
255 def can_bump(self):
239 """Check if the thread can be bumped by replying"""
256 """Check if the thread can be bumped by replying"""
@@ -242,31 +259,38 b' class Post(models.Model):'
242
259
243 return post_count <= settings.MAX_POSTS_PER_THREAD
260 return post_count <= settings.MAX_POSTS_PER_THREAD
244
261
245 def bump(self):
262 def delete_with_posts(self):
246 """Bump (move to up) thread"""
263 """Completely delete thread"""
247
264
248 if self.can_bump():
265 if self.replies.count() > 0:
249 self.bump_time = timezone.now()
266 map(Post.objects.delete_post, self.replies.all())
267
268 self.delete()
250
269
251 def get_last_replies(self):
270 def get_last_replies(self):
271 """Get last replies, not including opening post"""
272
252 if settings.LAST_REPLIES_COUNT > 0:
273 if settings.LAST_REPLIES_COUNT > 0:
253 reply_count = self.get_reply_count()
274 reply_count = self.get_reply_count()
254
275
255 if reply_count > 0:
276 if reply_count > 0:
256 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
277 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
257 reply_count)
278 reply_count - 1)
258 last_replies = self.replies.all().order_by('pub_time')[
279 last_replies = self.replies.all().order_by('pub_time')[
259 reply_count - reply_count_to_show:]
280 reply_count - reply_count_to_show:]
260
281
261 return last_replies
282 return last_replies
262
283
263 def get_tags(self):
284 def get_replies(self):
264 """Get a sorted tag list"""
285 """Get sorted thread posts"""
265
286
266 return self.tags.order_by('name')
287 return self.replies.all().order_by('pub_time')
267
288
268 def get_sorted_referenced_posts(self):
289 def add_tag(self, tag):
269 return self.referenced_posts.order_by('id')
290 """Connect thread to a tag and tag to a thread"""
270
291
271 def is_referenced(self):
292 self.tags.add(tag)
272 return self.referenced_posts.all().exists()
293 tag.threads.add(self)
294
295 def __unicode__(self):
296 return str(self.get_replies()[0].id) No newline at end of file
@@ -1,4 +1,4 b''
1 from boards.models import Post
1 from boards.models import Thread
2 from django.db import models
2 from django.db import models
3 from django.db.models import Count
3 from django.db.models import Count
4
4
@@ -20,8 +20,8 b' class TagManager(models.Manager):'
20
20
21 class Tag(models.Model):
21 class Tag(models.Model):
22 """
22 """
23 A tag is a text node assigned to the post. The tag serves as a board
23 A tag is a text node assigned to the thread. The tag serves as a board
24 section. There can be multiple tags for each message
24 section. There can be multiple tags for each thread
25 """
25 """
26
26
27 objects = TagManager()
27 objects = TagManager()
@@ -30,7 +30,7 b' class Tag(models.Model):'
30 app_label = 'boards'
30 app_label = 'boards'
31
31
32 name = models.CharField(max_length=100)
32 name = models.CharField(max_length=100)
33 threads = models.ManyToManyField('Post', null=True,
33 threads = models.ManyToManyField(Thread, null=True,
34 blank=True, related_name='tag+')
34 blank=True, related_name='tag+')
35 linked = models.ForeignKey('Tag', null=True, blank=True)
35 linked = models.ForeignKey('Tag', null=True, blank=True)
36
36
@@ -43,14 +43,15 b' class Tag(models.Model):'
43 def get_post_count(self):
43 def get_post_count(self):
44 return self.threads.count()
44 return self.threads.count()
45
45
46 def get_popularity(self):
46 # TODO Reenable this method after migration
47 posts_with_tag = Post.objects.get_threads(tag=self)
47 # def get_popularity(self):
48 reply_count = 0
48 # posts_with_tag = Thread.objects.get_threads(tag=self)
49 for post in posts_with_tag:
49 # reply_count = 0
50 reply_count += post.get_reply_count()
50 # for post in posts_with_tag:
51 reply_count += OPENING_POST_POPULARITY_WEIGHT
51 # reply_count += post.get_reply_count()
52
52 # reply_count += OPENING_POST_POPULARITY_WEIGHT
53 return reply_count
53 #
54 # return reply_count
54
55
55 def get_linked_tags(self):
56 def get_linked_tags(self):
56 tag_list = []
57 tag_list = []
@@ -104,7 +104,7 b' p {'
104 border: solid 1px #888;
104 border: solid 1px #888;
105 color: #fff;
105 color: #fff;
106 padding: 10px;
106 padding: 10px;
107 margin: 5;
107 margin: 5px;
108 }
108 }
109
109
110 .form-row {
110 .form-row {
@@ -55,10 +55,10 b''
55 </div>
55 </div>
56 {% endif %}
56 {% endif %}
57 </div>
57 </div>
58 {% if post.tags.exists %}
58 {% if post.thread.tags.exists %}
59 <div class="metadata">
59 <div class="metadata">
60 <span class="tags">{% trans 'Tags' %}:
60 <span class="tags">{% trans 'Tags' %}:
61 {% for tag in post.tags.all %}
61 {% for tag in post.thread.get_tags %}
62 <a class="tag" href="{% url 'tag' tag.name %}">
62 <a class="tag" href="{% url 'tag' tag.name %}">
63 {{ tag.name }}</a>
63 {{ tag.name }}</a>
64 {% endfor %}
64 {% endfor %}
@@ -68,47 +68,50 b''
68 {% cache 600 thread_short thread.thread.last_edit_time moderator LANGUAGE_CODE %}
68 {% cache 600 thread_short thread.thread.last_edit_time moderator LANGUAGE_CODE %}
69 <div class="thread">
69 <div class="thread">
70 {% if thread.bumpable %}
70 {% if thread.bumpable %}
71 <div class="post" id="{{ thread.thread.id }}">
71 <div class="post" id="{{ thread.op.id }}">
72 {% else %}
72 {% else %}
73 <div class="post dead_post" id="{{ thread.thread.id }}">
73 <div class="post dead_post" id="{{ thread.op.id }}">
74 {% endif %}
74 {% endif %}
75 {% if thread.thread.image %}
75 {% if thread.op.image %}
76 <div class="image">
76 <div class="image">
77 <a class="thumb"
77 <a class="thumb"
78 href="{{ thread.thread.image.url }}"><img
78 href="{{ thread.op.image.url }}"><img
79 src="{{ thread.thread.image.url_200x150 }}"
79 src="{{ thread.op.image.url_200x150 }}"
80 alt="{{ thread.thread.id }}"
80 alt="{{ thread.op.id }}"
81 data-width="{{ thread.thread.image_width }}"
81 data-width="{{ thread.op.image_width }}"
82 data-height="{{ thread.thread.image_height }}" />
82 data-height="{{ thread.op.image_height }}"/>
83 </a>
83 </a>
84 </div>
84 </div>
85 {% endif %}
85 {% endif %}
86 <div class="message">
86 <div class="message">
87 <div class="post-info">
87 <div class="post-info">
88 <span class="title">{{ thread.thread.title }}</span>
88 <span class="title">{{ thread.op.title }}</span>
89 <a class="post_id" href="{% url 'thread' thread.thread.id %}"
89 <a class="post_id" href="{% url 'thread' thread.op.id %}"
90 >({{ thread.thread.id }})</a>
90 >({{ thread.op.id }})</a>
91 [{{ thread.thread.pub_time }}]
91 [{{ thread.op.pub_time }}]
92 [<a class="link" href="{% url 'thread' thread.thread.id %}#form"
92 [<a class="link" href="
93 {% url 'thread' thread.op.id %}#form"
93 >{% trans "Reply" %}</a>]
94 >{% trans "Reply" %}</a>]
94
95
95 {% if moderator %}
96 {% if moderator %}
96 <span class="moderator_info">
97 <span class="moderator_info">
97 [<a href="{% url 'delete' post_id=thread.thread.id %}?next={{ request.path }}"
98 [<a href="
99 {% url 'delete' post_id=thread.op.id %}?next={{ request.path }}"
98 >{% trans 'Delete' %}</a>]
100 >{% trans 'Delete' %}</a>]
99 ({{ thread.thread.poster_ip }})
101 ({{ thread.thread.poster_ip }})
100 [<a href="{% url 'ban' post_id=thread.thread.id %}?next={{ request.path }}"
102 [<a href="
103 {% url 'ban' post_id=thread.op.id %}?next={{ request.path }}"
101 >{% trans 'Ban IP' %}</a>]
104 >{% trans 'Ban IP' %}</a>]
102 </span>
105 </span>
103 {% endif %}
106 {% endif %}
104 </div>
107 </div>
105 {% autoescape off %}
108 {% autoescape off %}
106 {{ thread.thread.text.rendered|truncatewords_html:50 }}
109 {{ thread.op.text.rendered|truncatewords_html:50 }}
107 {% endautoescape %}
110 {% endautoescape %}
108 {% if thread.thread.is_referenced %}
111 {% if thread.op.is_referenced %}
109 <div class="refmap">
112 <div class="refmap">
110 {% trans "Replies" %}:
113 {% trans "Replies" %}:
111 {% for ref_post in thread.thread.get_sorted_referenced_posts %}
114 {% for ref_post in thread.op.get_sorted_referenced_posts %}
112 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
115 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
113 >{% if not forloop.last %},{% endif %}
116 >{% if not forloop.last %},{% endif %}
114 {% endfor %}
117 {% endfor %}
@@ -153,7 +156,7 b''
153 <div class="post-info">
156 <div class="post-info">
154 <span class="title">{{ post.title }}</span>
157 <span class="title">{{ post.title }}</span>
155 <a class="post_id" href="
158 <a class="post_id" href="
156 {% url 'thread' thread.thread.id %}#{{ post.id }}">
159 {% url 'thread' thread.op.id %}#{{ post.id }}">
157 ({{ post.id }})</a>
160 ({{ post.id }})</a>
158 [{{ post.pub_time }}]
161 [{{ post.pub_time }}]
159 </div>
162 </div>
@@ -257,7 +260,7 b''
257 {% block metapanel %}
260 {% block metapanel %}
258
261
259 <span class="metapanel">
262 <span class="metapanel">
260 <b><a href="{% url "authors" %}">Neboard</a> 1.3</b>
263 <b><a href="{% url "authors" %}">Neboard</a> 1.4</b>
261 {% trans "Pages:" %}
264 {% trans "Pages:" %}
262 {% for page in pages %}
265 {% for page in pages %}
263 [<a href="
266 [<a href="
@@ -15,84 +15,82 b''
15 <script src="{% static 'js/thread_update.js' %}"></script>
15 <script src="{% static 'js/thread_update.js' %}"></script>
16 <script src="{% static 'js/thread.js' %}"></script>
16 <script src="{% static 'js/thread.js' %}"></script>
17
17
18 {% if posts %}
18 {% cache 600 thread_view thread.last_edit_time moderator LANGUAGE_CODE %}
19 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
19 {% if bumpable %}
20 {% if bumpable %}
20 <div class="bar-bg">
21 <div class="bar-bg">
21 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
22 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
23 </div>
24 <div class="bar-text">
25 {{ posts_left }} {% trans 'posts to bumplimit' %}
26 </div>
27 </div>
22 </div>
28 {% endif %}
23 <div class="bar-text">
29 <div class="thread">
24 {{ posts_left }} {% trans 'posts to bumplimit' %}
30 {% for post in posts %}
31 {% if bumpable %}
32 <div class="post" id="{{ post.id }}">
33 {% else %}
34 <div class="post dead_post" id="{{ post.id }}">
35 {% endif %}
36 {% if post.image %}
37 <div class="image">
38 <a
39 class="thumb"
40 href="{{ post.image.url }}"><img
41 src="{{ post.image.url_200x150 }}"
42 alt="{{ post.id }}"
43 data-width="{{ post.image_width }}"
44 data-height="{{ post.image_height }}"/>
45 </a>
46 </div>
25 </div>
47 {% endif %}
26 </div>
48 <div class="message">
27 {% endif %}
49 <div class="post-info">
28 <div class="thread">
50 <span class="title">{{ post.title }}</span>
29 {% for post in thread.get_replies %}
51 <a class="post_id" href="#{{ post.id }}">
30 {% if bumpable %}
52 ({{ post.id }})</a>
31 <div class="post" id="{{ post.id }}">
53 [{{ post.pub_time }}]
32 {% else %}
54 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
33 <div class="post dead_post" id="{{ post.id }}">
55 ; return false;">&gt;&gt;</a>]
34 {% endif %}
56
35 {% if post.image %}
57 {% if moderator %}
36 <div class="image">
58 <span class="moderator_info">
37 <a
59 [<a href="{% url 'delete' post_id=post.id %}"
38 class="thumb"
60 >{% trans 'Delete' %}</a>]
39 href="{{ post.image.url }}"><img
61 ({{ post.poster_ip }})
40 src="{{ post.image.url_200x150 }}"
62 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
41 alt="{{ post.id }}"
63 >{% trans 'Ban IP' %}</a>]
42 data-width="{{ post.image_width }}"
64 </span>
43 data-height="{{ post.image_height }}"/>
65 {% endif %}
44 </a>
66 </div>
67 {% autoescape off %}
68 {{ post.text.rendered }}
69 {% endautoescape %}
70 {% if post.is_referenced %}
71 <div class="refmap">
72 {% trans "Replies" %}:
73 {% for ref_post in post.get_sorted_referenced_posts %}
74 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
75 >{% if not forloop.last %},{% endif %}
76 {% endfor %}
77 </div>
45 </div>
78 {% endif %}
46 {% endif %}
47 <div class="message">
48 <div class="post-info">
49 <span class="title">{{ post.title }}</span>
50 <a class="post_id" href="#{{ post.id }}">
51 ({{ post.id }})</a>
52 [{{ post.pub_time }}]
53 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
54 ; return false;">&gt;&gt;</a>]
55
56 {% if moderator %}
57 <span class="moderator_info">
58 [<a href="{% url 'delete' post_id=post.id %}"
59 >{% trans 'Delete' %}</a>]
60 ({{ post.poster_ip }})
61 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
62 >{% trans 'Ban IP' %}</a>]
63 </span>
64 {% endif %}
79 </div>
65 </div>
80 {% if forloop.first %}
66 {% autoescape off %}
81 <div class="metadata">
67 {{ post.text.rendered }}
82 <span class="tags">
68 {% endautoescape %}
83 {% for tag in post.get_tags %}
69 {% if post.is_referenced %}
84 <a class="tag" href="{% url 'tag' tag.name %}">
70 <div class="refmap">
85 #{{ tag.name }}</a
71 {% trans "Replies" %}:
86 >{% if not forloop.last %},{% endif %}
72 {% for ref_post in post.get_sorted_referenced_posts %}
87 {% endfor %}
73 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
88 </span>
74 >{% if not forloop.last %},{% endif %}
89 </div>
90 {% endif %}
91 </div>
92 {% endfor %}
75 {% endfor %}
93 </div>
76 </div>
94 {% endcache %}
95 {% endif %}
77 {% endif %}
78 </div>
79 {% if forloop.first %}
80 <div class="metadata">
81 <span class="tags">
82 {% for tag in thread.get_tags %}
83 <a class="tag" href="{% url 'tag' tag.name %}">
84 #{{ tag.name }}</a
85 >{% if not forloop.last %},{% endif %}
86 {% endfor %}
87 </span>
88 </div>
89 {% endif %}
90 </div>
91 {% endfor %}
92 </div>
93 {% endcache %}
96
94
97 <form id="form" enctype="multipart/form-data" method="post"
95 <form id="form" enctype="multipart/form-data" method="post"
98 >{% csrf_token %}
96 >{% csrf_token %}
@@ -152,10 +150,10 b''
152 {% get_current_language as LANGUAGE_CODE %}
150 {% get_current_language as LANGUAGE_CODE %}
153
151
154 <span class="metapanel" data-last-update="{{ last_update }}">
152 <span class="metapanel" data-last-update="{{ last_update }}">
155 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
153 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
156 <span id="reply-count">{{ posts.0.get_reply_count }}</span> {% trans 'replies' %},
154 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
157 <span id="image-count">{{ posts.0.get_images_count }}</span> {% trans 'images' %}.
155 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
158 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
156 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
159 [<a href="rss/">RSS</a>]
157 [<a href="rss/">RSS</a>]
160 {% endcache %}
158 {% endcache %}
161 </span>
159 </span>
@@ -30,8 +30,7 b' class PostTests(TestCase):'
30
30
31 post = self._create_post()
31 post = self._create_post()
32
32
33 self.assertIsNotNone(post)
33 self.assertIsNotNone(post, 'No post was created')
34 self.assertIsNone(post.thread, 'Opening post has a thread')
35
34
36 def test_delete_post(self):
35 def test_delete_post(self):
37 """Test post deletion"""
36 """Test post deletion"""
@@ -46,10 +45,11 b' class PostTests(TestCase):'
46 def test_delete_thread(self):
45 def test_delete_thread(self):
47 """Test thread deletion"""
46 """Test thread deletion"""
48
47
49 thread = self._create_post()
48 opening_post = self._create_post()
49 thread = opening_post.thread_new
50 reply = Post.objects.create_post("", "", thread=thread)
50 reply = Post.objects.create_post("", "", thread=thread)
51
51
52 Post.objects.delete_post(thread)
52 thread.delete_with_posts()
53
53
54 self.assertFalse(Post.objects.filter(id=reply.id).exists())
54 self.assertFalse(Post.objects.filter(id=reply.id).exists())
55
55
@@ -57,10 +57,10 b' class PostTests(TestCase):'
57 """Test adding post to a thread"""
57 """Test adding post to a thread"""
58
58
59 op = self._create_post()
59 op = self._create_post()
60 post = Post.objects.create_post("", "", thread=op)
60 post = Post.objects.create_post("", "", thread=op.thread_new)
61
61
62 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
62 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
63 self.assertEqual(op.last_edit_time, post.pub_time,
63 self.assertEqual(op.thread_new.last_edit_time, post.pub_time,
64 'Post\'s create time doesn\'t match thread last edit'
64 'Post\'s create time doesn\'t match thread last edit'
65 ' time')
65 ' time')
66
66
@@ -80,18 +80,23 b' class PostTests(TestCase):'
80 opening_post = self._create_post()
80 opening_post = self._create_post()
81
81
82 for i in range(0, 2):
82 for i in range(0, 2):
83 Post.objects.create_post('title', 'text', thread=opening_post)
83 Post.objects.create_post('title', 'text',
84 thread=opening_post.thread_new)
84
85
85 thread = Post.objects.get_thread(opening_post.id)
86 thread = Post.objects.get_thread(opening_post.id)
86
87
87 self.assertEqual(3, len(thread))
88 self.assertEqual(3, thread.replies.count())
88
89
89 def test_create_post_with_tag(self):
90 def test_create_post_with_tag(self):
90 """Test adding tag to post"""
91 """Test adding tag to post"""
91
92
92 tag = Tag.objects.create(name='test_tag')
93 tag = Tag.objects.create(name='test_tag')
93 post = Post.objects.create_post(title='title', text='text', tags=[tag])
94 post = Post.objects.create_post(title='title', text='text', tags=[tag])
94 self.assertIsNotNone(post)
95
96 thread = post.thread_new
97 self.assertIsNotNone(post, 'Post not created')
98 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
99 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
95
100
96 def test_thread_max_count(self):
101 def test_thread_max_count(self):
97 """Test deletion of old posts when the max thread count is reached"""
102 """Test deletion of old posts when the max thread count is reached"""
@@ -124,7 +129,7 b' class PostTests(TestCase):'
124
129
125 post = Post.objects.create_post("", "", tags=[tag])
130 post = Post.objects.create_post("", "", tags=[tag])
126
131
127 self.assertTrue(linked_tag in post.tags.all(),
132 self.assertTrue(linked_tag in post.thread_new.tags.all(),
128 'Linked tag was not added')
133 'Linked tag was not added')
129
134
130
135
@@ -162,7 +167,8 b' class PagesTest(TestCase):'
162 u'Not existing tag is opened')
167 u'Not existing tag is opened')
163
168
164 reply_id = Post.objects.create_post('', TEST_TEXT,
169 reply_id = Post.objects.create_post('', TEST_TEXT,
165 thread=Post.objects.all()[0])
170 thread=Post.objects.all()[0]
171 .thread)
166 response_not_existing = client.get(THREAD_PAGE + str(
172 response_not_existing = client.get(THREAD_PAGE + str(
167 reply_id) + '/')
173 reply_id) + '/')
168 self.assertEqual(PAGE_404,
174 self.assertEqual(PAGE_404,
@@ -54,11 +54,12 b' def index(request, page=0):'
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
55
55
56 threads = []
56 threads = []
57 for thread in Post.objects.get_threads(page=int(page)):
57 for thread_to_show in Post.objects.get_threads(page=int(page)):
58 threads.append({
58 threads.append({
59 'thread': thread,
59 'thread': thread_to_show,
60 'bumpable': thread.can_bump(),
60 'op': thread_to_show.get_replies()[0],
61 'last_replies': thread.get_last_replies(),
61 'bumpable': thread_to_show.can_bump(),
62 'last_replies': thread_to_show.get_last_replies(),
62 })
63 })
63
64
64 # TODO Make this generic for tag and threads list pages
65 # TODO Make this generic for tag and threads list pages
@@ -111,9 +112,12 b' def _new_post(request, form, opening_pos'
111 if len(tag_name) > 0:
112 if len(tag_name) > 0:
112 tag, created = Tag.objects.get_or_create(name=tag_name)
113 tag, created = Tag.objects.get_or_create(name=tag_name)
113 tags.append(tag)
114 tags.append(tag)
115 post_thread = None
116 else:
117 post_thread = opening_post.thread_new
114
118
115 post = Post.objects.create_post(title=title, text=text, ip=ip,
119 post = Post.objects.create_post(title=title, text=text, ip=ip,
116 thread=opening_post, image=image,
120 thread=post_thread, image=image,
117 tags=tags, user=_get_user(request))
121 tags=tags, user=_get_user(request))
118
122
119 thread_to_show = (opening_post.id if opening_post else post.id)
123 thread_to_show = (opening_post.id if opening_post else post.id)
@@ -133,12 +137,13 b' def tag(request, tag_name, page=0):'
133
137
134 tag = get_object_or_404(Tag, name=tag_name)
138 tag = get_object_or_404(Tag, name=tag_name)
135 threads = []
139 threads = []
136 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
140 for thread_to_show in Post.objects.get_threads(page=int(page)):
137 threads.append({
141 threads.append({
138 'thread': thread,
142 'thread': thread_to_show,
139 'bumpable': thread.can_bump(),
143 'op': thread_to_show.get_replies()[0],
140 'last_replies': thread.get_last_replies(),
144 'bumpable': thread_to_show.can_bump(),
141 })
145 'last_replies': thread_to_show.get_last_replies(),
146 })
142
147
143 if request.method == 'POST':
148 if request.method == 'POST':
144 form = ThreadForm(request.POST, request.FILES,
149 form = ThreadForm(request.POST, request.FILES,
@@ -196,20 +201,21 b' def thread(request, post_id):'
196 else:
201 else:
197 form = postFormClass(error_class=PlainErrorList, **kwargs)
202 form = postFormClass(error_class=PlainErrorList, **kwargs)
198
203
199 posts = Post.objects.get_thread(post_id)
204 thread_to_show = get_object_or_404(Post, id=post_id).thread_new
200
205
201 context = _init_default_context(request)
206 context = _init_default_context(request)
202
207
203 context['posts'] = posts
208 posts = thread_to_show.get_replies()
204 context['form'] = form
209 context['form'] = form
205 context['bumpable'] = posts[0].can_bump()
210 context['bumpable'] = thread_to_show.can_bump()
206 if context['bumpable']:
211 if context['bumpable']:
207 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
212 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts\
208 posts)
213 .count()
209 context['bumplimit_progress'] = str(
214 context['bumplimit_progress'] = str(
210 float(context['posts_left']) /
215 float(context['posts_left']) /
211 neboard.settings.MAX_POSTS_PER_THREAD * 100)
216 neboard.settings.MAX_POSTS_PER_THREAD * 100)
212 context["last_update"] = _datetime_to_epoch(posts[0].last_edit_time)
217 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
218 context["thread"] = thread_to_show
213
219
214 return render(request, 'boards/thread.html', context)
220 return render(request, 'boards/thread.html', context)
215
221
@@ -9,6 +9,8 b' denied". Use second only for autoban for'
9 [DONE] Clean up tests and make them run ALWAYS
9 [DONE] Clean up tests and make them run ALWAYS
10 [DONE] Use transactions in tests
10 [DONE] Use transactions in tests
11 [DONE] Thread autoupdate (JS + API)
11 [DONE] Thread autoupdate (JS + API)
12 [IN PROGRESS] Split up post model into post and thread,
13 and move everything that is used only in 1st post to thread model.
12
14
13 [NOT STARTED] Tree view (JS)
15 [NOT STARTED] Tree view (JS)
14 [NOT STARTED] Adding tags to images filename
16 [NOT STARTED] Adding tags to images filename
@@ -22,8 +24,6 b' denied". Use second only for autoban for'
22 [NOT STARTED] Character counter in the post field
24 [NOT STARTED] Character counter in the post field
23 [NOT STARTED] Save image thumbnails size to the separate field
25 [NOT STARTED] Save image thumbnails size to the separate field
24 [NOT STARTED] Whitelist functionality. Permin autoban of an address
26 [NOT STARTED] Whitelist functionality. Permin autoban of an address
25 [NOT STARTED] Split up post model into post and thread,
26 and move everything that is used only in 1st post to thread model.
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
28 performance), posts per day/week/month, users (or IPs)
28 performance), posts per day/week/month, users (or IPs)
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
General Comments 0
You need to be logged in to leave comments. Login now