##// END OF EJS Templates
Added KeyPair model for signing and verifying data, that will be user for...
neko259 -
r793:8a6f4ef5 decentral
parent child Browse files
Show More
@@ -0,0 +1,74 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 # Adding field 'Post.public_key'
12 db.add_column('boards_post', 'public_key',
13 self.gf('django.db.models.fields.TextField')(blank=True, null=True),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'Post.public_key'
19 db.delete_column('boards_post', 'public_key')
20
21
22 models = {
23 'boards.ban': {
24 'Meta': {'object_name': 'Ban'},
25 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
26 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
27 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
28 'reason': ('django.db.models.fields.CharField', [], {'max_length': '200', 'default': "'Auto'"})
29 },
30 'boards.post': {
31 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
32 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
33 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'images': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'null': 'True', 'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'db_index': 'True'}),
35 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
36 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
37 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
38 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
39 'public_key': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}),
40 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'blank': 'True', 'null': 'True', 'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'db_index': 'True'}),
41 'refmap': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}),
42 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
43 'text_markup_type': ('django.db.models.fields.CharField', [], {'max_length': '30', 'default': "'bbcode'"}),
44 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
45 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
46 },
47 'boards.postimage': {
48 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
49 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
50 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
51 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
52 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
53 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
54 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
55 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
56 },
57 'boards.tag': {
58 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
59 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
61 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Thread']", 'null': 'True', 'blank': 'True', 'related_name': "'tag+'"})
62 },
63 'boards.thread': {
64 'Meta': {'object_name': 'Thread'},
65 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
66 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
67 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
68 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
69 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Post']", 'null': 'True', 'blank': 'True', 'related_name': "'tre+'"}),
70 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']"})
71 }
72 }
73
74 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,85 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 # Adding model 'KeyPair'
12 db.create_table('boards_keypair', (
13 ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('public_key', self.gf('django.db.models.fields.TextField')()),
15 ('private_key', self.gf('django.db.models.fields.TextField')()),
16 ('key_type', self.gf('django.db.models.fields.TextField')()),
17 ))
18 db.send_create_signal('boards', ['KeyPair'])
19
20
21 def backwards(self, orm):
22 # Deleting model 'KeyPair'
23 db.delete_table('boards_keypair')
24
25
26 models = {
27 'boards.ban': {
28 'Meta': {'object_name': 'Ban'},
29 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
30 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
31 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
32 'reason': ('django.db.models.fields.CharField', [], {'max_length': '200', 'default': "'Auto'"})
33 },
34 'boards.keypair': {
35 'Meta': {'object_name': 'KeyPair'},
36 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37 'key_type': ('django.db.models.fields.TextField', [], {}),
38 'private_key': ('django.db.models.fields.TextField', [], {}),
39 'public_key': ('django.db.models.fields.TextField', [], {})
40 },
41 'boards.post': {
42 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
43 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
44 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
45 'images': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.PostImage']", 'db_index': 'True', 'related_name': "'ip+'", 'symmetrical': 'False', 'blank': 'True', 'null': 'True'}),
46 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
47 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
48 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
49 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
50 'public_key': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}),
51 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Post']", 'db_index': 'True', 'related_name': "'rfp+'", 'symmetrical': 'False', 'blank': 'True', 'null': 'True'}),
52 'refmap': ('django.db.models.fields.TextField', [], {'blank': 'True', 'null': 'True'}),
53 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
54 'text_markup_type': ('django.db.models.fields.CharField', [], {'max_length': '30', 'default': "'bbcode'"}),
55 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
56 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
57 },
58 'boards.postimage': {
59 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
60 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
61 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
62 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
63 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
64 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
65 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
66 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
67 },
68 'boards.tag': {
69 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
70 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
71 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
72 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Thread']", 'null': 'True', 'blank': 'True', 'related_name': "'tag+'"})
73 },
74 'boards.thread': {
75 'Meta': {'object_name': 'Thread'},
76 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
77 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
78 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
80 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Post']", 'null': 'True', 'blank': 'True', 'related_name': "'tre+'"}),
81 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']"})
82 }
83 }
84
85 complete_apps = ['boards'] No newline at end of file
@@ -1,91 +1,91 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 from south.utils import datetime_utils as datetime
2 from south.utils import datetime_utils as datetime
3 from south.db import db
3 from south.db import db
4 from south.v2 import DataMigration
4 from south.v2 import DataMigration
5 from django.db import models
5 from django.db import models
6
6
7 from boards.models import Post
7 from boards.models import Post
8
8
9 class Migration(DataMigration):
9 class Migration(DataMigration):
10
10
11
11
12 def forwards(self, orm):
12 def forwards(self, orm):
13 "Write your forwards methods here."
13 "Write your forwards methods here."
14 # Note: Don't use "from appname.models import ModelName".
14 # Note: Don't use "from appname.models import ModelName".
15 # Use orm.ModelName to refer to models in this application,
15 # Use orm.ModelName to refer to models in this application,
16 # and orm['appname.ModelName'] for models in other applications.
16 # and orm['appname.ModelName'] for models in other applications.
17 for post in Post.objects.all():
17 for post in orm['boards.Post'].objects.all():
18 post.build_refmap()
18 post.build_refmap()
19 post.save()
19 post.save()
20
20
21 def backwards(self, orm):
21 def backwards(self, orm):
22 "Write your backwards methods here."
22 "Write your backwards methods here."
23
23
24 models = {
24 models = {
25 'boards.ban': {
25 'boards.ban': {
26 'Meta': {'object_name': 'Ban'},
26 'Meta': {'object_name': 'Ban'},
27 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
27 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
28 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
28 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
29 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
29 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
30 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
30 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
31 },
31 },
32 'boards.post': {
32 'boards.post': {
33 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
33 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
34 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
34 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
35 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
35 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
36 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
36 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
37 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
37 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
38 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
38 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
39 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
39 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
40 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
40 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
41 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
41 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
42 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
42 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
43 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
43 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
44 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
44 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
45 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
45 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
46 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
46 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
47 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
47 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
48 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
48 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
49 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
49 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
50 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
50 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
51 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
51 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
52 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
52 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
53 },
53 },
54 'boards.setting': {
54 'boards.setting': {
55 'Meta': {'object_name': 'Setting'},
55 'Meta': {'object_name': 'Setting'},
56 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
56 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
57 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
57 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
58 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
58 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
59 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
59 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
60 },
60 },
61 'boards.tag': {
61 'boards.tag': {
62 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
62 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
63 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
63 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
64 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
64 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
65 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
65 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
66 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
66 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
67 },
67 },
68 'boards.thread': {
68 'boards.thread': {
69 'Meta': {'object_name': 'Thread'},
69 'Meta': {'object_name': 'Thread'},
70 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
70 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
71 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
71 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
72 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
72 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
73 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
73 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
74 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
74 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
75 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
75 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
76 },
76 },
77 'boards.user': {
77 'boards.user': {
78 'Meta': {'object_name': 'User'},
78 'Meta': {'object_name': 'User'},
79 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
79 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
80 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
80 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
81 'hidden_tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ht+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Tag']"}),
81 'hidden_tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ht+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Tag']"}),
82 'hidden_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'hth+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
82 'hidden_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'hth+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
83 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
83 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
84 'rank': ('django.db.models.fields.IntegerField', [], {}),
84 'rank': ('django.db.models.fields.IntegerField', [], {}),
85 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
85 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
86 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
86 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
87 }
87 }
88 }
88 }
89
89
90 complete_apps = ['boards']
90 complete_apps = ['boards']
91 symmetrical = True
91 symmetrical = True
@@ -1,7 +1,8 b''
1 __author__ = 'neko259'
1 __author__ = 'neko259'
2
2
3 from boards.models.image import PostImage
3 from boards.models.image import PostImage
4 from boards.models.thread import Thread
4 from boards.models.thread import Thread
5 from boards.models.post import Post
5 from boards.models.post import Post
6 from boards.models.tag import Tag
6 from boards.models.tag import Tag
7 from boards.models.user import Ban
7 from boards.models.user import Ban
8 from boards.models.sync_key import KeyPair
@@ -1,346 +1,354 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import logging
3 import logging
4 import re
4 import re
5
5
6 from django.core.cache import cache
6 from django.core.cache import cache
7 from django.core.urlresolvers import reverse
7 from django.core.urlresolvers import reverse
8 from django.db import models, transaction
8 from django.db import models, transaction
9 from django.template.loader import render_to_string
9 from django.template.loader import render_to_string
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
12
12
13 from boards.models import PostImage
13 from boards.models import PostImage
14 from boards.models.base import Viewable
14 from boards.models.base import Viewable
15 from boards.models.thread import Thread
15 from boards.models.thread import Thread
16
16
17
17
18 APP_LABEL_BOARDS = 'boards'
18 APP_LABEL_BOARDS = 'boards'
19
19
20 CACHE_KEY_PPD = 'ppd'
20 CACHE_KEY_PPD = 'ppd'
21 CACHE_KEY_POST_URL = 'post_url'
21 CACHE_KEY_POST_URL = 'post_url'
22
22
23 POSTS_PER_DAY_RANGE = 7
23 POSTS_PER_DAY_RANGE = 7
24
24
25 BAN_REASON_AUTO = 'Auto'
25 BAN_REASON_AUTO = 'Auto'
26
26
27 IMAGE_THUMB_SIZE = (200, 150)
27 IMAGE_THUMB_SIZE = (200, 150)
28
28
29 TITLE_MAX_LENGTH = 200
29 TITLE_MAX_LENGTH = 200
30
30
31 DEFAULT_MARKUP_TYPE = 'bbcode'
31 DEFAULT_MARKUP_TYPE = 'bbcode'
32
32
33 # TODO This should be removed
33 # TODO This should be removed
34 NO_IP = '0.0.0.0'
34 NO_IP = '0.0.0.0'
35
35
36 # TODO Real user agent should be saved instead of this
36 # TODO Real user agent should be saved instead of this
37 UNKNOWN_UA = ''
37 UNKNOWN_UA = ''
38
38
39 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
39 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
40
40
41 logger = logging.getLogger(__name__)
41 logger = logging.getLogger(__name__)
42
42
43
43
44 class PostManager(models.Manager):
44 class PostManager(models.Manager):
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
46 tags=None):
46 tags=None):
47 """
47 """
48 Creates new post
48 Creates new post
49 """
49 """
50
50
51 if not tags:
51 if not tags:
52 tags = []
52 tags = []
53
53
54 posting_time = timezone.now()
54 posting_time = timezone.now()
55 if not thread:
55 if not thread:
56 thread = Thread.objects.create(bump_time=posting_time,
56 thread = Thread.objects.create(bump_time=posting_time,
57 last_edit_time=posting_time)
57 last_edit_time=posting_time)
58 new_thread = True
58 new_thread = True
59 else:
59 else:
60 thread.bump()
60 thread.bump()
61 thread.last_edit_time = posting_time
61 thread.last_edit_time = posting_time
62 thread.save()
62 thread.save()
63 new_thread = False
63 new_thread = False
64
64
65 post = self.create(title=title,
65 post = self.create(title=title,
66 text=text,
66 text=text,
67 pub_time=posting_time,
67 pub_time=posting_time,
68 thread_new=thread,
68 thread_new=thread,
69 poster_ip=ip,
69 poster_ip=ip,
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
71 # last!
71 # last!
72 last_edit_time=posting_time)
72 last_edit_time=posting_time)
73
73
74 if image:
74 if image:
75 post_image = PostImage.objects.create(image=image)
75 post_image = PostImage.objects.create(image=image)
76 post.images.add(post_image)
76 post.images.add(post_image)
77 logger.info('Created image #%d for post #%d' % (post_image.id,
77 logger.info('Created image #%d for post #%d' % (post_image.id,
78 post.id))
78 post.id))
79
79
80 thread.replies.add(post)
80 thread.replies.add(post)
81 list(map(thread.add_tag, tags))
81 list(map(thread.add_tag, tags))
82
82
83 if new_thread:
83 if new_thread:
84 Thread.objects.process_oldest_threads()
84 Thread.objects.process_oldest_threads()
85 self.connect_replies(post)
85 self.connect_replies(post)
86
86
87 logger.info('Created post #%d with title %s' % (post.id,
87 logger.info('Created post #%d with title %s' % (post.id,
88 post.get_title()))
88 post.get_title()))
89
89
90 return post
90 return post
91
91
92 def delete_post(self, post):
92 def delete_post(self, post):
93 """
93 """
94 Deletes post and update or delete its thread
94 Deletes post and update or delete its thread
95 """
95 """
96
96
97 post_id = post.id
97 post_id = post.id
98
98
99 thread = post.get_thread()
99 thread = post.get_thread()
100
100
101 if post.is_opening():
101 if post.is_opening():
102 thread.delete()
102 thread.delete()
103 else:
103 else:
104 thread.last_edit_time = timezone.now()
104 thread.last_edit_time = timezone.now()
105 thread.save()
105 thread.save()
106
106
107 post.delete()
107 post.delete()
108
108
109 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
109 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
110
110
111 def delete_posts_by_ip(self, ip):
111 def delete_posts_by_ip(self, ip):
112 """
112 """
113 Deletes all posts of the author with same IP
113 Deletes all posts of the author with same IP
114 """
114 """
115
115
116 posts = self.filter(poster_ip=ip)
116 posts = self.filter(poster_ip=ip)
117 for post in posts:
117 for post in posts:
118 self.delete_post(post)
118 self.delete_post(post)
119
119
120 def connect_replies(self, post):
120 def connect_replies(self, post):
121 """
121 """
122 Connects replies to a post to show them as a reflink map
122 Connects replies to a post to show them as a reflink map
123 """
123 """
124
124
125 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
125 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
126 post_id = reply_number.group(1)
126 post_id = reply_number.group(1)
127 ref_post = self.filter(id=post_id)
127 ref_post = self.filter(id=post_id)
128 if ref_post.count() > 0:
128 if ref_post.count() > 0:
129 referenced_post = ref_post[0]
129 referenced_post = ref_post[0]
130 referenced_post.referenced_posts.add(post)
130 referenced_post.referenced_posts.add(post)
131 referenced_post.last_edit_time = post.pub_time
131 referenced_post.last_edit_time = post.pub_time
132 referenced_post.build_refmap()
132 referenced_post.build_refmap()
133 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
133 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
134
134
135 referenced_thread = referenced_post.get_thread()
135 referenced_thread = referenced_post.get_thread()
136 referenced_thread.last_edit_time = post.pub_time
136 referenced_thread.last_edit_time = post.pub_time
137 referenced_thread.save(update_fields=['last_edit_time'])
137 referenced_thread.save(update_fields=['last_edit_time'])
138
138
139 def get_posts_per_day(self):
139 def get_posts_per_day(self):
140 """
140 """
141 Gets average count of posts per day for the last 7 days
141 Gets average count of posts per day for the last 7 days
142 """
142 """
143
143
144 day_end = date.today()
144 day_end = date.today()
145 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
145 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
146
146
147 cache_key = CACHE_KEY_PPD + str(day_end)
147 cache_key = CACHE_KEY_PPD + str(day_end)
148 ppd = cache.get(cache_key)
148 ppd = cache.get(cache_key)
149 if ppd:
149 if ppd:
150 return ppd
150 return ppd
151
151
152 day_time_start = timezone.make_aware(datetime.combine(
152 day_time_start = timezone.make_aware(datetime.combine(
153 day_start, dtime()), timezone.get_current_timezone())
153 day_start, dtime()), timezone.get_current_timezone())
154 day_time_end = timezone.make_aware(datetime.combine(
154 day_time_end = timezone.make_aware(datetime.combine(
155 day_end, dtime()), timezone.get_current_timezone())
155 day_end, dtime()), timezone.get_current_timezone())
156
156
157 posts_per_period = float(self.filter(
157 posts_per_period = float(self.filter(
158 pub_time__lte=day_time_end,
158 pub_time__lte=day_time_end,
159 pub_time__gte=day_time_start).count())
159 pub_time__gte=day_time_start).count())
160
160
161 ppd = posts_per_period / POSTS_PER_DAY_RANGE
161 ppd = posts_per_period / POSTS_PER_DAY_RANGE
162
162
163 cache.set(cache_key, ppd)
163 cache.set(cache_key, ppd)
164 return ppd
164 return ppd
165
165
166
166
167 class Post(models.Model, Viewable):
167 class Post(models.Model, Viewable):
168 """A post is a message."""
168 """A post is a message."""
169
169
170 objects = PostManager()
170 objects = PostManager()
171
171
172 class Meta:
172 class Meta:
173 app_label = APP_LABEL_BOARDS
173 app_label = APP_LABEL_BOARDS
174 ordering = ('id',)
174 ordering = ('id',)
175
175
176 title = models.CharField(max_length=TITLE_MAX_LENGTH)
176 title = models.CharField(max_length=TITLE_MAX_LENGTH)
177 pub_time = models.DateTimeField()
177 pub_time = models.DateTimeField()
178 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
178 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
179 escape_html=False)
179 escape_html=False)
180
180
181 images = models.ManyToManyField(PostImage, null=True, blank=True,
181 images = models.ManyToManyField(PostImage, null=True, blank=True,
182 related_name='ip+', db_index=True)
182 related_name='ip+', db_index=True)
183
183
184 poster_ip = models.GenericIPAddressField()
184 poster_ip = models.GenericIPAddressField()
185 poster_user_agent = models.TextField()
185 poster_user_agent = models.TextField()
186
186
187 thread_new = models.ForeignKey('Thread', null=True, default=None,
187 thread_new = models.ForeignKey('Thread', null=True, default=None,
188 db_index=True)
188 db_index=True)
189 last_edit_time = models.DateTimeField()
189 last_edit_time = models.DateTimeField()
190
190
191 # Replies to the post
191 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
192 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
192 null=True,
193 null=True,
193 blank=True, related_name='rfp+',
194 blank=True, related_name='rfp+',
194 db_index=True)
195 db_index=True)
196
197 # Replies map. This is built from the referenced posts list to speed up
198 # page loading (no need to get all the referenced posts from the database).
195 refmap = models.TextField(null=True, blank=True)
199 refmap = models.TextField(null=True, blank=True)
196
200
201 # Sender key. If the message was downloaded from another server, this
202 # indicates the server.
203 public_key = models.TextField(null=True, blank=True)
204
197 def __unicode__(self):
205 def __unicode__(self):
198 return '#' + str(self.id) + ' ' + self.title + ' (' + \
206 return '#' + str(self.id) + ' ' + self.title + ' (' + \
199 self.text.raw[:50] + ')'
207 self.text.raw[:50] + ')'
200
208
201 def get_title(self):
209 def get_title(self):
202 """
210 """
203 Gets original post title or part of its text.
211 Gets original post title or part of its text.
204 """
212 """
205
213
206 title = self.title
214 title = self.title
207 if not title:
215 if not title:
208 title = self.text.rendered
216 title = self.text.rendered
209
217
210 return title
218 return title
211
219
212 def build_refmap(self):
220 def build_refmap(self):
213 """
221 """
214 Builds a replies map string from replies list. This is a cache to stop
222 Builds a replies map string from replies list. This is a cache to stop
215 the server from recalculating the map on every post show.
223 the server from recalculating the map on every post show.
216 """
224 """
217 map_string = ''
225 map_string = ''
218
226
219 first = True
227 first = True
220 for refpost in self.referenced_posts.all():
228 for refpost in self.referenced_posts.all():
221 if not first:
229 if not first:
222 map_string += ', '
230 map_string += ', '
223 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
231 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
224 refpost.id)
232 refpost.id)
225 first = False
233 first = False
226
234
227 self.refmap = map_string
235 self.refmap = map_string
228
236
229 def get_sorted_referenced_posts(self):
237 def get_sorted_referenced_posts(self):
230 return self.refmap
238 return self.refmap
231
239
232 def is_referenced(self):
240 def is_referenced(self):
233 return len(self.refmap) > 0
241 return len(self.refmap) > 0
234
242
235 def is_opening(self):
243 def is_opening(self):
236 """
244 """
237 Checks if this is an opening post or just a reply.
245 Checks if this is an opening post or just a reply.
238 """
246 """
239
247
240 return self.get_thread().get_opening_post_id() == self.id
248 return self.get_thread().get_opening_post_id() == self.id
241
249
242 @transaction.atomic
250 @transaction.atomic
243 def add_tag(self, tag):
251 def add_tag(self, tag):
244 edit_time = timezone.now()
252 edit_time = timezone.now()
245
253
246 thread = self.get_thread()
254 thread = self.get_thread()
247 thread.add_tag(tag)
255 thread.add_tag(tag)
248 self.last_edit_time = edit_time
256 self.last_edit_time = edit_time
249 self.save(update_fields=['last_edit_time'])
257 self.save(update_fields=['last_edit_time'])
250
258
251 thread.last_edit_time = edit_time
259 thread.last_edit_time = edit_time
252 thread.save(update_fields=['last_edit_time'])
260 thread.save(update_fields=['last_edit_time'])
253
261
254 @transaction.atomic
262 @transaction.atomic
255 def remove_tag(self, tag):
263 def remove_tag(self, tag):
256 edit_time = timezone.now()
264 edit_time = timezone.now()
257
265
258 thread = self.get_thread()
266 thread = self.get_thread()
259 thread.remove_tag(tag)
267 thread.remove_tag(tag)
260 self.last_edit_time = edit_time
268 self.last_edit_time = edit_time
261 self.save(update_fields=['last_edit_time'])
269 self.save(update_fields=['last_edit_time'])
262
270
263 thread.last_edit_time = edit_time
271 thread.last_edit_time = edit_time
264 thread.save(update_fields=['last_edit_time'])
272 thread.save(update_fields=['last_edit_time'])
265
273
266 def get_url(self, thread=None):
274 def get_url(self, thread=None):
267 """
275 """
268 Gets full url to the post.
276 Gets full url to the post.
269 """
277 """
270
278
271 cache_key = CACHE_KEY_POST_URL + str(self.id)
279 cache_key = CACHE_KEY_POST_URL + str(self.id)
272 link = cache.get(cache_key)
280 link = cache.get(cache_key)
273
281
274 if not link:
282 if not link:
275 if not thread:
283 if not thread:
276 thread = self.get_thread()
284 thread = self.get_thread()
277
285
278 opening_id = thread.get_opening_post_id()
286 opening_id = thread.get_opening_post_id()
279
287
280 if self.id != opening_id:
288 if self.id != opening_id:
281 link = reverse('thread', kwargs={
289 link = reverse('thread', kwargs={
282 'post_id': opening_id}) + '#' + str(self.id)
290 'post_id': opening_id}) + '#' + str(self.id)
283 else:
291 else:
284 link = reverse('thread', kwargs={'post_id': self.id})
292 link = reverse('thread', kwargs={'post_id': self.id})
285
293
286 cache.set(cache_key, link)
294 cache.set(cache_key, link)
287
295
288 return link
296 return link
289
297
290 def get_thread(self):
298 def get_thread(self):
291 """
299 """
292 Gets post's thread.
300 Gets post's thread.
293 """
301 """
294
302
295 return self.thread_new
303 return self.thread_new
296
304
297 def get_referenced_posts(self):
305 def get_referenced_posts(self):
298 return self.referenced_posts.only('id', 'thread_new')
306 return self.referenced_posts.only('id', 'thread_new')
299
307
300 def get_text(self):
308 def get_text(self):
301 return self.text
309 return self.text
302
310
303 def get_view(self, moderator=False, need_open_link=False,
311 def get_view(self, moderator=False, need_open_link=False,
304 truncated=False, *args, **kwargs):
312 truncated=False, *args, **kwargs):
305 if 'is_opening' in kwargs:
313 if 'is_opening' in kwargs:
306 is_opening = kwargs['is_opening']
314 is_opening = kwargs['is_opening']
307 else:
315 else:
308 is_opening = self.is_opening()
316 is_opening = self.is_opening()
309
317
310 if 'thread' in kwargs:
318 if 'thread' in kwargs:
311 thread = kwargs['thread']
319 thread = kwargs['thread']
312 else:
320 else:
313 thread = self.get_thread()
321 thread = self.get_thread()
314
322
315 if 'can_bump' in kwargs:
323 if 'can_bump' in kwargs:
316 can_bump = kwargs['can_bump']
324 can_bump = kwargs['can_bump']
317 else:
325 else:
318 can_bump = thread.can_bump()
326 can_bump = thread.can_bump()
319
327
320 if is_opening:
328 if is_opening:
321 opening_post_id = self.id
329 opening_post_id = self.id
322 else:
330 else:
323 opening_post_id = thread.get_opening_post_id()
331 opening_post_id = thread.get_opening_post_id()
324
332
325 return render_to_string('boards/post.html', {
333 return render_to_string('boards/post.html', {
326 'post': self,
334 'post': self,
327 'moderator': moderator,
335 'moderator': moderator,
328 'is_opening': is_opening,
336 'is_opening': is_opening,
329 'thread': thread,
337 'thread': thread,
330 'bumpable': can_bump,
338 'bumpable': can_bump,
331 'need_open_link': need_open_link,
339 'need_open_link': need_open_link,
332 'truncated': truncated,
340 'truncated': truncated,
333 'opening_post_id': opening_post_id,
341 'opening_post_id': opening_post_id,
334 })
342 })
335
343
336 def get_first_image(self):
344 def get_first_image(self):
337 return self.images.earliest('id')
345 return self.images.earliest('id')
338
346
339 def delete(self, using=None):
347 def delete(self, using=None):
340 """
348 """
341 Deletes all post images and the post itself.
349 Deletes all post images and the post itself.
342 """
350 """
343
351
344 self.images.all().delete()
352 self.images.all().delete()
345
353
346 super(Post, self).delete(using)
354 super(Post, self).delete(using)
@@ -1,23 +1,23 b''
1 VERSION = '1.8.1 Kara'
1 VERSION = '1.8.1 Kara'
2 SITE_NAME = 'Neboard'
2 SITE_NAME = 'n3b0a2d'
3
3
4 CACHE_TIMEOUT = 600 # Timeout for caching, if cache is used
4 CACHE_TIMEOUT = 600 # Timeout for caching, if cache is used
5 LOGIN_TIMEOUT = 3600 # Timeout between login tries
5 LOGIN_TIMEOUT = 3600 # Timeout between login tries
6 MAX_TEXT_LENGTH = 30000 # Max post length in characters
6 MAX_TEXT_LENGTH = 30000 # Max post length in characters
7 MAX_IMAGE_SIZE = 8 * 1024 * 1024 # Max image size
7 MAX_IMAGE_SIZE = 8 * 1024 * 1024 # Max image size
8
8
9 # Thread bumplimit
9 # Thread bumplimit
10 MAX_POSTS_PER_THREAD = 10
10 MAX_POSTS_PER_THREAD = 10
11 # Old posts will be archived or deleted if this value is reached
11 # Old posts will be archived or deleted if this value is reached
12 MAX_THREAD_COUNT = 5
12 MAX_THREAD_COUNT = 5
13 THREADS_PER_PAGE = 3
13 THREADS_PER_PAGE = 3
14 DEFAULT_THEME = 'md'
14 DEFAULT_THEME = 'md'
15 LAST_REPLIES_COUNT = 3
15 LAST_REPLIES_COUNT = 3
16
16
17 # Enable archiving threads instead of deletion when the thread limit is reached
17 # Enable archiving threads instead of deletion when the thread limit is reached
18 ARCHIVE_THREADS = True
18 ARCHIVE_THREADS = True
19 # Limit posting speed
19 # Limit posting speed
20 LIMIT_POSTING_SPEED = False
20 LIMIT_POSTING_SPEED = False
21
21
22 # This password is used to add admin permissions to the user
22 # This password is used to add admin permissions to the user
23 MASTER_PASSWORD = u'password' No newline at end of file
23 MASTER_PASSWORD = u'password'
@@ -1,455 +1,460 b''
1 html {
1 html {
2 background: #555;
2 background: #555;
3 color: #ffffff;
3 color: #ffffff;
4 }
4 }
5
5
6 body {
6 body {
7 margin: 0;
7 margin: 0;
8 }
8 }
9
9
10 #admin_panel {
10 #admin_panel {
11 background: #FF0000;
11 background: #FF0000;
12 color: #00FF00
12 color: #00FF00
13 }
13 }
14
14
15 .input_field_error {
15 .input_field_error {
16 color: #FF0000;
16 color: #FF0000;
17 }
17 }
18
18
19 .title {
19 .title {
20 font-weight: bold;
20 font-weight: bold;
21 color: #ffcc00;
21 color: #ffcc00;
22 }
22 }
23
23
24 .link, a {
24 .link, a {
25 color: #afdcec;
25 color: #afdcec;
26 }
26 }
27
27
28 .block {
28 .block {
29 display: inline-block;
29 display: inline-block;
30 vertical-align: top;
30 vertical-align: top;
31 }
31 }
32
32
33 .tag {
33 .tag {
34 color: #FFD37D;
34 color: #FFD37D;
35 }
35 }
36
36
37 .post_id {
37 .post_id {
38 color: #fff380;
38 color: #fff380;
39 }
39 }
40
40
41 .post, .dead_post, .archive_post, #posts-table {
41 .post, .dead_post, .archive_post, #posts-table {
42 background: #333;
42 background: #333;
43 padding: 10px;
43 padding: 10px;
44 clear: left;
44 clear: left;
45 word-wrap: break-word;
45 word-wrap: break-word;
46 border-top: 1px solid #777;
46 border-top: 1px solid #777;
47 border-bottom: 1px solid #777;
47 border-bottom: 1px solid #777;
48 }
48 }
49
49
50 .post + .post {
50 .post + .post {
51 border-top: none;
51 border-top: none;
52 }
52 }
53
53
54 .dead_post + .dead_post {
54 .dead_post + .dead_post {
55 border-top: none;
55 border-top: none;
56 }
56 }
57
57
58 .archive_post + .archive_post {
58 .archive_post + .archive_post {
59 border-top: none;
59 border-top: none;
60 }
60 }
61
61
62 .metadata {
62 .metadata {
63 padding-top: 5px;
63 padding-top: 5px;
64 margin-top: 10px;
64 margin-top: 10px;
65 border-top: solid 1px #666;
65 border-top: solid 1px #666;
66 color: #ddd;
66 color: #ddd;
67 }
67 }
68
68
69 .navigation_panel, .tag_info {
69 .navigation_panel, .tag_info {
70 background: #444;
70 background: #444;
71 margin-bottom: 5px;
71 margin-bottom: 5px;
72 margin-top: 5px;
72 margin-top: 5px;
73 padding: 10px;
73 padding: 10px;
74 border-bottom: solid 1px #888;
74 border-bottom: solid 1px #888;
75 border-top: solid 1px #888;
75 border-top: solid 1px #888;
76 color: #eee;
76 color: #eee;
77 }
77 }
78
78
79 .navigation_panel .link {
79 .navigation_panel .link {
80 border-right: 1px solid #fff;
80 border-right: 1px solid #fff;
81 font-weight: bold;
81 font-weight: bold;
82 margin-right: 1ex;
82 margin-right: 1ex;
83 padding-right: 1ex;
83 padding-right: 1ex;
84 }
84 }
85 .navigation_panel .link:last-child {
85 .navigation_panel .link:last-child {
86 border-left: 1px solid #fff;
86 border-left: 1px solid #fff;
87 border-right: none;
87 border-right: none;
88 float: right;
88 float: right;
89 margin-left: 1ex;
89 margin-left: 1ex;
90 margin-right: 0;
90 margin-right: 0;
91 padding-left: 1ex;
91 padding-left: 1ex;
92 padding-right: 0;
92 padding-right: 0;
93 }
93 }
94
94
95 .navigation_panel::after, .post::after {
95 .navigation_panel::after, .post::after {
96 clear: both;
96 clear: both;
97 content: ".";
97 content: ".";
98 display: block;
98 display: block;
99 height: 0;
99 height: 0;
100 line-height: 0;
100 line-height: 0;
101 visibility: hidden;
101 visibility: hidden;
102 }
102 }
103
103
104 p {
104 p {
105 margin-top: .5em;
105 margin-top: .5em;
106 margin-bottom: .5em;
106 margin-bottom: .5em;
107 }
107 }
108
108
109 br {
109 br {
110 margin-bottom: .5em;
110 margin-bottom: .5em;
111 }
111 }
112
112
113 .post-form-w {
113 .post-form-w {
114 background: #333344;
114 background: #333344;
115 border-top: solid 1px #888;
115 border-top: solid 1px #888;
116 border-bottom: solid 1px #888;
116 border-bottom: solid 1px #888;
117 color: #fff;
117 color: #fff;
118 padding: 10px;
118 padding: 10px;
119 margin-bottom: 5px;
119 margin-bottom: 5px;
120 margin-top: 5px;
120 margin-top: 5px;
121 }
121 }
122
122
123 .form-row {
123 .form-row {
124 width: 100%;
124 width: 100%;
125 }
125 }
126
126
127 .form-label {
127 .form-label {
128 padding: .25em 1ex .25em 0;
128 padding: .25em 1ex .25em 0;
129 vertical-align: top;
129 vertical-align: top;
130 }
130 }
131
131
132 .form-input {
132 .form-input {
133 padding: .25em 0;
133 padding: .25em 0;
134 }
134 }
135
135
136 .form-errors {
136 .form-errors {
137 font-weight: bolder;
137 font-weight: bolder;
138 vertical-align: middle;
138 vertical-align: middle;
139 }
139 }
140
140
141 .post-form input:not([name="image"]), .post-form textarea {
141 .post-form input:not([name="image"]), .post-form textarea {
142 background: #333;
142 background: #333;
143 color: #fff;
143 color: #fff;
144 border: solid 1px;
144 border: solid 1px;
145 padding: 0;
145 padding: 0;
146 font: medium sans-serif;
146 font: medium sans-serif;
147 width: 100%;
147 width: 100%;
148 }
148 }
149
149
150 .form-submit {
150 .form-submit {
151 display: table;
151 display: table;
152 margin-bottom: 1ex;
152 margin-bottom: 1ex;
153 margin-top: 1ex;
153 margin-top: 1ex;
154 }
154 }
155
155
156 .form-title {
156 .form-title {
157 font-weight: bold;
157 font-weight: bold;
158 font-size: 2ex;
158 font-size: 2ex;
159 margin-bottom: 0.5ex;
159 margin-bottom: 0.5ex;
160 }
160 }
161
161
162 .post-form input[type="submit"], input[type="submit"] {
162 .post-form input[type="submit"], input[type="submit"] {
163 background: #222;
163 background: #222;
164 border: solid 2px #fff;
164 border: solid 2px #fff;
165 color: #fff;
165 color: #fff;
166 padding: 0.5ex;
166 padding: 0.5ex;
167 }
167 }
168
168
169 input[type="submit"]:hover {
169 input[type="submit"]:hover {
170 background: #060;
170 background: #060;
171 }
171 }
172
172
173 blockquote {
173 blockquote {
174 border-left: solid 2px;
174 border-left: solid 2px;
175 padding-left: 5px;
175 padding-left: 5px;
176 color: #B1FB17;
176 color: #B1FB17;
177 margin: 0;
177 margin: 0;
178 }
178 }
179
179
180 .post > .image {
180 .post > .image {
181 float: left;
181 float: left;
182 margin: 0 1ex .5ex 0;
182 margin: 0 1ex .5ex 0;
183 min-width: 1px;
183 min-width: 1px;
184 text-align: center;
184 text-align: center;
185 display: table-row;
185 display: table-row;
186 }
186 }
187
187
188 .post > .metadata {
188 .post > .metadata {
189 clear: left;
189 clear: left;
190 }
190 }
191
191
192 .get {
192 .get {
193 font-weight: bold;
193 font-weight: bold;
194 color: #d55;
194 color: #d55;
195 }
195 }
196
196
197 * {
197 * {
198 text-decoration: none;
198 text-decoration: none;
199 }
199 }
200
200
201 .dead_post {
201 .dead_post {
202 background-color: #442222;
202 background-color: #442222;
203 }
203 }
204
204
205 .archive_post {
205 .archive_post {
206 background-color: #000;
206 background-color: #000;
207 }
207 }
208
208
209 .mark_btn {
209 .mark_btn {
210 border: 1px solid;
210 border: 1px solid;
211 min-width: 2ex;
211 min-width: 2ex;
212 padding: 2px 2ex;
212 padding: 2px 2ex;
213 }
213 }
214
214
215 .mark_btn:hover {
215 .mark_btn:hover {
216 background: #555;
216 background: #555;
217 }
217 }
218
218
219 .quote {
219 .quote {
220 color: #92cf38;
220 color: #92cf38;
221 font-style: italic;
221 font-style: italic;
222 }
222 }
223
223
224 .multiquote {
224 .multiquote {
225 padding: 3px;
225 padding: 3px;
226 display: inline-block;
226 display: inline-block;
227 background: #222;
227 background: #222;
228 border-style: solid;
228 border-style: solid;
229 border-width: 1px 1px 1px 4px;
229 border-width: 1px 1px 1px 4px;
230 font-size: 0.9em;
230 font-size: 0.9em;
231 }
231 }
232
232
233 .spoiler {
233 .spoiler {
234 background: white;
234 background: white;
235 color: white;
235 color: white;
236 }
236 }
237
237
238 .spoiler:hover {
238 .spoiler:hover {
239 color: black;
239 color: black;
240 }
240 }
241
241
242 .comment {
242 .comment {
243 color: #eb2;
243 color: #eb2;
244 }
244 }
245
245
246 a:hover {
246 a:hover {
247 text-decoration: underline;
247 text-decoration: underline;
248 }
248 }
249
249
250 .last-replies {
250 .last-replies {
251 margin-left: 3ex;
251 margin-left: 3ex;
252 margin-right: 3ex;
252 margin-right: 3ex;
253 }
253 }
254
254
255 .thread {
255 .thread {
256 margin-bottom: 3ex;
256 margin-bottom: 3ex;
257 margin-top: 1ex;
257 margin-top: 1ex;
258 }
258 }
259
259
260 .post:target {
260 .post:target {
261 border: solid 2px white;
261 border: solid 2px white;
262 }
262 }
263
263
264 pre{
264 pre{
265 white-space:pre-wrap
265 white-space:pre-wrap
266 }
266 }
267
267
268 li {
268 li {
269 list-style-position: inside;
269 list-style-position: inside;
270 }
270 }
271
271
272 .fancybox-skin {
272 .fancybox-skin {
273 position: relative;
273 position: relative;
274 background-color: #fff;
274 background-color: #fff;
275 color: #ddd;
275 color: #ddd;
276 text-shadow: none;
276 text-shadow: none;
277 }
277 }
278
278
279 .fancybox-image {
279 .fancybox-image {
280 border: 1px solid black;
280 border: 1px solid black;
281 }
281 }
282
282
283 .image-mode-tab {
283 .image-mode-tab {
284 background: #444;
284 background: #444;
285 color: #eee;
285 color: #eee;
286 margin-top: 5px;
286 margin-top: 5px;
287 padding: 5px;
287 padding: 5px;
288 border-top: 1px solid #888;
288 border-top: 1px solid #888;
289 border-bottom: 1px solid #888;
289 border-bottom: 1px solid #888;
290 }
290 }
291
291
292 .image-mode-tab > label {
292 .image-mode-tab > label {
293 margin: 0 1ex;
293 margin: 0 1ex;
294 }
294 }
295
295
296 .image-mode-tab > label > input {
296 .image-mode-tab > label > input {
297 margin-right: .5ex;
297 margin-right: .5ex;
298 }
298 }
299
299
300 #posts-table {
300 #posts-table {
301 margin-top: 5px;
301 margin-top: 5px;
302 margin-bottom: 5px;
302 margin-bottom: 5px;
303 }
303 }
304
304
305 .tag_info > h2 {
305 .tag_info > h2 {
306 margin: 0;
306 margin: 0;
307 }
307 }
308
308
309 .post-info {
309 .post-info {
310 color: #ddd;
310 color: #ddd;
311 margin-bottom: 1ex;
311 margin-bottom: 1ex;
312 }
312 }
313
313
314 .moderator_info {
314 .moderator_info {
315 color: #e99d41;
315 color: #e99d41;
316 float: right;
316 float: right;
317 font-weight: bold;
317 font-weight: bold;
318 }
318 }
319
319
320 .refmap {
320 .refmap {
321 font-size: 0.9em;
321 font-size: 0.9em;
322 color: #ccc;
322 color: #ccc;
323 margin-top: 1em;
323 margin-top: 1em;
324 }
324 }
325
325
326 .fav {
326 .fav {
327 color: yellow;
327 color: yellow;
328 }
328 }
329
329
330 .not_fav {
330 .not_fav {
331 color: #ccc;
331 color: #ccc;
332 }
332 }
333
333
334 .role {
334 .role {
335 text-decoration: underline;
335 text-decoration: underline;
336 }
336 }
337
337
338 .form-email {
338 .form-email {
339 display: none;
339 display: none;
340 }
340 }
341
341
342 .footer {
342 .footer {
343 margin: 5px;
343 margin: 5px;
344 }
344 }
345
345
346 .bar-value {
346 .bar-value {
347 background: rgba(50, 55, 164, 0.45);
347 background: rgba(50, 55, 164, 0.45);
348 font-size: 0.9em;
348 font-size: 0.9em;
349 height: 1.5em;
349 height: 1.5em;
350 }
350 }
351
351
352 .bar-bg {
352 .bar-bg {
353 position: relative;
353 position: relative;
354 border-top: solid 1px #888;
354 border-top: solid 1px #888;
355 border-bottom: solid 1px #888;
355 border-bottom: solid 1px #888;
356 margin-top: 5px;
356 margin-top: 5px;
357 overflow: hidden;
357 overflow: hidden;
358 }
358 }
359
359
360 .bar-text {
360 .bar-text {
361 padding: 2px;
361 padding: 2px;
362 position: absolute;
362 position: absolute;
363 left: 0;
363 left: 0;
364 top: 0;
364 top: 0;
365 }
365 }
366
366
367 .page_link {
367 .page_link {
368 background: #444;
368 background: #444;
369 border-top: solid 1px #888;
369 border-top: solid 1px #888;
370 border-bottom: solid 1px #888;
370 border-bottom: solid 1px #888;
371 padding: 5px;
371 padding: 5px;
372 color: #eee;
372 color: #eee;
373 font-size: 2ex;
373 font-size: 2ex;
374 }
374 }
375
375
376 .skipped_replies {
376 .skipped_replies {
377 margin: 5px;
377 margin: 5px;
378 }
378 }
379
379
380 .current_page {
380 .current_page {
381 border: solid 1px #afdcec;
381 border: solid 1px #afdcec;
382 padding: 2px;
382 padding: 2px;
383 }
383 }
384
384
385 .current_mode {
385 .current_mode {
386 font-weight: bold;
386 font-weight: bold;
387 }
387 }
388
388
389 .gallery_image {
389 .gallery_image {
390 border: solid 1px;
390 border: solid 1px;
391 padding: 0.5ex;
391 padding: 0.5ex;
392 margin: 0.5ex;
392 margin: 0.5ex;
393 text-align: center;
393 text-align: center;
394 }
394 }
395
395
396 code {
396 code {
397 border: dashed 1px #ccc;
397 border: dashed 1px #ccc;
398 background: #111;
398 background: #111;
399 padding: 2px;
399 padding: 2px;
400 font-size: 1.2em;
400 font-size: 1.2em;
401 display: inline-block;
401 display: inline-block;
402 }
402 }
403
403
404 pre {
404 pre {
405 overflow: auto;
405 overflow: auto;
406 }
406 }
407
407
408 .img-full {
408 .img-full {
409 background: #222;
409 background: #222;
410 border: solid 1px white;
410 border: solid 1px white;
411 }
411 }
412
412
413 .tag_item {
413 .tag_item {
414 display: inline-block;
414 display: inline-block;
415 border: 1px dashed #666;
415 border: 1px dashed #666;
416 margin: 0.2ex;
416 margin: 0.2ex;
417 padding: 0.1ex;
417 padding: 0.1ex;
418 }
418 }
419
419
420 #id_models li {
420 #id_models li {
421 list-style: none;
421 list-style: none;
422 }
422 }
423
423
424 #id_q {
424 #id_q {
425 margin-left: 1ex;
425 margin-left: 1ex;
426 }
426 }
427
427
428 ul {
428 ul {
429 padding-left: 0px;
429 padding-left: 0px;
430 }
430 }
431
431
432 .quote-header {
432 .quote-header {
433 border-bottom: 2px solid #ddd;
433 border-bottom: 2px solid #ddd;
434 margin-bottom: 1ex;
434 margin-bottom: 1ex;
435 padding-bottom: .5ex;
435 padding-bottom: .5ex;
436 color: #ddd;
436 color: #ddd;
437 font-size: 1.2em;
437 font-size: 1.2em;
438 }
438 }
439
439
440 /* Post */
441 .post > .message, .post > .image {
442 padding-left: 1em;
443 }
444
440 /* Reflink preview */
445 /* Reflink preview */
441 .post_preview {
446 .post_preview {
442 border-left: 1px solid #777;
447 border-left: 1px solid #777;
443 border-right: 1px solid #777;
448 border-right: 1px solid #777;
444 }
449 }
445
450
446 /* Code highlighter */
451 /* Code highlighter */
447 .hljs {
452 .hljs {
448 color: #fff;
453 color: #fff;
449 background: #000;
454 background: #000;
450 display: inline-block;
455 display: inline-block;
451 }
456 }
452
457
453 .hljs, .hljs-subst, .hljs-tag .hljs-title, .lisp .hljs-title, .clojure .hljs-built_in, .nginx .hljs-title {
458 .hljs, .hljs-subst, .hljs-tag .hljs-title, .lisp .hljs-title, .clojure .hljs-built_in, .nginx .hljs-title {
454 color: #fff;
459 color: #fff;
455 }
460 }
@@ -1,263 +1,279 b''
1 # coding=utf-8
1 # coding=utf-8
2 import time
2 import time
3 import logging
3 import logging
4 from django.core.paginator import Paginator
4 from django.core.paginator import Paginator
5
5
6 from django.test import TestCase
6 from django.test import TestCase
7 from django.test.client import Client
7 from django.test.client import Client
8 from django.core.urlresolvers import reverse, NoReverseMatch
8 from django.core.urlresolvers import reverse, NoReverseMatch
9 from boards.abstracts.settingsmanager import get_settings_manager
9 from boards.abstracts.settingsmanager import get_settings_manager
10
10
11 from boards.models import Post, Tag, Thread
11 from boards.models import Post, Tag, Thread, KeyPair
12 from boards import urls
12 from boards import urls
13 from boards import settings
13 from boards import settings
14 import neboard
14 import neboard
15
15
16 TEST_TAG = 'test_tag'
16 TEST_TAG = 'test_tag'
17
17
18 PAGE_404 = 'boards/404.html'
18 PAGE_404 = 'boards/404.html'
19
19
20 TEST_TEXT = 'test text'
20 TEST_TEXT = 'test text'
21
21
22 NEW_THREAD_PAGE = '/'
22 NEW_THREAD_PAGE = '/'
23 THREAD_PAGE_ONE = '/thread/1/'
23 THREAD_PAGE_ONE = '/thread/1/'
24 THREAD_PAGE = '/thread/'
24 THREAD_PAGE = '/thread/'
25 TAG_PAGE = '/tag/'
25 TAG_PAGE = '/tag/'
26 HTTP_CODE_REDIRECT = 302
26 HTTP_CODE_REDIRECT = 302
27 HTTP_CODE_OK = 200
27 HTTP_CODE_OK = 200
28 HTTP_CODE_NOT_FOUND = 404
28 HTTP_CODE_NOT_FOUND = 404
29
29
30 logger = logging.getLogger(__name__)
30 logger = logging.getLogger(__name__)
31
31
32
32
33 class PostTests(TestCase):
33 class PostTests(TestCase):
34
34
35 def _create_post(self):
35 def _create_post(self):
36 tag = Tag.objects.create(name=TEST_TAG)
36 tag = Tag.objects.create(name=TEST_TAG)
37 return Post.objects.create_post(title='title', text='text',
37 return Post.objects.create_post(title='title', text='text',
38 tags=[tag])
38 tags=[tag])
39
39
40 def test_post_add(self):
40 def test_post_add(self):
41 """Test adding post"""
41 """Test adding post"""
42
42
43 post = self._create_post()
43 post = self._create_post()
44
44
45 self.assertIsNotNone(post, 'No post was created.')
45 self.assertIsNotNone(post, 'No post was created.')
46 self.assertEqual(TEST_TAG, post.get_thread().tags.all()[0].name,
46 self.assertEqual(TEST_TAG, post.get_thread().tags.all()[0].name,
47 'No tags were added to the post.')
47 'No tags were added to the post.')
48
48
49 def test_delete_post(self):
49 def test_delete_post(self):
50 """Test post deletion"""
50 """Test post deletion"""
51
51
52 post = self._create_post()
52 post = self._create_post()
53 post_id = post.id
53 post_id = post.id
54
54
55 Post.objects.delete_post(post)
55 Post.objects.delete_post(post)
56
56
57 self.assertFalse(Post.objects.filter(id=post_id).exists())
57 self.assertFalse(Post.objects.filter(id=post_id).exists())
58
58
59 def test_delete_thread(self):
59 def test_delete_thread(self):
60 """Test thread deletion"""
60 """Test thread deletion"""
61
61
62 opening_post = self._create_post()
62 opening_post = self._create_post()
63 thread = opening_post.get_thread()
63 thread = opening_post.get_thread()
64 reply = Post.objects.create_post("", "", thread=thread)
64 reply = Post.objects.create_post("", "", thread=thread)
65
65
66 thread.delete()
66 thread.delete()
67
67
68 self.assertFalse(Post.objects.filter(id=reply.id).exists())
68 self.assertFalse(Post.objects.filter(id=reply.id).exists())
69
69
70 def test_post_to_thread(self):
70 def test_post_to_thread(self):
71 """Test adding post to a thread"""
71 """Test adding post to a thread"""
72
72
73 op = self._create_post()
73 op = self._create_post()
74 post = Post.objects.create_post("", "", thread=op.get_thread())
74 post = Post.objects.create_post("", "", thread=op.get_thread())
75
75
76 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
76 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
77 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
77 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
78 'Post\'s create time doesn\'t match thread last edit'
78 'Post\'s create time doesn\'t match thread last edit'
79 ' time')
79 ' time')
80
80
81 def test_delete_posts_by_ip(self):
81 def test_delete_posts_by_ip(self):
82 """Test deleting posts with the given ip"""
82 """Test deleting posts with the given ip"""
83
83
84 post = self._create_post()
84 post = self._create_post()
85 post_id = post.id
85 post_id = post.id
86
86
87 Post.objects.delete_posts_by_ip('0.0.0.0')
87 Post.objects.delete_posts_by_ip('0.0.0.0')
88
88
89 self.assertFalse(Post.objects.filter(id=post_id).exists())
89 self.assertFalse(Post.objects.filter(id=post_id).exists())
90
90
91 def test_get_thread(self):
91 def test_get_thread(self):
92 """Test getting all posts of a thread"""
92 """Test getting all posts of a thread"""
93
93
94 opening_post = self._create_post()
94 opening_post = self._create_post()
95
95
96 for i in range(0, 2):
96 for i in range(0, 2):
97 Post.objects.create_post('title', 'text',
97 Post.objects.create_post('title', 'text',
98 thread=opening_post.get_thread())
98 thread=opening_post.get_thread())
99
99
100 thread = opening_post.get_thread()
100 thread = opening_post.get_thread()
101
101
102 self.assertEqual(3, thread.replies.count())
102 self.assertEqual(3, thread.replies.count())
103
103
104 def test_create_post_with_tag(self):
104 def test_create_post_with_tag(self):
105 """Test adding tag to post"""
105 """Test adding tag to post"""
106
106
107 tag = Tag.objects.create(name='test_tag')
107 tag = Tag.objects.create(name='test_tag')
108 post = Post.objects.create_post(title='title', text='text', tags=[tag])
108 post = Post.objects.create_post(title='title', text='text', tags=[tag])
109
109
110 thread = post.get_thread()
110 thread = post.get_thread()
111 self.assertIsNotNone(post, 'Post not created')
111 self.assertIsNotNone(post, 'Post not created')
112 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
112 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
113 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
113 self.assertTrue(thread in tag.threads.all(), 'Thread not added to tag')
114
114
115 def test_thread_max_count(self):
115 def test_thread_max_count(self):
116 """Test deletion of old posts when the max thread count is reached"""
116 """Test deletion of old posts when the max thread count is reached"""
117
117
118 for i in range(settings.MAX_THREAD_COUNT + 1):
118 for i in range(settings.MAX_THREAD_COUNT + 1):
119 self._create_post()
119 self._create_post()
120
120
121 self.assertEqual(settings.MAX_THREAD_COUNT,
121 self.assertEqual(settings.MAX_THREAD_COUNT,
122 len(Thread.objects.filter(archived=False)))
122 len(Thread.objects.filter(archived=False)))
123
123
124 def test_pages(self):
124 def test_pages(self):
125 """Test that the thread list is properly split into pages"""
125 """Test that the thread list is properly split into pages"""
126
126
127 for i in range(settings.MAX_THREAD_COUNT):
127 for i in range(settings.MAX_THREAD_COUNT):
128 self._create_post()
128 self._create_post()
129
129
130 all_threads = Thread.objects.filter(archived=False)
130 all_threads = Thread.objects.filter(archived=False)
131
131
132 paginator = Paginator(Thread.objects.filter(archived=False),
132 paginator = Paginator(Thread.objects.filter(archived=False),
133 settings.THREADS_PER_PAGE)
133 settings.THREADS_PER_PAGE)
134 posts_in_second_page = paginator.page(2).object_list
134 posts_in_second_page = paginator.page(2).object_list
135 first_post = posts_in_second_page[0]
135 first_post = posts_in_second_page[0]
136
136
137 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
137 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
138 first_post.id)
138 first_post.id)
139
139
140
140
141 class PagesTest(TestCase):
141 class PagesTest(TestCase):
142
142
143 def test_404(self):
143 def test_404(self):
144 """Test receiving error 404 when opening a non-existent page"""
144 """Test receiving error 404 when opening a non-existent page"""
145
145
146 tag_name = u'test_tag'
146 tag_name = u'test_tag'
147 tag = Tag.objects.create(name=tag_name)
147 tag = Tag.objects.create(name=tag_name)
148 client = Client()
148 client = Client()
149
149
150 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
150 Post.objects.create_post('title', TEST_TEXT, tags=[tag])
151
151
152 existing_post_id = Post.objects.all()[0].id
152 existing_post_id = Post.objects.all()[0].id
153 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
153 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
154 '/')
154 '/')
155 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
155 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
156 u'Cannot open existing thread')
156 u'Cannot open existing thread')
157
157
158 response_not_existing = client.get(THREAD_PAGE + str(
158 response_not_existing = client.get(THREAD_PAGE + str(
159 existing_post_id + 1) + '/')
159 existing_post_id + 1) + '/')
160 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
160 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
161 u'Not existing thread is opened')
161 u'Not existing thread is opened')
162
162
163 response_existing = client.get(TAG_PAGE + tag_name + '/')
163 response_existing = client.get(TAG_PAGE + tag_name + '/')
164 self.assertEqual(HTTP_CODE_OK,
164 self.assertEqual(HTTP_CODE_OK,
165 response_existing.status_code,
165 response_existing.status_code,
166 u'Cannot open existing tag')
166 u'Cannot open existing tag')
167
167
168 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
168 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
169 self.assertEqual(PAGE_404,
169 self.assertEqual(PAGE_404,
170 response_not_existing.templates[0].name,
170 response_not_existing.templates[0].name,
171 u'Not existing tag is opened')
171 u'Not existing tag is opened')
172
172
173 reply_id = Post.objects.create_post('', TEST_TEXT,
173 reply_id = Post.objects.create_post('', TEST_TEXT,
174 thread=Post.objects.all()[0]
174 thread=Post.objects.all()[0]
175 .get_thread())
175 .get_thread())
176 response_not_existing = client.get(THREAD_PAGE + str(
176 response_not_existing = client.get(THREAD_PAGE + str(
177 reply_id) + '/')
177 reply_id) + '/')
178 self.assertEqual(PAGE_404,
178 self.assertEqual(PAGE_404,
179 response_not_existing.templates[0].name,
179 response_not_existing.templates[0].name,
180 u'Reply is opened as a thread')
180 u'Reply is opened as a thread')
181
181
182
182
183 class FormTest(TestCase):
183 class FormTest(TestCase):
184 def test_post_validation(self):
184 def test_post_validation(self):
185 client = Client()
185 client = Client()
186
186
187 valid_tags = u'tag1 tag_2 тег_3'
187 valid_tags = u'tag1 tag_2 тег_3'
188 invalid_tags = u'$%_356 ---'
188 invalid_tags = u'$%_356 ---'
189
189
190 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
190 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
191 'text': TEST_TEXT,
191 'text': TEST_TEXT,
192 'tags': valid_tags})
192 'tags': valid_tags})
193 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
193 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
194 msg='Posting new message failed: got code ' +
194 msg='Posting new message failed: got code ' +
195 str(response.status_code))
195 str(response.status_code))
196
196
197 self.assertEqual(1, Post.objects.count(),
197 self.assertEqual(1, Post.objects.count(),
198 msg='No posts were created')
198 msg='No posts were created')
199
199
200 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
200 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
201 'tags': invalid_tags})
201 'tags': invalid_tags})
202 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
202 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
203 'where it should fail')
203 'where it should fail')
204
204
205 # Change posting delay so we don't have to wait for 30 seconds or more
205 # Change posting delay so we don't have to wait for 30 seconds or more
206 old_posting_delay = neboard.settings.POSTING_DELAY
206 old_posting_delay = neboard.settings.POSTING_DELAY
207 # Wait fot the posting delay or we won't be able to post
207 # Wait fot the posting delay or we won't be able to post
208 settings.POSTING_DELAY = 1
208 settings.POSTING_DELAY = 1
209 time.sleep(neboard.settings.POSTING_DELAY + 1)
209 time.sleep(neboard.settings.POSTING_DELAY + 1)
210 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
210 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
211 'tags': valid_tags})
211 'tags': valid_tags})
212 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
212 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
213 msg=u'Posting new message failed: got code ' +
213 msg=u'Posting new message failed: got code ' +
214 str(response.status_code))
214 str(response.status_code))
215 # Restore posting delay
215 # Restore posting delay
216 settings.POSTING_DELAY = old_posting_delay
216 settings.POSTING_DELAY = old_posting_delay
217
217
218 self.assertEqual(2, Post.objects.count(),
218 self.assertEqual(2, Post.objects.count(),
219 msg=u'No posts were created')
219 msg=u'No posts were created')
220
220
221
221
222 class ViewTest(TestCase):
222 class ViewTest(TestCase):
223
223
224 def test_all_views(self):
224 def test_all_views(self):
225 """
225 """
226 Try opening all views defined in ulrs.py that don't need additional
226 Try opening all views defined in ulrs.py that don't need additional
227 parameters
227 parameters
228 """
228 """
229
229
230 client = Client()
230 client = Client()
231 for url in urls.urlpatterns:
231 for url in urls.urlpatterns:
232 try:
232 try:
233 view_name = url.name
233 view_name = url.name
234 logger.debug('Testing view %s' % view_name)
234 logger.debug('Testing view %s' % view_name)
235
235
236 try:
236 try:
237 response = client.get(reverse(view_name))
237 response = client.get(reverse(view_name))
238
238
239 self.assertEqual(HTTP_CODE_OK, response.status_code,
239 self.assertEqual(HTTP_CODE_OK, response.status_code,
240 '%s view not opened' % view_name)
240 '%s view not opened' % view_name)
241 except NoReverseMatch:
241 except NoReverseMatch:
242 # This view just needs additional arguments
242 # This view just needs additional arguments
243 pass
243 pass
244 except Exception as e:
244 except Exception as e:
245 self.fail('Got exception %s at %s view' % (e, view_name))
245 self.fail('Got exception %s at %s view' % (e, view_name))
246 except AttributeError:
246 except AttributeError:
247 # This is normal, some views do not have names
247 # This is normal, some views do not have names
248 pass
248 pass
249
249
250
250
251 class AbstractTest(TestCase):
251 class AbstractTest(TestCase):
252 def test_settings_manager(self):
252 def test_settings_manager(self):
253 request = MockRequest()
253 request = MockRequest()
254 settings_manager = get_settings_manager(request)
254 settings_manager = get_settings_manager(request)
255
255
256 settings_manager.set_setting('test_setting', 'test_value')
256 settings_manager.set_setting('test_setting', 'test_value')
257 self.assertEqual('test_value', settings_manager.get_setting(
257 self.assertEqual('test_value', settings_manager.get_setting(
258 'test_setting'), u'Setting update failed.')
258 'test_setting'), u'Setting update failed.')
259
259
260
260
261 class MockRequest:
261 class MockRequest:
262 def __init__(self):
262 def __init__(self):
263 self.session = dict()
263 self.session = dict()
264
265
266 class KeyTest(TestCase):
267 def test_create_key(self):
268 key = KeyPair.objects.generate_key('ecdsa')
269
270 self.assertIsNotNone(key, 'The key was not created.')
271
272 def test_validation(self):
273 key = KeyPair.objects.generate_key(key_type='ecdsa')
274 message = 'msg'
275 signature = key.sign(message)
276 valid = KeyPair.objects.verify(key.public_key, message, signature,
277 key_type='ecdsa')
278
279 self.assertTrue(valid, 'Message verification failed.')
@@ -1,7 +1,8 b''
1 south>=0.8.4
1 south>=0.8.4
2 haystack
2 haystack
3 pillow
3 pillow
4 django>=1.6
4 django>=1.6
5 django_cleanup
5 django_cleanup
6 django-markupfield
6 django-markupfield
7 bbcode
7 bbcode
8 ecdsa
General Comments 0
You need to be logged in to leave comments. Login now