##// 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 from boards.mdx_neboard import formatters
10 from boards.mdx_neboard import formatters
11 from boards.models.post import TITLE_MAX_LENGTH
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 from neboard import settings
13 from neboard import settings
14 from boards import utils
14 from boards import utils
15 import boards.settings as board_settings
15 import boards.settings as board_settings
@@ -147,7 +147,7 b' class PostForm(NeboardForm):'
147 for chunk in image.chunks():
147 for chunk in image.chunks():
148 md5.update(chunk)
148 md5.update(chunk)
149 image_hash = md5.hexdigest()
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 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
151 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
152
152
153 return image
153 return image
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -7,7 +7,7 b' msgid ""'
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
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 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
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 msgid "Tags"
98 msgid "Tags"
99 msgstr "Π’Π΅Π³ΠΈ"
99 msgstr "Π’Π΅Π³ΠΈ"
100
100
101 #: forms.py:225 forms.py:344
101 #: forms.py:224 forms.py:343
102 msgid "Inappropriate characters in tags."
102 msgid "Inappropriate characters in tags."
103 msgstr "НСдопустимыС символы Π² Ρ‚Π΅Π³Π°Ρ…."
103 msgstr "НСдопустимыС символы Π² Ρ‚Π΅Π³Π°Ρ…."
104
104
105 #: forms.py:253 forms.py:274
105 #: forms.py:252 forms.py:273
106 msgid "Captcha validation failed"
106 msgid "Captcha validation failed"
107 msgstr "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΊΠ°ΠΏΡ‡ΠΈ ΠΏΡ€ΠΎΠ²Π°Π»Π΅Π½Π°"
107 msgstr "ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° ΠΊΠ°ΠΏΡ‡ΠΈ ΠΏΡ€ΠΎΠ²Π°Π»Π΅Π½Π°"
108
108
109 #: forms.py:280
109 #: forms.py:279
110 msgid "Theme"
110 msgid "Theme"
111 msgstr "Π’Π΅ΠΌΠ°"
111 msgstr "Π’Π΅ΠΌΠ°"
112
112
113 #: forms.py:285
113 #: forms.py:284
114 msgid "Enable moderation panel"
114 msgid "Enable moderation panel"
115 msgstr "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ панСль ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ†ΠΈΠΈ"
115 msgstr "Π’ΠΊΠ»ΡŽΡ‡ΠΈΡ‚ΡŒ панСль ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ†ΠΈΠΈ"
116
116
117 #: forms.py:300
117 #: forms.py:299
118 msgid "No such user found"
118 msgid "No such user found"
119 msgstr "Π”Π°Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½"
119 msgstr "Π”Π°Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½"
120
120
121 #: forms.py:314
121 #: forms.py:313
122 #, python-format
122 #, python-format
123 msgid "Wait %s minutes after last login"
123 msgid "Wait %s minutes after last login"
124 msgstr "ΠŸΠΎΠ΄ΠΎΠΆΠ΄ΠΈΡ‚Π΅ %s ΠΌΠΈΠ½ΡƒΡ‚ послС послСднСго Π²Ρ…ΠΎΠ΄Π°"
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 msgid "Repository"
147 msgid "Repository"
148 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
148 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
149
149
150 #: templates/boards/base.html:14
150 #: templates/boards/base.html:11
151 msgid "Feed"
151 msgid "Feed"
152 msgstr "Π›Π΅Π½Ρ‚Π°"
152 msgstr "Π›Π΅Π½Ρ‚Π°"
153
153
154 #: templates/boards/base.html:31
154 #: templates/boards/base.html:28
155 msgid "All threads"
155 msgid "All threads"
156 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
156 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
157
157
158 #: templates/boards/base.html:36
158 #: templates/boards/base.html:33
159 msgid "Tag management"
159 msgid "Tag management"
160 msgstr "Π£ΠΏΡ€Π°Π²Π»Π΅Π½ΠΈΠ΅ Ρ‚Π΅Π³Π°ΠΌΠΈ"
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 msgid "Settings"
163 msgid "Settings"
164 msgstr "Настройки"
164 msgstr "Настройки"
165
165
166 #: templates/boards/base.html:50 templates/boards/login.html:6
166 #: templates/boards/base.html:47 templates/boards/login.html:6
167 #: templates/boards/login.html.py:21
167 #: templates/boards/login.html.py:16
168 msgid "Login"
168 msgid "Login"
169 msgstr "Π’Ρ…ΠΎΠ΄"
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 #, python-format
176 #, python-format
173 msgid "Speed: %(ppd)s posts per day"
177 msgid "Speed: %(ppd)s posts per day"
174 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
178 msgstr "Π‘ΠΊΠΎΡ€ΠΎΡΡ‚ΡŒ: %(ppd)s сообщСний Π² дСнь"
175
179
176 #: templates/boards/base.html:54
180 #: templates/boards/base.html:52
177 msgid "Up"
181 msgid "Up"
178 msgstr "Π’Π²Π΅Ρ€Ρ…"
182 msgstr "Π’Π²Π΅Ρ€Ρ…"
179
183
180 #: templates/boards/login.html:15
184 #: templates/boards/login.html:19
181 msgid "User ID"
182 msgstr "ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
183
184 #: templates/boards/login.html:24
185 msgid "Insert your user id above"
185 msgid "Insert your user id above"
186 msgstr "Π’ΡΡ‚Π°Π²ΡŒΡ‚Π΅ свой ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π²Ρ‹ΡˆΠ΅"
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 msgid "Comment"
386 msgid "Comment"
387 msgstr "ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ"
387 msgstr "ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΉ"
388
388
389 #~ msgid "Archive"
389 #~ msgid "User ID"
390 #~ msgstr "Архив"
390 #~ msgstr "ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
@@ -1,3 +1,5 b''
1 # coding=utf-8
2
1 import markdown
3 import markdown
2 from markdown.inlinepatterns import Pattern, SubstituteTagPattern
4 from markdown.inlinepatterns import Pattern, SubstituteTagPattern
3 from markdown.util import etree
5 from markdown.util import etree
@@ -12,6 +14,7 b" REFLINK_PATTERN = r'((>>)(\\d+))'"
12 SPOILER_PATTERN = r'%%([^(%%)]+)%%'
14 SPOILER_PATTERN = r'%%([^(%%)]+)%%'
13 COMMENT_PATTERN = r'^(//(.+))'
15 COMMENT_PATTERN = r'^(//(.+))'
14 STRIKETHROUGH_PATTERN = r'~(.+)~'
16 STRIKETHROUGH_PATTERN = r'~(.+)~'
17 DASH_PATTERN = r'--'
15
18
16
19
17 class TextFormatter():
20 class TextFormatter():
@@ -144,6 +147,11 b' class CodePattern(TextFormatter):'
144 format_left = ' '
147 format_left = ' '
145
148
146
149
150 class DashPattern(Pattern):
151 def handleMatch(self, m):
152 return u'β€”'
153
154
147 class NeboardMarkdown(markdown.Extension):
155 class NeboardMarkdown(markdown.Extension):
148 def extendMarkdown(self, md, md_globals):
156 def extendMarkdown(self, md, md_globals):
149 self._add_neboard_patterns(md)
157 self._add_neboard_patterns(md)
@@ -162,6 +170,7 b' class NeboardMarkdown(markdown.Extension'
162 spoiler = SpoilerPattern(SPOILER_PATTERN, md)
170 spoiler = SpoilerPattern(SPOILER_PATTERN, md)
163 comment = CommentPattern(COMMENT_PATTERN, md)
171 comment = CommentPattern(COMMENT_PATTERN, md)
164 strikethrough = StrikeThroughPattern(STRIKETHROUGH_PATTERN, md)
172 strikethrough = StrikeThroughPattern(STRIKETHROUGH_PATTERN, md)
173 dash = DashPattern(DASH_PATTERN, md)
165
174
166 md.inlinePatterns[u'autolink_ext'] = autolink
175 md.inlinePatterns[u'autolink_ext'] = autolink
167 md.inlinePatterns[u'spoiler'] = spoiler
176 md.inlinePatterns[u'spoiler'] = spoiler
@@ -169,6 +178,7 b' class NeboardMarkdown(markdown.Extension'
169 md.inlinePatterns[u'comment'] = comment
178 md.inlinePatterns[u'comment'] = comment
170 md.inlinePatterns[u'reflink'] = reflink
179 md.inlinePatterns[u'reflink'] = reflink
171 md.inlinePatterns[u'quote'] = quote
180 md.inlinePatterns[u'quote'] = quote
181 md.inlinePatterns[u'dash'] = dash
172
182
173
183
174 def make_extension(configs=None):
184 def make_extension(configs=None):
@@ -1,8 +1,9 b''
1 from django.shortcuts import redirect
1 from django.shortcuts import redirect
2 from boards import views, utils
2 from boards import utils
3 from boards.models import Ban
3 from boards.models import Ban
4 from django.utils.html import strip_spaces_between_tags
4 from django.utils.html import strip_spaces_between_tags
5 from django.conf import settings
5 from django.conf import settings
6 from boards.views.banned import BannedView
6
7
7 RESPONSE_CONTENT_TYPE = 'Content-Type'
8 RESPONSE_CONTENT_TYPE = 'Content-Type'
8
9
@@ -17,7 +18,7 b' class BanMiddleware:'
17
18
18 def process_view(self, request, view_func, view_args, view_kwargs):
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 ip = utils.get_client_ip(request)
22 ip = utils.get_client_ip(request)
22 bans = Ban.objects.filter(ip=ip)
23 bans = Ban.objects.filter(ip=ip)
23
24
@@ -1,7 +1,8 b''
1 __author__ = 'neko259'
1 __author__ = 'neko259'
2
2
3 from boards.models.image import PostImage
4 from boards.models.thread import Thread
3 from boards.models.post import Post
5 from boards.models.post import Post
4 from boards.models.post import Thread
5 from boards.models.tag import Tag
6 from boards.models.tag import Tag
6 from boards.models.user import Ban
7 from boards.models.user import Ban
7 from boards.models.user import Setting
8 from boards.models.user import Setting
@@ -10,9 +10,13 b' import hashlib'
10 from django.core.cache import cache
10 from django.core.cache import cache
11 from django.core.urlresolvers import reverse
11 from django.core.urlresolvers import reverse
12 from django.db import models, transaction
12 from django.db import models, transaction
13 from django.template.loader import render_to_string
13 from django.utils import timezone
14 from django.utils import timezone
14 from markupfield.fields import MarkupField
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 from neboard import settings
20 from neboard import settings
17 from boards import thumbs
21 from boards import thumbs
18
22
@@ -21,7 +25,6 b" APP_LABEL_BOARDS = 'boards'"
21
25
22 CACHE_KEY_PPD = 'ppd'
26 CACHE_KEY_PPD = 'ppd'
23 CACHE_KEY_POST_URL = 'post_url'
27 CACHE_KEY_POST_URL = 'post_url'
24 CACHE_KEY_OPENING_POST = 'opening_post_id'
25
28
26 POSTS_PER_DAY_RANGE = range(7)
29 POSTS_PER_DAY_RANGE = range(7)
27
30
@@ -33,10 +36,20 b' TITLE_MAX_LENGTH = 200'
33
36
34 DEFAULT_MARKUP_TYPE = 'markdown'
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 NO_PARENT = -1
41 NO_PARENT = -1
42
43 # TODO This should be removed
37 NO_IP = '0.0.0.0'
44 NO_IP = '0.0.0.0'
45
46 # TODO Real user agent should be saved instead of this
38 UNKNOWN_UA = ''
47 UNKNOWN_UA = ''
48
49 # TODO This should be checked for usage and removed because a nativa
50 # paginator is used now
39 ALL_PAGES = -1
51 ALL_PAGES = -1
52
40 IMAGES_DIRECTORY = 'images/'
53 IMAGES_DIRECTORY = 'images/'
41 FILE_EXTENSION_DELIMITER = '.'
54 FILE_EXTENSION_DELIMITER = '.'
42
55
@@ -70,13 +83,18 b' class PostManager(models.Manager):'
70 text=text,
83 text=text,
71 pub_time=posting_time,
84 pub_time=posting_time,
72 thread_new=thread,
85 thread_new=thread,
73 image=image,
74 poster_ip=ip,
86 poster_ip=ip,
75 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
87 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
76 # last!
88 # last!
77 last_edit_time=posting_time,
89 last_edit_time=posting_time,
78 user=user)
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 thread.replies.add(post)
98 thread.replies.add(post)
81 if tags:
99 if tags:
82 linked_tags = []
100 linked_tags = []
@@ -124,6 +142,9 b' class PostManager(models.Manager):'
124 map(self.delete_post, posts)
142 map(self.delete_post, posts)
125
143
126 # TODO Move this method to thread manager
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 def _delete_old_threads(self):
148 def _delete_old_threads(self):
128 """
149 """
129 Preserves maximum thread count. If there are too many threads,
150 Preserves maximum thread count. If there are too many threads,
@@ -193,7 +214,7 b' class PostManager(models.Manager):'
193 return ppd
214 return ppd
194
215
195
216
196 class Post(models.Model):
217 class Post(models.Model, Viewable):
197 """A post is a message."""
218 """A post is a message."""
198
219
199 objects = PostManager()
220 objects = PostManager()
@@ -202,38 +223,13 b' class Post(models.Model):'
202 app_label = APP_LABEL_BOARDS
223 app_label = APP_LABEL_BOARDS
203 ordering = ('id',)
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 title = models.CharField(max_length=TITLE_MAX_LENGTH)
226 title = models.CharField(max_length=TITLE_MAX_LENGTH)
220 pub_time = models.DateTimeField()
227 pub_time = models.DateTimeField()
221 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
228 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
222 escape_html=False)
229 escape_html=False)
223
230
224 image_width = models.IntegerField(default=0)
231 images = models.ManyToManyField(PostImage, null=True, blank=True,
225 image_height = models.IntegerField(default=0)
232 related_name='ip+', db_index=True)
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)
237
233
238 poster_ip = models.GenericIPAddressField()
234 poster_ip = models.GenericIPAddressField()
239 poster_user_agent = models.TextField()
235 poster_user_agent = models.TextField()
@@ -289,18 +285,6 b' class Post(models.Model):'
289
285
290 return self.get_thread().get_opening_post_id() == self.id
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 @transaction.atomic
288 @transaction.atomic
305 def add_tag(self, tag):
289 def add_tag(self, tag):
306 edit_time = timezone.now()
290 edit_time = timezone.now()
@@ -359,145 +343,39 b' class Post(models.Model):'
359 def get_referenced_posts(self):
343 def get_referenced_posts(self):
360 return self.referenced_posts.only('id', 'thread_new')
344 return self.referenced_posts.only('id', 'thread_new')
361
345
362
346 def get_text(self):
363 class Thread(models.Model):
347 return self.text
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()
394
348
395 def get_images_count(self):
349 def get_view(self, moderator=False, need_open_link=False,
396 return self.replies.filter(image_width__gt=0).count()
350 truncated=False, *args, **kwargs):
397
351 if 'is_opening' in kwargs:
398 def can_bump(self):
352 is_opening = kwargs['is_opening']
399 """
353 else:
400 Checks if the thread can be bumped by replying to it.
354 is_opening = self.is_opening()
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
409
355
410 def delete_with_posts(self):
356 if 'thread' in kwargs:
411 """
357 thread = kwargs['thread']
412 Completely deletes thread and all its posts
358 else:
413 """
359 thread = self.get_thread()
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()
427
360
428 if reply_count > 0:
361 if 'can_bump' in kwargs:
429 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
362 can_bump = kwargs['can_bump']
430 reply_count - 1)
363 else:
431 last_replies = self.replies.order_by(
364 can_bump = thread.can_bump()
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
446
365
447 def get_replies(self, view_fields_only=False):
366 opening_post_id = thread.get_opening_post_id()
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)
465
367
466 def remove_tag(self, tag):
368 return render_to_string('boards/post.html', {
467 self.tags.remove(tag)
369 'post': self,
468 tag.threads.remove(self)
370 'moderator': moderator,
469
371 'is_opening': is_opening,
470 def get_opening_post(self, only_id=False):
372 'thread': thread,
471 """
373 'bumpable': can_bump,
472 Gets the first post of the thread
374 'need_open_link': need_open_link,
473 """
375 'truncated': truncated,
474
376 'opening_post_id': opening_post_id,
475 query = self.replies.order_by('pub_time')
377 })
476 if only_id:
477 query = query.only('id')
478 opening_post = query.first()
479
480 return opening_post
481
378
482 def get_opening_post_id(self):
379 def get_first_image(self):
483 """
380 return self.images.earliest('id')
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)
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 from boards.models import Thread, Post
2 from boards.models import Thread, Post
2 from django.db import models
3 from django.db import models
3 from django.db.models import Count, Sum
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 __author__ = 'neko259'
8 __author__ = 'neko259'
6
9
10 # TODO Tag popularity ratings are not used any more, remove all of this
7 MAX_TAG_FONT = 1
11 MAX_TAG_FONT = 1
8 MIN_TAG_FONT = 0.2
12 MIN_TAG_FONT = 0.2
9
13
@@ -20,13 +24,12 b' class TagManager(models.Manager):'
20 """
24 """
21
25
22 tags = self.annotate(Count('threads')) \
26 tags = self.annotate(Count('threads')) \
23 .filter(threads__count__gt=0).filter(threads__archived=False) \
27 .filter(threads__count__gt=0).order_by('name')
24 .order_by('name')
25
28
26 return tags
29 return tags
27
30
28
31
29 class Tag(models.Model):
32 class Tag(models.Model, Viewable):
30 """
33 """
31 A tag is a text node assigned to the thread. The tag serves as a board
34 A tag is a text node assigned to the thread. The tag serves as a board
32 section. There can be multiple tags for each thread
35 section. There can be multiple tags for each thread
@@ -56,6 +59,7 b' class Tag(models.Model):'
56 def get_thread_count(self):
59 def get_thread_count(self):
57 return self.threads.count()
60 return self.threads.count()
58
61
62 # TODO Remove, not used any more
59 def get_popularity(self):
63 def get_popularity(self):
60 """
64 """
61 Gets tag's popularity value as a percentage of overall board post
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 linked_tag.get_linked_tags_list(tag_list)
102 linked_tag.get_linked_tags_list(tag_list)
99
103
104 # TODO Remove
100 def get_font_value(self):
105 def get_font_value(self):
101 """
106 """
102 Gets tag font value to differ most popular tags in the list
107 Gets tag font value to differ most popular tags in the list
@@ -126,3 +131,11 b' class Tag(models.Model):'
126 posts_count = 0
131 posts_count = 0
127
132
128 return posts_count
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 -moz-box-sizing: border-box;
73 -moz-box-sizing: border-box;
74 -webkit-box-sizing: border-box;
74 -webkit-box-sizing: border-box;
75 box-sizing: border-box;
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 color: #00FF00
12 color: #00FF00
13 }
13 }
14
14
15 .input_field {
16
17 }
18
19 .input_field_name {
20
21 }
22
23 .input_field_error {
15 .input_field_error {
24 color: #FF0000;
16 color: #FF0000;
25 }
17 }
@@ -39,9 +31,7 b' body {'
39 }
31 }
40
32
41 .tag {
33 .tag {
42 /*color: #F5FFC9;*/
43 color: #FFD37D;
34 color: #FFD37D;
44 /*color: #b4cfec;*/
45 }
35 }
46
36
47 .post_id {
37 .post_id {
@@ -165,7 +155,7 b' p {'
165 margin-bottom: 0.5ex;
155 margin-bottom: 0.5ex;
166 }
156 }
167
157
168 input[type="submit"] {
158 .post-form input[type="submit"], input[type="submit"] {
169 background: #222;
159 background: #222;
170 border: solid 2px #fff;
160 border: solid 2px #fff;
171 color: #fff;
161 color: #fff;
@@ -413,3 +403,19 b' pre {'
413 margin: 0.2ex;
403 margin: 0.2ex;
414 padding: 0.1ex;
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 .swappable-form-full > form {
360 .swappable-form-full > form {
361 display: table;
361 display: table;
362 width: 100%;
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 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
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 var fullForm = $('.swappable-form-full');
24 var fullForm = $('.swappable-form-full');
26
25
27 function swapForm() {
26 function swapForm() {
28 compactForm.toggle();
27 if (isCompact) {
29 fullForm.toggle();
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) {
33 scrollToBottom();
32 var oldText = compactForm.find('textarea')[0].value;
33 fullForm.find('textarea')[0].value = oldText;
34 } else {
34 } else {
35 var oldText = fullForm.find('textarea')[0].value;
35 fullForm.find('textarea').appendTo($('.compact-form-text'));
36 compactForm.find('textarea')[0].value = oldText;
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 isCompact = !isCompact;
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 $('.form-email').parent().parent().hide();
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 $( document ).ready(function() {
43 $( document ).ready(function() {
35 hideEmailFromForm();
44 hideEmailFromForm();
36
45
@@ -42,4 +51,6 b' function hideEmailFromForm() {'
42 addImgPreview();
51 addImgPreview();
43
52
44 addRefLinkPreview();
53 addRefLinkPreview();
54
55 highlightCode();
45 });
56 });
@@ -56,4 +56,6 b' function scrollToBottom() {'
56 $target.animate({scrollTop: $target.height()}, "fast");
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 post.appendTo(lastPost.parent());
70 post.appendTo(lastPost.parent());
71 addRefLinkPreview(post[0]);
71 addRefLinkPreview(post[0]);
72 highlightCode();
72
73
73 lastPost = post;
74 lastPost = post;
74 blink(post);
75 blink(post);
@@ -90,6 +91,7 b' function updateThread() {'
90
91
91 oldPost.replaceWith(post);
92 oldPost.replaceWith(post);
92 addRefLinkPreview(post[0]);
93 addRefLinkPreview(post[0]);
94 highlightCode();
93
95
94 blink(post);
96 blink(post);
95 }
97 }
@@ -6,12 +6,10 b''
6 <!DOCTYPE html>
6 <!DOCTYPE html>
7 <html>
7 <html>
8 <head>
8 <head>
9 <link rel="stylesheet" type="text/css"
9 <link rel="stylesheet" type="text/css" href="{% static 'css/base.css' %}" media="all"/>
10 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"
11 <link rel="stylesheet" type="text/css" href="{% static theme_css %}" media="all"/>
12 href="{% static theme_css %}" media="all"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title="{% trans 'Feed' %}"/>
13 <link rel="alternate" type="application/rss+xml" href="rss/" title=
14 "{% trans 'Feed' %}"/>
15
13
16 <link rel="icon" type="image/png"
14 <link rel="icon" type="image/png"
17 href="{% static 'favicon.png' %}">
15 href="{% static 'favicon.png' %}">
@@ -43,11 +41,13 b''
43 <script src="{% static 'js/popup.js' %}"></script>
41 <script src="{% static 'js/popup.js' %}"></script>
44 <script src="{% static 'js/image.js' %}"></script>
42 <script src="{% static 'js/image.js' %}"></script>
45 <script src="{% static 'js/refpopup.js' %}"></script>
43 <script src="{% static 'js/refpopup.js' %}"></script>
44 <script src="{% static 'js/3party/highlight.min.js' %}"></script>
46 <script src="{% static 'js/main.js' %}"></script>
45 <script src="{% static 'js/main.js' %}"></script>
47
46
48 <div class="navigation_panel">
47 <div class="navigation_panel">
49 {% block metapanel %}{% endblock %}
48 {% block metapanel %}{% endblock %}
50 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 [<a href="{% url "haystack_search" %}">{% trans 'Search' %}</a>]
51 {% with ppd=posts_per_day|floatformat:2 %}
51 {% with ppd=posts_per_day|floatformat:2 %}
52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
53 {% endwith %}
53 {% endwith %}
@@ -46,19 +46,21 b''
46 </span>
46 </span>
47 {% endif %}
47 {% endif %}
48 </div>
48 </div>
49 {% if post.image %}
49 {% if post.images.exists %}
50 {% with post.images.all.0 as image %}
50 <div class="image">
51 <div class="image">
51 <a
52 <a
52 class="thumb"
53 class="thumb"
53 href="{{ post.image.url }}"><img
54 href="{{ image.image.url }}"><img
54 src="{{ post.image.url_200x150 }}"
55 src="{{ image.image.url_200x150 }}"
55 alt="{{ post.id }}"
56 alt="{{ post.id }}"
56 width="{{ post.image_pre_width }}"
57 width="{{ image.pre_width }}"
57 height="{{ post.image_pre_height }}"
58 height="{{ image.pre_height }}"
58 data-width="{{ post.image_width }}"
59 data-width="{{ image.width }}"
59 data-height="{{ post.image_height }}"/>
60 data-height="{{ image.height }}"/>
60 </a>
61 </a>
61 </div>
62 </div>
63 {% endwith %}
62 {% endif %}
64 {% endif %}
63 <div class="message">
65 <div class="message">
64 {% autoescape off %}
66 {% autoescape off %}
@@ -81,6 +83,7 b''
81 {% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}
83 {% cache 600 post_thread thread.id thread.last_edit_time LANGUAGE_CODE need_open_link %}
82 <div class="metadata">
84 <div class="metadata">
83 {% if is_opening and need_open_link %}
85 {% if is_opening and need_open_link %}
86 {{ thread.get_reply_count }} {% trans 'replies' %},
84 {{ thread.get_images_count }} {% trans 'images' %}.
87 {{ thread.get_images_count }} {% trans 'images' %}.
85 {% endif %}
88 {% endif %}
86 <span class="tags">
89 <span class="tags">
@@ -1,7 +1,7 b''
1 {% load i18n %}
1 {% load i18n %}
2
2
3 {% if obj.image %}
3 {% if obj.images.exists %}
4 <img src="{{ obj.image.url_200x150 }}"
4 <img src="{{ obj.get_first_image.image.url_200x150 }}"
5 alt="{% trans 'Post image' %}" />
5 alt="{% trans 'Post image' %}" />
6 {% endif %}
6 {% endif %}
7 {{ obj.text.rendered|safe }}
7 {{ obj.text.rendered|safe }}
@@ -1,96 +1,3 b''
1 {% load i18n %}
1 <div class="post">
2 {% load board %}
2 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">#{{ tag.name }}</a>
3 {% load cache %}
3 </div> No newline at end of file
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 %}
@@ -49,23 +49,10 b''
49 <script src="{% static 'js/panel.js' %}"></script>
49 <script src="{% static 'js/panel.js' %}"></script>
50 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
50 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
51 <div class="post-form" id="compact-form">
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 <div class="swappable-form-full">
52 <div class="swappable-form-full">
67 <form enctype="multipart/form-data" method="post"
53 <form enctype="multipart/form-data" method="post"
68 >{% csrf_token %}
54 >{% csrf_token %}
55 <div class="compact-form-text"></div>
69 {{ form.as_div }}
56 {{ form.as_div }}
70 <div class="form-submit">
57 <div class="form-submit">
71 <input type="submit" value="{% trans "Post" %}"/>
58 <input type="submit" value="{% trans "Post" %}"/>
@@ -25,22 +25,24 b''
25 <div id="posts-table">
25 <div id="posts-table">
26 {% for post in posts %}
26 {% for post in posts %}
27 <div class="gallery_image">
27 <div class="gallery_image">
28 {% with post.get_first_image as image %}
28 <div>
29 <div>
29 <a
30 <a
30 class="thumb"
31 class="thumb"
31 href="{{ post.image.url }}"><img
32 href="{{ image.image.url }}"><img
32 src="{{ post.image.url_200x150 }}"
33 src="{{ image.image.url_200x150 }}"
33 alt="{{ post.id }}"
34 alt="{{ post.id }}"
34 width="{{ post.image_pre_width }}"
35 width="{{ image.pre_width }}"
35 height="{{ post.image_pre_height }}"
36 height="{{ image.pre_height }}"
36 data-width="{{ post.image_width }}"
37 data-width="{{ image.width }}"
37 data-height="{{ post.image_height }}"/>
38 data-height="{{ image.height }}"/>
38 </a>
39 </a>
39 </div>
40 </div>
40 <div class="gallery_image_metadata">
41 <div class="gallery_image_metadata">
41 {{ post.image_width }}x{{ post.image_height }}
42 {{ image.width }}x{{ image.height }}
42 {% image_actions post.image.url request.get_host %}
43 {% image_actions image.image.url request.get_host %}
43 </div>
44 </div>
45 {% endwith %}
44 </div>
46 </div>
45 {% endfor %}
47 {% endfor %}
46 </div>
48 </div>
@@ -1,8 +1,7 b''
1 from django.shortcuts import get_object_or_404
1 from django.shortcuts import get_object_or_404
2 from boards.models import Post
3 from boards.views import thread, api
4 from django import template
2 from django import template
5
3
4
6 register = template.Library()
5 register = template.Library()
7
6
8 actions = [
7 actions = [
@@ -21,7 +20,7 b' actions = ['
21 def post_url(*args, **kwargs):
20 def post_url(*args, **kwargs):
22 post_id = args[0]
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 return post.get_url()
25 return post.get_url()
27
26
@@ -53,6 +52,7 b' def image_actions(*args, **kwargs):'
53 return result
52 return result
54
53
55
54
55 # TODO Use get_view of a post instead of this
56 @register.inclusion_tag('boards/post.html', name='post_view')
56 @register.inclusion_tag('boards/post.html', name='post_view')
57 def post_view(post, moderator=False, need_open_link=False, truncated=False,
57 def post_view(post, moderator=False, need_open_link=False, truncated=False,
58 **kwargs):
58 **kwargs):
@@ -161,8 +161,7 b' class PagesTest(TestCase):'
161
161
162 response_not_existing = client.get(THREAD_PAGE + str(
162 response_not_existing = client.get(THREAD_PAGE + str(
163 existing_post_id + 1) + '/')
163 existing_post_id + 1) + '/')
164 self.assertEqual(PAGE_404,
164 self.assertEqual(PAGE_404, response_not_existing.templates[0].name,
165 response_not_existing.templates[0].name,
166 u'Not existing thread is opened')
165 u'Not existing thread is opened')
167
166
168 response_existing = client.get(TAG_PAGE + tag_name + '/')
167 response_existing = client.get(TAG_PAGE + tag_name + '/')
@@ -177,7 +176,7 b' class PagesTest(TestCase):'
177
176
178 reply_id = Post.objects.create_post('', TEST_TEXT,
177 reply_id = Post.objects.create_post('', TEST_TEXT,
179 thread=Post.objects.all()[0]
178 thread=Post.objects.all()[0]
180 .thread)
179 .get_thread())
181 response_not_existing = client.get(THREAD_PAGE + str(
180 response_not_existing = client.get(THREAD_PAGE + str(
182 reply_id) + '/')
181 reply_id) + '/')
183 self.assertEqual(PAGE_404,
182 self.assertEqual(PAGE_404,
@@ -75,7 +75,10 b" urlpatterns = patterns('',"
75 name='get_thread'),
75 name='get_thread'),
76 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
76 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post,
77 name='add_post'),
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 name='get_tag_popularity'),
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 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 import hashlib
4 from django.utils import timezone
5 from django.utils import timezone
5
6
6 from neboard import settings
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 import time
11 import time
12 import neboard
8
13
9
14
10 KEY_CAPTCHA_FAILS = 'key_captcha_fails'
15 KEY_CAPTCHA_FAILS = 'key_captcha_fails'
@@ -77,4 +82,48 b' def get_client_ip(request):'
77 def datetime_to_epoch(datetime):
82 def datetime_to_epoch(datetime):
78 return int(time.mktime(timezone.localtime(
83 return int(time.mktime(timezone.localtime(
79 datetime,timezone.get_current_timezone()).timetuple())
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 from boards.views.base import BaseBoardView
3 from boards.views.base import BaseBoardView
4 from boards.models.tag import Tag
4 from boards.models.tag import Tag
5
5
6
6 class AllTagsView(BaseBoardView):
7 class AllTagsView(BaseBoardView):
7
8
8 def get(self, request):
9 def get(self, request):
@@ -1,6 +1,5 b''
1 import string
1 import string
2
2
3 from django.core.urlresolvers import reverse
4 from django.db import transaction
3 from django.db import transaction
5 from django.shortcuts import render, redirect
4 from django.shortcuts import render, redirect
6
5
@@ -30,7 +29,7 b' class AllThreadsView(PostMixin, BaseBoar'
30 def get(self, request, page=DEFAULT_PAGE, form=None):
29 def get(self, request, page=DEFAULT_PAGE, form=None):
31 context = self.get_context_data(request=request)
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 if not form:
34 if not form:
36 form = ThreadForm(error_class=PlainErrorList)
35 form = ThreadForm(error_class=PlainErrorList)
@@ -70,7 +69,8 b' class AllThreadsView(PostMixin, BaseBoar'
70 context[PARAMETER_PAGINATOR] = paginator
69 context[PARAMETER_PAGINATOR] = paginator
71 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
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 Parses tag list string and returns tag object list.
75 Parses tag list string and returns tag object list.
76 """
76 """
@@ -120,7 +120,7 b' class AllThreadsView(PostMixin, BaseBoar'
120
120
121 post = Post.objects.create_post(title=title, text=text, ip=ip,
121 post = Post.objects.create_post(title=title, text=text, ip=ip,
122 image=image, tags=tags,
122 image=image, tags=tags,
123 user=self._get_user(request))
123 user=utils.get_user(request))
124
124
125 if html_response:
125 if html_response:
126 return redirect(post.get_url())
126 return redirect(post.get_url())
@@ -37,9 +37,6 b' def api_get_threaddiff(request, thread_i'
37
37
38 thread = get_object_or_404(Post, id=thread_id).get_thread()
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 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
40 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000000,
44 timezone.get_current_timezone())
41 timezone.get_current_timezone())
45
42
@@ -90,7 +87,7 b' def api_add_post(request, opening_post_i'
90 status = STATUS_ERROR
87 status = STATUS_ERROR
91 if form.is_valid():
88 if form.is_valid():
92 post = ThreadView().new_post(request, form, opening_post,
89 post = ThreadView().new_post(request, form, opening_post,
93 html_response=False)
90 html_response=False)
94 if not post:
91 if not post:
95 status = STATUS_ERROR
92 status = STATUS_ERROR
96 else:
93 else:
@@ -156,7 +153,7 b' def api_get_threads(request, count):'
156
153
157 # TODO Add tags, replies and images count
154 # TODO Add tags, replies and images count
158 opening_posts.append(_get_post_data(opening_post.id,
155 opening_posts.append(_get_post_data(opening_post.id,
159 include_last_update=True))
156 include_last_update=True))
160
157
161 return HttpResponse(content=json.dumps(opening_posts))
158 return HttpResponse(content=json.dumps(opening_posts))
162
159
@@ -239,10 +236,11 b' def _get_post_data(post_id, format_type='
239 'title': post.title,
236 'title': post.title,
240 'text': post.text.rendered,
237 'text': post.text.rendered,
241 }
238 }
242 if post.image:
239 if post.images.exists():
243 post_json['image'] = post.image.url
240 post_image = post.get_first_image()
244 post_json['image_preview'] = post.image.url_200x150
241 post_json['image'] = post_image.image.url
242 post_json['image_preview'] = post_image.image.url_200x150
245 if include_last_update:
243 if include_last_update:
246 post_json['bump_time'] = datetime_to_epoch(
244 post_json['bump_time'] = datetime_to_epoch(
247 post.thread_new.bump_time)
245 post.thread_new.bump_time)
248 return post_json
246 return post_json
@@ -1,5 +1,6 b''
1 from django.db import transaction
1 from django.db import transaction
2 from django.shortcuts import get_object_or_404
2 from django.shortcuts import get_object_or_404
3 from boards import utils
3
4
4 from boards.views.base import BaseBoardView
5 from boards.views.base import BaseBoardView
5 from boards.models import Post, Ban
6 from boards.models import Post, Ban
@@ -10,7 +11,7 b' class BanUserView(BaseBoardView, Redirec'
10
11
11 @transaction.atomic
12 @transaction.atomic
12 def get(self, request, post_id):
13 def get(self, request, post_id):
13 user = self._get_user(request)
14 user = utils.get_user(request)
14 post = get_object_or_404(Post, id=post_id)
15 post = get_object_or_404(Post, id=post_id)
15
16
16 if user.is_moderator():
17 if user.is_moderator():
@@ -1,20 +1,13 b''
1 from datetime import datetime, timedelta
2 import hashlib
3 from django.db import transaction
1 from django.db import transaction
4 from django.db.models import Count
5 from django.template import RequestContext
2 from django.template import RequestContext
6 from django.utils import timezone
7 from django.views.generic import View
3 from django.views.generic import View
4
8 from boards import utils
5 from boards import utils
9 from boards.models import User, Post
6 from boards.models.user import Ban
10 from boards.models.post import SETTING_MODERATE
7
11 from boards.models.user import RANK_USER, Ban
12 import neboard
13
8
14 BAN_REASON_SPAM = 'Autoban: spam bot'
9 BAN_REASON_SPAM = 'Autoban: spam bot'
15
10
16 OLD_USER_AGE_DAYS = 90
17
18 PARAMETER_FORM = 'form'
11 PARAMETER_FORM = 'form'
19
12
20
13
@@ -22,96 +15,11 b' class BaseBoardView(View):'
22
15
23 def get_context_data(self, **kwargs):
16 def get_context_data(self, **kwargs):
24 request = kwargs['request']
17 request = kwargs['request']
25 context = self._default_context(request)
18 # 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
35 context = RequestContext(request)
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 return context
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 @transaction.atomic
23 @transaction.atomic
116 def _ban_current_user(self, request):
24 def _ban_current_user(self, request):
117 """
25 """
@@ -1,5 +1,6 b''
1 from django.shortcuts import redirect, get_object_or_404
1 from django.shortcuts import redirect, get_object_or_404
2 from django.db import transaction
2 from django.db import transaction
3 from boards import utils
3
4
4 from boards.views.base import BaseBoardView
5 from boards.views.base import BaseBoardView
5 from boards.views.mixins import RedirectNextMixin
6 from boards.views.mixins import RedirectNextMixin
@@ -10,7 +11,7 b' class DeletePostView(BaseBoardView, Redi'
10
11
11 @transaction.atomic
12 @transaction.atomic
12 def get(self, request, post_id):
13 def get(self, request, post_id):
13 user = self._get_user(request)
14 user = utils.get_user(request)
14 post = get_object_or_404(Post, id=post_id)
15 post = get_object_or_404(Post, id=post_id)
15
16
16 opening_post = post.is_opening()
17 opening_post = post.is_opening()
@@ -9,7 +9,7 b' from boards.forms import AddTagForm, Pla'
9 class PostAdminView(BaseBoardView, DispatcherMixin):
9 class PostAdminView(BaseBoardView, DispatcherMixin):
10
10
11 def get(self, request, post_id, form=None):
11 def get(self, request, post_id, form=None):
12 user = self._get_user(request)
12 user = utils.get_user(request)
13 if not user.is_moderator:
13 if not user.is_moderator:
14 redirect('index')
14 redirect('index')
15
15
@@ -30,7 +30,7 b' class PostAdminView(BaseBoardView, Dispa'
30 return render(request, 'boards/post_admin.html', context)
30 return render(request, 'boards/post_admin.html', context)
31
31
32 def post(self, request, post_id):
32 def post(self, request, post_id):
33 user = self._get_user(request)
33 user = utils.get_user(request)
34 if not user.is_moderator:
34 if not user.is_moderator:
35 redirect('index')
35 redirect('index')
36
36
@@ -1,5 +1,6 b''
1 from django.db import transaction
1 from django.db import transaction
2 from django.shortcuts import render, redirect
2 from django.shortcuts import render, redirect
3 from boards import utils
3
4
4 from boards.views.base import BaseBoardView, PARAMETER_FORM
5 from boards.views.base import BaseBoardView, PARAMETER_FORM
5 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
6 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
@@ -10,15 +11,16 b' class SettingsView(BaseBoardView):'
10
11
11 def get(self, request):
12 def get(self, request):
12 context = self.get_context_data(request=request)
13 context = self.get_context_data(request=request)
13 user = context['user']
14 user = utils.get_user(request)
14 is_moderator = user.is_moderator()
15 is_moderator = user.is_moderator()
15
16
16 selected_theme = context['theme']
17 selected_theme = utils.get_theme(request, user)
17
18
18 if is_moderator:
19 if is_moderator:
19 form = ModeratorSettingsForm(initial={
20 form = ModeratorSettingsForm(initial={
20 'theme': selected_theme,
21 'theme': selected_theme,
21 'moderate': context['moderator']
22 'moderate': user.get_setting(SETTING_MODERATE) and \
23 user.is_moderator()
22 }, error_class=PlainErrorList)
24 }, error_class=PlainErrorList)
23 else:
25 else:
24 form = SettingsForm(initial={'theme': selected_theme},
26 form = SettingsForm(initial={'theme': selected_theme},
@@ -29,8 +31,7 b' class SettingsView(BaseBoardView):'
29 return render(request, 'boards/settings.html', context)
31 return render(request, 'boards/settings.html', context)
30
32
31 def post(self, request):
33 def post(self, request):
32 context = self.get_context_data(request=request)
34 user = utils.get_user(request)
33 user = context['user']
34 is_moderator = user.is_moderator()
35 is_moderator = user.is_moderator()
35
36
36 with transaction.atomic():
37 with transaction.atomic():
@@ -2,6 +2,7 b' from django.shortcuts import render'
2
2
3 from boards.views.base import BaseBoardView
3 from boards.views.base import BaseBoardView
4
4
5
5 class StaticPageView(BaseBoardView):
6 class StaticPageView(BaseBoardView):
6
7
7 def get(self, request, name):
8 def get(self, request, name):
@@ -1,4 +1,5 b''
1 from django.shortcuts import get_object_or_404
1 from django.shortcuts import get_object_or_404
2 from boards import utils
2 from boards.models import Tag, Post
3 from boards.models import Tag, Post
3 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
4 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
4 from boards.views.mixins import DispatcherMixin, RedirectNextMixin
5 from boards.views.mixins import DispatcherMixin, RedirectNextMixin
@@ -47,7 +48,7 b' class TagView(AllThreadsView, Dispatcher'
47 return self.get(request, tag_name, page, form)
48 return self.get(request, tag_name, page, form)
48
49
49 def subscribe(self, request):
50 def subscribe(self, request):
50 user = self._get_user(request)
51 user = utils.get_user(request)
51 tag = get_object_or_404(Tag, name=self.tag_name)
52 tag = get_object_or_404(Tag, name=self.tag_name)
52
53
53 if not tag in user.fav_tags.all():
54 if not tag in user.fav_tags.all():
@@ -56,7 +57,7 b' class TagView(AllThreadsView, Dispatcher'
56 return self.redirect_to_next(request)
57 return self.redirect_to_next(request)
57
58
58 def unsubscribe(self, request):
59 def unsubscribe(self, request):
59 user = self._get_user(request)
60 user = utils.get_user(request)
60 tag = get_object_or_404(Tag, name=self.tag_name)
61 tag = get_object_or_404(Tag, name=self.tag_name)
61
62
62 if tag in user.fav_tags.all():
63 if tag in user.fav_tags.all():
@@ -70,7 +71,7 b' class TagView(AllThreadsView, Dispatcher'
70 shown.
71 shown.
71 """
72 """
72
73
73 user = self._get_user(request)
74 user = utils.get_user(request)
74 tag = get_object_or_404(Tag, name=self.tag_name)
75 tag = get_object_or_404(Tag, name=self.tag_name)
75
76
76 user.hide_tag(tag)
77 user.hide_tag(tag)
@@ -80,7 +81,7 b' class TagView(AllThreadsView, Dispatcher'
80 Removed tag from user's hidden tags.
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 tag = get_object_or_404(Tag, name=self.tag_name)
85 tag = get_object_or_404(Tag, name=self.tag_name)
85
86
86 user.unhide_tag(tag)
87 user.unhide_tag(tag)
@@ -1,17 +1,18 b''
1 import string
2 from django.core.urlresolvers import reverse
1 from django.core.urlresolvers import reverse
3 from django.db import transaction
2 from django.db import transaction
4 from django.http import Http404
3 from django.http import Http404
5 from django.shortcuts import get_object_or_404, render, redirect
4 from django.shortcuts import get_object_or_404, render, redirect
6 from django.views.generic.edit import FormMixin
5 from django.views.generic.edit import FormMixin
6
7 from boards import utils
7 from boards import utils
8 from boards.forms import PostForm, PlainErrorList
8 from boards.forms import PostForm, PlainErrorList
9 from boards.models import Post, Ban, Tag
9 from boards.models import Post, Ban
10 from boards.views.banned import BannedView
10 from boards.views.banned import BannedView
11 from boards.views.base import BaseBoardView, PARAMETER_FORM
11 from boards.views.base import BaseBoardView, PARAMETER_FORM
12 from boards.views.posting_mixin import PostMixin
12 from boards.views.posting_mixin import PostMixin
13 import neboard
13 import neboard
14
14
15
15 MODE_GALLERY = 'gallery'
16 MODE_GALLERY = 'gallery'
16 MODE_NORMAL = 'normal'
17 MODE_NORMAL = 'normal'
17
18
@@ -23,7 +24,10 b" PARAMETER_BUMPABLE = 'bumpable'"
23 class ThreadView(BaseBoardView, PostMixin, FormMixin):
24 class ThreadView(BaseBoardView, PostMixin, FormMixin):
24
25
25 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
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 # If this is not OP, don't show it as it is
32 # If this is not OP, don't show it as it is
29 if not opening_post or not opening_post.is_opening():
33 if not opening_post or not opening_post.is_opening():
@@ -55,8 +59,8 b' class ThreadView(BaseBoardView, PostMixi'
55
59
56 document = 'boards/thread.html'
60 document = 'boards/thread.html'
57 elif MODE_GALLERY == mode:
61 elif MODE_GALLERY == mode:
58 posts = thread_to_show.get_replies(view_fields_only=True)
62 context['posts'] = thread_to_show.get_replies_with_images(
59 context['posts'] = posts.filter(image_width__gt=0)
63 view_fields_only=True)
60
64
61 document = 'boards/thread_gallery.html'
65 document = 'boards/thread_gallery.html'
62 else:
66 else:
@@ -116,7 +120,7 b' class ThreadView(BaseBoardView, PostMixi'
116 post = Post.objects.create_post(title=title, text=text, ip=ip,
120 post = Post.objects.create_post(title=title, text=text, ip=ip,
117 thread=post_thread, image=image,
121 thread=post_thread, image=image,
118 tags=tags,
122 tags=tags,
119 user=self._get_user(request))
123 user=utils.get_user(request))
120
124
121 thread_to_show = (opening_post.id if opening_post else post.id)
125 thread_to_show = (opening_post.id if opening_post else post.id)
122
126
@@ -26,3 +26,12 b' thread.'
26 * Update last update time with thread update
26 * Update last update time with thread update
27 * Added z-index to the images to move the dragged image to front
27 * Added z-index to the images to move the dragged image to front
28 * [CODE] Major view refactoring. Now almost all views are class-based
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 state the exclusion of warranty; and each file should have at least
631 state the exclusion of warranty; and each file should have at least
632 the "copyright" line and a pointer to where the full notice is found.
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.>
634 neboard - a free imageboard
635 Copyright (C) <year> <name of author>
635 Copyright (C) 2013-2014 nekorin
636
636
637 This program is free software: you can redistribute it and/or modify
637 This program is free software: you can redistribute it and/or modify
638 it under the terms of the GNU General Public License as published by
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 If the program does terminal interaction, make it output a short
652 If the program does terminal interaction, make it output a short
653 notice like this when it starts in an interactive mode:
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 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
656 This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 This is free software, and you are welcome to redistribute it
657 This is free software, and you are welcome to redistribute it
658 under certain conditions; type `show c' for details.
658 under certain conditions; type `show c' for details.
@@ -105,6 +105,7 b' TEMPLATE_CONTEXT_PROCESSORS = ('
105 'django.core.context_processors.static',
105 'django.core.context_processors.static',
106 'django.core.context_processors.request',
106 'django.core.context_processors.request',
107 'django.contrib.auth.context_processors.auth',
107 'django.contrib.auth.context_processors.auth',
108 'boards.context_processors.user_and_ui_processor',
108 )
109 )
109
110
110 MIDDLEWARE_CLASSES = (
111 MIDDLEWARE_CLASSES = (
@@ -137,15 +138,22 b' INSTALLED_APPS = ('
137 'django.contrib.messages',
138 'django.contrib.messages',
138 'django.contrib.staticfiles',
139 'django.contrib.staticfiles',
139 # Uncomment the next line to enable the admin:
140 # Uncomment the next line to enable the admin:
140 'django.contrib.admin',
141 'django.contrib.admin',
141 # Uncomment the next line to enable admin documentation:
142 # Uncomment the next line to enable admin documentation:
142 # 'django.contrib.admindocs',
143 # 'django.contrib.admindocs',
143 'django.contrib.humanize',
144 'django.contrib.humanize',
144 'django_cleanup',
145 'django_cleanup',
145 'boards',
146
146 'captcha',
147 # Migrations
147 'south',
148 'south',
148 'debug_toolbar',
149 'debug_toolbar',
150
151 'captcha',
152
153 # Search
154 'haystack',
155
156 'boards',
149 )
157 )
150
158
151 DEBUG_TOOLBAR_PANELS = (
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 MARKUP_FIELD_TYPES = (
221 MARKUP_FIELD_TYPES = (
205 ('markdown', markdown_extended),
222 ('markdown', markdown_extended),
206 )
223 )
@@ -230,7 +247,7 b' POSTING_DELAY = 20 # seconds'
230
247
231 COMPRESS_HTML = True
248 COMPRESS_HTML = True
232
249
233 VERSION = '1.7.4 Anubis'
250 VERSION = '1.8.0 Kara'
234
251
235 # Debug mode middlewares
252 # Debug mode middlewares
236 if DEBUG:
253 if DEBUG:
@@ -1,3 +1,4 b''
1 haystack
1 pillow
2 pillow
2 django>=1.6
3 django>=1.6
3 django_cleanup
4 django_cleanup
General Comments 0
You need to be logged in to leave comments. Login now