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