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', 't |
|
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 |
|
|
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 |
|
|
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 |
|
|
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( |
|
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. |
|
93 | if not threads.exists(): | |
99 | raise Http404 |
|
94 | raise Http404 | |
100 | else: |
|
95 | else: | |
101 |
threads = |
|
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 |
|
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 |
|
|
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 = |
|
123 | threads = Thread.objects.filter(tags=tag) | |
131 | else: |
|
124 | else: | |
132 |
threads = |
|
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 = |
|
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( |
|
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 |
|
262 | def delete_with_posts(self): | |
246 |
""" |
|
263 | """Completely delete thread""" | |
247 |
|
264 | |||
248 |
if self. |
|
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_ |
|
284 | def get_replies(self): | |
264 |
"""Get |
|
285 | """Get sorted thread posts""" | |
265 |
|
286 | |||
266 |
return self. |
|
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 |
|
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 |
|
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 |
|
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( |
|
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 |
|
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. |
|
71 | <div class="post" id="{{ thread.op.id }}"> | |
72 | {% else %} |
|
72 | {% else %} | |
73 |
<div class="post dead_post" id="{{ thread. |
|
73 | <div class="post dead_post" id="{{ thread.op.id }}"> | |
74 | {% endif %} |
|
74 | {% endif %} | |
75 |
{% if thread. |
|
75 | {% if thread.op.image %} | |
76 | <div class="image"> |
|
76 | <div class="image"> | |
77 | <a class="thumb" |
|
77 | <a class="thumb" | |
78 |
href="{{ thread. |
|
78 | href="{{ thread.op.image.url }}"><img | |
79 |
src="{{ thread. |
|
79 | src="{{ thread.op.image.url_200x150 }}" | |
80 |
alt="{{ thread. |
|
80 | alt="{{ thread.op.id }}" | |
81 |
data-width="{{ thread. |
|
81 | data-width="{{ thread.op.image_width }}" | |
82 |
data-height="{{ thread. |
|
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. |
|
88 | <span class="title">{{ thread.op.title }}</span> | |
89 |
<a class="post_id" href="{% url 'thread' thread. |
|
89 | <a class="post_id" href="{% url 'thread' thread.op.id %}" | |
90 |
>({{ thread. |
|
90 | >({{ thread.op.id }})</a> | |
91 |
[{{ thread. |
|
91 | [{{ thread.op.pub_time }}] | |
92 |
[<a class="link" href=" |
|
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. |
|
109 | {{ thread.op.text.rendered|truncatewords_html:50 }} | |
107 | {% endautoescape %} |
|
110 | {% endautoescape %} | |
108 |
{% if thread. |
|
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. |
|
114 | {% for ref_post in thread.op.get_sorted_referenced_posts %} | |
112 | <a href="{% post_url ref_post.id %}">>>{{ ref_post.id }}</a |
|
115 | <a href="{% post_url ref_post.id %}">>>{{ 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. |
|
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. |
|
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 |
|
|
26 | </div> | |
48 | <div class="message"> |
|
27 | {% endif %} | |
49 |
|
|
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 |
|
|
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;">>></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 |
|
|
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 |
|
|
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 %}">>>{{ 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;">>></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 |
{% |
|
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 %}">>>{{ 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 |
|
153 | {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %} | |
156 |
<span id="reply-count">{{ |
|
154 | <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %}, | |
157 |
<span id="image-count">{{ |
|
155 | <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}. | |
158 |
{% trans 'Last update: ' %}{{ |
|
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 |
|
|
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 |
|
|
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', |
|
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( |
|
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 |
' |
|
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= |
|
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( |
|
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 |
' |
|
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'] = |
|
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 - |
|
212 | context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts\ | |
208 |
|
|
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( |
|
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