##// 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 5 class PostAdmin(admin.ModelAdmin):
6 6
7 7 list_display = ('id', 'title', 'text')
8 list_filter = ('pub_time', 'tags')
8 list_filter = ('pub_time', 'thread_new')
9 9 search_fields = ('id', 'title', 'text')
10 10
11 11
@@ -14,6 +14,7 b" SPOILER_PATTERN = r'%%(.+)%%'"
14 14 COMMENT_PATTERN = r'^(//(.+))'
15 15 STRIKETHROUGH_PATTERN = r'~(.+)~'
16 16
17
17 18 class AutolinkPattern(Pattern):
18 19 def handleMatch(self, m):
19 20 link_element = etree.Element('a')
@@ -1,6 +1,7 b''
1 1 __author__ = 'neko259'
2 2
3 3 from boards.models.post import Post
4 from boards.models.post import Thread
4 5 from boards.models.tag import Tag
5 6 from boards.models.user import Ban
6 7 from boards.models.user import Setting
@@ -38,18 +38,25 b' class PostManager(models.Manager):'
38 38 def create_post(self, title, text, image=None, thread=None,
39 39 ip=NO_IP, tags=None, user=None):
40 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 49 post = self.create(title=title,
43 50 text=text,
44 51 pub_time=posting_time,
45 thread=thread,
52 thread_new=thread,
46 53 image=image,
47 54 poster_ip=ip,
48 55 poster_user_agent=UNKNOWN_UA,
49 56 last_edit_time=posting_time,
50 bump_time=posting_time,
51 57 user=user)
52 58
59 thread.replies.add(post)
53 60 if tags:
54 61 linked_tags = []
55 62 for tag in tags:
@@ -58,29 +65,16 b' class PostManager(models.Manager):'
58 65 linked_tags.extend(tag_linked_tags)
59 66
60 67 tags.extend(linked_tags)
61 map(post.tags.add, tags)
62 for tag in tags:
63 tag.threads.add(post)
68 map(thread.add_tag, tags)
64 69
65 if thread:
66 thread.replies.add(post)
67 thread.bump()
68 thread.last_edit_time = posting_time
69 thread.save()
70 else:
71 70 self._delete_old_threads()
72
73 71 self.connect_replies(post)
74 72
75 73 return post
76 74
75 # TODO Remove this method after migration
77 76 def delete_post(self, post):
78 if post.replies.count() > 0:
79 map(self.delete_post, post.replies.all())
80
81 # Update thread's last edit time
82 thread = post.thread
83 if thread:
77 thread = post.thread_new
84 78 thread.last_edit_time = timezone.now()
85 79 thread.save()
86 80
@@ -90,15 +84,16 b' class PostManager(models.Manager):'
90 84 posts = self.filter(poster_ip=ip)
91 85 map(self.delete_post, posts)
92 86
87 # TODO Remove this method after migration
93 88 def get_threads(self, tag=None, page=ALL_PAGES,
94 89 order_by='-bump_time'):
95 90 if tag:
96 91 threads = tag.threads
97 92
98 if threads.count() == 0:
93 if not threads.exists():
99 94 raise Http404
100 95 else:
101 threads = self.filter(thread=None)
96 threads = Thread.objects.all()
102 97
103 98 threads = threads.order_by(order_by)
104 99
@@ -113,26 +108,25 b' class PostManager(models.Manager):'
113 108
114 109 return threads
115 110
111 # TODO Remove this method after migration
116 112 def get_thread(self, opening_post_id):
117 113 try:
118 opening_post = self.get(id=opening_post_id, thread=None)
114 opening_post = self.get(id=opening_post_id)
119 115 except Post.DoesNotExist:
120 116 raise Http404
121 117
122 if opening_post.replies:
123 thread = [opening_post]
124 thread.extend(opening_post.replies.all().order_by('pub_time'))
118 return opening_post.thread_new
125 119
126 return thread
127
120 # TODO Move this method to thread manager
128 121 def get_thread_page_count(self, tag=None):
129 122 if tag:
130 threads = self.filter(thread=None, tags=tag)
123 threads = Thread.objects.filter(tags=tag)
131 124 else:
132 threads = self.filter(thread=None)
125 threads = Thread.objects.all()
133 126
134 127 return self._get_page_count(threads.count())
135 128
129 # TODO Move this method to thread manager
136 130 def _delete_old_threads(self):
137 131 """
138 132 Preserves maximum thread count. If there are too many threads,
@@ -143,14 +137,14 b' class PostManager(models.Manager):'
143 137 # Maybe make some 'old' field in the model to indicate the thread
144 138 # must not be shown and be able for replying.
145 139
146 threads = self.get_threads()
140 threads = Thread.objects.all()
147 141 thread_count = threads.count()
148 142
149 143 if thread_count > settings.MAX_THREAD_COUNT:
150 144 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
151 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 149 def connect_replies(self, post):
156 150 """Connect replies to a post to show them as a refmap"""
@@ -204,13 +198,10 b' class Post(models.Model):'
204 198 poster_user_agent = models.TextField()
205 199
206 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 202 last_edit_time = models.DateTimeField()
209 bump_time = models.DateTimeField()
210 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 205 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
215 206 null=True,
216 207 blank=True, related_name='rfp+')
@@ -226,14 +217,40 b' class Post(models.Model):'
226 217
227 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 249 def get_reply_count(self):
230 250 return self.replies.count()
231 251
232 252 def get_images_count(self):
233 images_count = 1 if self.image else 0
234 images_count += self.replies.filter(image_width__gt=0).count()
235
236 return images_count
253 return self.replies.filter(image_width__gt=0).count()
237 254
238 255 def can_bump(self):
239 256 """Check if the thread can be bumped by replying"""
@@ -242,31 +259,38 b' class Post(models.Model):'
242 259
243 260 return post_count <= settings.MAX_POSTS_PER_THREAD
244 261
245 def bump(self):
246 """Bump (move to up) thread"""
262 def delete_with_posts(self):
263 """Completely delete thread"""
247 264
248 if self.can_bump():
249 self.bump_time = timezone.now()
265 if self.replies.count() > 0:
266 map(Post.objects.delete_post, self.replies.all())
267
268 self.delete()
250 269
251 270 def get_last_replies(self):
271 """Get last replies, not including opening post"""
272
252 273 if settings.LAST_REPLIES_COUNT > 0:
253 274 reply_count = self.get_reply_count()
254 275
255 276 if reply_count > 0:
256 277 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
257 reply_count)
278 reply_count - 1)
258 279 last_replies = self.replies.all().order_by('pub_time')[
259 280 reply_count - reply_count_to_show:]
260 281
261 282 return last_replies
262 283
263 def get_tags(self):
264 """Get a sorted tag list"""
284 def get_replies(self):
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):
269 return self.referenced_posts.order_by('id')
289 def add_tag(self, tag):
290 """Connect thread to a tag and tag to a thread"""
270 291
271 def is_referenced(self):
272 return self.referenced_posts.all().exists()
292 self.tags.add(tag)
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 2 from django.db import models
3 3 from django.db.models import Count
4 4
@@ -20,8 +20,8 b' class TagManager(models.Manager):'
20 20
21 21 class Tag(models.Model):
22 22 """
23 A tag is a text node assigned to the post. The tag serves as a board
24 section. There can be multiple tags for each message
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 thread
25 25 """
26 26
27 27 objects = TagManager()
@@ -30,7 +30,7 b' class Tag(models.Model):'
30 30 app_label = 'boards'
31 31
32 32 name = models.CharField(max_length=100)
33 threads = models.ManyToManyField('Post', null=True,
33 threads = models.ManyToManyField(Thread, null=True,
34 34 blank=True, related_name='tag+')
35 35 linked = models.ForeignKey('Tag', null=True, blank=True)
36 36
@@ -43,14 +43,15 b' class Tag(models.Model):'
43 43 def get_post_count(self):
44 44 return self.threads.count()
45 45
46 def get_popularity(self):
47 posts_with_tag = Post.objects.get_threads(tag=self)
48 reply_count = 0
49 for post in posts_with_tag:
50 reply_count += post.get_reply_count()
51 reply_count += OPENING_POST_POPULARITY_WEIGHT
52
53 return reply_count
46 # TODO Reenable this method after migration
47 # def get_popularity(self):
48 # posts_with_tag = Thread.objects.get_threads(tag=self)
49 # reply_count = 0
50 # for post in posts_with_tag:
51 # reply_count += post.get_reply_count()
52 # reply_count += OPENING_POST_POPULARITY_WEIGHT
53 #
54 # return reply_count
54 55
55 56 def get_linked_tags(self):
56 57 tag_list = []
@@ -104,7 +104,7 b' p {'
104 104 border: solid 1px #888;
105 105 color: #fff;
106 106 padding: 10px;
107 margin: 5;
107 margin: 5px;
108 108 }
109 109
110 110 .form-row {
@@ -55,10 +55,10 b''
55 55 </div>
56 56 {% endif %}
57 57 </div>
58 {% if post.tags.exists %}
58 {% if post.thread.tags.exists %}
59 59 <div class="metadata">
60 60 <span class="tags">{% trans 'Tags' %}:
61 {% for tag in post.tags.all %}
61 {% for tag in post.thread.get_tags %}
62 62 <a class="tag" href="{% url 'tag' tag.name %}">
63 63 {{ tag.name }}</a>
64 64 {% endfor %}
@@ -68,47 +68,50 b''
68 68 {% cache 600 thread_short thread.thread.last_edit_time moderator LANGUAGE_CODE %}
69 69 <div class="thread">
70 70 {% if thread.bumpable %}
71 <div class="post" id="{{ thread.thread.id }}">
71 <div class="post" id="{{ thread.op.id }}">
72 72 {% else %}
73 <div class="post dead_post" id="{{ thread.thread.id }}">
73 <div class="post dead_post" id="{{ thread.op.id }}">
74 74 {% endif %}
75 {% if thread.thread.image %}
75 {% if thread.op.image %}
76 76 <div class="image">
77 77 <a class="thumb"
78 href="{{ thread.thread.image.url }}"><img
79 src="{{ thread.thread.image.url_200x150 }}"
80 alt="{{ thread.thread.id }}"
81 data-width="{{ thread.thread.image_width }}"
82 data-height="{{ thread.thread.image_height }}" />
78 href="{{ thread.op.image.url }}"><img
79 src="{{ thread.op.image.url_200x150 }}"
80 alt="{{ thread.op.id }}"
81 data-width="{{ thread.op.image_width }}"
82 data-height="{{ thread.op.image_height }}"/>
83 83 </a>
84 84 </div>
85 85 {% endif %}
86 86 <div class="message">
87 87 <div class="post-info">
88 <span class="title">{{ thread.thread.title }}</span>
89 <a class="post_id" href="{% url 'thread' thread.thread.id %}"
90 >({{ thread.thread.id }})</a>
91 [{{ thread.thread.pub_time }}]
92 [<a class="link" href="{% url 'thread' thread.thread.id %}#form"
88 <span class="title">{{ thread.op.title }}</span>
89 <a class="post_id" href="{% url 'thread' thread.op.id %}"
90 >({{ thread.op.id }})</a>
91 [{{ thread.op.pub_time }}]
92 [<a class="link" href="
93 {% url 'thread' thread.op.id %}#form"
93 94 >{% trans "Reply" %}</a>]
94 95
95 96 {% if moderator %}
96 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 100 >{% trans 'Delete' %}</a>]
99 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 104 >{% trans 'Ban IP' %}</a>]
102 105 </span>
103 106 {% endif %}
104 107 </div>
105 108 {% autoescape off %}
106 {{ thread.thread.text.rendered|truncatewords_html:50 }}
109 {{ thread.op.text.rendered|truncatewords_html:50 }}
107 110 {% endautoescape %}
108 {% if thread.thread.is_referenced %}
111 {% if thread.op.is_referenced %}
109 112 <div class="refmap">
110 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 115 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
113 116 >{% if not forloop.last %},{% endif %}
114 117 {% endfor %}
@@ -153,7 +156,7 b''
153 156 <div class="post-info">
154 157 <span class="title">{{ post.title }}</span>
155 158 <a class="post_id" href="
156 {% url 'thread' thread.thread.id %}#{{ post.id }}">
159 {% url 'thread' thread.op.id %}#{{ post.id }}">
157 160 ({{ post.id }})</a>
158 161 [{{ post.pub_time }}]
159 162 </div>
@@ -257,7 +260,7 b''
257 260 {% block metapanel %}
258 261
259 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 264 {% trans "Pages:" %}
262 265 {% for page in pages %}
263 266 [<a href="
@@ -15,8 +15,7 b''
15 15 <script src="{% static 'js/thread_update.js' %}"></script>
16 16 <script src="{% static 'js/thread.js' %}"></script>
17 17
18 {% if posts %}
19 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
18 {% cache 600 thread_view thread.last_edit_time moderator LANGUAGE_CODE %}
20 19 {% if bumpable %}
21 20 <div class="bar-bg">
22 21 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
@@ -27,7 +26,7 b''
27 26 </div>
28 27 {% endif %}
29 28 <div class="thread">
30 {% for post in posts %}
29 {% for post in thread.get_replies %}
31 30 {% if bumpable %}
32 31 <div class="post" id="{{ post.id }}">
33 32 {% else %}
@@ -80,7 +79,7 b''
80 79 {% if forloop.first %}
81 80 <div class="metadata">
82 81 <span class="tags">
83 {% for tag in post.get_tags %}
82 {% for tag in thread.get_tags %}
84 83 <a class="tag" href="{% url 'tag' tag.name %}">
85 84 #{{ tag.name }}</a
86 85 >{% if not forloop.last %},{% endif %}
@@ -92,7 +91,6 b''
92 91 {% endfor %}
93 92 </div>
94 93 {% endcache %}
95 {% endif %}
96 94
97 95 <form id="form" enctype="multipart/form-data" method="post"
98 96 >{% csrf_token %}
@@ -152,10 +150,10 b''
152 150 {% get_current_language as LANGUAGE_CODE %}
153 151
154 152 <span class="metapanel" data-last-update="{{ last_update }}">
155 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
156 <span id="reply-count">{{ posts.0.get_reply_count }}</span> {% trans 'replies' %},
157 <span id="image-count">{{ posts.0.get_images_count }}</span> {% trans 'images' %}.
158 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
153 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
154 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
155 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
156 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
159 157 [<a href="rss/">RSS</a>]
160 158 {% endcache %}
161 159 </span>
@@ -30,8 +30,7 b' class PostTests(TestCase):'
30 30
31 31 post = self._create_post()
32 32
33 self.assertIsNotNone(post)
34 self.assertIsNone(post.thread, 'Opening post has a thread')
33 self.assertIsNotNone(post, 'No post was created')
35 34
36 35 def test_delete_post(self):
37 36 """Test post deletion"""
@@ -46,10 +45,11 b' class PostTests(TestCase):'
46 45 def test_delete_thread(self):
47 46 """Test thread deletion"""
48 47
49 thread = self._create_post()
48 opening_post = self._create_post()
49 thread = opening_post.thread_new
50 50 reply = Post.objects.create_post("", "", thread=thread)
51 51
52 Post.objects.delete_post(thread)
52 thread.delete_with_posts()
53 53
54 54 self.assertFalse(Post.objects.filter(id=reply.id).exists())
55 55
@@ -57,10 +57,10 b' class PostTests(TestCase):'
57 57 """Test adding post to a thread"""
58 58
59 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 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 64 'Post\'s create time doesn\'t match thread last edit'
65 65 ' time')
66 66
@@ -80,18 +80,23 b' class PostTests(TestCase):'
80 80 opening_post = self._create_post()
81 81
82 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 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 90 def test_create_post_with_tag(self):
90 91 """Test adding tag to post"""
91 92
92 93 tag = Tag.objects.create(name='test_tag')
93 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 101 def test_thread_max_count(self):
97 102 """Test deletion of old posts when the max thread count is reached"""
@@ -124,7 +129,7 b' class PostTests(TestCase):'
124 129
125 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 133 'Linked tag was not added')
129 134
130 135
@@ -162,7 +167,8 b' class PagesTest(TestCase):'
162 167 u'Not existing tag is opened')
163 168
164 169 reply_id = Post.objects.create_post('', TEST_TEXT,
165 thread=Post.objects.all()[0])
170 thread=Post.objects.all()[0]
171 .thread)
166 172 response_not_existing = client.get(THREAD_PAGE + str(
167 173 reply_id) + '/')
168 174 self.assertEqual(PAGE_404,
@@ -54,11 +54,12 b' def index(request, page=0):'
54 54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
55 55
56 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 58 threads.append({
59 'thread': thread,
60 'bumpable': thread.can_bump(),
61 'last_replies': thread.get_last_replies(),
59 'thread': thread_to_show,
60 'op': thread_to_show.get_replies()[0],
61 'bumpable': thread_to_show.can_bump(),
62 'last_replies': thread_to_show.get_last_replies(),
62 63 })
63 64
64 65 # TODO Make this generic for tag and threads list pages
@@ -111,9 +112,12 b' def _new_post(request, form, opening_pos'
111 112 if len(tag_name) > 0:
112 113 tag, created = Tag.objects.get_or_create(name=tag_name)
113 114 tags.append(tag)
115 post_thread = None
116 else:
117 post_thread = opening_post.thread_new
114 118
115 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 121 tags=tags, user=_get_user(request))
118 122
119 123 thread_to_show = (opening_post.id if opening_post else post.id)
@@ -133,11 +137,12 b' def tag(request, tag_name, page=0):'
133 137
134 138 tag = get_object_or_404(Tag, name=tag_name)
135 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 141 threads.append({
138 'thread': thread,
139 'bumpable': thread.can_bump(),
140 'last_replies': thread.get_last_replies(),
142 'thread': thread_to_show,
143 'op': thread_to_show.get_replies()[0],
144 'bumpable': thread_to_show.can_bump(),
145 'last_replies': thread_to_show.get_last_replies(),
141 146 })
142 147
143 148 if request.method == 'POST':
@@ -196,20 +201,21 b' def thread(request, post_id):'
196 201 else:
197 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 206 context = _init_default_context(request)
202 207
203 context['posts'] = posts
208 posts = thread_to_show.get_replies()
204 209 context['form'] = form
205 context['bumpable'] = posts[0].can_bump()
210 context['bumpable'] = thread_to_show.can_bump()
206 211 if context['bumpable']:
207 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
208 posts)
212 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts\
213 .count()
209 214 context['bumplimit_progress'] = str(
210 215 float(context['posts_left']) /
211 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 220 return render(request, 'boards/thread.html', context)
215 221
@@ -9,6 +9,8 b' denied". Use second only for autoban for'
9 9 [DONE] Clean up tests and make them run ALWAYS
10 10 [DONE] Use transactions in tests
11 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 15 [NOT STARTED] Tree view (JS)
14 16 [NOT STARTED] Adding tags to images filename
@@ -22,8 +24,6 b' denied". Use second only for autoban for'
22 24 [NOT STARTED] Character counter in the post field
23 25 [NOT STARTED] Save image thumbnails size to the separate field
24 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 27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
28 28 performance), posts per day/week/month, users (or IPs)
29 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