##// END OF EJS Templates
Merged in 2.0 branch
neko259 -
r747:668c0b11 merge default
parent child Browse files
Show More
@@ -0,0 +1,143 b''
1 from django.shortcuts import get_object_or_404
2 from boards.models import Tag
3
4 __author__ = 'neko259'
5
6 SESSION_SETTING = 'setting'
7
8 PERMISSION_MODERATE = 'moderator'
9
10 SETTING_THEME = 'theme'
11 SETTING_FAVORITE_TAGS = 'favorite_tags'
12 SETTING_HIDDEN_TAGS = 'hidden_tags'
13 SETTING_PERMISSIONS = 'permissions'
14
15 DEFAULT_THEME = 'md'
16
17
18 def get_settings_manager(request):
19 """
20 Get settings manager based on the request object. Currently only
21 session-based manager is supported. In the future, cookie-based or
22 database-based managers could be implemented.
23 """
24 return SessionSettingsManager(request.session)
25
26
27 class SettingsManager:
28 """
29 Base settings manager class. get_setting and set_setting methods should
30 be overriden.
31 """
32 def __init__(self):
33 pass
34
35 def get_theme(self):
36 theme = self.get_setting(SETTING_THEME)
37 if not theme:
38 theme = DEFAULT_THEME
39 self.set_setting(SETTING_THEME, theme)
40
41 return theme
42
43 def set_theme(self, theme):
44 self.set_setting(SETTING_THEME, theme)
45
46 def has_permission(self, permission):
47 permissions = self.get_setting(SETTING_PERMISSIONS)
48 if permissions:
49 return permission in permissions
50 else:
51 return False
52
53 def get_setting(self, setting):
54 pass
55
56 def set_setting(self, setting, value):
57 pass
58
59 def add_permission(self, permission):
60 permissions = self.get_setting(SETTING_PERMISSIONS)
61 if not permissions:
62 permissions = [permission]
63 else:
64 permissions.append(permission)
65 self.set_setting(SETTING_PERMISSIONS, permissions)
66
67 def del_permission(self, permission):
68 permissions = self.get_setting(SETTING_PERMISSIONS)
69 if not permissions:
70 permissions = []
71 else:
72 permissions.remove(permission)
73 self.set_setting(SETTING_PERMISSIONS, permissions)
74
75 def get_fav_tags(self):
76 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
77 tags = []
78 if tag_names:
79 for tag_name in tag_names:
80 tag = get_object_or_404(Tag, name=tag_name)
81 tags.append(tag)
82
83 return tags
84
85 def add_fav_tag(self, tag):
86 tags = self.get_setting(SETTING_FAVORITE_TAGS)
87 if not tags:
88 tags = [tag.name]
89 else:
90 if not tag.name in tags:
91 tags.append(tag.name)
92 self.set_setting(SETTING_FAVORITE_TAGS, tags)
93
94 def del_fav_tag(self, tag):
95 tags = self.get_setting(SETTING_FAVORITE_TAGS)
96 if tag.name in tags:
97 tags.remove(tag.name)
98 self.set_setting(SETTING_FAVORITE_TAGS, tags)
99
100 def get_hidden_tags(self):
101 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
102 tags = []
103 if tag_names:
104 for tag_name in tag_names:
105 tag = get_object_or_404(Tag, name=tag_name)
106 tags.append(tag)
107
108 return tags
109
110 def add_hidden_tag(self, tag):
111 tags = self.get_setting(SETTING_HIDDEN_TAGS)
112 if not tags:
113 tags = [tag.name]
114 else:
115 if not tag.name in tags:
116 tags.append(tag.name)
117 self.set_setting(SETTING_HIDDEN_TAGS, tags)
118
119 def del_hidden_tag(self, tag):
120 tags = self.get_setting(SETTING_HIDDEN_TAGS)
121 if tag.name in tags:
122 tags.remove(tag.name)
123 self.set_setting(SETTING_HIDDEN_TAGS, tags)
124
125
126 class SessionSettingsManager(SettingsManager):
127 """
128 Session-based settings manager. All settings are saved to the user's
129 session.
130 """
131 def __init__(self, session):
132 SettingsManager.__init__(self)
133 self.session = session
134
135 def get_setting(self, setting):
136 if setting in self.session:
137 return self.session[setting]
138 else:
139 return None
140
141 def set_setting(self, setting, value):
142 self.session[setting] = value
143
@@ -0,0 +1,146 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 model 'Setting'
12 db.delete_table(u'boards_setting')
13
14 # Deleting model 'User'
15 db.delete_table(u'boards_user')
16
17 # Removing M2M table for field hidden_threads on 'User'
18 db.delete_table(db.shorten_name(u'boards_user_hidden_threads'))
19
20 # Removing M2M table for field fav_threads on 'User'
21 db.delete_table(db.shorten_name(u'boards_user_fav_threads'))
22
23 # Removing M2M table for field fav_tags on 'User'
24 db.delete_table(db.shorten_name(u'boards_user_fav_tags'))
25
26 # Removing M2M table for field hidden_tags on 'User'
27 db.delete_table(db.shorten_name(u'boards_user_hidden_tags'))
28
29 # Deleting field 'Post.user'
30 db.delete_column(u'boards_post', 'user_id')
31
32
33 def backwards(self, orm):
34 # Adding model 'Setting'
35 db.create_table(u'boards_setting', (
36 ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['boards.User'])),
37 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
38 ('value', self.gf('django.db.models.fields.CharField')(max_length=50)),
39 ('name', self.gf('django.db.models.fields.CharField')(max_length=50)),
40 ))
41 db.send_create_signal('boards', ['Setting'])
42
43 # Adding model 'User'
44 db.create_table(u'boards_user', (
45 ('registration_time', self.gf('django.db.models.fields.DateTimeField')()),
46 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
47 ('user_id', self.gf('django.db.models.fields.CharField')(max_length=50)),
48 ('rank', self.gf('django.db.models.fields.IntegerField')()),
49 ))
50 db.send_create_signal('boards', ['User'])
51
52 # Adding M2M table for field hidden_threads on 'User'
53 m2m_table_name = db.shorten_name(u'boards_user_hidden_threads')
54 db.create_table(m2m_table_name, (
55 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
56 ('user', models.ForeignKey(orm['boards.user'], null=False)),
57 ('post', models.ForeignKey(orm['boards.post'], null=False))
58 ))
59 db.create_unique(m2m_table_name, ['user_id', 'post_id'])
60
61 # Adding M2M table for field fav_threads on 'User'
62 m2m_table_name = db.shorten_name(u'boards_user_fav_threads')
63 db.create_table(m2m_table_name, (
64 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
65 ('user', models.ForeignKey(orm['boards.user'], null=False)),
66 ('post', models.ForeignKey(orm['boards.post'], null=False))
67 ))
68 db.create_unique(m2m_table_name, ['user_id', 'post_id'])
69
70 # Adding M2M table for field fav_tags on 'User'
71 m2m_table_name = db.shorten_name(u'boards_user_fav_tags')
72 db.create_table(m2m_table_name, (
73 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
74 ('user', models.ForeignKey(orm['boards.user'], null=False)),
75 ('tag', models.ForeignKey(orm['boards.tag'], null=False))
76 ))
77 db.create_unique(m2m_table_name, ['user_id', 'tag_id'])
78
79 # Adding M2M table for field hidden_tags on 'User'
80 m2m_table_name = db.shorten_name(u'boards_user_hidden_tags')
81 db.create_table(m2m_table_name, (
82 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
83 ('user', models.ForeignKey(orm['boards.user'], null=False)),
84 ('tag', models.ForeignKey(orm['boards.tag'], null=False))
85 ))
86 db.create_unique(m2m_table_name, ['user_id', 'tag_id'])
87
88 # Adding field 'Post.user'
89 db.add_column(u'boards_post', 'user',
90 self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['boards.User'], null=True),
91 keep_default=False)
92
93
94 models = {
95 'boards.ban': {
96 'Meta': {'object_name': 'Ban'},
97 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
98 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
99 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
100 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
101 },
102 'boards.post': {
103 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
104 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
105 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
106 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
107 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
108 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
109 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
110 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
111 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
112 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
113 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
114 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
115 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
116 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
117 },
118 'boards.postimage': {
119 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
120 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
121 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
122 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
123 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
124 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
125 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
126 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
127 },
128 'boards.tag': {
129 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
130 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
131 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
132 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
133 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
134 },
135 'boards.thread': {
136 'Meta': {'object_name': 'Thread'},
137 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
138 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
139 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
140 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
141 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
142 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
143 }
144 }
145
146 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,73 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 'Tag.linked'
12 db.delete_column(u'boards_tag', 'linked_id')
13
14
15 def backwards(self, orm):
16 # Adding field 'Tag.linked'
17 db.add_column(u'boards_tag', 'linked',
18 self.gf('django.db.models.fields.related.ForeignKey')(to=orm['boards.Tag'], null=True, blank=True),
19 keep_default=False)
20
21
22 models = {
23 'boards.ban': {
24 'Meta': {'object_name': 'Ban'},
25 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
26 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
27 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
28 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
29 },
30 'boards.post': {
31 'Meta': {'ordering': "('id',)", 'object_name': 'Post'},
32 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'images': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ip+'", 'to': "orm['boards.PostImage']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
35 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
36 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
37 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
38 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
39 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'rfp+'", 'to': "orm['boards.Post']", 'blank': 'True', 'symmetrical': 'False', 'null': 'True', 'db_index': 'True'}),
40 'refmap': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
41 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
42 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'bbcode'", 'max_length': '30'}),
43 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
44 'title': ('django.db.models.fields.CharField', [], {'max_length': '200'})
45 },
46 'boards.postimage': {
47 'Meta': {'ordering': "('id',)", 'object_name': 'PostImage'},
48 'hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
49 'height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
50 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
51 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
52 'pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
53 'pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
54 'width': ('django.db.models.fields.IntegerField', [], {'default': '0'})
55 },
56 'boards.tag': {
57 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
58 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'db_index': 'True'}),
60 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
61 },
62 'boards.thread': {
63 'Meta': {'object_name': 'Thread'},
64 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
65 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
66 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
67 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
68 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
69 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
70 }
71 }
72
73 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,24 b''
1 from django.shortcuts import render
2
3 from boards.abstracts.settingsmanager import PERMISSION_MODERATE,\
4 get_settings_manager
5 from boards.forms import LoginForm
6 from boards.views.base import BaseBoardView, CONTEXT_FORM
7
8
9 __author__ = 'neko259'
10
11
12 class LogoutView(BaseBoardView):
13
14 def get(self, request, form=None):
15 settings_manager = get_settings_manager(request)
16 settings_manager.del_permission(PERMISSION_MODERATE)
17
18 context = self.get_context_data(request=request)
19
20 if not form:
21 form = LoginForm()
22 context[CONTEXT_FORM] = form
23
24 return render(request, 'boards/login.html', context) No newline at end of file
@@ -1,5 +1,5 b''
1 from django.contrib import admin
1 from django.contrib import admin
2 from boards.models import Post, Tag, User, Ban, Thread
2 from boards.models import Post, Tag, Ban, Thread
3
3
4
4
5 class PostAdmin(admin.ModelAdmin):
5 class PostAdmin(admin.ModelAdmin):
@@ -11,15 +11,7 b' class PostAdmin(admin.ModelAdmin):'
11
11
12 class TagAdmin(admin.ModelAdmin):
12 class TagAdmin(admin.ModelAdmin):
13
13
14 list_display = ('name', 'linked')
14 list_display = ('name',)
15 list_filter = ('linked',)
16
17
18 class UserAdmin(admin.ModelAdmin):
19
20 list_display = ('user_id', 'rank')
21 search_fields = ('user_id',)
22
23
15
24 class ThreadAdmin(admin.ModelAdmin):
16 class ThreadAdmin(admin.ModelAdmin):
25
17
@@ -35,6 +27,5 b' class ThreadAdmin(admin.ModelAdmin):'
35
27
36 admin.site.register(Post, PostAdmin)
28 admin.site.register(Post, PostAdmin)
37 admin.site.register(Tag, TagAdmin)
29 admin.site.register(Tag, TagAdmin)
38 admin.site.register(User, UserAdmin)
39 admin.site.register(Ban)
30 admin.site.register(Ban)
40 admin.site.register(Thread, ThreadAdmin)
31 admin.site.register(Thread, ThreadAdmin)
@@ -1,8 +1,10 b''
1 from boards.abstracts.settingsmanager import PERMISSION_MODERATE, \
2 get_settings_manager
3
1 __author__ = 'neko259'
4 __author__ = 'neko259'
2
5
3 from boards import utils, settings
6 from boards import settings
4 from boards.models import Post
7 from boards.models import Post
5 from boards.models.post import SETTING_MODERATE
6
8
7 CONTEXT_SITE_NAME = 'site_name'
9 CONTEXT_SITE_NAME = 'site_name'
8 CONTEXT_VERSION = 'version'
10 CONTEXT_VERSION = 'version'
@@ -17,21 +19,17 b" CONTEXT_USER = 'user'"
17 def user_and_ui_processor(request):
19 def user_and_ui_processor(request):
18 context = {}
20 context = {}
19
21
20 user = utils.get_user(request)
21 context[CONTEXT_USER] = user
22 context[CONTEXT_TAGS] = user.fav_tags.all()
23 context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day())
22 context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day())
24
23
25 theme = utils.get_theme(request, user)
24 settings_manager = get_settings_manager(request)
25 context[CONTEXT_TAGS] = settings_manager.get_fav_tags()
26 theme = settings_manager.get_theme()
26 context[CONTEXT_THEME] = theme
27 context[CONTEXT_THEME] = theme
27 context[CONTEXT_THEME_CSS] = 'css/' + theme + '/base_page.css'
28 context[CONTEXT_THEME_CSS] = 'css/' + theme + '/base_page.css'
28
29
29 # This shows the moderator panel
30 # This shows the moderator panel
30 moderate = user.get_setting(SETTING_MODERATE)
31 moderate = settings_manager.has_permission(PERMISSION_MODERATE)
31 if moderate == 'True':
32 context[CONTEXT_MODERATOR] = moderate
32 context[CONTEXT_MODERATOR] = user.is_moderator()
33 else:
34 context[CONTEXT_MODERATOR] = False
35
33
36 context[CONTEXT_VERSION] = settings.VERSION
34 context[CONTEXT_VERSION] = settings.VERSION
37 context[CONTEXT_SITE_NAME] = settings.SITE_NAME
35 context[CONTEXT_SITE_NAME] = settings.SITE_NAME
@@ -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, PostImage
12 from boards.models import 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
@@ -20,8 +20,7 b" ATTRIBUTE_PLACEHOLDER = 'placeholder'"
20
20
21 LAST_POST_TIME = 'last_post_time'
21 LAST_POST_TIME = 'last_post_time'
22 LAST_LOGIN_TIME = 'last_login_time'
22 LAST_LOGIN_TIME = 'last_login_time'
23 TEXT_PLACEHOLDER = _('''Type message here. You can reply to message >>123 like
23 TEXT_PLACEHOLDER = _('''Type message here. Use formatting panel for more advanced usage.''')
24 this. 2 new lines are required to start new paragraph.''')
25 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
24 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
26
25
27 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
26 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
@@ -187,10 +186,6 b' class PostForm(NeboardForm):'
187 if not 'user_id' in self.session:
186 if not 'user_id' in self.session:
188 return
187 return
189
188
190 user = User.objects.get(id=self.session['user_id'])
191 if user.is_veteran():
192 posting_delay = VETERAN_POSTING_DELAY
193 else:
194 posting_delay = settings.POSTING_DELAY
189 posting_delay = settings.POSTING_DELAY
195
190
196 if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
191 if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
@@ -282,57 +277,6 b' class SettingsForm(NeboardForm):'
282 label=_('Theme'))
277 label=_('Theme'))
283
278
284
279
285 class ModeratorSettingsForm(SettingsForm):
286
287 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
288 'panel'))
289
290
291 class LoginForm(NeboardForm):
292
293 user_id = forms.CharField()
294
295 session = None
296
297 def clean_user_id(self):
298 user_id = self.cleaned_data['user_id']
299 if user_id:
300 users = User.objects.filter(user_id=user_id)
301 if len(users) == 0:
302 raise forms.ValidationError(_('No such user found'))
303
304 return user_id
305
306 def _validate_login_speed(self):
307 can_post = True
308
309 if LAST_LOGIN_TIME in self.session:
310 now = time.time()
311 last_login_time = self.session[LAST_LOGIN_TIME]
312
313 current_delay = int(now - last_login_time)
314
315 if current_delay < board_settings.LOGIN_TIMEOUT:
316 error_message = _('Wait %s minutes after last login') % str(
317 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
318 self._errors['user_id'] = self.error_class([error_message])
319
320 can_post = False
321
322 if can_post:
323 self.session[LAST_LOGIN_TIME] = time.time()
324
325 def clean(self):
326 if not self.session:
327 raise forms.ValidationError('Humans have sessions')
328
329 self._validate_login_speed()
330
331 cleaned_data = super(LoginForm, self).clean()
332
333 return cleaned_data
334
335
336 class AddTagForm(NeboardForm):
280 class AddTagForm(NeboardForm):
337
281
338 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
282 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
@@ -354,4 +298,44 b' class AddTagForm(NeboardForm):'
354
298
355
299
356 class SearchForm(NeboardForm):
300 class SearchForm(NeboardForm):
357 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False) No newline at end of file
301 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
302
303
304 class LoginForm(NeboardForm):
305
306 password = forms.CharField()
307
308 session = None
309
310 def clean_password(self):
311 password = self.cleaned_data['password']
312 if board_settings.MASTER_PASSWORD != password:
313 raise forms.ValidationError(_('Invalid master password'))
314
315 return password
316
317 def _validate_login_speed(self):
318 can_post = True
319
320 if LAST_LOGIN_TIME in self.session:
321 now = time.time()
322 last_login_time = self.session[LAST_LOGIN_TIME]
323
324 current_delay = int(now - last_login_time)
325
326 if current_delay < board_settings.LOGIN_TIMEOUT:
327 error_message = _('Wait %s minutes after last login') % str(
328 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
329 self._errors['password'] = self.error_class([error_message])
330
331 can_post = False
332
333 if can_post:
334 self.session[LAST_LOGIN_TIME] = time.time()
335
336 def clean(self):
337 self._validate_login_speed()
338
339 cleaned_data = super(LoginForm, self).clean()
340
341 return cleaned_data
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-06-29 13:46+0300\n"
10 "POT-Creation-Date: 2014-07-20 20:11+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"
@@ -18,56 +18,53 b' msgstr ""'
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
20
21 #: authors.py:5
21 #: authors.py:9
22 msgid "author"
22 msgid "author"
23 msgstr "автор"
23 msgstr "автор"
24
24
25 #: authors.py:6
25 #: authors.py:10
26 msgid "developer"
26 msgid "developer"
27 msgstr "разработчик"
27 msgstr "разработчик"
28
28
29 #: authors.py:7
29 #: authors.py:11
30 msgid "javascript developer"
30 msgid "javascript developer"
31 msgstr "разработчик javascript"
31 msgstr "разработчик javascript"
32
32
33 #: authors.py:8
33 #: authors.py:12
34 msgid "designer"
34 msgid "designer"
35 msgstr "дизайнер"
35 msgstr "дизайнер"
36
36
37 #: forms.py:23
37 #: forms.py:23
38 msgid ""
38 msgid "Type message here. Use formatting panel for more advanced usage."
39 "Type message here. You can reply to message >>123 like\n"
40 " this. 2 new lines are required to start new paragraph."
41 msgstr ""
39 msgstr ""
42 "Введите сообщение здесь. Вы можете ответить на сообщение >>123 вот так. 2 "
40 "Вводите сообщение сюда. Используйте панель для более сложного форматирования."
43 "переноса строки обязательны для создания нового абзаца."
44
41
45 #: forms.py:25
42 #: forms.py:24
46 msgid "tag1 several_words_tag"
43 msgid "tag1 several_words_tag"
47 msgstr "тег1 тег_из_нескольких_слов"
44 msgstr "тег1 тег_из_нескольких_слов"
48
45
49 #: forms.py:27
46 #: forms.py:26
50 msgid "Such image was already posted"
47 msgid "Such image was already posted"
51 msgstr "Такое изображение уже было загружено"
48 msgstr "Такое изображение уже было загружено"
52
49
53 #: forms.py:29
50 #: forms.py:28
54 msgid "Title"
51 msgid "Title"
55 msgstr "Заголовок"
52 msgstr "Заголовок"
56
53
57 #: forms.py:30
54 #: forms.py:29
58 msgid "Text"
55 msgid "Text"
59 msgstr "Текст"
56 msgstr "Текст"
60
57
61 #: forms.py:31
58 #: forms.py:30
62 msgid "Tag"
59 msgid "Tag"
63 msgstr "Тег"
60 msgstr "Тег"
64
61
65 #: forms.py:32 templates/boards/base.html:50 templates/search/search.html:9
62 #: forms.py:31 templates/boards/base.html:54 templates/search/search.html:9
66 #: templates/search/search.html.py:13
63 #: templates/search/search.html.py:13
67 msgid "Search"
64 msgid "Search"
68 msgstr "Поиск"
65 msgstr "Поиск"
69
66
70 #: forms.py:109
67 #: forms.py:108
71 msgid "Image"
68 msgid "Image"
72 msgstr "Изображение"
69 msgstr "Изображение"
73
70
@@ -94,36 +91,32 b' msgstr "\xd0\x98\xd0\xb7\xd0\xbe\xd0\xb1\xd1\x80\xd0\xb0\xd0\xb6\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5 \xd0\xb4\xd0\xbe\xd0\xbb\xd0\xb6\xd0\xbd\xd0\xbe \xd0\xb1\xd1\x8b\xd1\x82\xd1\x8c \xd0\xbc\xd0\xb5\xd0\xbd\xd0\xb5\xd0\xb5 %s \xd0\xb1\xd0\xb0\xd0\xb9\xd1\x82"'
94 msgid "Either text or image must be entered."
91 msgid "Either text or image must be entered."
95 msgstr "Текст или картинка должны быть введены."
92 msgstr "Текст или картинка должны быть введены."
96
93
97 #: forms.py:202
94 #: forms.py:199
98 #, python-format
95 #, python-format
99 msgid "Wait %s seconds after last posting"
96 msgid "Wait %s seconds after last posting"
100 msgstr "Подождите %s секунд после последнего постинга"
97 msgstr "Подождите %s секунд после последнего постинга"
101
98
102 #: forms.py:218 templates/boards/tags.html:7 templates/boards/rss/post.html:10
99 #: forms.py:215 templates/boards/tags.html:7 templates/boards/rss/post.html:10
103 msgid "Tags"
100 msgid "Tags"
104 msgstr "Теги"
101 msgstr "Теги"
105
102
106 #: forms.py:225 forms.py:344
103 #: forms.py:222 forms.py:290
107 msgid "Inappropriate characters in tags."
104 msgid "Inappropriate characters in tags."
108 msgstr "Недопустимые символы в тегах."
105 msgstr "Недопустимые символы в тегах."
109
106
110 #: forms.py:253 forms.py:274
107 #: forms.py:250 forms.py:271
111 msgid "Captcha validation failed"
108 msgid "Captcha validation failed"
112 msgstr "Проверка капчи провалена"
109 msgstr "Проверка капчи провалена"
113
110
114 #: forms.py:280
111 #: forms.py:277
115 msgid "Theme"
112 msgid "Theme"
116 msgstr "Тема"
113 msgstr "Тема"
117
114
118 #: forms.py:285
115 #: forms.py:313
119 msgid "Enable moderation panel"
116 msgid "Invalid master password"
120 msgstr "Включить панель модерации"
117 msgstr "Неверный мастер-пароль"
121
118
122 #: forms.py:300
119 #: forms.py:327
123 msgid "No such user found"
124 msgstr "Данный пользователь не найден"
125
126 #: forms.py:314
127 #, python-format
120 #, python-format
128 msgid "Wait %s minutes after last login"
121 msgid "Wait %s minutes after last login"
129 msgstr "Подождите %s минут после последнего входа"
122 msgstr "Подождите %s минут после последнего входа"
@@ -168,17 +161,21 b' msgstr "\xd0\xa3\xd0\xbf\xd1\x80\xd0\xb0\xd0\xb2\xd0\xbb\xd0\xb5\xd0\xbd\xd0\xb8\xd0\xb5 \xd1\x82\xd0\xb5\xd0\xb3\xd0\xb0\xd0\xbc\xd0\xb8"'
168 msgid "Settings"
161 msgid "Settings"
169 msgstr "Настройки"
162 msgstr "Настройки"
170
163
171 #: templates/boards/base.html:49 templates/boards/login.html:6
164 #: templates/boards/base.html:50
165 msgid "Logout"
166 msgstr "Выход"
167
168 #: templates/boards/base.html:52 templates/boards/login.html:6
172 #: templates/boards/login.html.py:16
169 #: templates/boards/login.html.py:16
173 msgid "Login"
170 msgid "Login"
174 msgstr "Вход"
171 msgstr "Вход"
175
172
176 #: templates/boards/base.html:52
173 #: templates/boards/base.html:56
177 #, python-format
174 #, python-format
178 msgid "Speed: %(ppd)s posts per day"
175 msgid "Speed: %(ppd)s posts per day"
179 msgstr "Скорость: %(ppd)s сообщений в день"
176 msgstr "Скорость: %(ppd)s сообщений в день"
180
177
181 #: templates/boards/base.html:54
178 #: templates/boards/base.html:58
182 msgid "Up"
179 msgid "Up"
183 msgstr "Вверх"
180 msgstr "Вверх"
184
181
@@ -186,7 +183,7 b' msgstr "\xd0\x92\xd0\xb2\xd0\xb5\xd1\x80\xd1\x85"'
186 msgid "Insert your user id above"
183 msgid "Insert your user id above"
187 msgstr "Вставьте свой ID пользователя выше"
184 msgstr "Вставьте свой ID пользователя выше"
188
185
189 #: templates/boards/post.html:21 templates/boards/staticpages/help.html:19
186 #: templates/boards/post.html:21 templates/boards/staticpages/help.html:17
190 msgid "Quote"
187 msgid "Quote"
191 msgstr "Цитата"
188 msgstr "Цитата"
192
189
@@ -216,8 +213,8 b' msgstr "\xd0\x9e\xd1\x82\xd0\xb2\xd0\xb5\xd1\x82\xd1\x8b"'
216
213
217 #: templates/boards/post.html:86 templates/boards/thread.html:88
214 #: templates/boards/post.html:86 templates/boards/thread.html:88
218 #: templates/boards/thread_gallery.html:61
215 #: templates/boards/thread_gallery.html:61
219 msgid "replies"
216 msgid "messages"
220 msgstr "ответов"
217 msgstr "сообщений"
221
218
222 #: templates/boards/post.html:87 templates/boards/thread.html:89
219 #: templates/boards/post.html:87 templates/boards/thread.html:89
223 #: templates/boards/thread_gallery.html:62
220 #: templates/boards/thread_gallery.html:62
@@ -249,7 +246,7 b' msgstr "\xd0\x9f\xd1\x80\xd0\xb5\xd0\xb4\xd1\x8b\xd0\xb4\xd1\x83\xd1\x89\xd0\xb0\xd1\x8f \xd1\x81\xd1\x82\xd1\x80\xd0\xb0\xd0\xbd\xd0\xb8\xd1\x86\xd0\xb0"'
249 msgid "Skipped %(count)s replies. Open thread to see all replies."
246 msgid "Skipped %(count)s replies. Open thread to see all replies."
250 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
247 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
251
248
252 #: templates/boards/posting_general.html:121 templates/search/search.html:35
249 #: templates/boards/posting_general.html:121 templates/search/search.html:33
253 msgid "Next page"
250 msgid "Next page"
254 msgstr "Следующая страница"
251 msgstr "Следующая страница"
255
252
@@ -278,35 +275,19 b' msgstr "\xd0\xa1\xd0\xb8\xd0\xbd\xd1\x82\xd0\xb0\xd0\xba\xd1\x81\xd0\xb8\xd1\x81 \xd1\x82\xd0\xb5\xd0\xba\xd1\x81\xd1\x82\xd0\xb0"'
278 msgid "Pages:"
275 msgid "Pages:"
279 msgstr "Страницы: "
276 msgstr "Страницы: "
280
277
281 #: templates/boards/settings.html:14
278 #: templates/boards/settings.html:15
282 msgid "User:"
283 msgstr "Пользователь:"
284
285 #: templates/boards/settings.html:16
286 msgid "You are moderator."
279 msgid "You are moderator."
287 msgstr "Вы модератор."
280 msgstr "Вы модератор."
288
281
289 #: templates/boards/settings.html:19
282 #: templates/boards/settings.html:19
290 msgid "You are veteran."
291 msgstr "Вы ветеран."
292
293 #: templates/boards/settings.html:22
294 msgid "Posts:"
295 msgstr "Сообщений:"
296
297 #: templates/boards/settings.html:23
298 msgid "First access:"
299 msgstr "Первый доступ:"
300
301 #: templates/boards/settings.html:25
302 msgid "Last access:"
303 msgstr "Последний доступ: "
304
305 #: templates/boards/settings.html:29
306 msgid "Hidden tags:"
283 msgid "Hidden tags:"
307 msgstr "Скрытые теги:"
284 msgstr "Скрытые теги:"
308
285
309 #: templates/boards/settings.html:44
286 #: templates/boards/settings.html:26
287 msgid "No hidden tags."
288 msgstr "Нет скрытых тегов."
289
290 #: templates/boards/settings.html:35
310 msgid "Save"
291 msgid "Save"
311 msgstr "Сохранить"
292 msgstr "Сохранить"
312
293
@@ -356,37 +337,25 b' msgid "Syntax"'
356 msgstr "Синтаксис"
337 msgstr "Синтаксис"
357
338
358 #: templates/boards/staticpages/help.html:11
339 #: templates/boards/staticpages/help.html:11
359 msgid "2 line breaks for a new line."
360 msgstr "2 перевода строки создают новый абзац."
361
362 #: templates/boards/staticpages/help.html:12
363 msgid "Italic text"
340 msgid "Italic text"
364 msgstr "Курсивный текст"
341 msgstr "Курсивный текст"
365
342
366 #: templates/boards/staticpages/help.html:13
343 #: templates/boards/staticpages/help.html:12
367 msgid "Bold text"
344 msgid "Bold text"
368 msgstr "Полужирный текст"
345 msgstr "Полужирный текст"
369
346
370 #: templates/boards/staticpages/help.html:14
347 #: templates/boards/staticpages/help.html:13
371 msgid "Spoiler"
348 msgid "Spoiler"
372 msgstr "Спойлер"
349 msgstr "Спойлер"
373
350
374 #: templates/boards/staticpages/help.html:15
351 #: templates/boards/staticpages/help.html:14
375 msgid "Link to a post"
352 msgid "Link to a post"
376 msgstr "Ссылка на сообщение"
353 msgstr "Ссылка на сообщение"
377
354
378 #: templates/boards/staticpages/help.html:16
355 #: templates/boards/staticpages/help.html:15
379 msgid "Strikethrough text"
356 msgid "Strikethrough text"
380 msgstr "Зачеркнутый текст"
357 msgstr "Зачеркнутый текст"
381
358
382 #: templates/boards/staticpages/help.html:17
359 #: templates/boards/staticpages/help.html:16
383 msgid "You need to new line before:"
384 msgstr "Перед этими тегами нужна новая строка:"
385
386 #: templates/boards/staticpages/help.html:18
387 msgid "Comment"
360 msgid "Comment"
388 msgstr "Комментарий"
361 msgstr "Комментарий"
389
390 #: templates/search/search.html:30
391 msgid "No results found."
392 msgstr "Результаты не найдены."
@@ -13,4 +13,4 b' class Command(BaseCommand):'
13
13
14 @transaction.atomic
14 @transaction.atomic
15 def handle(self, *args, **options):
15 def handle(self, *args, **options):
16 Post.objects.all().update(poster_ip=NO_IP, user=None) No newline at end of file
16 Post.objects.all().update(poster_ip=NO_IP) No newline at end of file
@@ -1,8 +1,7 b''
1 # coding=utf-8
1 # coding=utf-8
2
2
3 import markdown
3 import re
4 from markdown.inlinepatterns import Pattern
4 import bbcode
5 from markdown.util import etree
6
5
7 import boards
6 import boards
8
7
@@ -10,13 +9,7 b' import boards'
10 __author__ = 'neko259'
9 __author__ = 'neko259'
11
10
12
11
13 AUTOLINK_PATTERN = r'(https?://\S+)'
12 REFLINK_PATTERN = re.compile(r'\d+')
14 QUOTE_PATTERN = r'^(?<!>)(>[^>].*)$'
15 REFLINK_PATTERN = r'((>>)(\d+))'
16 SPOILER_PATTERN = r'%%([^(%%)]+)%%'
17 COMMENT_PATTERN = r'^(//(.+))'
18 STRIKETHROUGH_PATTERN = r'~(.+)~'
19 DASH_PATTERN = r'--'
20
13
21
14
22 class TextFormatter():
15 class TextFormatter():
@@ -38,7 +31,7 b' class TextFormatter():'
38 format_right = ''
31 format_right = ''
39
32
40
33
41 class AutolinkPattern(Pattern):
34 class AutolinkPattern():
42 def handleMatch(self, m):
35 def handleMatch(self, m):
43 link_element = etree.Element('a')
36 link_element = etree.Element('a')
44 href = m.group(2)
37 href = m.group(2)
@@ -48,44 +41,22 b' class AutolinkPattern(Pattern):'
48 return link_element
41 return link_element
49
42
50
43
51 class QuotePattern(Pattern, TextFormatter):
44 class QuotePattern(TextFormatter):
52 name = ''
45 name = 'q'
53 preview_left = '<span class="quote">&gt; '
46 preview_left = '<span class="multiquote">'
54 preview_right = '</span>'
47 preview_right = '</span>'
55
48
56 format_left = '&gt;'
49 format_left = '[quote]'
57
50 format_right = '[/quote]'
58 def handleMatch(self, m):
59 quote_element = etree.Element('span')
60 quote_element.set('class', 'quote')
61 quote_element.text = m.group(2)
62
63 return quote_element
64
51
65
52
66 class ReflinkPattern(Pattern):
53 class SpoilerPattern(TextFormatter):
67 def handleMatch(self, m):
54 name = 'spoiler'
68 post_id = m.group(4)
69
70 posts = boards.models.Post.objects.filter(id=post_id)
71 if posts.count() > 0:
72 ref_element = etree.Element('a')
73
74 post = posts[0]
75
76 ref_element.set('href', post.get_url())
77 ref_element.text = m.group(2)
78
79 return ref_element
80
81
82 class SpoilerPattern(Pattern, TextFormatter):
83 name = 's'
84 preview_left = '<span class="spoiler">'
55 preview_left = '<span class="spoiler">'
85 preview_right = '</span>'
56 preview_right = '</span>'
86
57
87 format_left = '%%'
58 format_left = '[spoiler]'
88 format_right = '%%'
59 format_right = '[/spoiler]'
89
60
90 def handleMatch(self, m):
61 def handleMatch(self, m):
91 quote_element = etree.Element('span')
62 quote_element = etree.Element('span')
@@ -95,35 +66,22 b' class SpoilerPattern(Pattern, TextFormat'
95 return quote_element
66 return quote_element
96
67
97
68
98 class CommentPattern(Pattern, TextFormatter):
69 class CommentPattern(TextFormatter):
99 name = ''
70 name = ''
100 preview_left = '<span class="comment">// '
71 preview_left = '<span class="comment">// '
101 preview_right = '</span>'
72 preview_right = '</span>'
102
73
103 format_left = '//'
74 format_left = '[comment]'
104
75 format_right = '[/comment]'
105 def handleMatch(self, m):
106 quote_element = etree.Element('span')
107 quote_element.set('class', 'comment')
108 quote_element.text = '//' + m.group(3)
109
110 return quote_element
111
76
112
77
113 class StrikeThroughPattern(Pattern, TextFormatter):
78 class StrikeThroughPattern(TextFormatter):
114 name = 's'
79 name = 's'
115 preview_left = '<span class="strikethrough">'
80 preview_left = '<span class="strikethrough">'
116 preview_right = '</span>'
81 preview_right = '</span>'
117
82
118 format_left = '~'
83 format_left = '[s]'
119 format_right = '~'
84 format_right = '[/s]'
120
121 def handleMatch(self, m):
122 quote_element = etree.Element('span')
123 quote_element.set('class', 'strikethrough')
124 quote_element.text = m.group(2)
125
126 return quote_element
127
85
128
86
129 class ItalicPattern(TextFormatter):
87 class ItalicPattern(TextFormatter):
@@ -131,8 +89,8 b' class ItalicPattern(TextFormatter):'
131 preview_left = '<i>'
89 preview_left = '<i>'
132 preview_right = '</i>'
90 preview_right = '</i>'
133
91
134 format_left = '_'
92 format_left = '[i]'
135 format_right = '_'
93 format_right = '[/i]'
136
94
137
95
138 class BoldPattern(TextFormatter):
96 class BoldPattern(TextFormatter):
@@ -140,8 +98,8 b' class BoldPattern(TextFormatter):'
140 preview_left = '<b>'
98 preview_left = '<b>'
141 preview_right = '</b>'
99 preview_right = '</b>'
142
100
143 format_left = '__'
101 format_left = '[b]'
144 format_right = '__'
102 format_right = '[/b]'
145
103
146
104
147 class CodePattern(TextFormatter):
105 class CodePattern(TextFormatter):
@@ -149,52 +107,39 b' class CodePattern(TextFormatter):'
149 preview_left = '<code>'
107 preview_left = '<code>'
150 preview_right = '</code>'
108 preview_right = '</code>'
151
109
152 format_left = ' '
110 format_left = '[code]'
153
111 format_right = '[/code]'
154
155 class DashPattern(Pattern):
156 def handleMatch(self, m):
157 return u'—'
158
112
159
113
160 class NeboardMarkdown(markdown.Extension):
114 def render_reflink(tag_name, value, options, parent, context):
161 def extendMarkdown(self, md, md_globals):
115 if not REFLINK_PATTERN.match(value):
162 self._add_neboard_patterns(md)
116 return u'>>%s' % value
163 self._delete_patterns(md)
164
117
165 def _delete_patterns(self, md):
118 post_id = int(value)
166 del md.parser.blockprocessors['quote']
167
168 del md.inlinePatterns['image_link']
169 del md.inlinePatterns['image_reference']
170
119
171 def _add_neboard_patterns(self, md):
120 posts = boards.models.Post.objects.filter(id=post_id)
172 autolink = AutolinkPattern(AUTOLINK_PATTERN, md)
121 if posts.exists():
173 quote = QuotePattern(QUOTE_PATTERN, md)
122 post = posts[0]
174 reflink = ReflinkPattern(REFLINK_PATTERN, md)
175 spoiler = SpoilerPattern(SPOILER_PATTERN, md)
176 comment = CommentPattern(COMMENT_PATTERN, md)
177 strikethrough = StrikeThroughPattern(STRIKETHROUGH_PATTERN, md)
178 dash = DashPattern(DASH_PATTERN, md)
179
123
180 md.inlinePatterns[u'autolink_ext'] = autolink
124 return u'<a href=%s>&gt;&gt;%s</a>' % (post.get_url(), post_id)
181 md.inlinePatterns[u'spoiler'] = spoiler
125 else:
182 md.inlinePatterns[u'strikethrough'] = strikethrough
126 return u'>>%s' % value
183 md.inlinePatterns[u'comment'] = comment
184 md.inlinePatterns[u'reflink'] = reflink
185 md.inlinePatterns[u'quote'] = quote
186 md.inlinePatterns[u'dash'] = dash
187
127
188
128
189 def make_extension(configs=None):
129 def bbcode_extended(markup):
190 return NeboardMarkdown(configs=configs)
130 parser = bbcode.Parser(newline='</p><p>')
191
131 parser.add_formatter('post', render_reflink, strip=True)
192 neboard_extension = make_extension()
132 parser.add_simple_formatter('quote',
193
133 u'<span class="multiquote">%(value)s</span>')
194
134 parser.add_simple_formatter('comment',
195 def markdown_extended(markup):
135 u'<span class="comment">//%(value)s</span>')
196 return markdown.markdown(markup, [neboard_extension, 'nl2br'],
136 parser.add_simple_formatter('spoiler',
197 safe_mode='escape')
137 u'<span class="spoiler">%(value)s</span>')
138 parser.add_simple_formatter('s',
139 u'<span class="strikethrough">%(value)s</span>')
140 parser.add_simple_formatter('code',
141 u'<pre><code>%(value)s</pre></code>')
142 return '<p>%s</p>' % parser.format(markup)
198
143
199 formatters = [
144 formatters = [
200 QuotePattern,
145 QuotePattern,
@@ -5,5 +5,3 b' from boards.models.thread import Thread'
5 from boards.models.post import Post
5 from boards.models.post import Post
6 from boards.models.tag import Tag
6 from boards.models.tag import Tag
7 from boards.models.user import Ban
7 from boards.models.user import Ban
8 from boards.models.user import Setting
9 from boards.models.user import User
@@ -20,7 +20,7 b" APP_LABEL_BOARDS = 'boards'"
20 CACHE_KEY_PPD = 'ppd'
20 CACHE_KEY_PPD = 'ppd'
21 CACHE_KEY_POST_URL = 'post_url'
21 CACHE_KEY_POST_URL = 'post_url'
22
22
23 POSTS_PER_DAY_RANGE = range(7)
23 POSTS_PER_DAY_RANGE = 7
24
24
25 BAN_REASON_AUTO = 'Auto'
25 BAN_REASON_AUTO = 'Auto'
26
26
@@ -28,7 +28,7 b' IMAGE_THUMB_SIZE = (200, 150)'
28
28
29 TITLE_MAX_LENGTH = 200
29 TITLE_MAX_LENGTH = 200
30
30
31 DEFAULT_MARKUP_TYPE = 'markdown'
31 DEFAULT_MARKUP_TYPE = 'bbcode'
32
32
33 # TODO This should be removed
33 # TODO This should be removed
34 NO_IP = '0.0.0.0'
34 NO_IP = '0.0.0.0'
@@ -36,17 +36,14 b" NO_IP = '0.0.0.0'"
36 # TODO Real user agent should be saved instead of this
36 # TODO Real user agent should be saved instead of this
37 UNKNOWN_UA = ''
37 UNKNOWN_UA = ''
38
38
39 SETTING_MODERATE = "moderate"
39 REGEX_REPLY = re.compile(r'&gt;&gt;(\d+)')
40
41 REGEX_REPLY = re.compile('>>(\d+)')
42
40
43 logger = logging.getLogger(__name__)
41 logger = logging.getLogger(__name__)
44
42
45
43
46 class PostManager(models.Manager):
44 class PostManager(models.Manager):
47
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
48 def create_post(self, title, text, image=None, thread=None,
46 tags=None):
49 ip=NO_IP, tags=None, user=None):
50 """
47 """
51 Creates new post
48 Creates new post
52 """
49 """
@@ -69,8 +66,7 b' class PostManager(models.Manager):'
69 poster_ip=ip,
66 poster_ip=ip,
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
67 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
71 # last!
68 # last!
72 last_edit_time=posting_time,
69 last_edit_time=posting_time)
73 user=user)
74
70
75 if image:
71 if image:
76 post_image = PostImage.objects.create(image=image)
72 post_image = PostImage.objects.create(image=image)
@@ -80,20 +76,14 b' class PostManager(models.Manager):'
80
76
81 thread.replies.add(post)
77 thread.replies.add(post)
82 if tags:
78 if tags:
83 linked_tags = []
84 for tag in tags:
85 tag_linked_tags = tag.get_linked_tags()
86 if len(tag_linked_tags) > 0:
87 linked_tags.extend(tag_linked_tags)
88
89 tags.extend(linked_tags)
90 map(thread.add_tag, tags)
79 map(thread.add_tag, tags)
91
80
92 if new_thread:
81 if new_thread:
93 Thread.objects.process_oldest_threads()
82 Thread.objects.process_oldest_threads()
94 self.connect_replies(post)
83 self.connect_replies(post)
95
84
96 logger.info('Created post #%d' % post.id)
85 logger.info('Created post #%d with title %s' % (post.id,
86 post.get_title()))
97
87
98 return post
88 return post
99
89
@@ -114,7 +104,7 b' class PostManager(models.Manager):'
114
104
115 post.delete()
105 post.delete()
116
106
117 logger.info('Deleted post #%d' % post_id)
107 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
118
108
119 def delete_posts_by_ip(self, ip):
109 def delete_posts_by_ip(self, ip):
120 """
110 """
@@ -129,7 +119,7 b' class PostManager(models.Manager):'
129 Connects replies to a post to show them as a reflink map
119 Connects replies to a post to show them as a reflink map
130 """
120 """
131
121
132 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
122 for reply_number in re.finditer(REGEX_REPLY, post.text.rendered):
133 post_id = reply_number.group(1)
123 post_id = reply_number.group(1)
134 ref_post = self.filter(id=post_id)
124 ref_post = self.filter(id=post_id)
135 if ref_post.count() > 0:
125 if ref_post.count() > 0:
@@ -148,28 +138,26 b' class PostManager(models.Manager):'
148 Gets average count of posts per day for the last 7 days
138 Gets average count of posts per day for the last 7 days
149 """
139 """
150
140
151 today = date.today()
141 day_end = date.today()
152 ppd = cache.get(CACHE_KEY_PPD + str(today))
142 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
143
144 cache_key = CACHE_KEY_PPD + str(day_end)
145 ppd = cache.get(cache_key)
153 if ppd:
146 if ppd:
154 return ppd
147 return ppd
155
148
156 posts_per_days = []
157 for i in POSTS_PER_DAY_RANGE:
158 day_end = today - timedelta(i + 1)
159 day_start = today - timedelta(i + 2)
160
161 day_time_start = timezone.make_aware(datetime.combine(
149 day_time_start = timezone.make_aware(datetime.combine(
162 day_start, dtime()), timezone.get_current_timezone())
150 day_start, dtime()), timezone.get_current_timezone())
163 day_time_end = timezone.make_aware(datetime.combine(
151 day_time_end = timezone.make_aware(datetime.combine(
164 day_end, dtime()), timezone.get_current_timezone())
152 day_end, dtime()), timezone.get_current_timezone())
165
153
166 posts_per_days.append(float(self.filter(
154 posts_per_period = float(self.filter(
167 pub_time__lte=day_time_end,
155 pub_time__lte=day_time_end,
168 pub_time__gte=day_time_start).count()))
156 pub_time__gte=day_time_start).count())
169
157
170 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
158 ppd = posts_per_period / POSTS_PER_DAY_RANGE
171 len(posts_per_days))
159
172 cache.set(CACHE_KEY_PPD + str(today), ppd)
160 cache.set(cache_key, ppd)
173 return ppd
161 return ppd
174
162
175
163
@@ -196,7 +184,6 b' class Post(models.Model, Viewable):'
196 thread_new = models.ForeignKey('Thread', null=True, default=None,
184 thread_new = models.ForeignKey('Thread', null=True, default=None,
197 db_index=True)
185 db_index=True)
198 last_edit_time = models.DateTimeField()
186 last_edit_time = models.DateTimeField()
199 user = models.ForeignKey('User', null=True, default=None, db_index=True)
200
187
201 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
188 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
202 null=True,
189 null=True,
@@ -220,13 +207,18 b' class Post(models.Model, Viewable):'
220 return title
207 return title
221
208
222 def build_refmap(self):
209 def build_refmap(self):
210 """
211 Builds a replies map string from replies list. This is a cache to stop
212 the server from recalculating the map on every post show.
213 """
223 map_string = ''
214 map_string = ''
224
215
225 first = True
216 first = True
226 for refpost in self.referenced_posts.all():
217 for refpost in self.referenced_posts.all():
227 if not first:
218 if not first:
228 map_string += ', '
219 map_string += ', '
229 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(), refpost.id)
220 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
221 refpost.id)
230 first = False
222 first = False
231
223
232 self.refmap = map_string
224 self.refmap = map_string
@@ -251,10 +243,10 b' class Post(models.Model, Viewable):'
251 thread = self.get_thread()
243 thread = self.get_thread()
252 thread.add_tag(tag)
244 thread.add_tag(tag)
253 self.last_edit_time = edit_time
245 self.last_edit_time = edit_time
254 self.save()
246 self.save(update_fields=['last_edit_time'])
255
247
256 thread.last_edit_time = edit_time
248 thread.last_edit_time = edit_time
257 thread.save()
249 thread.save(update_fields=['last_edit_time'])
258
250
259 @transaction.atomic
251 @transaction.atomic
260 def remove_tag(self, tag):
252 def remove_tag(self, tag):
@@ -263,10 +255,10 b' class Post(models.Model, Viewable):'
263 thread = self.get_thread()
255 thread = self.get_thread()
264 thread.remove_tag(tag)
256 thread.remove_tag(tag)
265 self.last_edit_time = edit_time
257 self.last_edit_time = edit_time
266 self.save()
258 self.save(update_fields=['last_edit_time'])
267
259
268 thread.last_edit_time = edit_time
260 thread.last_edit_time = edit_time
269 thread.save()
261 thread.save(update_fields=['last_edit_time'])
270
262
271 def get_url(self, thread=None):
263 def get_url(self, thread=None):
272 """
264 """
@@ -343,9 +335,9 b' class Post(models.Model, Viewable):'
343
335
344 def delete(self, using=None):
336 def delete(self, using=None):
345 """
337 """
346 Delete all post images and the post itself.
338 Deletes all post images and the post itself.
347 """
339 """
348
340
349 self.images.all().delete()
341 self.images.all().delete()
350
342
351 super(Post, self).delete(using) No newline at end of file
343 super(Post, self).delete(using)
@@ -1,10 +1,12 b''
1 from django.template.loader import render_to_string
1 from django.template.loader import render_to_string
2 from boards.models import Thread, Post
3 from django.db import models
2 from django.db import models
4 from django.db.models import Count, Sum
3 from django.db.models import Count, Sum
5 from django.core.urlresolvers import reverse
4 from django.core.urlresolvers import reverse
5
6 from boards.models import Thread
6 from boards.models.base import Viewable
7 from boards.models.base import Viewable
7
8
9
8 __author__ = 'neko259'
10 __author__ = 'neko259'
9
11
10
12
@@ -36,7 +38,6 b' class Tag(models.Model, Viewable):'
36 name = models.CharField(max_length=100, db_index=True)
38 name = models.CharField(max_length=100, db_index=True)
37 threads = models.ManyToManyField(Thread, null=True,
39 threads = models.ManyToManyField(Thread, null=True,
38 blank=True, related_name='tag+')
40 blank=True, related_name='tag+')
39 linked = models.ForeignKey('Tag', null=True, blank=True)
40
41
41 def __unicode__(self):
42 def __unicode__(self):
42 return self.name
43 return self.name
@@ -51,29 +52,6 b' class Tag(models.Model, Viewable):'
51 def get_thread_count(self):
52 def get_thread_count(self):
52 return self.threads.count()
53 return self.threads.count()
53
54
54 def get_linked_tags(self):
55 """
56 Gets tags linked to the current one.
57 """
58
59 tag_list = []
60 self.get_linked_tags_list(tag_list)
61
62 return tag_list
63
64 def get_linked_tags_list(self, tag_list=[]):
65 """
66 Returns the list of tags linked to current. The list can be got
67 through returned value or tag_list parameter
68 """
69
70 linked_tag = self.linked
71
72 if linked_tag and not (linked_tag in tag_list):
73 tag_list.append(linked_tag)
74
75 linked_tag.get_linked_tags_list(tag_list)
76
77 def get_post_count(self, archived=False):
55 def get_post_count(self, archived=False):
78 """
56 """
79 Gets posts count for the tag's threads.
57 Gets posts count for the tag's threads.
@@ -1,122 +1,10 b''
1 from django.db import models
1 from django.db import models
2 from django.db.models import Count
3 from boards import settings
4 from boards.models import Post
5 from django.core.cache import cache
6
2
7 __author__ = 'neko259'
3 __author__ = 'neko259'
8
4
9 RANK_ADMIN = 0
10 RANK_MODERATOR = 10
11 RANK_USER = 100
12
13 BAN_REASON_AUTO = 'Auto'
5 BAN_REASON_AUTO = 'Auto'
14 BAN_REASON_MAX_LENGTH = 200
6 BAN_REASON_MAX_LENGTH = 200
15
7
16 VETERAN_POSTS = 1000
17
18
19 class User(models.Model):
20
21 class Meta:
22 app_label = 'boards'
23
24 user_id = models.CharField(max_length=50)
25 rank = models.IntegerField()
26
27 registration_time = models.DateTimeField()
28
29 fav_tags = models.ManyToManyField('Tag', null=True, blank=True)
30 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
31 blank=True)
32
33 hidden_tags = models.ManyToManyField('Tag', null=True, blank=True,
34 related_name='ht+')
35 hidden_threads = models.ManyToManyField('Post', null=True, blank=True,
36 related_name='hth+')
37
38 def save_setting(self, name, value):
39 setting, created = Setting.objects.get_or_create(name=name, user=self)
40 setting.value = str(value)
41 setting.save()
42
43 return setting
44
45 def get_setting(self, name):
46 if Setting.objects.filter(name=name, user=self).exists():
47 setting = Setting.objects.get(name=name, user=self)
48 setting_value = setting.value
49 else:
50 setting_value = None
51
52 return setting_value
53
54 def is_moderator(self):
55 return RANK_MODERATOR >= self.rank
56
57 def get_sorted_fav_tags(self):
58 cache_key = self._get_tag_cache_key()
59 fav_tags = cache.get(cache_key)
60 if fav_tags:
61 return fav_tags
62
63 tags = self.fav_tags.annotate(Count('threads')) \
64 .filter(threads__count__gt=0).order_by('name')
65
66 if tags:
67 cache.set(cache_key, tags)
68
69 return tags
70
71 def get_post_count(self):
72 return Post.objects.filter(user=self).count()
73
74 def __unicode__(self):
75 return self.user_id + '(' + str(self.rank) + ')'
76
77 def get_last_access_time(self):
78 """
79 Gets user's last post time.
80 """
81
82 posts = Post.objects.filter(user=self)
83 if posts.exists() > 0:
84 return posts.latest('pub_time').pub_time
85
86 def add_tag(self, tag):
87 self.fav_tags.add(tag)
88 cache.delete(self._get_tag_cache_key())
89
90 def remove_tag(self, tag):
91 self.fav_tags.remove(tag)
92 cache.delete(self._get_tag_cache_key())
93
94 def hide_tag(self, tag):
95 self.hidden_tags.add(tag)
96
97 def unhide_tag(self, tag):
98 self.hidden_tags.remove(tag)
99
100 def is_veteran(self):
101 """
102 Returns if a user is old (veteran).
103 """
104
105 return self.get_post_count() >= VETERAN_POSTS
106
107 def _get_tag_cache_key(self):
108 return self.user_id + '_tags'
109
110
111 class Setting(models.Model):
112
113 class Meta:
114 app_label = 'boards'
115
116 name = models.CharField(max_length=50)
117 value = models.CharField(max_length=50)
118 user = models.ForeignKey(User)
119
120
8
121 class Ban(models.Model):
9 class Ban(models.Model):
122
10
@@ -17,4 +17,7 b' LAST_REPLIES_COUNT = 3'
17 # Enable archiving threads instead of deletion when the thread limit is reached
17 # Enable archiving threads instead of deletion when the thread limit is reached
18 ARCHIVE_THREADS = True
18 ARCHIVE_THREADS = True
19 # Limit posting speed
19 # Limit posting speed
20 LIMIT_POSTING_SPEED = False No newline at end of file
20 LIMIT_POSTING_SPEED = False
21
22 # This password is used to add admin permissions to the user
23 MASTER_PASSWORD = u'password' No newline at end of file
@@ -217,6 +217,16 b' blockquote {'
217 font-style: italic;
217 font-style: italic;
218 }
218 }
219
219
220 .multiquote {
221 border-left: solid 4px #ccc;
222 padding: 3px;
223 display: inline-block;
224 background: #222;
225 border-right: solid 1px #ccc;
226 border-top: solid 1px #ccc;
227 border-bottom: solid 1px #ccc;
228 }
229
220 .spoiler {
230 .spoiler {
221 background: white;
231 background: white;
222 color: white;
232 color: white;
@@ -35,10 +35,10 b' function moveCaretToEnd(el) {'
35 }
35 }
36
36
37 function addQuickReply(postId) {
37 function addQuickReply(postId) {
38 var textToAdd = '>>' + postId + '\n\n';
38 var textToAdd = '[post]' + postId + '[/post]\n';
39 var selection = window.getSelection().toString();
39 var selection = window.getSelection().toString();
40 if (selection.length > 0) {
40 if (selection.length > 0) {
41 textToAdd += '> ' + selection + '\n\n';
41 textToAdd += '[quote]' + selection + '[/quote]\n';
42 }
42 }
43
43
44 var textAreaId = 'textarea';
44 var textAreaId = 'textarea';
@@ -46,7 +46,11 b''
46
46
47 <div class="navigation_panel">
47 <div class="navigation_panel">
48 {% block metapanel %}{% endblock %}
48 {% block metapanel %}{% endblock %}
49 {% if moderator %}
50 [<a href="{% url "logout" %}">{% trans 'Logout' %}</a>]
51 {% else %}
49 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
52 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
53 {% endif %}
50 [<a href="{% url "search" %}">{% trans 'Search' %}</a>]
54 [<a href="{% url "search" %}">{% trans 'Search' %}</a>]
51 {% with ppd=posts_per_day|floatformat:2 %}
55 {% with ppd=posts_per_day|floatformat:2 %}
52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
56 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
@@ -83,7 +83,7 b''
83 {% 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 %}
84 <div class="metadata">
84 <div class="metadata">
85 {% if is_opening and need_open_link %}
85 {% if is_opening and need_open_link %}
86 {{ thread.get_reply_count }} {% trans 'replies' %},
86 {{ thread.get_reply_count }} {% trans 'messages' %},
87 {{ thread.get_images_count }} {% trans 'images' %}.
87 {{ thread.get_images_count }} {% trans 'images' %}.
88 {% endif %}
88 {% endif %}
89 <span class="tags">
89 <span class="tags">
@@ -44,14 +44,14 b''
44 {% if tag %}
44 {% if tag %}
45 <div class="tag_info">
45 <div class="tag_info">
46 <h2>
46 <h2>
47 {% if tag in user.fav_tags.all %}
47 {% if tag in fav_tags %}
48 <a href="{% url 'tag' tag.name %}?method=unsubscribe&next={{ request.path }}"
48 <a href="{% url 'tag' tag.name %}?method=unsubscribe&next={{ request.path }}"
49 class="fav"></a>
49 class="fav"></a>
50 {% else %}
50 {% else %}
51 <a href="{% url 'tag' tag.name %}?method=subscribe&next={{ request.path }}"
51 <a href="{% url 'tag' tag.name %}?method=subscribe&next={{ request.path }}"
52 class="not_fav"></a>
52 class="not_fav"></a>
53 {% endif %}
53 {% endif %}
54 {% if tag in user.hidden_tags.all %}
54 {% if tag in hidden_tags %}
55 <a href="{% url 'tag' tag.name %}?method=unhide&next={{ request.path }}"
55 <a href="{% url 'tag' tag.name %}?method=unhide&next={{ request.path }}"
56 title="{% trans 'Show tag' %}"
56 title="{% trans 'Show tag' %}"
57 class="fav">H</a>
57 class="fav">H</a>
@@ -11,20 +11,10 b''
11
11
12 <div class="post">
12 <div class="post">
13 <p>
13 <p>
14 {% trans 'User:' %} <b>{{ user.user_id }}</b>.
14 {% if moderator %}
15 {% if user.is_moderator %}
16 {% trans 'You are moderator.' %}
15 {% trans 'You are moderator.' %}
17 {% endif %}
16 {% endif %}
18 {% if user.is_veteran %}
19 {% trans 'You are veteran.' %}
20 {% endif %}
21 </p>
17 </p>
22 <p>{% trans 'Posts:' %} {{ user.get_post_count }}</p>
23 <p>{% trans 'First access:' %} {{ user.registration_time|naturaltime }}</p>
24 {% if user.get_last_access_time %}
25 <p>{% trans 'Last access:' %} {{ user.get_last_access_time|naturaltime }}</p>
26 {% endif %}
27 {% with hidden_tags=user.hidden_tags.all %}
28 {% if hidden_tags %}
18 {% if hidden_tags %}
29 <p>{% trans 'Hidden tags:' %}
19 <p>{% trans 'Hidden tags:' %}
30 {% for tag in hidden_tags %}
20 {% for tag in hidden_tags %}
@@ -32,8 +22,9 b''
32 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
22 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
33 {% endfor %}
23 {% endfor %}
34 </p>
24 </p>
25 {% else %}
26 <p>{% trans 'No hidden tags.' %}</p>
35 {% endif %}
27 {% endif %}
36 {% endwith %}
37 </div>
28 </div>
38
29
39 <div class="post-form-w">
30 <div class="post-form-w">
@@ -8,13 +8,11 b''
8
8
9 {% block staticcontent %}
9 {% block staticcontent %}
10 <h2>{% trans 'Syntax' %}</h2>
10 <h2>{% trans 'Syntax' %}</h2>
11 <p>{% trans '2 line breaks for a new line.' %}</p>
11 <p>[i]<i>{% trans 'Italic text' %}</i>[/i]</p>
12 <p>_<i>{% trans 'Italic text' %}</i>_</p>
12 <p>[b]<b>{% trans 'Bold text' %}</b>[/b]</p>
13 <p>__<b>{% trans 'Bold text' %}</b>__</p>
13 <p>[spoiler]<span class="spoiler">{% trans 'Spoiler' %}</span>[/spoiler]</p>
14 <p>%%<span class="spoiler">{% trans 'Spoiler' %}</span>%%</p>
14 <p>[post]123[/post] -- {% trans 'Link to a post' %}</p>
15 <p><a>>>123</a> -- {% trans 'Link to a post' %}</p>
15 <p>[s]<span class="strikethrough">{% trans 'Strikethrough text' %}</span>[/s]</p>
16 <p>~<span class="strikethrough">{% trans 'Strikethrough text' %}</span>~</p>
16 <p>[comment]<span class="comment">{% trans 'Comment' %}</span>[/comment]</p>
17 <p>{% trans 'You need to new line before:' %}</p>
17 <p>[quote]<span class="multiquote">{% trans 'Quote' %}</span>[/quote]</p>
18 <p><span class="comment">//{% trans 'Comment' %}</span></p>
19 <p><span class="quote">> {% trans 'Quote' %}</span></p>
20 {% endblock %}
18 {% endblock %}
@@ -85,7 +85,7 b''
85
85
86 <span class="metapanel" data-last-update="{{ last_update }}">
86 <span class="metapanel" data-last-update="{{ last_update }}">
87 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
87 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
88 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'replies' %},
88 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
89 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
89 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
90 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
90 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
91 [<a href="rss/">RSS</a>]
91 [<a href="rss/">RSS</a>]
@@ -58,7 +58,7 b''
58 <span class="metapanel" data-last-update="{{ last_update }}">
58 <span class="metapanel" data-last-update="{{ last_update }}">
59 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
59 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
60 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }}
60 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }}
61 {% trans 'replies' %},
61 {% trans 'messages' %},
62 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
62 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
63 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
63 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
64 [<a href="rss/">RSS</a>]
64 [<a href="rss/">RSS</a>]
@@ -6,12 +6,15 b' from django.core.paginator import Pagina'
6 from django.test import TestCase
6 from django.test import TestCase
7 from django.test.client import Client
7 from django.test.client import Client
8 from django.core.urlresolvers import reverse, NoReverseMatch
8 from django.core.urlresolvers import reverse, NoReverseMatch
9 from boards.abstracts.settingsmanager import get_settings_manager
9
10
10 from boards.models import Post, Tag, Thread
11 from boards.models import Post, Tag, Thread
11 from boards import urls
12 from boards import urls
12 from boards import settings
13 from boards import settings
13 import neboard
14 import neboard
14
15
16 TEST_TAG = 'test_tag'
17
15 PAGE_404 = 'boards/404.html'
18 PAGE_404 = 'boards/404.html'
16
19
17 TEST_TEXT = 'test text'
20 TEST_TEXT = 'test text'
@@ -30,15 +33,18 b' logger = logging.getLogger(__name__)'
30 class PostTests(TestCase):
33 class PostTests(TestCase):
31
34
32 def _create_post(self):
35 def _create_post(self):
33 return Post.objects.create_post(title='title',
36 tag = Tag.objects.create(name=TEST_TAG)
34 text='text')
37 return Post.objects.create_post(title='title', text='text',
38 tags=[tag])
35
39
36 def test_post_add(self):
40 def test_post_add(self):
37 """Test adding post"""
41 """Test adding post"""
38
42
39 post = self._create_post()
43 post = self._create_post()
40
44
41 self.assertIsNotNone(post, 'No post was created')
45 self.assertIsNotNone(post, 'No post was created.')
46 self.assertEqual(TEST_TAG, post.get_thread().tags.all()[0].name,
47 'No tags were added to the post.')
42
48
43 def test_delete_post(self):
49 def test_delete_post(self):
44 """Test post deletion"""
50 """Test post deletion"""
@@ -131,17 +137,6 b' class PostTests(TestCase):'
131 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
137 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
132 first_post.id)
138 first_post.id)
133
139
134 def test_linked_tag(self):
135 """Test adding a linked tag"""
136
137 linked_tag = Tag.objects.create(name=u'tag1')
138 tag = Tag.objects.create(name=u'tag2', linked=linked_tag)
139
140 post = Post.objects.create_post("", "", tags=[tag])
141
142 self.assertTrue(linked_tag in post.get_thread().tags.all(),
143 'Linked tag was not added')
144
145
140
146 class PagesTest(TestCase):
141 class PagesTest(TestCase):
147
142
@@ -234,10 +229,10 b' class FormTest(TestCase):'
234 class ViewTest(TestCase):
229 class ViewTest(TestCase):
235
230
236 def test_all_views(self):
231 def test_all_views(self):
237 '''
232 """
238 Try opening all views defined in ulrs.py that don't need additional
233 Try opening all views defined in ulrs.py that don't need additional
239 parameters
234 parameters
240 '''
235 """
241
236
242 client = Client()
237 client = Client()
243 for url in urls.urlpatterns:
238 for url in urls.urlpatterns:
@@ -258,3 +253,18 b' class ViewTest(TestCase):'
258 except AttributeError:
253 except AttributeError:
259 # This is normal, some views do not have names
254 # This is normal, some views do not have names
260 pass
255 pass
256
257
258 class AbstractTest(TestCase):
259 def test_settings_manager(self):
260 request = MockRequest()
261 settings_manager = get_settings_manager(request)
262
263 settings_manager.set_setting('test_setting', 'test_value')
264 self.assertEqual('test_value', settings_manager.get_setting(
265 'test_setting'), u'Setting update failed.')
266
267
268 class MockRequest:
269 def __init__(self):
270 self.session = dict()
@@ -2,7 +2,7 b' from django.conf.urls import patterns, u'
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4 from boards.views import api, tag_threads, all_threads, \
4 from boards.views import api, tag_threads, all_threads, \
5 login, settings, all_tags
5 login, settings, all_tags, logout
6 from boards.views.authors import AuthorsView
6 from boards.views.authors import AuthorsView
7 from boards.views.delete_post import DeletePostView
7 from boards.views.delete_post import DeletePostView
8 from boards.views.ban import BanUserView
8 from boards.views.ban import BanUserView
@@ -24,6 +24,7 b" urlpatterns = patterns('',"
24
24
25 # login page
25 # login page
26 url(r'^login/$', login.LoginView.as_view(), name='login'),
26 url(r'^login/$', login.LoginView.as_view(), name='login'),
27 url(r'^logout/$', logout.LogoutView.as_view(), name='logout'),
27
28
28 # /boards/tag/tag_name/
29 # /boards/tag/tag_name/
29 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
30 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
@@ -5,11 +5,8 b' import hashlib'
5 import time
5 import time
6
6
7 from django.utils import timezone
7 from django.utils import timezone
8 import boards
9
8
10 from neboard import settings
9 from neboard import settings
11 from boards.models import User
12 from boards.models.user import RANK_USER
13
10
14
11
15 KEY_CAPTCHA_FAILS = 'key_captcha_fails'
12 KEY_CAPTCHA_FAILS = 'key_captcha_fails'
@@ -82,48 +79,4 b' def get_client_ip(request):'
82 def datetime_to_epoch(datetime):
79 def datetime_to_epoch(datetime):
83 return int(time.mktime(timezone.localtime(
80 return int(time.mktime(timezone.localtime(
84 datetime,timezone.get_current_timezone()).timetuple())
81 datetime,timezone.get_current_timezone()).timetuple())
85 * 1000000 + datetime.microsecond)
82 * 1000000 + datetime.microsecond) No newline at end of file
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 = boards.settings.DEFAULT_THEME
128
129 return theme No newline at end of file
@@ -5,6 +5,7 b' from django.shortcuts import render, red'
5
5
6 from boards import utils, settings
6 from boards import utils, settings
7 from boards.abstracts.paginator import get_paginator
7 from boards.abstracts.paginator import get_paginator
8 from boards.abstracts.settingsmanager import get_settings_manager
8 from boards.forms import ThreadForm, PlainErrorList
9 from boards.forms import ThreadForm, PlainErrorList
9 from boards.models import Post, Thread, Ban, Tag
10 from boards.models import Post, Thread, Ban, Tag
10 from boards.views.banned import BannedView
11 from boards.views.banned import BannedView
@@ -28,16 +29,17 b' DEFAULT_PAGE = 1'
28
29
29 class AllThreadsView(PostMixin, BaseBoardView):
30 class AllThreadsView(PostMixin, BaseBoardView):
30
31
31 user = None
32 def __init__(self):
33 self.settings_manager = None
34 super(AllThreadsView, self).__init__()
32
35
33 def get(self, request, page=DEFAULT_PAGE, form=None):
36 def get(self, request, page=DEFAULT_PAGE, form=None):
34 context = self.get_context_data(request=request)
37 context = self.get_context_data(request=request)
35
38
36 self.user = utils.get_user(request)
37
38 if not form:
39 if not form:
39 form = ThreadForm(error_class=PlainErrorList)
40 form = ThreadForm(error_class=PlainErrorList)
40
41
42 self.settings_manager = get_settings_manager(request)
41 paginator = get_paginator(self.get_threads(),
43 paginator = get_paginator(self.get_threads(),
42 settings.THREADS_PER_PAGE)
44 settings.THREADS_PER_PAGE)
43 paginator.current_page = int(page)
45 paginator.current_page = int(page)
@@ -122,9 +124,8 b' class AllThreadsView(PostMixin, BaseBoar'
122
124
123 tags = self.parse_tags_string(tag_strings)
125 tags = self.parse_tags_string(tag_strings)
124
126
125 post = Post.objects.create_post(title=title, text=text, ip=ip,
127 post = Post.objects.create_post(title=title, text=text, image=image,
126 image=image, tags=tags,
128 ip=ip, tags=tags)
127 user=utils.get_user(request))
128
129
129 if html_response:
130 if html_response:
130 return redirect(post.get_url())
131 return redirect(post.get_url())
@@ -135,4 +136,4 b' class AllThreadsView(PostMixin, BaseBoar'
135 """
136 """
136
137
137 return Thread.objects.all().order_by('-bump_time')\
138 return Thread.objects.all().order_by('-bump_time')\
138 .exclude(tags__in=self.user.hidden_tags.all())
139 .exclude(tags__in=self.settings_manager.get_hidden_tags())
@@ -1,7 +1,8 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
4
3
4 from boards.abstracts.settingsmanager import PERMISSION_MODERATE, \
5 get_settings_manager
5 from boards.views.base import BaseBoardView
6 from boards.views.base import BaseBoardView
6 from boards.models import Post, Ban
7 from boards.models import Post, Ban
7 from boards.views.mixins import RedirectNextMixin
8 from boards.views.mixins import RedirectNextMixin
@@ -11,10 +12,11 b' class BanUserView(BaseBoardView, Redirec'
11
12
12 @transaction.atomic
13 @transaction.atomic
13 def get(self, request, post_id):
14 def get(self, request, post_id):
14 user = utils.get_user(request)
15 post = get_object_or_404(Post, id=post_id)
15 post = get_object_or_404(Post, id=post_id)
16
16
17 if user.is_moderator():
17 settings_manager = get_settings_manager(request)
18
19 if settings_manager.has_permission(PERMISSION_MODERATE):
18 # TODO Show confirmation page before ban
20 # TODO Show confirmation page before ban
19 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
21 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
20 if created:
22 if created:
@@ -1,7 +1,8 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
4
3
4 from boards.abstracts.settingsmanager import PERMISSION_MODERATE,\
5 get_settings_manager
5 from boards.views.base import BaseBoardView
6 from boards.views.base import BaseBoardView
6 from boards.views.mixins import RedirectNextMixin
7 from boards.views.mixins import RedirectNextMixin
7 from boards.models import Post
8 from boards.models import Post
@@ -11,12 +12,12 b' class DeletePostView(BaseBoardView, Redi'
11
12
12 @transaction.atomic
13 @transaction.atomic
13 def get(self, request, post_id):
14 def get(self, request, post_id):
14 user = utils.get_user(request)
15 post = get_object_or_404(Post, id=post_id)
15 post = get_object_or_404(Post, id=post_id)
16
16
17 opening_post = post.is_opening()
17 opening_post = post.is_opening()
18
18
19 if user.is_moderator():
19 settings_manager = get_settings_manager(request)
20 if settings_manager.has_permission(PERMISSION_MODERATE):
20 # TODO Show confirmation page before deletion
21 # TODO Show confirmation page before deletion
21 Post.objects.delete_post(post)
22 Post.objects.delete_post(post)
22
23
@@ -1,8 +1,11 b''
1 from django.shortcuts import render, redirect
1 from django.shortcuts import render, redirect
2
3 from boards.abstracts.settingsmanager import PERMISSION_MODERATE, \
4 get_settings_manager
2 from boards.forms import LoginForm, PlainErrorList
5 from boards.forms import LoginForm, PlainErrorList
3 from boards.models import User
4 from boards.views.base import BaseBoardView, CONTEXT_FORM
6 from boards.views.base import BaseBoardView, CONTEXT_FORM
5
7
8
6 __author__ = 'neko259'
9 __author__ = 'neko259'
7
10
8
11
@@ -23,8 +26,8 b' class LoginView(BaseBoardView):'
23 form.session = request.session
26 form.session = request.session
24
27
25 if form.is_valid():
28 if form.is_valid():
26 user = User.objects.get(user_id=form.cleaned_data['user_id'])
29 settings_manager = get_settings_manager(request)
27 request.session['user_id'] = user.id
30 settings_manager.add_permission(PERMISSION_MODERATE)
28 return redirect('index')
31 return redirect('index')
29 else:
32 else:
30 return self.get(request, form)
33 return self.get(request, form)
@@ -1,16 +1,19 b''
1 from django.shortcuts import render, get_object_or_404, redirect
1 from django.shortcuts import render, get_object_or_404, redirect
2
2
3 from boards.abstracts.settingsmanager import PERMISSION_MODERATE,\
4 get_settings_manager
3 from boards.views.base import BaseBoardView
5 from boards.views.base import BaseBoardView
4 from boards.views.mixins import DispatcherMixin
6 from boards.views.mixins import DispatcherMixin
5 from boards.models.post import Post
7 from boards.models.post import Post
6 from boards.models.tag import Tag
8 from boards.models.tag import Tag
7 from boards.forms import AddTagForm, PlainErrorList
9 from boards.forms import AddTagForm, PlainErrorList
8
10
11
9 class PostAdminView(BaseBoardView, DispatcherMixin):
12 class PostAdminView(BaseBoardView, DispatcherMixin):
10
13
11 def get(self, request, post_id, form=None):
14 def get(self, request, post_id, form=None):
12 user = utils.get_user(request)
15 settings_manager = get_settings_manager(request)
13 if not user.is_moderator:
16 if not settings_manager.has_permission(PERMISSION_MODERATE):
14 redirect('index')
17 redirect('index')
15
18
16 post = get_object_or_404(Post, id=post_id)
19 post = get_object_or_404(Post, id=post_id)
@@ -30,8 +33,8 b' class PostAdminView(BaseBoardView, Dispa'
30 return render(request, 'boards/post_admin.html', context)
33 return render(request, 'boards/post_admin.html', context)
31
34
32 def post(self, request, post_id):
35 def post(self, request, post_id):
33 user = utils.get_user(request)
36 settings_manager = get_settings_manager(request)
34 if not user.is_moderator:
37 if not settings_manager.has_permission(PERMISSION_MODERATE):
35 redirect('index')
38 redirect('index')
36
39
37 post = get_object_or_404(Post, id=post_id)
40 post = get_object_or_404(Post, id=post_id)
@@ -27,8 +27,7 b' class BoardSearchView(View):'
27 if form.is_valid():
27 if form.is_valid():
28 query = form.cleaned_data[FORM_QUERY]
28 query = form.cleaned_data[FORM_QUERY]
29 if len(query) >= 3:
29 if len(query) >= 3:
30 results = SearchQuerySet().auto_query(query).order_by('-id') \
30 results = SearchQuerySet().auto_query(query).order_by('-id')
31 .highlight()
32 paginator = get_paginator(results, 10)
31 paginator = get_paginator(results, 10)
33
32
34 if REQUEST_PAGE in request.GET:
33 if REQUEST_PAGE in request.GET:
@@ -1,53 +1,38 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
4
3
4 from boards.abstracts.settingsmanager import get_settings_manager
5 from boards.views.base import BaseBoardView, CONTEXT_FORM
5 from boards.views.base import BaseBoardView, CONTEXT_FORM
6 from boards.forms import SettingsForm, ModeratorSettingsForm, PlainErrorList
6 from boards.forms import SettingsForm, PlainErrorList
7 from boards.models.post import SETTING_MODERATE
7
8 CONTEXT_HIDDEN_TAGS = 'hidden_tags'
8
9
9
10
10 class SettingsView(BaseBoardView):
11 class SettingsView(BaseBoardView):
11
12
12 def get(self, request):
13 def get(self, request):
13 context = self.get_context_data(request=request)
14 context = self.get_context_data(request=request)
14 user = utils.get_user(request)
15 settings_manager = get_settings_manager(request)
15 is_moderator = user.is_moderator()
16
17 selected_theme = utils.get_theme(request, user)
18
16
19 if is_moderator:
17 selected_theme = settings_manager.get_theme()
20 form = ModeratorSettingsForm(initial={
18
21 'theme': selected_theme,
22 'moderate': user.get_setting(SETTING_MODERATE) and \
23 user.is_moderator()
24 }, error_class=PlainErrorList)
25 else:
26 form = SettingsForm(initial={'theme': selected_theme},
19 form = SettingsForm(initial={'theme': selected_theme},
27 error_class=PlainErrorList)
20 error_class=PlainErrorList)
28
21
29 context[CONTEXT_FORM] = form
22 context[CONTEXT_FORM] = form
23 context[CONTEXT_HIDDEN_TAGS] = settings_manager.get_hidden_tags()
30
24
31 return render(request, 'boards/settings.html', context)
25 return render(request, 'boards/settings.html', context)
32
26
33 def post(self, request):
27 def post(self, request):
34 user = utils.get_user(request)
28 settings_manager = get_settings_manager(request)
35 is_moderator = user.is_moderator()
36
29
37 with transaction.atomic():
30 with transaction.atomic():
38 if is_moderator:
39 form = ModeratorSettingsForm(request.POST,
40 error_class=PlainErrorList)
41 else:
42 form = SettingsForm(request.POST, error_class=PlainErrorList)
31 form = SettingsForm(request.POST, error_class=PlainErrorList)
43
32
44 if form.is_valid():
33 if form.is_valid():
45 selected_theme = form.cleaned_data['theme']
34 selected_theme = form.cleaned_data['theme']
46
35
47 user.save_setting('theme', selected_theme)
36 settings_manager.set_theme(selected_theme)
48
49 if is_moderator:
50 moderate = form.cleaned_data['moderate']
51 user.save_setting(SETTING_MODERATE, moderate)
52
37
53 return redirect('settings')
38 return redirect('settings')
@@ -1,10 +1,12 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
3 from boards.models import Tag, Post
3 from boards.abstracts.settingsmanager import get_settings_manager
4 from boards.models import Tag
4 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
5 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
5 from boards.views.mixins import DispatcherMixin, RedirectNextMixin
6 from boards.views.mixins import DispatcherMixin, RedirectNextMixin
6 from boards.forms import ThreadForm, PlainErrorList
7 from boards.forms import ThreadForm, PlainErrorList
7
8
9
8 __author__ = 'neko259'
10 __author__ = 'neko259'
9
11
10
12
@@ -20,9 +22,14 b' class TagView(AllThreadsView, Dispatcher'
20 def get_context_data(self, **kwargs):
22 def get_context_data(self, **kwargs):
21 context = super(TagView, self).get_context_data(**kwargs)
23 context = super(TagView, self).get_context_data(**kwargs)
22
24
25 settings_manager = get_settings_manager(kwargs['request'])
26
23 tag = get_object_or_404(Tag, name=self.tag_name)
27 tag = get_object_or_404(Tag, name=self.tag_name)
24 context['tag'] = tag
28 context['tag'] = tag
25
29
30 context['fav_tags'] = settings_manager.get_fav_tags()
31 context['hidden_tags'] = settings_manager.get_hidden_tags()
32
26 return context
33 return context
27
34
28 def get(self, request, tag_name, page=DEFAULT_PAGE, form=None):
35 def get(self, request, tag_name, page=DEFAULT_PAGE, form=None):
@@ -48,20 +55,18 b' class TagView(AllThreadsView, Dispatcher'
48 return self.get(request, tag_name, page, form)
55 return self.get(request, tag_name, page, form)
49
56
50 def subscribe(self, request):
57 def subscribe(self, request):
51 user = utils.get_user(request)
52 tag = get_object_or_404(Tag, name=self.tag_name)
58 tag = get_object_or_404(Tag, name=self.tag_name)
53
59
54 if not tag in user.fav_tags.all():
60 settings_manager = get_settings_manager(request)
55 user.add_tag(tag)
61 settings_manager.add_fav_tag(tag)
56
62
57 return self.redirect_to_next(request)
63 return self.redirect_to_next(request)
58
64
59 def unsubscribe(self, request):
65 def unsubscribe(self, request):
60 user = utils.get_user(request)
61 tag = get_object_or_404(Tag, name=self.tag_name)
66 tag = get_object_or_404(Tag, name=self.tag_name)
62
67
63 if tag in user.fav_tags.all():
68 settings_manager = get_settings_manager(request)
64 user.remove_tag(tag)
69 settings_manager.del_fav_tag(tag)
65
70
66 return self.redirect_to_next(request)
71 return self.redirect_to_next(request)
67
72
@@ -71,17 +76,17 b' class TagView(AllThreadsView, Dispatcher'
71 shown.
76 shown.
72 """
77 """
73
78
74 user = utils.get_user(request)
75 tag = get_object_or_404(Tag, name=self.tag_name)
79 tag = get_object_or_404(Tag, name=self.tag_name)
76
80
77 user.hide_tag(tag)
81 settings_manager = get_settings_manager(request)
82 settings_manager.add_hidden_tag(tag)
78
83
79 def unhide(self, request):
84 def unhide(self, request):
80 """
85 """
81 Removed tag from user's hidden tags.
86 Removed tag from user's hidden tags.
82 """
87 """
83
88
84 user = utils.get_user(request)
85 tag = get_object_or_404(Tag, name=self.tag_name)
89 tag = get_object_or_404(Tag, name=self.tag_name)
86
90
87 user.unhide_tag(tag)
91 settings_manager = get_settings_manager(request)
92 settings_manager.del_hidden_tag(tag)
@@ -128,10 +128,8 b' class ThreadView(BaseBoardView, PostMixi'
128
128
129 post_thread = opening_post.get_thread()
129 post_thread = opening_post.get_thread()
130
130
131 post = Post.objects.create_post(title=title, text=text, ip=ip,
131 post = Post.objects.create_post(title=title, text=text, image=image,
132 thread=post_thread, image=image,
132 thread=post_thread, ip=ip, tags=tags)
133 tags=tags,
134 user=utils.get_user(request))
135
133
136 thread_to_show = (opening_post.id if opening_post else post.id)
134 thread_to_show = (opening_post.id if opening_post else post.id)
137
135
@@ -35,3 +35,9 b' elements instead of swapping them'
35 images to a post
35 images to a post
36 * Added search over posts and tags
36 * Added search over posts and tags
37 * [ADMIN] Command to remove empty users
37 * [ADMIN] Command to remove empty users
38
39 # 2.0 D'Anna
40 * Removed users. Now settings are stored in sessions
41 * Changed markdown to bbcode
42 * Removed linked tags
43 * [ADMIN] Added title to the post logs to make them more informative
@@ -1,6 +1,6 b''
1 # Django settings for neboard project.
1 # Django settings for neboard project.
2 import os
2 import os
3 from boards.mdx_neboard import markdown_extended
3 from boards.mdx_neboard import bbcode_extended
4
4
5 DEBUG = True
5 DEBUG = True
6 TEMPLATE_DEBUG = DEBUG
6 TEMPLATE_DEBUG = DEBUG
@@ -217,7 +217,7 b' HAYSTACK_CONNECTIONS = {'
217 }
217 }
218
218
219 MARKUP_FIELD_TYPES = (
219 MARKUP_FIELD_TYPES = (
220 ('markdown', markdown_extended),
220 ('bbcode', bbcode_extended),
221 )
221 )
222
222
223 THEMES = [
223 THEMES = [
@@ -1,10 +1,10 b''
1 south>=0.8.4
1 line_profiler
2 line_profiler
2 haystack
3 haystack
3 pillow
4 pillow
4 django>=1.6
5 django>=1.6
5 django_cleanup
6 django_cleanup
6 django-markupfield
7 django-markupfield
7 markdown
8 python-markdown
9 django-simple-captcha
8 django-simple-captcha
10 line-profiler
9 line-profiler
10 bbcode
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now