##// END OF EJS Templates
Merged 1.8-dev into default
neko259 -
r704:7f7c33ba merge 1.8 default
parent child Browse files
Show More
@@ -0,0 +1,31 b''
1 from boards import utils
2 from boards.models import Post
3 from boards.models.post import SETTING_MODERATE
4 import neboard
5
6 __author__ = 'neko259'
7
8
9 def user_and_ui_processor(request):
10 context = {}
11
12 user = utils.get_user(request)
13 context['user'] = user
14 context['tags'] = user.fav_tags.all()
15 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
16
17 theme = utils.get_theme(request, user)
18 context['theme'] = theme
19 context['theme_css'] = 'css/' + theme + '/base_page.css'
20
21 # This shows the moderator panel
22 moderate = user.get_setting(SETTING_MODERATE)
23 if moderate == 'True':
24 context['moderator'] = user.is_moderator()
25 else:
26 context['moderator'] = False
27
28 context['version'] = neboard.settings.VERSION
29 context['site_name'] = neboard.settings.SITE_NAME
30
31 return context No newline at end of file
@@ -0,0 +1,1 b''
1 __author__ = 'vurdalak'
@@ -0,0 +1,1 b''
1 __author__ = 'vurdalak'
@@ -0,0 +1,29 b''
1 from datetime import datetime, timedelta
2 from django.core.management import BaseCommand
3 from django.db import transaction
4 from django.db.models import Count
5 from boards.models import User, Post
6
7 __author__ = 'neko259'
8
9 OLD_USER_AGE_DAYS = 90
10
11
12 class Command(BaseCommand):
13 help = 'Removes empty users (that don\'t have posts or tags'
14
15 @transaction.atomic
16 def handle(self, *args, **options):
17 old_registration_date = datetime.now().date() - timedelta(
18 OLD_USER_AGE_DAYS)
19
20 old_users = User.objects.annotate(tags_count=Count('fav_tags')).filter(
21 tags_count=0).filter(registration_time__lt=old_registration_date)
22 deleted_users = 0
23 for user in old_users:
24 if not Post.objects.filter(user=user).exists():
25 self.stdout.write('Deleting user %s' % user.user_id)
26 user.delete()
27 deleted_users += 1
28
29 self.stdout.write('Deleted %d users' % deleted_users) No newline at end of file
@@ -0,0 +1,118 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 'PostImage'
12 db.create_table(u'boards_postimage', (
13 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('width', self.gf('django.db.models.fields.IntegerField')(default=0)),
15 ('height', self.gf('django.db.models.fields.IntegerField')(default=0)),
16 ('pre_width', self.gf('django.db.models.fields.IntegerField')(default=0)),
17 ('pre_height', self.gf('django.db.models.fields.IntegerField')(default=0)),
18 ('image', self.gf('boards.thumbs.ImageWithThumbsField')(max_length=100, blank=True)),
19 ('hash', self.gf('django.db.models.fields.CharField')(max_length=36)),
20 ))
21 db.send_create_signal('boards', ['PostImage'])
22
23 # Adding M2M table for field images on 'Post'
24 m2m_table_name = db.shorten_name(u'boards_post_images')
25 db.create_table(m2m_table_name, (
26 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
27 ('post', models.ForeignKey(orm['boards.post'], null=False)),
28 ('postimage', models.ForeignKey(orm['boards.postimage'], null=False))
29 ))
30 db.create_unique(m2m_table_name, ['post_id', 'postimage_id'])
31
32
33 def backwards(self, orm):
34 # Deleting model 'PostImage'
35 db.delete_table(u'boards_postimage')
36
37 # Removing M2M table for field images on 'Post'
38 db.delete_table(db.shorten_name(u'boards_post_images'))
39
40
41 models = {
42 'boards.ban': {
43 'Meta': {'object_name': 'Ban'},
44 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
45 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
47 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
48 },
49 'boards.post': {
50 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
51 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
52 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
53 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
54 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
55 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
56 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
57 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
58 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
59 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
60 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
61 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
62 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
63 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
64 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
65 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
66 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
67 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
68 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
69 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
70 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
71 },
72 'boards.postimage': {
73 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
74 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
76 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
77 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
78 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
79 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
80 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
81 },
82 'boards.setting': {
83 'Meta': {'object_name': 'Setting'},
84 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
85 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
86 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
87 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
88 },
89 'boards.tag': {
90 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
91 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
93 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
94 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
95 },
96 'boards.thread': {
97 'Meta': {'object_name': 'Thread'},
98 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
99 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
100 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
101 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
102 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
103 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
104 },
105 'boards.user': {
106 'Meta': {'object_name': 'User'},
107 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
108 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
109 'hidden_tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ht+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Tag']"}),
110 'hidden_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'hth+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
111 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
112 'rank': ('django.db.models.fields.IntegerField', [], {}),
113 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
114 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
115 }
116 }
117
118 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,110 b''
1 # -*- coding: utf-8 -*-
2 from south.utils import datetime_utils as datetime
3 from south.db import db
4 from south.v2 import DataMigration
5 from django.db import models
6
7
8 class Migration(DataMigration):
9
10 def forwards(self, orm):
11 for post in orm['boards.Post'].objects.all():
12 if post.image:
13 image = orm['boards.PostImage']()
14
15 image.width = post.image_width
16 image.height = post.image_height
17
18 image.pre_width = post.image_pre_width
19 image.pre_height = post.image_pre_height
20
21 image.image = post.image
22 image.hash = post.image_hash
23
24 image.save()
25
26 post.images.add(image)
27 post.save()
28
29 def backwards(self, orm):
30 "Write your backwards methods here."
31
32 models = {
33 'boards.ban': {
34 'Meta': {'object_name': 'Ban'},
35 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
36 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
37 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
38 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
39 },
40 'boards.post': {
41 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
42 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
43 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
44 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
45 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
46 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
47 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
48 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
49 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
50 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
51 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
52 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
53 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
54 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
55 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
56 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
57 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
58 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
59 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
60 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
61 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
62 },
63 'boards.postimage': {
64 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
65 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
67 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
68 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
69 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
70 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
71 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
72 },
73 'boards.setting': {
74 'Meta': {'object_name': 'Setting'},
75 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
76 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
77 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
78 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
79 },
80 'boards.tag': {
81 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
82 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
83 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
84 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
85 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
86 },
87 'boards.thread': {
88 'Meta': {'object_name': 'Thread'},
89 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
90 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
91 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
93 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
94 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
95 },
96 'boards.user': {
97 'Meta': {'object_name': 'User'},
98 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
99 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
100 'hidden_tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ht+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Tag']"}),
101 'hidden_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'hth+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
102 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
103 'rank': ('django.db.models.fields.IntegerField', [], {}),
104 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
105 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
106 }
107 }
108
109 complete_apps = ['boards']
110 symmetrical = True
@@ -0,0 +1,141 b''
1 # -*- coding: utf-8 -*-
2 from south.utils import datetime_utils as datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Deleting field 'Post.image_pre_height'
12 db.delete_column(u'boards_post', 'image_pre_height')
13
14 # Deleting field 'Post.image'
15 db.delete_column(u'boards_post', 'image')
16
17 # Deleting field 'Post.image_pre_width'
18 db.delete_column(u'boards_post', 'image_pre_width')
19
20 # Deleting field 'Post.image_width'
21 db.delete_column(u'boards_post', 'image_width')
22
23 # Deleting field 'Post.image_height'
24 db.delete_column(u'boards_post', 'image_height')
25
26 # Deleting field 'Post.image_hash'
27 db.delete_column(u'boards_post', 'image_hash')
28
29
30 def backwards(self, orm):
31 # Adding field 'Post.image_pre_height'
32 db.add_column(u'boards_post', 'image_pre_height',
33 self.gf('django.db.models.fields.IntegerField')(default=0),
34 keep_default=False)
35
36
37 # User chose to not deal with backwards NULL issues for 'Post.image'
38 raise RuntimeError("Cannot reverse this migration. 'Post.image' and its values cannot be restored.")
39
40 # The following code is provided here to aid in writing a correct migration # Adding field 'Post.image'
41 db.add_column(u'boards_post', 'image',
42 self.gf('boards.thumbs.ImageWithThumbsField')(max_length=100, blank=True),
43 keep_default=False)
44
45 # Adding field 'Post.image_pre_width'
46 db.add_column(u'boards_post', 'image_pre_width',
47 self.gf('django.db.models.fields.IntegerField')(default=0),
48 keep_default=False)
49
50 # Adding field 'Post.image_width'
51 db.add_column(u'boards_post', 'image_width',
52 self.gf('django.db.models.fields.IntegerField')(default=0),
53 keep_default=False)
54
55 # Adding field 'Post.image_height'
56 db.add_column(u'boards_post', 'image_height',
57 self.gf('django.db.models.fields.IntegerField')(default=0),
58 keep_default=False)
59
60
61 # User chose to not deal with backwards NULL issues for 'Post.image_hash'
62 raise RuntimeError("Cannot reverse this migration. 'Post.image_hash' and its values cannot be restored.")
63
64 # The following code is provided here to aid in writing a correct migration # Adding field 'Post.image_hash'
65 db.add_column(u'boards_post', 'image_hash',
66 self.gf('django.db.models.fields.CharField')(max_length=36),
67 keep_default=False)
68
69
70 models = {
71 'boards.ban': {
72 'Meta': {'object_name': 'Ban'},
73 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
74 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
76 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
77 },
78 'boards.post': {
79 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
80 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
81 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
82 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
83 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
84 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
85 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
86 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
87 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
88 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
89 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
90 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
91 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
92 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
93 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
94 },
95 'boards.postimage': {
96 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
97 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
98 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
99 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
100 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
101 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
102 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
103 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
104 },
105 'boards.setting': {
106 'Meta': {'object_name': 'Setting'},
107 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
108 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
109 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
110 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
111 },
112 'boards.tag': {
113 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
114 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
115 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
116 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
117 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
118 },
119 'boards.thread': {
120 'Meta': {'object_name': 'Thread'},
121 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
122 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
123 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
124 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
125 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
126 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
127 },
128 'boards.user': {
129 'Meta': {'object_name': 'User'},
130 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
131 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
132 'hidden_tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'ht+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Tag']"}),
133 'hidden_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'hth+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
134 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
135 'rank': ('django.db.models.fields.IntegerField', [], {}),
136 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
137 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
138 }
139 }
140
141 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,10 b''
1 __author__ = 'neko259'
2
3
4 class Viewable():
5 def __init__(self):
6 pass
7
8 def get_view(self, *args, **kwargs):
9 """Get an HTML view for a model"""
10 pass No newline at end of file
@@ -0,0 +1,60 b''
1 import hashlib
2 import os
3 from random import random
4 import time
5 from django.db import models
6 from boards import thumbs
7
8 __author__ = 'neko259'
9
10
11 IMAGE_THUMB_SIZE = (200, 150)
12 IMAGES_DIRECTORY = 'images/'
13 FILE_EXTENSION_DELIMITER = '.'
14
15
16 class PostImage(models.Model):
17 class Meta:
18 app_label = 'boards'
19 ordering = ('id',)
20
21 @staticmethod
22 def _update_image_filename(filename):
23 """
24 Gets unique image filename
25 """
26
27 path = IMAGES_DIRECTORY
28 new_name = str(int(time.mktime(time.gmtime())))
29 new_name += str(int(random() * 1000))
30 new_name += FILE_EXTENSION_DELIMITER
31 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
32
33 return os.path.join(path, new_name)
34
35 width = models.IntegerField(default=0)
36 height = models.IntegerField(default=0)
37
38 pre_width = models.IntegerField(default=0)
39 pre_height = models.IntegerField(default=0)
40
41 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
42 blank=True, sizes=(IMAGE_THUMB_SIZE,),
43 width_field='width',
44 height_field='height',
45 preview_width_field='pre_width',
46 preview_height_field='pre_height')
47 hash = models.CharField(max_length=36)
48
49 def save(self, *args, **kwargs):
50 """
51 Saves the model and computes the image hash for deduplication purposes.
52 """
53
54 if not self.pk and self.image:
55 md5 = hashlib.md5()
56 for chunk in self.image.chunks():
57 md5.update(chunk)
58 self.hash = md5.hexdigest()
59 super(PostImage, self).save(*args, **kwargs)
60
@@ -0,0 +1,163 b''
1 import logging
2 from django.db.models import Count
3 from django.utils import timezone
4 from django.core.cache import cache
5 from django.db import models
6 from neboard import settings
7
8 __author__ = 'neko259'
9
10
11 logger = logging.getLogger(__name__)
12
13
14 CACHE_KEY_OPENING_POST = 'opening_post_id'
15
16
17 class Thread(models.Model):
18
19 class Meta:
20 app_label = 'boards'
21
22 tags = models.ManyToManyField('Tag')
23 bump_time = models.DateTimeField()
24 last_edit_time = models.DateTimeField()
25 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
26 blank=True, related_name='tre+')
27 archived = models.BooleanField(default=False)
28
29 def get_tags(self):
30 """
31 Gets a sorted tag list.
32 """
33
34 return self.tags.order_by('name')
35
36 def bump(self):
37 """
38 Bumps (moves to up) thread if possible.
39 """
40
41 if self.can_bump():
42 self.bump_time = timezone.now()
43
44 logger.info('Bumped thread %d' % self.id)
45
46 def get_reply_count(self):
47 return self.replies.count()
48
49 def get_images_count(self):
50 # TODO Use sum
51 total_count = 0
52 for post_with_image in self.replies.annotate(images_count=Count(
53 'images')):
54 total_count += post_with_image.images_count
55 return total_count
56
57 def can_bump(self):
58 """
59 Checks if the thread can be bumped by replying to it.
60 """
61
62 if self.archived:
63 return False
64
65 post_count = self.get_reply_count()
66
67 return post_count < settings.MAX_POSTS_PER_THREAD
68
69 def delete_with_posts(self):
70 """
71 Completely deletes thread and all its posts
72 """
73
74 if self.replies.exists():
75 self.replies.all().delete()
76
77 self.delete()
78
79 def get_last_replies(self):
80 """
81 Gets several last replies, not including opening post
82 """
83
84 if settings.LAST_REPLIES_COUNT > 0:
85 reply_count = self.get_reply_count()
86
87 if reply_count > 0:
88 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
89 reply_count - 1)
90 replies = self.get_replies()
91 last_replies = replies[reply_count - reply_count_to_show:]
92
93 return last_replies
94
95 def get_skipped_replies_count(self):
96 """
97 Gets number of posts between opening post and last replies.
98 """
99 reply_count = self.get_reply_count()
100 last_replies_count = min(settings.LAST_REPLIES_COUNT,
101 reply_count - 1)
102 return reply_count - last_replies_count - 1
103
104 def get_replies(self, view_fields_only=False):
105 """
106 Gets sorted thread posts
107 """
108
109 query = self.replies.order_by('pub_time').prefetch_related('images')
110 if view_fields_only:
111 query = query.defer('poster_user_agent', 'text_markup_type')
112 return query.all()
113
114 def get_replies_with_images(self, view_fields_only=False):
115 return self.get_replies(view_fields_only).annotate(images_count=Count(
116 'images')).filter(images_count__gt=0)
117
118 def add_tag(self, tag):
119 """
120 Connects thread to a tag and tag to a thread
121 """
122
123 self.tags.add(tag)
124 tag.threads.add(self)
125
126 def remove_tag(self, tag):
127 self.tags.remove(tag)
128 tag.threads.remove(self)
129
130 def get_opening_post(self, only_id=False):
131 """
132 Gets the first post of the thread
133 """
134
135 query = self.replies.order_by('pub_time')
136 if only_id:
137 query = query.only('id')
138 opening_post = query.first()
139
140 return opening_post
141
142 def get_opening_post_id(self):
143 """
144 Gets ID of the first thread post.
145 """
146
147 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
148 opening_post_id = cache.get(cache_key)
149 if not opening_post_id:
150 opening_post_id = self.get_opening_post(only_id=True).id
151 cache.set(cache_key, opening_post_id)
152
153 return opening_post_id
154
155 def __unicode__(self):
156 return str(self.id)
157
158 def get_pub_time(self):
159 """
160 Gets opening post's pub time because thread does not have its own one.
161 """
162
163 return self.get_opening_post().pub_time
@@ -0,0 +1,24 b''
1 from haystack import indexes
2 from boards.models import Post, Tag
3
4 __author__ = 'neko259'
5
6
7 class PostIndex(indexes.SearchIndex, indexes.Indexable):
8 text = indexes.CharField(document=True, use_template=True)
9
10 def get_model(self):
11 return Post
12
13 def index_queryset(self, using=None):
14 return self.get_model().objects.all()
15
16
17 class TagIndex(indexes.SearchIndex, indexes.Indexable):
18 text = indexes.CharField(document=True, use_template=True)
19
20 def get_model(self):
21 return Tag
22
23 def index_queryset(self, using=None):
24 return self.get_model().objects.get_not_empty_tags()
@@ -0,0 +1,1 b''
1 .hljs{display:block;padding:.5em;background:#f0f0f0}.hljs,.hljs-subst,.hljs-tag .hljs-title,.lisp .hljs-title,.clojure .hljs-built_in,.nginx .hljs-title{color:black}.hljs-string,.hljs-title,.hljs-constant,.hljs-parent,.hljs-tag .hljs-value,.hljs-rules .hljs-value,.hljs-rules .hljs-value .hljs-number,.hljs-preprocessor,.hljs-pragma,.haml .hljs-symbol,.ruby .hljs-symbol,.ruby .hljs-symbol .hljs-string,.hljs-aggregate,.hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.hljs-addition,.hljs-flow,.hljs-stream,.bash .hljs-variable,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.tex .hljs-special,.erlang_repl .hljs-function_or_atom,.asciidoc .hljs-header,.markdown .hljs-header,.coffeescript .hljs-attribute{color:#800}.smartquote,.hljs-comment,.hljs-annotation,.hljs-template_comment,.diff .hljs-header,.hljs-chunk,.asciidoc .hljs-blockquote,.markdown .hljs-blockquote{color:#888}.hljs-number,.hljs-date,.hljs-regexp,.hljs-literal,.hljs-hexcolor,.smalltalk .hljs-symbol,.smalltalk .hljs-char,.go .hljs-constant,.hljs-change,.lasso .hljs-variable,.makefile .hljs-variable,.asciidoc .hljs-bullet,.markdown .hljs-bullet,.asciidoc .hljs-link_url,.markdown .hljs-link_url{color:#080}.hljs-label,.hljs-javadoc,.ruby .hljs-string,.hljs-decorator,.hljs-filter .hljs-argument,.hljs-localvars,.hljs-array,.hljs-attr_selector,.hljs-important,.hljs-pseudo,.hljs-pi,.haml .hljs-bullet,.hljs-doctype,.hljs-deletion,.hljs-envvar,.hljs-shebang,.apache .hljs-sqbracket,.nginx .hljs-built_in,.tex .hljs-formula,.erlang_repl .hljs-reserved,.hljs-prompt,.asciidoc .hljs-link_label,.markdown .hljs-link_label,.vhdl .hljs-attribute,.clojure .hljs-attribute,.asciidoc .hljs-attribute,.lasso .hljs-attribute,.coffeescript .hljs-property,.hljs-phony{color:#88F}.hljs-keyword,.hljs-id,.hljs-title,.hljs-built_in,.hljs-aggregate,.css .hljs-tag,.hljs-javadoctag,.hljs-phpdoc,.hljs-yardoctag,.smalltalk .hljs-class,.hljs-winutils,.bash .hljs-variable,.apache .hljs-tag,.go .hljs-typename,.tex .hljs-command,.asciidoc .hljs-strong,.markdown .hljs-strong,.hljs-request,.hljs-status{font-weight:bold}.asciidoc .hljs-emphasis,.markdown .hljs-emphasis{font-style:italic}.nginx .hljs-built_in{font-weight:normal}.coffeescript .javascript,.javascript .xml,.lasso .markup,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata{opacity:.5} No newline at end of file
@@ -0,0 +1,1 b''
1 var hljs=new function(){function k(v){return v.replace(/&/gm,"&amp;").replace(/</gm,"&lt;").replace(/>/gm,"&gt;")}function t(v){return v.nodeName.toLowerCase()}function i(w,x){var v=w&&w.exec(x);return v&&v.index==0}function d(v){return Array.prototype.map.call(v.childNodes,function(w){if(w.nodeType==3){return b.useBR?w.nodeValue.replace(/\n/g,""):w.nodeValue}if(t(w)=="br"){return"\n"}return d(w)}).join("")}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^language-/,"")});return v.filter(function(x){return j(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset<y[0].offset)?w:y}return y[0].event=="start"?w:y}function A(H){function G(I){return" "+I.nodeName+'="'+k(I.value)+'"'}F+="<"+t(H)+Array.prototype.map.call(H.attributes,G).join("")+">"}function E(G){F+="</"+t(G)+">"}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=k(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+k(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};function E(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})}if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b=D.bK.split(" ").join("|")}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?\\b("+F.b+")\\b\\.?":F.b}).concat([D.tE]).concat([D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T<V.c.length;T++){if(i(V.c[T].bR,U)){return V.c[T]}}}function z(U,T){if(i(U.eR,T)){return U}if(U.eW){return z(U.parent,T)}}function A(T,U){return !J&&i(U.iR,T)}function E(V,T){var U=M.cI?T[0].toLowerCase():T[0];return V.k.hasOwnProperty(U)&&V.k[U]}function w(Z,X,W,V){var T=V?"":b.classPrefix,U='<span class="'+T,Y=W?"":"</span>";U+=Z+'">';return U+X+Y}function N(){var U=k(C);if(!I.k){return U}var T="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(U);while(V){T+=U.substr(X,V.index-X);var W=E(I,V);if(W){H+=W[1];T+=w(W[0],V[0])}else{T+=V[0]}X=I.lR.lastIndex;V=I.lR.exec(U)}return T+U.substr(X)}function F(){if(I.sL&&!f[I.sL]){return k(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):g(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=k(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+="</span>"}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=k(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"<unnamed>")+'"')}C+=X;return X.length||1}var M=j(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+="</span>"}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:k(L)}}else{throw O}}}function g(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:k(y)};var w=v;x.forEach(function(z){if(!j(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function h(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"<br>")}return v}function p(z){var y=d(z);var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):g(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=h(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function e(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function j(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=g;this.fixMarkup=h;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=e;this.getLanguage=j;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("cs",function(b){var a="abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async await ascending descending from get group into join let orderby partial select set value var where yield";return{k:a,c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",b:"///|<!--|-->"},{cN:"xmlDocTag",b:"</?",e:">"}]},b.CLCM,b.CBLCLM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},b.ASM,b.QSM,b.CNM,{bK:"protected public private internal",e:/[{;=]/,k:a,c:[{bK:"class namespace interface",starts:{c:[b.TM]}},{b:b.IR+"\\s*\\(",rB:true,c:[b.TM]}]}]}});hljs.registerLanguage("ruby",function(e){var h="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var g="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor";var a={cN:"yardoctag",b:"@[A-Za-z]+"};var i={cN:"comment",v:[{b:"#",e:"$",c:[a]},{b:"^\\=begin",e:"^\\=end",c:[a],r:10},{b:"^__END__",e:"\\n$"}]};var c={cN:"subst",b:"#\\{",e:"}",k:g};var d={cN:"string",c:[e.BE,c],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:"%[qw]?\\(",e:"\\)"},{b:"%[qw]?\\[",e:"\\]"},{b:"%[qw]?{",e:"}"},{b:"%[qw]?<",e:">",r:10},{b:"%[qw]?/",e:"/",r:10},{b:"%[qw]?%",e:"%",r:10},{b:"%[qw]?-",e:"-",r:10},{b:"%[qw]?\\|",e:"\\|",r:10},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]};var b={cN:"params",b:"\\(",e:"\\)",k:g};var f=[d,i,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]},i]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[e.inherit(e.TM,{b:h}),b,i]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[d,{b:h}],r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[i,{cN:"regexp",c:[e.BE,c],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];c.c=f;b.c=f;return{k:g,c:f}});hljs.registerLanguage("diff",function(a){return{c:[{cN:"chunk",r:10,v:[{b:/^\@\@ +\-\d+,\d+ +\+\d+,\d+ +\@\@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"header",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"change",b:"^\\!",e:"$"}]}});hljs.registerLanguage("javascript",function(a){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},a.ASM,a.QSM,a.CLCM,a.CBLCLM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBLCLM,a.REGEXP_MODE,{b:/</,e:/>;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBLCLM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/</,r:0,c:[d,{cN:"attribute",b:c,r:0},{b:"=",r:0,c:[{cN:"value",v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html"],cI:true,c:[{cN:"doctype",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"<!--",e:"-->",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{title:"style"},c:[b],starts:{e:"</style>",rE:true,sL:"css"}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true,}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("css",function(a){var b="[a-zA-Z-][a-zA-Z0-9_-]*";var c={cN:"function",b:b+"\\(",e:"\\)",c:["self",a.NM,a.ASM,a.QSM]};return{cI:true,i:"[=/|']",c:[a.CBLCLM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:true,eE:true,r:0,c:[c,a.ASM,a.QSM,a.NM]}]},{cN:"tag",b:b,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBLCLM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[c,a.NM,a.QSM,a.ASM,a.CBLCLM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});hljs.registerLanguage("http",function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}});hljs.registerLanguage("java",function(b){var a="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws";return{k:a,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}],r:10},b.CLCM,b.CBLCLM,b.ASM,b.QSM,{bK:"protected public private",e:/[{;=]/,k:a,c:[{cN:"class",bK:"class interface",eW:true,i:/[:"<>]/,c:[{bK:"extends implements",r:10},b.UTM]},{b:b.UIR+"\\s*\\(",rB:true,c:[b.UTM]}]},b.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("php",function(b){var e={cN:"variable",b:"\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var a={cN:"preprocessor",b:/<\?(php)?|\?>/};var c={cN:"string",c:[b.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},b.inherit(b.ASM,{i:null}),b.inherit(b.QSM,{i:null})]};var d={v:[b.BNM,b.CNM]};return{cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[b.CLCM,b.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"},a]},{cN:"comment",b:"__halt_compiler.+?;",eW:true,k:"__halt_compiler",l:b.UIR},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[b.BE]},a,e,{cN:"function",bK:"function",e:/[;{]/,i:"\\$|\\[|%",c:[b.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",e,b.CBLCLM,c,d]}]},{cN:"class",bK:"class interface",e:"{",i:/[:\(\$"]/,c:[{bK:"extends implements",r:10},b.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[b.UTM]},{bK:"use",e:";",c:[b.UTM]},{b:"=>"},c,d]}});hljs.registerLanguage("python",function(a){var f={cN:"prompt",b:/^(>>>|\.\.\.) /};var b={cN:"string",c:[a.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[f],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[f],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/,},{b:/(b|br)"/,e:/"/,},a.ASM,a.QSM]};var d={cN:"number",r:0,v:[{b:a.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:a.CNR+"[lLjJ]?"}]};var e={cN:"params",b:/\(/,e:/\)/,c:["self",f,d,b]};var c={e:/:/,i:/[${=;\n]/,c:[a.UTM,e]};return{k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[f,d,b,a.HCM,a.inherit(c,{cN:"function",bK:"def",r:10}),a.inherit(c,{cN:"class",bK:"class"}),{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("sql",function(a){return{cI:true,i:/[<>]/,c:[{cN:"operator",b:"\\b(begin|end|start|commit|rollback|savepoint|lock|alter|create|drop|rename|call|delete|do|handler|insert|load|replace|select|truncate|update|set|show|pragma|grant|merge)\\b(?!:)",e:";",eW:true,k:{keyword:"all partial global month current_timestamp using go revoke smallint indicator end-exec disconnect zone with character assertion to add current_user usage input local alter match collate real then rollback get read timestamp session_user not integer bit unique day minute desc insert execute like ilike|2 level decimal drop continue isolation found where constraints domain right national some module transaction relative second connect escape close system_user for deferred section cast current sqlstate allocate intersect deallocate numeric public preserve full goto initially asc no key output collation group by union session both last language constraint column of space foreign deferrable prior connection unknown action commit view or first into float year primary cascaded except restrict set references names table outer open select size are rows from prepare distinct leading create only next inner authorization schema corresponding option declare precision immediate else timezone_minute external varying translation true case exception join hour default double scroll value cursor descriptor values dec fetch procedure delete and false int is describe char as at in varchar null trailing any absolute current_time end grant privileges when cross check write current_date pad begin temporary exec time update catalog user sql date on identity timezone_hour natural whenever interval work order cascade diagnostics nchar having left call do handler load replace truncate start lock show pragma exists number trigger if before after each row merge matched database",aggregate:"count sum min max avg"},c:[{cN:"string",b:"'",e:"'",c:[a.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[a.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[a.BE]},a.CNM]},a.CBLCLM,{cN:"comment",b:"--",e:"$"}]}});hljs.registerLanguage("ini",function(a){return{cI:true,i:/\S/,c:[{cN:"comment",b:";",e:"$"},{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9\\[\\]_-]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:true,k:"on off true false yes no",c:[a.QSM,a.NM],r:0}]}]}});hljs.registerLanguage("perl",function(c){var d="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when";var f={cN:"subst",b:"[$@]\\{",e:"\\}",k:d};var g={b:"->{",e:"}"};var a={cN:"variable",v:[{b:/\$\d/},{b:/[\$\%\@\*](\^\w\b|#\w+(\:\:\w+)*|{\w+}|\w+(\:\:\w*)*)/},{b:/[\$\%\@\*][^\s\w{]/,r:0}]};var e={cN:"comment",b:"^(__END__|__DATA__)",e:"\\n$",r:5};var h=[c.BE,f,a];var b=[a,c.HCM,e,{cN:"comment",b:"^\\=\\w",e:"\\=cut",eW:true},g,{cN:"string",c:h,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[c.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[c.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+c.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[c.HCM,e,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[c.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0}];f.c=b;g.c=b;return{k:d,c:b}});hljs.registerLanguage("objectivec",function(a){var d={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign self synchronized id nonatomic super unichar IBOutlet IBAction strong weak @private @protected @public @try @property @end @throw @catch @finally @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"NSString NSDictionary CGRect CGPoint UIButton UILabel UITextView UIWebView MKMapView UISegmentedControl NSObject UITableViewDelegate UITableViewDataSource NSThread UIActivityIndicator UITabbar UIToolBar UIBarButtonItem UIImageView NSAutoreleasePool UITableView BOOL NSInteger CGFloat NSException NSLog NSMutableString NSMutableArray NSMutableDictionary NSURL NSIndexPath CGSize UITableViewCell UIView UIViewController UINavigationBar UINavigationController UITabBarController UIPopoverController UIPopoverControllerDelegate UIImage NSNumber UISearchBar NSFetchedResultsController NSFetchedResultsChangeType UIScrollView UIScrollViewDelegate UIEdgeInsets UIColor UIFont UIApplication NSNotFound NSNotificationCenter NSNotification UILocalNotification NSBundle NSFileManager NSTimeInterval NSDate NSCalendar NSUserDefaults UIWindow NSRange NSArray NSError NSURLRequest NSURLConnection UIInterfaceOrientation MPMoviePlayerController dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"};var c=/[a-zA-Z@][a-zA-Z0-9_]*/;var b="@interface @class @protocol @implementation";return{k:d,l:c,i:"</",c:[a.CLCM,a.CBLCLM,a.CNM,a.QSM,{cN:"string",b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"},{cN:"preprocessor",b:"#import",e:"$",c:[{cN:"title",b:'"',e:'"'},{cN:"title",b:"<",e:">"}]},{cN:"preprocessor",b:"#",e:"$"},{cN:"class",b:"("+b.split(" ").join("|")+")\\b",e:"({|$)",k:b,l:c,c:[a.UTM]},{cN:"variable",b:"\\."+a.UIR,r:0}]}});hljs.registerLanguage("coffeescript",function(c){var b={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",reserved:"case default function var void with const let enum export import native __hasProp __extends __slice __bind __indexOf",built_in:"npm require console print module exports global window document"};var a="[A-Za-z$_][0-9A-Za-z$_]*";var f=c.inherit(c.TM,{b:a});var e={cN:"subst",b:/#\{/,e:/}/,k:b};var d=[c.BNM,c.inherit(c.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[c.BE]},{b:/'/,e:/'/,c:[c.BE]},{b:/"""/,e:/"""/,c:[c.BE,e]},{b:/"/,e:/"/,c:[c.BE,e]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[e,c.HCM]},{b:"//[gim]*",r:0},{b:"/\\S(\\\\.|[^\\n])*?/[gim]*(?=\\s|\\W|$)"}]},{cN:"property",b:"@"+a},{b:"`",e:"`",eB:true,eE:true,sL:"javascript"}];e.c=d;return{k:b,c:d.concat([{cN:"comment",b:"###",e:"###"},c.HCM,{cN:"function",b:"("+a+"\\s*=\\s*)?(\\(.*\\))?\\s*\\B[-=]>",e:"[-=]>",rB:true,c:[f,{cN:"params",b:"\\(",rB:true,c:[{b:/\(/,e:/\)/,k:b,c:["self"].concat(d)}]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:true,i:/[:="\[\]]/,c:[f]},f]},{cN:"attribute",b:a+":",e:":",rB:true,eE:true,r:0}])}});hljs.registerLanguage("nginx",function(c){var b={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+c.UIR}]};var a={eW:true,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[c.HCM,{cN:"string",c:[c.BE,b],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:true,eE:true},{cN:"regexp",c:[c.BE,b],v:[{b:"\\s\\^",e:"\\s|{|;",rE:true},{b:"~\\*?\\s+",e:"\\s|{|;",rE:true},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},b]};return{c:[c.HCM,{b:c.UIR+"\\s",e:";|{",rB:true,c:[c.inherit(c.UTM,{starts:a})],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});hljs.registerLanguage("apache",function(a){var b={cN:"number",b:"[\\$%]\\d+"};return{cI:true,c:[a.HCM,{cN:"tag",b:"</?",e:">"},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",b]},b,a.QSM]}}],i:/\S/}});hljs.registerLanguage("cpp",function(a){var b={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long throw volatile static protected bool template mutable if public friend do return goto auto void enum else break new extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c"],k:b,i:"</",c:[a.CLCM,a.CBLCLM,a.QSM,{cN:"string",b:"'\\\\?.",e:"'",i:"."},{cN:"number",b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},a.CNM,{cN:"preprocessor",b:"#",e:"$",c:[{b:"include\\s*<",e:">",i:"\\n"},a.CLCM]},{cN:"stl_container",b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:b,r:10,c:["self"]}]}});hljs.registerLanguage("makefile",function(a){var b={cN:"variable",b:/\$\(/,e:/\)/,c:[a.BE]};return{c:[a.HCM,{b:/^\w+\s*\W*=/,rB:true,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:true,starts:{e:/$/,r:0,c:[b],}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,c:[a.QSM,b]}]}}); No newline at end of file
@@ -0,0 +1,2 b''
1 {{ object.title }}
2 {{ object.text }} No newline at end of file
@@ -0,0 +1,1 b''
1 {{ object.name }} No newline at end of file
@@ -0,0 +1,29 b''
1 {% extends 'boards/base.html' %}
2
3 {% load board %}
4 {% load i18n %}
5
6 {% block content %}
7 <div class="post-form-w">
8 <h3>{% trans 'Search' %}</h3>
9 <form method="get" action=".">
10 {{ form.as_p }}
11 <input type="submit" value="{% trans 'Search' %}">
12 </form>
13 </div>
14
15 {% if query %}
16 {% for result in page.object_list %}
17 {{ result.object.get_view }}
18 {% empty %}
19 <div class="post">{% trans 'No results found.' %}</div>
20 {% endfor %}
21
22 {% if page.has_previous or page.has_next %}
23 <div>
24 {% if page.has_previous %}<a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; {% trans 'Previous' %}{% if page.has_previous %}</a>{% endif %}
25 {% if page.has_next %}<a href="?q={{ query }}&amp;page= {{ page.next_page_number }}">{% endif %}{% trans 'Next' %} &raquo; {% if page.has_next %}</a>{% endif %}
26 </div>
27 {% endif %}
28 {% endif %}
29 {% endblock %} No newline at end of file
@@ -9,7 +9,7 b' from django.utils.translation import uge'
9 9
10 10 from boards.mdx_neboard import formatters
11 11 from boards.models.post import TITLE_MAX_LENGTH
12 from boards.models import User, Post
12 from boards.models import User, Post, PostImage
13 13 from neboard import settings
14 14 from boards import utils
15 15 import boards.settings as board_settings
@@ -147,7 +147,7 b' class PostForm(NeboardForm):'
147 147 for chunk in image.chunks():
148 148 md5.update(chunk)
149 149 image_hash = md5.hexdigest()
150 if Post.objects.filter(image_hash=image_hash).exists():
150 if PostImage.objects.filter(hash=image_hash).exists():
151 151 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
152 152
153 153 return image
1 NO CONTENT: modified file, binary diff hidden
@@ -7,7 +7,7 b' msgid ""'
7 7 msgstr ""
8 8 "Project-Id-Version: PACKAGE VERSION\n"
9 9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2014-05-08 21:35+0300\n"
10 "POT-Creation-Date: 2014-06-15 12:34+0300\n"
11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 13 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -98,27 +98,27 b' msgstr "\xd0\x9f\xd0\xbe\xd0\xb4\xd0\xbe\xd0\xb6\xd0\xb4\xd0\xb8\xd1\x82\xd0\xb5 %s \xd1\x81\xd0\xb5\xd0\xba\xd1\x83\xd0\xbd\xd0\xb4 \xd0\xbf\xd0\xbe\xd1\x81\xd0\xbb\xd0\xb5 \xd0\xbf\xd0\xbe\xd1\x81\xd0\xbb\xd0\xb5\xd0\xb4\xd0\xbd\xd0\xb5\xd0\xb3\xd0\xbe \xd0\xbf\xd0\xbe\xd1\x81\xd1\x82\xd0\xb8\xd0\xbd\xd0\xb3\xd0\xb0"'
98 98 msgid "Tags"
99 99 msgstr "Π’Π΅Π³ΠΈ"
100 100
101 #: forms.py:225 forms.py:344
101 #: forms.py:224 forms.py:343
102 102 msgid "Inappropriate characters in tags."
103 103 msgstr "НСдопустимыС символы Π² Ρ‚Π΅Π³Π°Ρ…."
104 104
105 #: forms.py:253 forms.py:274
105 #: forms.py:252 forms.py:273
106 106 msgid "Captcha validation failed"
107 107 msgstr "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΊΠ°ΠΏΡ‡ΠΈ ΠΏΡ€ΠΎΠ²Π°Π»Π΅Π½Π°"
108 108
109 #: forms.py:280
109 #: forms.py:279
110 110 msgid "Theme"
111 111 msgstr "Π’Π΅ΠΌΠ°"
112 112
113 #: forms.py:285
113 #: forms.py:284
114 114 msgid "Enable moderation panel"
115 115 msgstr "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ панСль ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ†ΠΈΠΈ"
116 116
117 #: forms.py:300
117 #: forms.py:299
118 118 msgid "No such user found"
119 119 msgstr "Π”Π°Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½"
120 120
121 #: forms.py:314
121 #: forms.py:313
122 122 #, python-format
123 123 msgid "Wait %s minutes after last login"
124 124 msgstr "ΠŸΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %s ΠΌΠΈΠ½ΡƒΡ‚ послС послСднСго Π²Ρ…ΠΎΠ΄Π°"
@@ -147,41 +147,41 b' msgstr "\xd0\xbb\xd0\xb8\xd1\x86\xd0\xb5\xd0\xbd\xd0\xb7\xd0\xb8\xd0\xb5\xd0\xb9"'
147 147 msgid "Repository"
148 148 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
149 149
150 #: templates/boards/base.html:14
150 #: templates/boards/base.html:11
151 151 msgid "Feed"
152 152 msgstr "Π›Π΅Π½Ρ‚Π°"
153 153
154 #: templates/boards/base.html:31
154 #: templates/boards/base.html:28
155 155 msgid "All threads"
156 156 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
157 157
158 #: templates/boards/base.html:36
158 #: templates/boards/base.html:33
159 159 msgid "Tag management"
160 160 msgstr "Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Ρ‚Π΅Π³Π°ΠΌΠΈ"
161 161
162 #: templates/boards/base.html:38 templates/boards/settings.html:7
162 #: templates/boards/base.html:35 templates/boards/settings.html:7
163 163 msgid "Settings"
164 164 msgstr "Настройки"
165 165
166 #: templates/boards/base.html:50 templates/boards/login.html:6
167 #: templates/boards/login.html.py:21
166 #: templates/boards/base.html:47 templates/boards/login.html:6
167 #: templates/boards/login.html.py:16
168 168 msgid "Login"
169 169 msgstr "Π’Ρ…ΠΎΠ΄"
170 170
171 #: templates/boards/base.html:52
171 #: templates/boards/base.html:48
172 msgid "Search"
173 msgstr "Поиск"
174
175 #: templates/boards/base.html:50
172 176 #, python-format
173 177 msgid "Speed: %(ppd)s posts per day"
174 178 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
175 179
176 #: templates/boards/base.html:54
180 #: templates/boards/base.html:52
177 181 msgid "Up"
178 182 msgstr "Π’Π²Π΅Ρ€Ρ…"
179 183
180 #: templates/boards/login.html:15
181 msgid "User ID"
182 msgstr "ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
183
184 #: templates/boards/login.html:24
184 #: templates/boards/login.html:19
185 185 msgid "Insert your user id above"
186 186 msgstr "Π’ΡΡ‚Π°Π²ΡŒΡ‚Π΅ свой ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π²Ρ‹ΡˆΠ΅"
187 187
@@ -386,5 +386,5 b' msgstr "\xd0\x9f\xd0\xb5\xd1\x80\xd0\xb5\xd0\xb4 \xd1\x8d\xd1\x82\xd0\xb8\xd0\xbc\xd0\xb8 \xd1\x82\xd0\xb5\xd0\xb3\xd0\xb0\xd0\xbc\xd0\xb8 \xd0\xbd\xd1\x83\xd0\xb6\xd0\xbd\xd0\xb0 \xd0\xbd\xd0\xbe\xd0\xb2\xd0\xb0\xd1\x8f \xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xba\xd0\xb0:"'
386 386 msgid "Comment"
387 387 msgstr "ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ"
388 388
389 #~ msgid "Archive"
390 #~ msgstr "Архив"
389 #~ msgid "User ID"
390 #~ msgstr "ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
@@ -1,3 +1,5 b''
1 # coding=utf-8
2
1 3 import markdown
2 4 from markdown.inlinepatterns import Pattern, SubstituteTagPattern
3 5 from markdown.util import etree
@@ -12,6 +14,7 b" REFLINK_PATTERN = r'((>>)(\\d+))'"
12 14 SPOILER_PATTERN = r'%%([^(%%)]+)%%'
13 15 COMMENT_PATTERN = r'^(//(.+))'
14 16 STRIKETHROUGH_PATTERN = r'~(.+)~'
17 DASH_PATTERN = r'--'
15 18
16 19
17 20 class TextFormatter():
@@ -144,6 +147,11 b' class CodePattern(TextFormatter):'
144 147 format_left = ' '
145 148
146 149
150 class DashPattern(Pattern):
151 def handleMatch(self, m):
152 return u'β€”'
153
154
147 155 class NeboardMarkdown(markdown.Extension):
148 156 def extendMarkdown(self, md, md_globals):
149 157 self._add_neboard_patterns(md)
@@ -162,6 +170,7 b' class NeboardMarkdown(markdown.Extension'
162 170 spoiler = SpoilerPattern(SPOILER_PATTERN, md)
163 171 comment = CommentPattern(COMMENT_PATTERN, md)
164 172 strikethrough = StrikeThroughPattern(STRIKETHROUGH_PATTERN, md)
173 dash = DashPattern(DASH_PATTERN, md)
165 174
166 175 md.inlinePatterns[u'autolink_ext'] = autolink
167 176 md.inlinePatterns[u'spoiler'] = spoiler
@@ -169,6 +178,7 b' class NeboardMarkdown(markdown.Extension'
169 178 md.inlinePatterns[u'comment'] = comment
170 179 md.inlinePatterns[u'reflink'] = reflink
171 180 md.inlinePatterns[u'quote'] = quote
181 md.inlinePatterns[u'dash'] = dash
172 182
173 183
174 184 def make_extension(configs=None):
@@ -1,8 +1,9 b''
1 1 from django.shortcuts import redirect
2 from boards import views, utils
2 from boards import utils
3 3 from boards.models import Ban
4 4 from django.utils.html import strip_spaces_between_tags
5 5 from django.conf import settings
6 from boards.views.banned import BannedView
6 7
7 8 RESPONSE_CONTENT_TYPE = 'Content-Type'
8 9
@@ -17,7 +18,7 b' class BanMiddleware:'
17 18
18 19 def process_view(self, request, view_func, view_args, view_kwargs):
19 20
20 if view_func != views.banned.BannedView.as_view:
21 if view_func != BannedView.as_view:
21 22 ip = utils.get_client_ip(request)
22 23 bans = Ban.objects.filter(ip=ip)
23 24
@@ -1,7 +1,8 b''
1 1 __author__ = 'neko259'
2 2
3 from boards.models.image import PostImage
4 from boards.models.thread import Thread
3 5 from boards.models.post import Post
4 from boards.models.post import Thread
5 6 from boards.models.tag import Tag
6 7 from boards.models.user import Ban
7 8 from boards.models.user import Setting
@@ -10,9 +10,13 b' import hashlib'
10 10 from django.core.cache import cache
11 11 from django.core.urlresolvers import reverse
12 12 from django.db import models, transaction
13 from django.template.loader import render_to_string
13 14 from django.utils import timezone
14 15 from markupfield.fields import MarkupField
16 from boards.models import PostImage
17 from boards.models.base import Viewable
15 18
19 from boards.models.thread import Thread
16 20 from neboard import settings
17 21 from boards import thumbs
18 22
@@ -21,7 +25,6 b" APP_LABEL_BOARDS = 'boards'"
21 25
22 26 CACHE_KEY_PPD = 'ppd'
23 27 CACHE_KEY_POST_URL = 'post_url'
24 CACHE_KEY_OPENING_POST = 'opening_post_id'
25 28
26 29 POSTS_PER_DAY_RANGE = range(7)
27 30
@@ -33,10 +36,20 b' TITLE_MAX_LENGTH = 200'
33 36
34 37 DEFAULT_MARKUP_TYPE = 'markdown'
35 38
39 # TODO This should be removed when no code relies on it because thread id field
40 # was removed a long time ago
36 41 NO_PARENT = -1
42
43 # TODO This should be removed
37 44 NO_IP = '0.0.0.0'
45
46 # TODO Real user agent should be saved instead of this
38 47 UNKNOWN_UA = ''
48
49 # TODO This should be checked for usage and removed because a nativa
50 # paginator is used now
39 51 ALL_PAGES = -1
52
40 53 IMAGES_DIRECTORY = 'images/'
41 54 FILE_EXTENSION_DELIMITER = '.'
42 55
@@ -70,13 +83,18 b' class PostManager(models.Manager):'
70 83 text=text,
71 84 pub_time=posting_time,
72 85 thread_new=thread,
73 image=image,
74 86 poster_ip=ip,
75 87 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
76 88 # last!
77 89 last_edit_time=posting_time,
78 90 user=user)
79 91
92 if image:
93 post_image = PostImage.objects.create(image=image)
94 post.images.add(post_image)
95 logger.info('Created image #%d for post #%d' % (post_image.id,
96 post.id))
97
80 98 thread.replies.add(post)
81 99 if tags:
82 100 linked_tags = []
@@ -124,6 +142,9 b' class PostManager(models.Manager):'
124 142 map(self.delete_post, posts)
125 143
126 144 # TODO Move this method to thread manager
145 # TODO Rename it, because the threads are archived instead of plain
146 # removal. Split the delete and archive methods and make a setting to
147 # enable or disable archiving.
127 148 def _delete_old_threads(self):
128 149 """
129 150 Preserves maximum thread count. If there are too many threads,
@@ -193,7 +214,7 b' class PostManager(models.Manager):'
193 214 return ppd
194 215
195 216
196 class Post(models.Model):
217 class Post(models.Model, Viewable):
197 218 """A post is a message."""
198 219
199 220 objects = PostManager()
@@ -202,38 +223,13 b' class Post(models.Model):'
202 223 app_label = APP_LABEL_BOARDS
203 224 ordering = ('id',)
204 225
205 # TODO Save original file name to some field
206 def _update_image_filename(self, filename):
207 """
208 Gets unique image filename
209 """
210
211 path = IMAGES_DIRECTORY
212 new_name = str(int(time.mktime(time.gmtime())))
213 new_name += str(int(random() * 1000))
214 new_name += FILE_EXTENSION_DELIMITER
215 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
216
217 return os.path.join(path, new_name)
218
219 226 title = models.CharField(max_length=TITLE_MAX_LENGTH)
220 227 pub_time = models.DateTimeField()
221 228 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
222 229 escape_html=False)
223 230
224 image_width = models.IntegerField(default=0)
225 image_height = models.IntegerField(default=0)
226
227 image_pre_width = models.IntegerField(default=0)
228 image_pre_height = models.IntegerField(default=0)
229
230 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
231 blank=True, sizes=(IMAGE_THUMB_SIZE,),
232 width_field='image_width',
233 height_field='image_height',
234 preview_width_field='image_pre_width',
235 preview_height_field='image_pre_height')
236 image_hash = models.CharField(max_length=36)
231 images = models.ManyToManyField(PostImage, null=True, blank=True,
232 related_name='ip+', db_index=True)
237 233
238 234 poster_ip = models.GenericIPAddressField()
239 235 poster_user_agent = models.TextField()
@@ -289,18 +285,6 b' class Post(models.Model):'
289 285
290 286 return self.get_thread().get_opening_post_id() == self.id
291 287
292 def save(self, *args, **kwargs):
293 """
294 Saves the model and computes the image hash for deduplication purposes.
295 """
296
297 if not self.pk and self.image:
298 md5 = hashlib.md5()
299 for chunk in self.image.chunks():
300 md5.update(chunk)
301 self.image_hash = md5.hexdigest()
302 super(Post, self).save(*args, **kwargs)
303
304 288 @transaction.atomic
305 289 def add_tag(self, tag):
306 290 edit_time = timezone.now()
@@ -359,145 +343,39 b' class Post(models.Model):'
359 343 def get_referenced_posts(self):
360 344 return self.referenced_posts.only('id', 'thread_new')
361 345
362
363 class Thread(models.Model):
364
365 class Meta:
366 app_label = APP_LABEL_BOARDS
367
368 tags = models.ManyToManyField('Tag')
369 bump_time = models.DateTimeField()
370 last_edit_time = models.DateTimeField()
371 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
372 blank=True, related_name='tre+')
373 archived = models.BooleanField(default=False)
374
375 def get_tags(self):
376 """
377 Gets a sorted tag list.
378 """
379
380 return self.tags.order_by('name')
381
382 def bump(self):
383 """
384 Bumps (moves to up) thread if possible.
385 """
386
387 if self.can_bump():
388 self.bump_time = timezone.now()
389
390 logger.info('Bumped thread %d' % self.id)
391
392 def get_reply_count(self):
393 return self.replies.count()
346 def get_text(self):
347 return self.text
394 348
395 def get_images_count(self):
396 return self.replies.filter(image_width__gt=0).count()
397
398 def can_bump(self):
399 """
400 Checks if the thread can be bumped by replying to it.
401 """
402
403 if self.archived:
404 return False
405
406 post_count = self.get_reply_count()
407
408 return post_count < settings.MAX_POSTS_PER_THREAD
349 def get_view(self, moderator=False, need_open_link=False,
350 truncated=False, *args, **kwargs):
351 if 'is_opening' in kwargs:
352 is_opening = kwargs['is_opening']
353 else:
354 is_opening = self.is_opening()
409 355
410 def delete_with_posts(self):
411 """
412 Completely deletes thread and all its posts
413 """
414
415 if self.replies.exists():
416 self.replies.all().delete()
417
418 self.delete()
419
420 def get_last_replies(self):
421 """
422 Gets several last replies, not including opening post
423 """
424
425 if settings.LAST_REPLIES_COUNT > 0:
426 reply_count = self.get_reply_count()
356 if 'thread' in kwargs:
357 thread = kwargs['thread']
358 else:
359 thread = self.get_thread()
427 360
428 if reply_count > 0:
429 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
430 reply_count - 1)
431 last_replies = self.replies.order_by(
432 'pub_time').defer('image_hash', 'poster_user_agent',
433 'text_markup_type')[
434 reply_count - reply_count_to_show:]
435
436 return last_replies
437
438 def get_skipped_replies_count(self):
439 """
440 Gets number of posts between opening post and last replies.
441 """
442 reply_count = self.get_reply_count()
443 last_replies_count = min(settings.LAST_REPLIES_COUNT,
444 reply_count - 1)
445 return reply_count - last_replies_count - 1
361 if 'can_bump' in kwargs:
362 can_bump = kwargs['can_bump']
363 else:
364 can_bump = thread.can_bump()
446 365
447 def get_replies(self, view_fields_only=False):
448 """
449 Gets sorted thread posts
450 """
451
452 query = self.replies.order_by('pub_time')
453 if view_fields_only:
454 query = query.defer(
455 'image_hash', 'poster_user_agent', 'text_markup_type')
456 return query.all()
457
458 def add_tag(self, tag):
459 """
460 Connects thread to a tag and tag to a thread
461 """
462
463 self.tags.add(tag)
464 tag.threads.add(self)
366 opening_post_id = thread.get_opening_post_id()
465 367
466 def remove_tag(self, tag):
467 self.tags.remove(tag)
468 tag.threads.remove(self)
469
470 def get_opening_post(self, only_id=False):
471 """
472 Gets the first post of the thread
473 """
474
475 query = self.replies.order_by('pub_time')
476 if only_id:
477 query = query.only('id')
478 opening_post = query.first()
479
480 return opening_post
368 return render_to_string('boards/post.html', {
369 'post': self,
370 'moderator': moderator,
371 'is_opening': is_opening,
372 'thread': thread,
373 'bumpable': can_bump,
374 'need_open_link': need_open_link,
375 'truncated': truncated,
376 'opening_post_id': opening_post_id,
377 })
481 378
482 def get_opening_post_id(self):
483 """
484 Gets ID of the first thread post.
485 """
486
487 cache_key = CACHE_KEY_OPENING_POST + str(self.id)
488 opening_post_id = cache.get(cache_key)
489 if not opening_post_id:
490 opening_post_id = self.get_opening_post(only_id=True).id
491 cache.set(cache_key, opening_post_id)
379 def get_first_image(self):
380 return self.images.earliest('id')
492 381
493 return opening_post_id
494
495 def __unicode__(self):
496 return str(self.id)
497
498 def get_pub_time(self):
499 """
500 Gets opening post's pub time because thread does not have its own one.
501 """
502
503 return self.get_opening_post().pub_time
@@ -1,9 +1,13 b''
1 from django.template.loader import render_to_string
1 2 from boards.models import Thread, Post
2 3 from django.db import models
3 4 from django.db.models import Count, Sum
5 from django.core.urlresolvers import reverse
6 from boards.models.base import Viewable
4 7
5 8 __author__ = 'neko259'
6 9
10 # TODO Tag popularity ratings are not used any more, remove all of this
7 11 MAX_TAG_FONT = 1
8 12 MIN_TAG_FONT = 0.2
9 13
@@ -20,13 +24,12 b' class TagManager(models.Manager):'
20 24 """
21 25
22 26 tags = self.annotate(Count('threads')) \
23 .filter(threads__count__gt=0).filter(threads__archived=False) \
24 .order_by('name')
27 .filter(threads__count__gt=0).order_by('name')
25 28
26 29 return tags
27 30
28 31
29 class Tag(models.Model):
32 class Tag(models.Model, Viewable):
30 33 """
31 34 A tag is a text node assigned to the thread. The tag serves as a board
32 35 section. There can be multiple tags for each thread
@@ -56,6 +59,7 b' class Tag(models.Model):'
56 59 def get_thread_count(self):
57 60 return self.threads.count()
58 61
62 # TODO Remove, not used any more
59 63 def get_popularity(self):
60 64 """
61 65 Gets tag's popularity value as a percentage of overall board post
@@ -97,6 +101,7 b' class Tag(models.Model):'
97 101
98 102 linked_tag.get_linked_tags_list(tag_list)
99 103
104 # TODO Remove
100 105 def get_font_value(self):
101 106 """
102 107 Gets tag font value to differ most popular tags in the list
@@ -126,3 +131,11 b' class Tag(models.Model):'
126 131 posts_count = 0
127 132
128 133 return posts_count
134
135 def get_url(self):
136 return reverse('tag', kwargs={'tag_name': self.name})
137
138 def get_view(self, *args, **kwargs):
139 return render_to_string('boards/tag.html', {
140 'tag': self,
141 })
@@ -73,4 +73,9 b' textarea, input {'
73 73 -moz-box-sizing: border-box;
74 74 -webkit-box-sizing: border-box;
75 75 box-sizing: border-box;
76 } No newline at end of file
76 }
77
78 .compact-form-text > textarea {
79 height: 100px;
80 width: 100%;
81 }
@@ -12,14 +12,6 b' body {'
12 12 color: #00FF00
13 13 }
14 14
15 .input_field {
16
17 }
18
19 .input_field_name {
20
21 }
22
23 15 .input_field_error {
24 16 color: #FF0000;
25 17 }
@@ -39,9 +31,7 b' body {'
39 31 }
40 32
41 33 .tag {
42 /*color: #F5FFC9;*/
43 34 color: #FFD37D;
44 /*color: #b4cfec;*/
45 35 }
46 36
47 37 .post_id {
@@ -165,7 +155,7 b' p {'
165 155 margin-bottom: 0.5ex;
166 156 }
167 157
168 input[type="submit"] {
158 .post-form input[type="submit"], input[type="submit"] {
169 159 background: #222;
170 160 border: solid 2px #fff;
171 161 color: #fff;
@@ -413,3 +403,19 b' pre {'
413 403 margin: 0.2ex;
414 404 padding: 0.1ex;
415 405 }
406
407 #id_models li {
408 list-style: none;
409 }
410
411 #id_q {
412 margin-left: 1ex;
413 }
414
415 ul {
416 padding-left: 0px;
417 }
418
419 .post_preview {
420 border: solid 2px white;
421 }
@@ -360,4 +360,12 b' li {'
360 360 .swappable-form-full > form {
361 361 display: table;
362 362 width: 100%;
363 } No newline at end of file
363 }
364
365 #id_models li {
366 list-style: none;
367 }
368
369 #id_q {
370 margin-left: 1ex;
371 }
@@ -1,4 +1,4 b''
1 var isCompact = true;
1 var isCompact = false;
2 2
3 3 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
4 4
@@ -21,25 +21,21 b' var isCompact = true;'
21 21 }
22 22 });
23 23
24 var compactForm = $('.swappable-form-compact');
25 24 var fullForm = $('.swappable-form-full');
26 25
27 26 function swapForm() {
28 compactForm.toggle();
29 fullForm.toggle();
27 if (isCompact) {
28 // TODO Use IDs (change the django form code) instead of absolute numbers
29 fullForm.find('textarea').appendTo(fullForm.find('.form-row')[4]);
30 fullForm.find('.file_wrap').appendTo(fullForm.find('.form-row')[7]);
31 fullForm.find('.form-row').show();
30 32
31 if (isCompact) {
32 var oldText = compactForm.find('textarea')[0].value;
33 fullForm.find('textarea')[0].value = oldText;
33 scrollToBottom();
34 34 } else {
35 var oldText = fullForm.find('textarea')[0].value;
36 compactForm.find('textarea')[0].value = oldText;
35 fullForm.find('textarea').appendTo($('.compact-form-text'));
36 fullForm.find('.file_wrap').insertBefore($('.compact-form-text'));
37 fullForm.find('.form-row').hide();
38 fullForm.find('input[type=text]').val('');
37 39 }
38 40 isCompact = !isCompact;
39
40 scrollToBottom();
41 41 }
42
43 if (compactForm.length > 0) {
44 fullForm.toggle();
45 }
@@ -31,6 +31,15 b' function hideEmailFromForm() {'
31 31 $('.form-email').parent().parent().hide();
32 32 }
33 33
34 /**
35 * Highlight code blocks with code highlighter
36 */
37 function highlightCode() {
38 $('pre code').each(function(i, e) {
39 hljs.highlightBlock(e);
40 });
41 }
42
34 43 $( document ).ready(function() {
35 44 hideEmailFromForm();
36 45
@@ -42,4 +51,6 b' function hideEmailFromForm() {'
42 51 addImgPreview();
43 52
44 53 addRefLinkPreview();
54
55 highlightCode();
45 56 });
@@ -56,4 +56,6 b' function scrollToBottom() {'
56 56 $target.animate({scrollTop: $target.height()}, "fast");
57 57 }
58 58
59 $('#full-form').toggle();
59 $(document).ready(function() {
60 swapForm();
61 })
@@ -69,6 +69,7 b' function updateThread() {'
69 69
70 70 post.appendTo(lastPost.parent());
71 71 addRefLinkPreview(post[0]);
72 highlightCode();
72 73
73 74 lastPost = post;
74 75 blink(post);
@@ -90,6 +91,7 b' function updateThread() {'
90 91
91 92 oldPost.replaceWith(post);
92 93 addRefLinkPreview(post[0]);
94 highlightCode();
93 95
94 96 blink(post);
95 97 }
@@ -6,12 +6,10 b''
6 6 <!DOCTYPE html>
7 7 <html>
8 8 <head>
9 <link rel="stylesheet" type="text/css"
10 href="{% static 'css/base.css' %}" media="all"/>
11 <link rel="stylesheet" type="text/css"
12 href="{% static theme_css %}" media="all"/>
13 <link rel="alternate" type="application/rss+xml" href="rss/" title=
14 "{% trans 'Feed' %}"/>
9 <link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}" media="all"/>
10 <link rel="stylesheet" type="text/css" href="{% static 'css/3party/highlight.css' %}" media="all"/>
11 <link rel="stylesheet" type="text/css" href="{% static theme_css %}" media="all"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title="{% trans 'Feed' %}"/>
15 13
16 14 <link rel="icon" type="image/png"
17 15 href="{% static 'favicon.png' %}">
@@ -43,11 +41,13 b''
43 41 <script src="{% static 'js/popup.js' %}"></script>
44 42 <script src="{% static 'js/image.js' %}"></script>
45 43 <script src="{% static 'js/refpopup.js' %}"></script>
44 <script src="{% static 'js/3party/highlight.min.js' %}"></script>
46 45 <script src="{% static 'js/main.js' %}"></script>
47 46
48 47 <div class="navigation_panel">
49 48 {% block metapanel %}{% endblock %}
50 49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 [<a href="{% url "haystack_search" %}">{% trans 'Search' %}</a>]
51 51 {% with ppd=posts_per_day|floatformat:2 %}
52 52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
53 53 {% endwith %}
@@ -46,19 +46,21 b''
46 46 </span>
47 47 {% endif %}
48 48 </div>
49 {% if post.image %}
49 {% if post.images.exists %}
50 {% with post.images.all.0 as image %}
50 51 <div class="image">
51 52 <a
52 53 class="thumb"
53 href="{{ post.image.url }}"><img
54 src="{{ post.image.url_200x150 }}"
54 href="{{ image.image.url }}"><img
55 src="{{ image.image.url_200x150 }}"
55 56 alt="{{ post.id }}"
56 width="{{ post.image_pre_width }}"
57 height="{{ post.image_pre_height }}"
58 data-width="{{ post.image_width }}"
59 data-height="{{ post.image_height }}"/>
57 width="{{ image.pre_width }}"
58 height="{{ image.pre_height }}"
59 data-width="{{ image.width }}"
60 data-height="{{ image.height }}"/>
60 61 </a>
61 62 </div>
63 {% endwith %}
62 64 {% endif %}
63 65 <div class="message">
64 66 {% autoescape off %}
@@ -81,6 +83,7 b''
81 83 {% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}
82 84 <div class="metadata">
83 85 {% if is_opening and need_open_link %}
86 {{ thread.get_reply_count }} {% trans 'replies' %},
84 87 {{ thread.get_images_count }} {% trans 'images' %}.
85 88 {% endif %}
86 89 <span class="tags">
@@ -1,7 +1,7 b''
1 1 {% load i18n %}
2 2
3 {% if obj.image %}
4 <img src="{{ obj.image.url_200x150 }}"
3 {% if obj.images.exists %}
4 <img src="{{ obj.get_first_image.image.url_200x150 }}"
5 5 alt="{% trans 'Post image' %}" />
6 6 {% endif %}
7 7 {{ obj.text.rendered|safe }}
@@ -1,96 +1,3 b''
1 {% load i18n %}
2 {% load board %}
3 {% load cache %}
4
5 {% get_current_language as LANGUAGE_CODE %}
6
7 {% spaceless %}
8 {% cache 600 post post.id post.last_edit_time thread.archived bumpable truncated moderator LANGUAGE_CODE need_open_link %}
9 {% if thread.archived %}
10 <div class="post archive_post" id="{{ post.id }}">
11 {% elif bumpable %}
12 <div class="post" id="{{ post.id }}">
13 {% else %}
14 <div class="post dead_post" id="{{ post.id }}">
15 {% endif %}
16
17 <div class="post-info">
18 <a class="post_id" href="{% post_object_url post thread=thread %}"
19 {% if not truncated and not thread.archived %}
20 onclick="javascript:addQuickReply('{{ post.id }}'); return false;"
21 title="{% trans 'Quote' %}"
22 {% endif %}
23 >({{ post.id }}) </a>
24 <span class="title">{{ post.title }} </span>
25 <span class="pub_time">{{ post.pub_time }}</span>
26 {% if thread.archived %}
27 β€” {{ thread.bump_time }}
28 {% endif %}
29 {% if is_opening and need_open_link %}
30 {% if thread.archived %}
31 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
32 {% else %}
33 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
34 {% endif %}
35 {% endif %}
36
37 {% if moderator %}
38 <span class="moderator_info">
39 [<a href="{% url 'post_admin' post_id=post.id %}"
40 >{% trans 'Edit' %}</a>]
41 [<a href="{% url 'delete' post_id=post.id %}"
42 >{% trans 'Delete' %}</a>]
43 ({{ post.poster_ip }})
44 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
45 >{% trans 'Ban IP' %}</a>]
46 </span>
47 {% endif %}
48 </div>
49 {% if post.image %}
50 <div class="image">
51 <a
52 class="thumb"
53 href="{{ post.image.url }}"><img
54 src="{{ post.image.url_200x150 }}"
55 alt="{{ post.id }}"
56 width="{{ post.image_pre_width }}"
57 height="{{ post.image_pre_height }}"
58 data-width="{{ post.image_width }}"
59 data-height="{{ post.image_height }}"/>
60 </a>
61 </div>
62 {% endif %}
63 <div class="message">
64 {% autoescape off %}
65 {% if truncated %}
66 {{ post.text.rendered|truncatewords_html:50 }}
67 {% else %}
68 {{ post.text.rendered }}
69 {% endif %}
70 {% endautoescape %}
71 {% if post.is_referenced %}
72 <div class="refmap">
73 {% autoescape off %}
74 {% trans "Replies" %}: {{ post.refmap }}
75 {% endautoescape %}
76 </div>
77 {% endif %}
78 </div>
79 {% endcache %}
80 {% if is_opening %}
81 {% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}
82 <div class="metadata">
83 {% if is_opening and need_open_link %}
84 {{ thread.get_images_count }} {% trans 'images' %}.
85 {% endif %}
86 <span class="tags">
87 {% for tag in thread.get_tags %}
88 <a class="tag" href="{% url 'tag' tag.name %}">
89 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
90 {% endfor %}
91 </span>
92 </div>
93 {% endcache %}
94 {% endif %}
95 </div>
96 {% endspaceless %}
1 <div class="post">
2 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">#{{ tag.name }}</a>
3 </div> No newline at end of file
@@ -49,23 +49,10 b''
49 49 <script src="{% static 'js/panel.js' %}"></script>
50 50 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
51 51 <div class="post-form" id="compact-form">
52 <div class="swappable-form-compact">
53 <form enctype="multipart/form-data" method="post"
54 >{% csrf_token %}
55 <input type="file" name="image" accept="image/*"/>
56 <div class="compact-form-text">
57 <textarea name="text" style="height: 100px;
58 width: 100%;
59 "></textarea>
60 </div>
61 <div class="form-submit">
62 <input type="submit" value="{% trans "Post" %}"/>
63 </div>
64 </form>
65 </div>
66 52 <div class="swappable-form-full">
67 53 <form enctype="multipart/form-data" method="post"
68 54 >{% csrf_token %}
55 <div class="compact-form-text"></div>
69 56 {{ form.as_div }}
70 57 <div class="form-submit">
71 58 <input type="submit" value="{% trans "Post" %}"/>
@@ -25,22 +25,24 b''
25 25 <div id="posts-table">
26 26 {% for post in posts %}
27 27 <div class="gallery_image">
28 {% with post.get_first_image as image %}
28 29 <div>
29 30 <a
30 31 class="thumb"
31 href="{{ post.image.url }}"><img
32 src="{{ post.image.url_200x150 }}"
32 href="{{ image.image.url }}"><img
33 src="{{ image.image.url_200x150 }}"
33 34 alt="{{ post.id }}"
34 width="{{ post.image_pre_width }}"
35 height="{{ post.image_pre_height }}"
36 data-width="{{ post.image_width }}"
37 data-height="{{ post.image_height }}"/>
35 width="{{ image.pre_width }}"
36 height="{{ image.pre_height }}"
37 data-width="{{ image.width }}"
38 data-height="{{ image.height }}"/>
38 39 </a>
39 40 </div>
40 41 <div class="gallery_image_metadata">
41 {{ post.image_width }}x{{ post.image_height }}
42 {% image_actions post.image.url request.get_host %}
42 {{ image.width }}x{{ image.height }}
43 {% image_actions image.image.url request.get_host %}
43 44 </div>
45 {% endwith %}
44 46 </div>
45 47 {% endfor %}
46 48 </div>
@@ -1,8 +1,7 b''
1 1 from django.shortcuts import get_object_or_404
2 from boards.models import Post
3 from boards.views import thread, api
4 2 from django import template
5 3
4
6 5 register = template.Library()
7 6
8 7 actions = [
@@ -21,7 +20,7 b' actions = ['
21 20 def post_url(*args, **kwargs):
22 21 post_id = args[0]
23 22
24 post = get_object_or_404(Post, id=post_id)
23 post = get_object_or_404('Post', id=post_id)
25 24
26 25 return post.get_url()
27 26
@@ -53,6 +52,7 b' def image_actions(*args, **kwargs):'
53 52 return result
54 53
55 54
55 # TODO Use get_view of a post instead of this
56 56 @register.inclusion_tag('boards/post.html', name='post_view')
57 57 def post_view(post, moderator=False, need_open_link=False, truncated=False,
58 58 **kwargs):
@@ -161,8 +161,7 b' class PagesTest(TestCase):'
161 161
162 162 response_not_existing = client.get(THREAD_PAGE + str(
163 163 existing_post_id + 1) + '/')
164 self.assertEqual(PAGE_404,
165 response_not_existing.templates[0].name,
164 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
166 165 u'Not existing thread is opened')
167 166
168 167 response_existing = client.get(TAG_PAGE + tag_name + '/')
@@ -177,7 +176,7 b' class PagesTest(TestCase):'
177 176
178 177 reply_id = Post.objects.create_post('', TEST_TEXT,
179 178 thread=Post.objects.all()[0]
180 .thread)
179 .get_thread())
181 180 response_not_existing = client.get(THREAD_PAGE + str(
182 181 reply_id) + '/')
183 182 self.assertEqual(PAGE_404,
@@ -75,7 +75,10 b" urlpatterns = patterns('',"
75 75 name='get_thread'),
76 76 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
77 77 name='add_post'),
78 url(r'api/get_tag_popularity/(?P<tag_name>\w+)$', api.get_tag_popularity,
78 url(r'^api/get_tag_popularity/(?P<tag_name>\w+)$', api.get_tag_popularity,
79 79 name='get_tag_popularity'),
80 80
81 # Search
82 url(r'^search/', include('haystack.urls')),
83
81 84 )
@@ -1,10 +1,15 b''
1 1 """
2 2 This module contains helper functions and helper classes.
3 3 """
4 import hashlib
4 5 from django.utils import timezone
5 6
6 7 from neboard import settings
8 from boards.models import Post, User
9 from boards.models.post import SETTING_MODERATE
10 from boards.models.user import RANK_USER
7 11 import time
12 import neboard
8 13
9 14
10 15 KEY_CAPTCHA_FAILS = 'key_captcha_fails'
@@ -77,4 +82,48 b' def get_client_ip(request):'
77 82 def datetime_to_epoch(datetime):
78 83 return int(time.mktime(timezone.localtime(
79 84 datetime,timezone.get_current_timezone()).timetuple())
80 * 1000000 + datetime.microsecond) No newline at end of file
85 * 1000000 + datetime.microsecond)
86
87
88 def get_user(request):
89 """
90 Get current user from the session. If the user does not exist, create
91 a new one.
92 """
93
94 session = request.session
95 if not 'user_id' in session:
96 request.session.save()
97
98 md5 = hashlib.md5()
99 md5.update(session.session_key)
100 new_id = md5.hexdigest()
101
102 while User.objects.filter(user_id=new_id).exists():
103 md5.update(str(timezone.now()))
104 new_id = md5.hexdigest()
105
106 time_now = timezone.now()
107 user = User.objects.create(user_id=new_id, rank=RANK_USER,
108 registration_time=time_now)
109
110 session['user_id'] = user.id
111 else:
112 user = User.objects.select_related('fav_tags').get(
113 id=session['user_id'])
114
115 return user
116
117
118 def get_theme(request, user=None):
119 """
120 Get user's CSS theme
121 """
122
123 if not user:
124 user = get_user(request)
125 theme = user.get_setting('theme')
126 if not theme:
127 theme = neboard.settings.DEFAULT_THEME
128
129 return theme No newline at end of file
@@ -3,6 +3,7 b' from django.shortcuts import render'
3 3 from boards.views.base import BaseBoardView
4 4 from boards.models.tag import Tag
5 5
6
6 7 class AllTagsView(BaseBoardView):
7 8
8 9 def get(self, request):
@@ -1,6 +1,5 b''
1 1 import string
2 2
3 from django.core.urlresolvers import reverse
4 3 from django.db import transaction
5 4 from django.shortcuts import render, redirect
6 5
@@ -30,7 +29,7 b' class AllThreadsView(PostMixin, BaseBoar'
30 29 def get(self, request, page=DEFAULT_PAGE, form=None):
31 30 context = self.get_context_data(request=request)
32 31
33 self.user = context['user']
32 self.user = utils.get_user(request)
34 33
35 34 if not form:
36 35 form = ThreadForm(error_class=PlainErrorList)
@@ -70,7 +69,8 b' class AllThreadsView(PostMixin, BaseBoar'
70 69 context[PARAMETER_PAGINATOR] = paginator
71 70 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
72 71
73 def parse_tags_string(self, tag_strings):
72 @staticmethod
73 def parse_tags_string(tag_strings):
74 74 """
75 75 Parses tag list string and returns tag object list.
76 76 """
@@ -120,7 +120,7 b' class AllThreadsView(PostMixin, BaseBoar'
120 120
121 121 post = Post.objects.create_post(title=title, text=text, ip=ip,
122 122 image=image, tags=tags,
123 user=self._get_user(request))
123 user=utils.get_user(request))
124 124
125 125 if html_response:
126 126 return redirect(post.get_url())
@@ -37,9 +37,6 b' def api_get_threaddiff(request, thread_i'
37 37
38 38 thread = get_object_or_404(Post, id=thread_id).get_thread()
39 39
40 logger.info('Getting thread #%s diff since %s' % (thread_id,
41 last_update_time))
42
43 40 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
44 41 timezone.get_current_timezone())
45 42
@@ -90,7 +87,7 b' def api_add_post(request, opening_post_i'
90 87 status = STATUS_ERROR
91 88 if form.is_valid():
92 89 post = ThreadView().new_post(request, form, opening_post,
93 html_response=False)
90 html_response=False)
94 91 if not post:
95 92 status = STATUS_ERROR
96 93 else:
@@ -156,7 +153,7 b' def api_get_threads(request, count):'
156 153
157 154 # TODO Add tags, replies and images count
158 155 opening_posts.append(_get_post_data(opening_post.id,
159 include_last_update=True))
156 include_last_update=True))
160 157
161 158 return HttpResponse(content=json.dumps(opening_posts))
162 159
@@ -239,10 +236,11 b' def _get_post_data(post_id, format_type='
239 236 'title': post.title,
240 237 'text': post.text.rendered,
241 238 }
242 if post.image:
243 post_json['image'] = post.image.url
244 post_json['image_preview'] = post.image.url_200x150
239 if post.images.exists():
240 post_image = post.get_first_image()
241 post_json['image'] = post_image.image.url
242 post_json['image_preview'] = post_image.image.url_200x150
245 243 if include_last_update:
246 244 post_json['bump_time'] = datetime_to_epoch(
247 post.thread_new.bump_time)
245 post.thread_new.bump_time)
248 246 return post_json
@@ -1,5 +1,6 b''
1 1 from django.db import transaction
2 2 from django.shortcuts import get_object_or_404
3 from boards import utils
3 4
4 5 from boards.views.base import BaseBoardView
5 6 from boards.models import Post, Ban
@@ -10,7 +11,7 b' class BanUserView(BaseBoardView, Redirec'
10 11
11 12 @transaction.atomic
12 13 def get(self, request, post_id):
13 user = self._get_user(request)
14 user = utils.get_user(request)
14 15 post = get_object_or_404(Post, id=post_id)
15 16
16 17 if user.is_moderator():
@@ -1,20 +1,13 b''
1 from datetime import datetime, timedelta
2 import hashlib
3 1 from django.db import transaction
4 from django.db.models import Count
5 2 from django.template import RequestContext
6 from django.utils import timezone
7 3 from django.views.generic import View
4
8 5 from boards import utils
9 from boards.models import User, Post
10 from boards.models.post import SETTING_MODERATE
11 from boards.models.user import RANK_USER, Ban
12 import neboard
6 from boards.models.user import Ban
7
13 8
14 9 BAN_REASON_SPAM = 'Autoban: spam bot'
15 10
16 OLD_USER_AGE_DAYS = 90
17
18 11 PARAMETER_FORM = 'form'
19 12
20 13
@@ -22,96 +15,11 b' class BaseBoardView(View):'
22 15
23 16 def get_context_data(self, **kwargs):
24 17 request = kwargs['request']
25 context = self._default_context(request)
26
27 context['version'] = neboard.settings.VERSION
28 context['site_name'] = neboard.settings.SITE_NAME
29
30 return context
31
32 def _default_context(self, request):
33 """Create context with default values that are used in most views"""
34
18 # context = self._default_context(request)
35 19 context = RequestContext(request)
36 20
37 user = self._get_user(request)
38 context['user'] = user
39 context['tags'] = user.fav_tags.all()
40 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
41
42 theme = self._get_theme(request, user)
43 context['theme'] = theme
44 context['theme_css'] = 'css/' + theme + '/base_page.css'
45
46 # This shows the moderator panel
47 moderate = user.get_setting(SETTING_MODERATE)
48 if moderate == 'True':
49 context['moderator'] = user.is_moderator()
50 else:
51 context['moderator'] = False
52
53 21 return context
54 22
55 def _get_user(self, request):
56 """
57 Get current user from the session. If the user does not exist, create
58 a new one.
59 """
60
61 session = request.session
62 if not 'user_id' in session:
63 request.session.save()
64
65 md5 = hashlib.md5()
66 md5.update(session.session_key)
67 new_id = md5.hexdigest()
68
69 while User.objects.filter(user_id=new_id).exists():
70 md5.update(str(timezone.now()))
71 new_id = md5.hexdigest()
72
73 time_now = timezone.now()
74 user = User.objects.create(user_id=new_id, rank=RANK_USER,
75 registration_time=time_now)
76
77 # TODO Move this to manage.py commands
78 #self._delete_old_users()
79
80 session['user_id'] = user.id
81 else:
82 user = User.objects.select_related('fav_tags').get(
83 id=session['user_id'])
84
85 return user
86
87 def _get_theme(self, request, user=None):
88 """
89 Get user's CSS theme
90 """
91
92 if not user:
93 user = self._get_user(request)
94 theme = user.get_setting('theme')
95 if not theme:
96 theme = neboard.settings.DEFAULT_THEME
97
98 return theme
99
100 def _delete_old_users(self):
101 """
102 Delete users with no favorite tags and posted messages. These can be spam
103 bots or just old user accounts
104 """
105
106 old_registration_date = datetime.now().date() - timedelta(
107 OLD_USER_AGE_DAYS)
108
109 for user in User.objects.annotate(tags_count=Count('fav_tags')).filter(
110 tags_count=0).filter(
111 registration_time__lt=old_registration_date):
112 if not Post.objects.filter(user=user).exists():
113 user.delete()
114
115 23 @transaction.atomic
116 24 def _ban_current_user(self, request):
117 25 """
@@ -1,5 +1,6 b''
1 1 from django.shortcuts import redirect, get_object_or_404
2 2 from django.db import transaction
3 from boards import utils
3 4
4 5 from boards.views.base import BaseBoardView
5 6 from boards.views.mixins import RedirectNextMixin
@@ -10,7 +11,7 b' class DeletePostView(BaseBoardView, Redi'
10 11
11 12 @transaction.atomic
12 13 def get(self, request, post_id):
13 user = self._get_user(request)
14 user = utils.get_user(request)
14 15 post = get_object_or_404(Post, id=post_id)
15 16
16 17 opening_post = post.is_opening()
@@ -9,7 +9,7 b' from boards.forms import AddTagForm, Pla'
9 9 class PostAdminView(BaseBoardView, DispatcherMixin):
10 10
11 11 def get(self, request, post_id, form=None):
12 user = self._get_user(request)
12 user = utils.get_user(request)
13 13 if not user.is_moderator:
14 14 redirect('index')
15 15
@@ -30,7 +30,7 b' class PostAdminView(BaseBoardView, Dispa'
30 30 return render(request, 'boards/post_admin.html', context)
31 31
32 32 def post(self, request, post_id):
33 user = self._get_user(request)
33 user = utils.get_user(request)
34 34 if not user.is_moderator:
35 35 redirect('index')
36 36
@@ -1,5 +1,6 b''
1 1 from django.db import transaction
2 2 from django.shortcuts import render, redirect
3 from boards import utils
3 4
4 5 from boards.views.base import BaseBoardView, PARAMETER_FORM
5 6 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
@@ -10,15 +11,16 b' class SettingsView(BaseBoardView):'
10 11
11 12 def get(self, request):
12 13 context = self.get_context_data(request=request)
13 user = context['user']
14 user = utils.get_user(request)
14 15 is_moderator = user.is_moderator()
15 16
16 selected_theme = context['theme']
17 selected_theme = utils.get_theme(request, user)
17 18
18 19 if is_moderator:
19 20 form = ModeratorSettingsForm(initial={
20 21 'theme': selected_theme,
21 'moderate': context['moderator']
22 'moderate': user.get_setting(SETTING_MODERATE) and \
23 user.is_moderator()
22 24 }, error_class=PlainErrorList)
23 25 else:
24 26 form = SettingsForm(initial={'theme': selected_theme},
@@ -29,8 +31,7 b' class SettingsView(BaseBoardView):'
29 31 return render(request, 'boards/settings.html', context)
30 32
31 33 def post(self, request):
32 context = self.get_context_data(request=request)
33 user = context['user']
34 user = utils.get_user(request)
34 35 is_moderator = user.is_moderator()
35 36
36 37 with transaction.atomic():
@@ -2,6 +2,7 b' from django.shortcuts import render'
2 2
3 3 from boards.views.base import BaseBoardView
4 4
5
5 6 class StaticPageView(BaseBoardView):
6 7
7 8 def get(self, request, name):
@@ -1,4 +1,5 b''
1 1 from django.shortcuts import get_object_or_404
2 from boards import utils
2 3 from boards.models import Tag, Post
3 4 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
4 5 from boards.views.mixins import DispatcherMixin, RedirectNextMixin
@@ -47,7 +48,7 b' class TagView(AllThreadsView, Dispatcher'
47 48 return self.get(request, tag_name, page, form)
48 49
49 50 def subscribe(self, request):
50 user = self._get_user(request)
51 user = utils.get_user(request)
51 52 tag = get_object_or_404(Tag, name=self.tag_name)
52 53
53 54 if not tag in user.fav_tags.all():
@@ -56,7 +57,7 b' class TagView(AllThreadsView, Dispatcher'
56 57 return self.redirect_to_next(request)
57 58
58 59 def unsubscribe(self, request):
59 user = self._get_user(request)
60 user = utils.get_user(request)
60 61 tag = get_object_or_404(Tag, name=self.tag_name)
61 62
62 63 if tag in user.fav_tags.all():
@@ -70,7 +71,7 b' class TagView(AllThreadsView, Dispatcher'
70 71 shown.
71 72 """
72 73
73 user = self._get_user(request)
74 user = utils.get_user(request)
74 75 tag = get_object_or_404(Tag, name=self.tag_name)
75 76
76 77 user.hide_tag(tag)
@@ -80,7 +81,7 b' class TagView(AllThreadsView, Dispatcher'
80 81 Removed tag from user's hidden tags.
81 82 """
82 83
83 user = self._get_user(request)
84 user = utils.get_user(request)
84 85 tag = get_object_or_404(Tag, name=self.tag_name)
85 86
86 87 user.unhide_tag(tag)
@@ -1,17 +1,18 b''
1 import string
2 1 from django.core.urlresolvers import reverse
3 2 from django.db import transaction
4 3 from django.http import Http404
5 4 from django.shortcuts import get_object_or_404, render, redirect
6 5 from django.views.generic.edit import FormMixin
6
7 7 from boards import utils
8 8 from boards.forms import PostForm, PlainErrorList
9 from boards.models import Post, Ban, Tag
9 from boards.models import Post, Ban
10 10 from boards.views.banned import BannedView
11 11 from boards.views.base import BaseBoardView, PARAMETER_FORM
12 12 from boards.views.posting_mixin import PostMixin
13 13 import neboard
14 14
15
15 16 MODE_GALLERY = 'gallery'
16 17 MODE_NORMAL = 'normal'
17 18
@@ -23,7 +24,10 b" PARAMETER_BUMPABLE = 'bumpable'"
23 24 class ThreadView(BaseBoardView, PostMixin, FormMixin):
24 25
25 26 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
26 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
27 try:
28 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
29 except IndexError:
30 raise Http404
27 31
28 32 # If this is not OP, don't show it as it is
29 33 if not opening_post or not opening_post.is_opening():
@@ -55,8 +59,8 b' class ThreadView(BaseBoardView, PostMixi'
55 59
56 60 document = 'boards/thread.html'
57 61 elif MODE_GALLERY == mode:
58 posts = thread_to_show.get_replies(view_fields_only=True)
59 context['posts'] = posts.filter(image_width__gt=0)
62 context['posts'] = thread_to_show.get_replies_with_images(
63 view_fields_only=True)
60 64
61 65 document = 'boards/thread_gallery.html'
62 66 else:
@@ -116,7 +120,7 b' class ThreadView(BaseBoardView, PostMixi'
116 120 post = Post.objects.create_post(title=title, text=text, ip=ip,
117 121 thread=post_thread, image=image,
118 122 tags=tags,
119 user=self._get_user(request))
123 user=utils.get_user(request))
120 124
121 125 thread_to_show = (opening_post.id if opening_post else post.id)
122 126
@@ -26,3 +26,12 b' thread.'
26 26 * Update last update time with thread update
27 27 * Added z-index to the images to move the dragged image to front
28 28 * [CODE] Major view refactoring. Now almost all views are class-based
29
30 # 1.8 Kara
31 * [CODE] Removed thread update logging
32 * [CODE] Refactored compact form. Now it uses the same one form and moves
33 elements instead of swapping them
34 * [CODE] Moved image to a separate model. This will allow to add multiple
35 images to a post
36 * Added search over posts and tags
37 * [ADMIN] Command to remove empty users
@@ -631,8 +631,8 b' to attach them to the start of each sour'
631 631 state the exclusion of warranty; and each file should have at least
632 632 the "copyright" line and a pointer to where the full notice is found.
633 633
634 <one line to give the program's name and a brief idea of what it does.>
635 Copyright (C) <year> <name of author>
634 neboard - a free imageboard
635 Copyright (C) 2013-2014 nekorin
636 636
637 637 This program is free software: you can redistribute it and/or modify
638 638 it under the terms of the GNU General Public License as published by
@@ -652,7 +652,7 b' Also add information on how to contact y'
652 652 If the program does terminal interaction, make it output a short
653 653 notice like this when it starts in an interactive mode:
654 654
655 <program> Copyright (C) <year> <name of author>
655 neboard Copyright (C) 2013-2014 nekorin
656 656 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 657 This is free software, and you are welcome to redistribute it
658 658 under certain conditions; type `show c' for details.
@@ -105,6 +105,7 b' TEMPLATE_CONTEXT_PROCESSORS = ('
105 105 'django.core.context_processors.static',
106 106 'django.core.context_processors.request',
107 107 'django.contrib.auth.context_processors.auth',
108 'boards.context_processors.user_and_ui_processor',
108 109 )
109 110
110 111 MIDDLEWARE_CLASSES = (
@@ -137,15 +138,22 b' INSTALLED_APPS = ('
137 138 'django.contrib.messages',
138 139 'django.contrib.staticfiles',
139 140 # Uncomment the next line to enable the admin:
140 'django.contrib.admin',
141 'django.contrib.admin',
141 142 # Uncomment the next line to enable admin documentation:
142 143 # 'django.contrib.admindocs',
143 144 'django.contrib.humanize',
144 145 'django_cleanup',
145 'boards',
146 'captcha',
146
147 # Migrations
147 148 'south',
148 149 'debug_toolbar',
150
151 'captcha',
152
153 # Search
154 'haystack',
155
156 'boards',
149 157 )
150 158
151 159 DEBUG_TOOLBAR_PANELS = (
@@ -201,6 +209,15 b' LOGGING = {'
201 209 },
202 210 }
203 211
212 HAYSTACK_CONNECTIONS = {
213 'default': {
214 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
215 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'),
216 },
217 }
218
219 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
220
204 221 MARKUP_FIELD_TYPES = (
205 222 ('markdown', markdown_extended),
206 223 )
@@ -230,7 +247,7 b' POSTING_DELAY = 20 # seconds'
230 247
231 248 COMPRESS_HTML = True
232 249
233 VERSION = '1.7.4 Anubis'
250 VERSION = '1.8.0 Kara'
234 251
235 252 # Debug mode middlewares
236 253 if DEBUG:
@@ -1,3 +1,4 b''
1 haystack
1 2 pillow
2 3 django>=1.6
3 4 django_cleanup
General Comments 0
You need to be logged in to leave comments. Login now